153#if defined(BUILD_TESTS)
155 [[nodiscard]]
SqlStatement& Statement(
this auto&& self)
noexcept
163 template <
typename Record>
164 static std::string
Inspect(Record
const& record);
167 template <
typename Record>
171 template <
typename FirstRecord,
typename... MoreRecords>
175 template <
typename Record>
179 template <
typename FirstRecord,
typename... MoreRecords>
190 template <DataMapperOptions QueryOptions = {},
typename Record>
191 RecordPrimaryKeyType<Record>
Create(Record& record);
200 template <
typename Record>
201 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
211 template <DataMapperOptions QueryOptions = {},
typename Record>
212 [[nodiscard]] RecordPrimaryKeyType<Record>
CreateCopyOf(Record
const& originalRecord);
237 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
238 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
248 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... InputParameters>
249 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
280 template <
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
281 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
321 template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
322 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
355 template <
typename First,
typename Second,
typename... Rest, DataMapperOptions QueryOptions = {}>
356 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
357 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
373 template <
typename Record, DataMapperOptions QueryOptions = {}>
377 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
381 fields += RecordTableName<Record>;
383 fields += FieldNameAt<I, Record>;
407 template <
typename Record>
408 void Update(Record& record);
417 template <
typename Record>
418 std::size_t
Delete(Record
const& record);
423 return _connection.
Query(tableName);
431 template <
typename Record>
432 bool IsModified(Record
const& record)
const noexcept;
447 template <ModifiedState state,
typename Record>
454 template <
typename Record>
464 template <
typename Record>
473 template <
typename T>
474 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
483 template <
typename Record,
typename... Args>
486 template <
typename Record,
typename ValueType>
487 void SetId(Record& record, ValueType&&
id);
489 template <
typename Record,
size_t InitialOffset = 1>
492 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
495 template <
typename FieldType>
496 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
498 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
501 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
504 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
507 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
508 void CallOnHasMany(Record& record, Callable
const& callback);
510 template <
size_t FieldIndex,
typename OtherRecord>
513 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
514 void CallOnHasManyThrough(Record& record, Callable
const& callback);
516 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue,
typename Callable>
517 void CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback);
519 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue>
520 std::shared_ptr<ReferencedRecord> LoadHasOneThroughByPK(PKValue
const& pkValue);
522 enum class PrimaryKeySource : std::uint8_t
528 template <
typename Record>
529 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record
const& record);
531 template <PrimaryKeySource UsePkOverr
ide,
typename Record>
532 RecordPrimaryKeyType<Record> CreateInternal(
533 Record
const& record,
534 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
535 pkOverride = std::nullopt);
537 SqlConnection _connection;
545 template <
typename FieldType>
546 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
548 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
555 if constexpr (IsField<FieldType>)
557 if constexpr (detail::OneOf<
typename FieldType::ValueType,
563 || IsSqlDynamicString<typename FieldType::ValueType>
564 || IsSqlDynamicBinary<typename FieldType::ValueType>)
573 template <DataMapperRecord Record>
574 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
576 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
580 Reflection::EnumerateMembers<Record>([&result]<
size_t I,
typename Field>() {
581 if constexpr (IsField<Field>)
589 || IsSqlDynamicString<typename Field::ValueType>
590 || IsSqlDynamicBinary<typename Field::ValueType>)
600 template <
typename Record>
601 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
603 Reflection::EnumerateMembers(record,
604 [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
605 if constexpr (IsField<Field>)
607 reader->BindOutputColumn(i++, &field.MutableValue());
609 else if constexpr (IsBelongsTo<Field>)
611 reader->BindOutputColumn(i++, &field.MutableValue());
613 else if constexpr (SqlOutputColumnBinder<Field>)
615 reader->BindOutputColumn(i++, &field);
620 template <
typename Record>
621 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
623 BindAllOutputColumnsWithOffset(reader, record, 1);
629 template <
typename ElementMask,
typename Record>
630 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
632 Reflection::EnumerateMembers<ElementMask>(
633 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
635 if constexpr (IsField<Field>)
638 field.MutableValue() =
639 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
641 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
643 else if constexpr (SqlGetColumnNativeType<Field>)
645 if constexpr (IsOptionalBelongsTo<Field>)
646 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
648 field = reader->GetColumn<Field>(indexFromQuery);
653 template <
typename Record>
654 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
656 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(
657 reader, record, indexFromQuery);
660 template <
typename FirstRecord,
typename SecondRecord>
662 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
664 auto& [firstRecord, secondRecord] = record;
666 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
667 if constexpr (IsField<Field>)
670 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(I + 1);
674 else if constexpr (SqlGetColumnNativeType<Field>)
677 field = reader->GetNullableColumn<
typename Field::BaseType>(I + 1);
679 field = reader->GetColumn<Field>(I + 1);
683 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
684 if constexpr (IsField<Field>)
687 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(
688 Reflection::CountMembers<FirstRecord> + I + 1);
690 field.MutableValue() =
691 reader->GetColumn<
typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
693 else if constexpr (SqlGetColumnNativeType<Field>)
697 reader->GetNullableColumn<
typename Field::BaseType>(Reflection::CountMembers<FirstRecord> + I + 1);
699 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
704 template <
typename Record>
705 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
707 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
709 if (outputColumnsBound)
710 BindAllOutputColumns(reader, record);
712 if (!reader.FetchRow())
715 if (!outputColumnsBound)
716 GetAllColumns(reader, record);
722template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
726 _formatter { dm.Connection().QueryFormatter() },
727 _fields { std::move(fields) }
729 this->_query.searchCondition.inputBindings = &_boundInputs;
732template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
736 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
737 RecordTableName<Record>,
738 this->_query.searchCondition.tableAlias,
739 this->_query.searchCondition.tableJoins,
740 this->_query.searchCondition.condition));
741 auto reader = stmt.ExecuteWithVariants(_boundInputs);
742 if (reader.FetchRow())
743 return reader.GetColumn<
size_t>(1);
747template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
752 auto const query = _formatter.SelectFirst(this->_query.distinct,
754 RecordTableName<Record>,
755 this->_query.searchCondition.tableAlias,
756 this->_query.searchCondition.tableJoins,
757 this->_query.searchCondition.condition,
758 this->_query.orderBy,
762 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
767template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
772 auto const query = _formatter.Delete(RecordTableName<Record>,
773 this->_query.searchCondition.tableAlias,
774 this->_query.searchCondition.tableJoins,
775 this->_query.searchCondition.condition);
779 [[maybe_unused]]
auto cursor = stmt.ExecuteWithVariants(_boundInputs);
782template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
786 auto records = std::vector<Record> {};
788 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
790 RecordTableName<Record>,
791 this->_query.searchCondition.tableAlias,
792 this->_query.searchCondition.tableJoins,
793 this->_query.searchCondition.condition,
794 this->_query.orderBy,
795 this->_query.groupBy));
796 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
802 if constexpr (QueryOptions.loadRelations)
804 for (
auto& record: records)
806 _dm.ConfigureRelationAutoLoading(record);
813template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
815#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
816 requires(is_aggregate_type(parent_of(
Field)))
818 requires std::is_member_object_pointer_v<
decltype(
Field)>
823 auto result = std::vector<value_type> {};
826 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
827 FullyQualifiedNamesOf<Field>.string_view(),
828 RecordTableName<Record>,
829 this->_query.searchCondition.tableAlias,
830 this->_query.searchCondition.tableJoins,
831 this->_query.searchCondition.condition,
832 this->_query.orderBy,
833 this->_query.groupBy));
834 auto reader = stmt.ExecuteWithVariants(_boundInputs);
835 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
838 auto& value = result.emplace_back();
839 if (outputColumnsBound)
840 reader.BindOutputColumn(1, &value);
842 if (!reader.FetchRow())
848 if (!outputColumnsBound)
849 value = reader.GetColumn<value_type>(1);
855template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
856template <
auto... ReferencedFields>
857 requires(
sizeof...(ReferencedFields) >= 2)
860 auto records = std::vector<Record> {};
861 auto stmt = SqlStatement { _dm.Connection() };
863 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
864 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
865 RecordTableName<Record>,
866 this->_query.searchCondition.tableAlias,
867 this->_query.searchCondition.tableJoins,
868 this->_query.searchCondition.condition,
869 this->_query.orderBy,
870 this->_query.groupBy));
872 auto reader = stmt.ExecuteWithVariants(_boundInputs);
873 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
876 auto& record = records.emplace_back();
877 if (outputColumnsBound)
878#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
879 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
881 reader.BindOutputColumns(&(record.*ReferencedFields)...);
883 if (!reader.FetchRow())
888 if (!outputColumnsBound)
890 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
891 detail::GetAllColumns<ElementMask>(reader, record);
898template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
901 std::optional<Record> record {};
903 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
905 RecordTableName<Record>,
906 this->_query.searchCondition.tableAlias,
907 this->_query.searchCondition.tableJoins,
908 this->_query.searchCondition.condition,
909 this->_query.orderBy,
911 Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record);
912 if constexpr (QueryOptions.loadRelations)
915 _dm.ConfigureRelationAutoLoading(record.value());
920template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
922#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
923 requires(is_aggregate_type(parent_of(
Field)))
925 requires std::is_member_object_pointer_v<
decltype(
Field)>
929 auto constexpr count = 1;
931 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
932 FullyQualifiedNamesOf<Field>.string_view(),
933 RecordTableName<Record>,
934 this->_query.searchCondition.tableAlias,
935 this->_query.searchCondition.tableJoins,
936 this->_query.searchCondition.condition,
937 this->_query.orderBy,
939 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
944template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
945template <
auto... ReferencedFields>
946 requires(
sizeof...(ReferencedFields) >= 2)
949 auto optionalRecord = std::optional<Record> {};
951 auto stmt = SqlStatement { _dm.Connection() };
952 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
953 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
954 RecordTableName<Record>,
955 this->_query.searchCondition.tableAlias,
956 this->_query.searchCondition.tableJoins,
957 this->_query.searchCondition.condition,
958 this->_query.orderBy,
961 auto& record = optionalRecord.emplace();
962 auto reader = stmt.ExecuteWithVariants(_boundInputs);
963 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
964 if (outputColumnsBound)
965#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
966 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
968 reader.BindOutputColumns(&(record.*ReferencedFields)...);
970 if (!reader.FetchRow())
972 if (!outputColumnsBound)
974 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
975 detail::GetAllColumns<ElementMask>(reader, record);
978 if constexpr (QueryOptions.loadRelations)
979 _dm.ConfigureRelationAutoLoading(record);
981 return optionalRecord;
984template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
987 auto records = std::vector<Record> {};
990 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
992 RecordTableName<Record>,
993 this->_query.searchCondition.tableAlias,
994 this->_query.searchCondition.tableJoins,
995 this->_query.searchCondition.condition,
996 this->_query.orderBy,
998 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1000 if constexpr (QueryOptions.loadRelations)
1002 for (
auto& record: records)
1003 _dm.ConfigureRelationAutoLoading(record);
1008template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1011 auto records = std::vector<Record> {};
1013 records.reserve(limit);
1015 _formatter.SelectRange(this->_query.distinct,
1017 RecordTableName<Record>,
1018 this->_query.searchCondition.tableAlias,
1019 this->_query.searchCondition.tableJoins,
1020 this->_query.searchCondition.condition,
1021 !this->_query.orderBy.empty()
1022 ? this->_query.orderBy
1023 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1024 this->_query.groupBy,
1027 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1028 if constexpr (QueryOptions.loadRelations)
1030 for (
auto& record: records)
1031 _dm.ConfigureRelationAutoLoading(record);
1036template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1037template <
auto... ReferencedFields>
1040 auto records = std::vector<Record> {};
1042 records.reserve(limit);
1044 _formatter.SelectRange(this->_query.distinct,
1045 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
1046 RecordTableName<Record>,
1047 this->_query.searchCondition.tableAlias,
1048 this->_query.searchCondition.tableJoins,
1049 this->_query.searchCondition.condition,
1050 !this->_query.orderBy.empty()
1051 ? this->_query.orderBy
1052 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1053 this->_query.groupBy,
1057 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1058 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1061 auto& record = records.emplace_back();
1062 if (outputColumnsBound)
1063#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1064 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1066 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1068 if (!reader.FetchRow())
1073 if (!outputColumnsBound)
1075 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1076 detail::GetAllColumns<ElementMask>(reader, record);
1080 if constexpr (QueryOptions.loadRelations)
1082 for (
auto& record: records)
1083 _dm.ConfigureRelationAutoLoading(record);
1089template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1090template <
auto... ReferencedFields>
1093 auto records = std::vector<Record> {};
1096 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1097 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
1098 RecordTableName<Record>,
1099 this->_query.searchCondition.tableAlias,
1100 this->_query.searchCondition.tableJoins,
1101 this->_query.searchCondition.condition,
1102 this->_query.orderBy,
1105 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1106 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1109 auto& record = records.emplace_back();
1110 if (outputColumnsBound)
1111#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1112 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1114 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1116 if (!reader.FetchRow())
1121 if (!outputColumnsBound)
1123 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1124 detail::GetAllColumns<ElementMask>(reader, record);
1128 if constexpr (QueryOptions.loadRelations)
1130 for (
auto& record: records)
1131 _dm.ConfigureRelationAutoLoading(record);
1137template <
typename Record, DataMapperOptions QueryOptions>
1140 std::vector<Record>* records)
1144 Record& record = records->emplace_back();
1145 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1147 records->pop_back();
1153template <
typename Record, DataMapperOptions QueryOptions>
1154void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1155 SqlResultCursor reader,
1156 std::optional<Record>* optionalRecord)
1158 Record& record = optionalRecord->emplace();
1159 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1160 optionalRecord->reset();
1163template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions>
1164void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1165 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1169 auto& record = records->emplace_back();
1170 auto& [firstRecord, secondRecord] = record;
1172 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1173 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1175 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1176 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1177 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1179 if (canSafelyBindAll)
1181 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1182 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1185 if (!reader.FetchRow())
1187 records->pop_back();
1191 if (!canSafelyBindAll)
1192 detail::GetAllColumns(reader, record);
1196template <
typename Record>
1202 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1208 if constexpr (Value::IsOptional)
1210 if (!value.Value().has_value())
1212 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1216 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1219 else if constexpr (IsBelongsTo<Value>)
1221 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1223 else if constexpr (std::same_as<typename Value::ValueType, char>)
1228 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1231 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1232 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1234 return "{\n" + std::move(str) +
"\n}";
1237template <
typename Record>
1243 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1244 detail::PopulateCreateTableBuilder<Record>(createTable);
1245 return migration.GetPlan().ToSql();
1248template <
typename FirstRecord,
typename... MoreRecords>
1251 std::vector<std::string> output;
1252 auto const append = [&output](
auto const& sql) {
1253 output.insert(output.end(), sql.begin(), sql.end());
1255 append(CreateTableString<FirstRecord>(serverType));
1256 (append(CreateTableString<MoreRecords>(serverType)), ...);
1260template <
typename Record>
1265 ZoneScopedN(
"DataMapper::CreateTable");
1266 ZoneTextObject(RecordTableName<Record>);
1268 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1269 for (
auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
1273template <
typename FirstRecord,
typename... MoreRecords>
1276 CreateTable<FirstRecord>();
1277 (CreateTable<MoreRecords>(), ...);
1280template <
typename Record>
1281std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
1283 std::optional<RecordPrimaryKeyType<Record>> result;
1284 Reflection::EnumerateMembers(
1285 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1286 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1287 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1289 using ValueType = PrimaryKeyType::ValueType;
1290 if constexpr (std::same_as<ValueType, SqlGuid>)
1292 if (!primaryKeyField.Value())
1297 else if constexpr (
requires { ValueType {} + 1; })
1299 if (primaryKeyField.Value() == ValueType {})
1301 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1302 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1303 FieldNameAt<PrimaryKeyIndex, Record>,
1304 RecordTableName<Record>));
1305 result = maxId.value_or(ValueType {}) + 1;
1313template <DataMapper::PrimaryKeySource UsePkOverr
ide,
typename Record>
1314RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
1315 Record
const& record,
1316 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
1319 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1321 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1323#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1324 constexpr auto ctx = std::meta::access_context::current();
1325 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1327 using FieldType =
typename[:std::meta::type_of(el):];
1328 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1329 query.Set(FieldNameOf<el>, SqlWildcard);
1332 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1333 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1334 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1340#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1342 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1344 using FieldType =
typename[:std::meta::type_of(el):];
1345 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1347 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1354 Reflection::CallOnMembers(record,
1355 [
this, &pkOverride, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
1356 Name
const& name, FieldType
const& field)
mutable {
1357 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1359 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1366 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1368 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1369 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1370 else if constexpr (HasPrimaryKey<Record>)
1372 if constexpr (UsePkOverride == PrimaryKeySource::Override)
1375 return RecordPrimaryKeyOf(record).Value();
1381template <
typename Record>
1385 return CreateInternal<PrimaryKeySource::Record>(record);
1388template <DataMapperOptions QueryOptions,
typename Record>
1392 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
1394 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
1396 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
1398 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1399 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
1401 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
1404template <DataMapperOptions QueryOptions,
typename Record>
1407 static_assert(!std::is_const_v<Record>);
1410 ZoneScopedN(
"DataMapper::Create");
1411 ZoneTextObject(RecordTableName<Record>);
1413 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
1415 SetId(record, *generatedKey);
1417 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
1419 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1422 SetModifiedState<ModifiedState::NotModified>(record);
1424 if constexpr (QueryOptions.loadRelations)
1427 if constexpr (HasPrimaryKey<Record>)
1431template <
typename Record>
1436 bool modified =
false;
1438#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1439 auto constexpr ctx = std::meta::access_context::current();
1440 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1442 if constexpr (
requires { record.[:el:].IsModified(); })
1444 modified = modified || record.[:el:].IsModified();
1448 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1449 if constexpr (
requires { field.IsModified(); })
1451 modified = modified || field.IsModified();
1459template <
typename Record>
1464 ZoneScopedN(
"DataMapper::Update");
1465 ZoneTextObject(RecordTableName<Record>);
1467 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1469#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1470 auto constexpr ctx = std::meta::access_context::current();
1471 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1473 using FieldType =
typename[:std::meta::type_of(el):];
1476 if (record.[:el:].IsModified())
1477 query.Set(FieldNameOf<el>, SqlWildcard);
1478 if constexpr (IsPrimaryKey<FieldType>)
1479 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1483 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1484 if (field.IsModified())
1485 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1488 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1489 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1496#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1497 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1499 if (record.[:el:].IsModified())
1505 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1507 using FieldType =
typename[:std::meta::type_of(el):];
1508 if constexpr (FieldType::IsPrimaryKey)
1515 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1516 if (field.IsModified())
1521 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1522 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1527 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1529 SetModifiedState<ModifiedState::NotModified>(record);
1532template <
typename Record>
1537 ZoneScopedN(
"DataMapper::Delete");
1538 ZoneTextObject(RecordTableName<Record>);
1540 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1542#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1543 auto constexpr ctx = std::meta::access_context::current();
1544 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1546 using FieldType =
typename[:std::meta::type_of(el):];
1547 if constexpr (FieldType::IsPrimaryKey)
1548 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1551 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& ) {
1552 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1553 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1559#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1561 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1563 using FieldType =
typename[:std::meta::type_of(el):];
1564 if constexpr (FieldType::IsPrimaryKey)
1571 Reflection::CallOnMembersWithoutName(
1572 record, [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
1573 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1578 auto cursor = _stmt.
Execute();
1583template <
typename Record,
DataMapperOptions QueryOptions,
typename... PrimaryKeyTypes>
1588 ZoneScopedN(
"DataMapper::QuerySingle(PK)");
1589 ZoneTextObject(RecordTableName<Record>);
1591 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1593 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1596 queryBuilder.Field(FieldNameAt<I, Record>);
1598 if constexpr (FieldType::IsPrimaryKey)
1599 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1603 _stmt.
Prepare(queryBuilder.First());
1604 auto reader = _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1606 auto resultRecord = std::optional<Record> { Record {} };
1608 return std::nullopt;
1611 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1613 if constexpr (QueryOptions.loadRelations)
1619 return resultRecord;
1622template <
typename Record,
typename... Args>
1627 ZoneScopedN(
"DataMapper::QuerySingle(Builder)");
1628 ZoneTextObject(RecordTableName<Record>);
1630 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1634 auto const composedSql = selectQuery.
First().ToSql();
1635 ZoneTextObject(composedSql);
1637 auto reader = _stmt.
Execute(std::forward<Args>(args)...);
1639 auto resultRecord = std::optional<Record> { Record {} };
1641 return std::nullopt;
1644 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1646 return resultRecord;
1652template <
typename Record, DataMapperOptions QueryOptions,
typename... InputParameters>
1654 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1656 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1658 ZoneScopedN(
"DataMapper::Query(ComposedQuery)");
1659 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1662template <
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
1663std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1665 ZoneScopedN(
"DataMapper::Query(string)");
1666 ZoneTextObject(sqlQueryString);
1668 auto result = std::vector<Record> {};
1669 if constexpr (std::same_as<Record, SqlVariantRow>)
1671 _stmt.
Prepare(sqlQueryString);
1676 auto& record = result.emplace_back();
1677 record.reserve(numResultColumns);
1678 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1686 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1688 _stmt.
Prepare(sqlQueryString);
1689 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1693 auto& record = result.emplace_back();
1695 if (canSafelyBindOutputColumns)
1696 BindOutputColumns(record, reader);
1698 if (!reader.FetchRow())
1701 if (!canSafelyBindOutputColumns)
1702 detail::GetAllColumns(reader, record);
1708 for (
auto& record: result)
1710 SetModifiedState<ModifiedState::NotModified>(record);
1711 if constexpr (QueryOptions.loadRelations)
1719template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
1721std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
1723 using value_type = std::tuple<First, Second, Rest...>;
1724 auto result = std::vector<value_type> {};
1726 ZoneScopedN(
"DataMapper::Query(ComposedQuery -> tuple)");
1727 auto const tupleSql = selectQuery.ToSql();
1728 ZoneTextObject(tupleSql);
1730 auto reader = _stmt.
Execute();
1732 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1735 if constexpr (I > 0)
1737 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1738 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1739 }(std::make_index_sequence<I> {});
1744 auto const BindElements = [&](
auto& record) {
1745 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1746 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1747 auto& element = std::get<I>(record);
1748 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1749 this->BindOutputColumns<TupleElement, offset>(element, reader);
1753 auto const GetElements = [&](
auto& record) {
1754 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1755 auto& element = std::get<I>(record);
1756 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1757 detail::GetAllColumns(reader, element, offset - 1);
1761 bool const canSafelyBindOutputColumns = [&]() {
1763 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1764 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1772 auto& record = result.emplace_back();
1774 if (canSafelyBindOutputColumns)
1775 BindElements(record);
1777 if (!reader.FetchRow())
1780 if (!canSafelyBindOutputColumns)
1781 GetElements(record);
1787 for (
auto& record: result)
1789 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1790 auto& element = std::get<I>(record);
1791 SetModifiedState<ModifiedState::NotModified>(element);
1792 if constexpr (QueryOptions.loadRelations)
1802template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
1804 InputParameters&&... inputParameters)
1808 ZoneScopedN(
"DataMapper::Query(ComposedQuery, ElementMask)");
1809 auto const maskedSql = selectQuery.ToSql();
1810 ZoneTextObject(maskedSql);
1813 auto records = std::vector<Record> {};
1816 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1818 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1822 auto& record = records.emplace_back();
1824 if (canSafelyBindOutputColumns)
1825 BindOutputColumns<ElementMask>(record, reader);
1827 if (!reader.FetchRow())
1830 if (!canSafelyBindOutputColumns)
1831 detail::GetAllColumns<ElementMask>(reader, record);
1837 for (
auto& record: records)
1839 SetModifiedState<ModifiedState::NotModified>(record);
1840 if constexpr (QueryOptions.loadRelations)
1847template <DataMapper::ModifiedState state,
typename Record>
1850 static_assert(!std::is_const_v<Record>);
1853 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1854 if constexpr (
requires { field.SetModified(
false); })
1856 if constexpr (state == ModifiedState::Modified)
1857 field.SetModified(
true);
1859 field.SetModified(
false);
1864template <
typename Record,
typename Callable>
1865inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1869 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1870 if constexpr (IsField<FieldType>)
1872 if constexpr (FieldType::IsPrimaryKey)
1874 return callable.template operator()<I, FieldType>(field);
1880template <
typename Record,
typename Callable>
1881inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1883 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1885 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1886 if constexpr (IsField<FieldType>)
1888 if constexpr (FieldType::IsPrimaryKey)
1890 return callable.template operator()<I, FieldType>();
1896template <
typename Record,
typename Callable>
1897inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1899 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1901 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1902 if constexpr (IsBelongsTo<FieldType>)
1904 return callable.template operator()<I, FieldType>();
1909template <
typename FieldType>
1910std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1912 using ReferencedRecord = FieldType::ReferencedRecord;
1914 ZoneScopedN(
"DataMapper::LoadBelongsTo");
1915 ZoneTextObject(RecordTableName<ReferencedRecord>);
1917 std::optional<ReferencedRecord> record { std::nullopt };
1919#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1920 auto constexpr ctx = std::meta::access_context::current();
1921 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1923 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1924 if constexpr (IsField<BelongsToFieldType>)
1925 if constexpr (BelongsToFieldType::IsPrimaryKey)
1927 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1928 record = std::move(result);
1931 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1935 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1936 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1937 record = std::move(result);
1940 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1946template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1947void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1949 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1950 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1952 using FieldType = HasMany<OtherRecord>;
1953 using ReferencedRecord = FieldType::ReferencedRecord;
1955 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1956 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1958 .Build([&](
auto& query) {
1959 Reflection::EnumerateMembers<ReferencedRecord>(
1960 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1961 if constexpr (FieldWithStorage<ReferencedFieldType>)
1963 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1967 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard)
1968 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
1969 callback(query, primaryKeyField);
1973template <
size_t FieldIndex,
typename OtherRecord>
1974SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
1976 return _connection.
Query(RecordTableName<OtherRecord>)
1978 .
Build([](
auto& q) {
1979 Reflection::EnumerateMembers<OtherRecord>([&]<
size_t I,
typename F>() {
1980 if constexpr (FieldWithStorage<F>)
1981 q.Field(FieldNameAt<I, OtherRecord>);
1984 .Where(FieldNameAt<FieldIndex, OtherRecord>, SqlWildcard)
1985 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
1988template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1989void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1991 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1992 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1994 ZoneScopedN(
"DataMapper::LoadHasMany");
1995 ZoneTextObject(RecordTableName<OtherRecord>);
1997 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1998 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
2002template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2003void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
2005 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2006 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2008 ZoneScopedN(
"DataMapper::LoadHasOneThrough");
2009 ZoneTextObject(RecordTableName<ReferencedRecord>);
2012 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2014 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
2016 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
2018 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
2023 _connection.
Query(RecordTableName<ReferencedRecord>)
2025 .Build([&](
auto& query) {
2026 Reflection::EnumerateMembers<ReferencedRecord>(
2027 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2028 if constexpr (FieldWithStorage<ReferencedFieldType>)
2030 query.Field(SqlQualifiedTableColumnName {
2031 RecordTableName<ReferencedRecord>,
2032 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2036 .InnerJoin(RecordTableName<ThroughRecord>,
2037 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
2038 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2039 .InnerJoin(RecordTableName<Record>,
2040 FieldNameAt<PrimaryKeyIndex, Record>,
2041 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2042 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2044 SqlQualifiedTableColumnName {
2045 RecordTableName<Record>,
2046 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2049 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
2051 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
2059template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue>
2060std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue
const& pkValue)
2062 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2064 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2065 std::shared_ptr<ReferencedRecord> result;
2068 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
2070 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
2072 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
2074 _connection.
Query(RecordTableName<ReferencedRecord>)
2076 .Build([&](
auto& query) {
2077 Reflection::EnumerateMembers<ReferencedRecord>(
2078 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2079 if constexpr (FieldWithStorage<ReferencedFieldType>)
2081 query.Field(SqlQualifiedTableColumnName {
2082 RecordTableName<ReferencedRecord>,
2083 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2087 .InnerJoin(RecordTableName<ThroughRecord>,
2088 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
2089 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2090 .InnerJoin(RecordTableName<Record>,
2091 FieldNameAt<PrimaryKeyIndex, Record>,
2092 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2093 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2095 SqlQualifiedTableColumnName {
2096 RecordTableName<Record>,
2097 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2100 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
2101 result = std::make_shared<ReferencedRecord>(std::move(*link));
2109template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
2110void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
2112 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2115 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2117 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
2118 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2119 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2122 CallOnBelongsTo<ThroughRecord>(
2123 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
2124 using ThroughBelongsToReferenceRecordFieldType =
2125 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2126 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2129 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2131 .
Build([&](
auto& query) {
2132 Reflection::EnumerateMembers<ReferencedRecord>(
2133 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2134 if constexpr (FieldWithStorage<ReferencedFieldType>)
2136 query.Field(SqlQualifiedTableColumnName {
2137 RecordTableName<ReferencedRecord>,
2138 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2142 .InnerJoin(RecordTableName<ThroughRecord>,
2143 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2144 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2145 FieldNameAt<PrimaryKeyIndex, Record> })
2147 SqlQualifiedTableColumnName {
2148 RecordTableName<ThroughRecord>,
2149 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2152 callback(query, primaryKeyField);
2160template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue,
typename Callable>
2161void DataMapper::CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback)
2163 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2165 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2168 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
2169 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2170 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2173 CallOnBelongsTo<ThroughRecord>(
2174 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
2175 using ThroughBelongsToReferenceRecordFieldType =
2176 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2177 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2180 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2182 .
Build([&](
auto& query) {
2183 Reflection::EnumerateMembers<ReferencedRecord>(
2184 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2185 if constexpr (FieldWithStorage<ReferencedFieldType>)
2187 query.Field(SqlQualifiedTableColumnName {
2188 RecordTableName<ReferencedRecord>,
2189 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2193 .InnerJoin(RecordTableName<ThroughRecord>,
2194 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2195 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2196 FieldNameAt<PrimaryKeyIndex, Record> })
2198 SqlQualifiedTableColumnName {
2199 RecordTableName<ThroughRecord>,
2200 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2203 callback(query, pkValue);
2210template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2211void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2213 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2215 ZoneScopedN(
"DataMapper::LoadHasManyThrough");
2216 ZoneTextObject(RecordTableName<ReferencedRecord>);
2218 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2219 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2220 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2224template <
typename Record>
2227 static_assert(!std::is_const_v<Record>);
2230 ZoneScopedN(
"DataMapper::LoadRelations");
2231 ZoneTextObject(RecordTableName<Record>);
2233#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2234 constexpr auto ctx = std::meta::access_context::current();
2235 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2237 using FieldType =
typename[:std::meta::type_of(el):];
2238 if constexpr (IsBelongsTo<FieldType>)
2240 auto& field = record.[:el:];
2241 field = LoadBelongsTo<FieldType>(field.Value());
2243 else if constexpr (IsHasMany<FieldType>)
2245 LoadHasMany<el>(record, record.[:el:]);
2247 else if constexpr (IsHasOneThrough<FieldType>)
2249 LoadHasOneThrough(record, record.[:el:]);
2251 else if constexpr (IsHasManyThrough<FieldType>)
2253 LoadHasManyThrough(record, record.[:el:]);
2257 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2258 if constexpr (IsBelongsTo<FieldType>)
2260 field = LoadBelongsTo<FieldType>(field.Value());
2262 else if constexpr (IsHasMany<FieldType>)
2264 LoadHasMany<FieldIndex>(record, field);
2266 else if constexpr (IsHasOneThrough<FieldType>)
2268 LoadHasOneThrough(record, field);
2270 else if constexpr (IsHasManyThrough<FieldType>)
2272 LoadHasManyThrough(record, field);
2279template <
typename Record,
typename ValueType>
2280inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2285#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2287 auto constexpr ctx = std::meta::access_context::current();
2288 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2290 using FieldType =
typename[:std::meta::type_of(el):];
2291 if constexpr (IsField<FieldType>)
2293 if constexpr (FieldType::IsPrimaryKey)
2295 record.[:el:] = std::forward<ValueType>(
id);
2300 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
2301 if constexpr (IsField<FieldType>)
2303 if constexpr (FieldType::IsPrimaryKey)
2305 field = std::forward<FieldType>(
id);
2313template <
typename Record,
size_t InitialOffset>
2314inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2317 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2321template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2322Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2325 static_assert(!std::is_const_v<Record>);
2327#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2328 auto constexpr ctx = std::meta::access_context::current();
2329 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2330 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2332 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2333 using FieldType =
typename[:std::meta::type_of(el):];
2334 if constexpr (IsField<FieldType>)
2338 else if constexpr (SqlOutputColumnBinder<FieldType>)
2344 Reflection::EnumerateMembers<ElementMask>(
2345 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2346 if constexpr (IsField<Field>)
2350 else if constexpr (SqlOutputColumnBinder<Field>)
2359template <
typename Record>
2365 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2366 if constexpr (IsBelongsTo<FieldType>)
2368 field.SetAutoLoader(
typename FieldType::Loader {
2369 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2371 return dm.LoadBelongsTo<FieldType>(value);
2375 if constexpr (IsHasMany<FieldType>)
2377 if constexpr (HasPrimaryKey<Record>)
2379 using ReferencedRecord = FieldType::ReferencedRecord;
2384 .count = [pkValue]() ->
size_t {
2386 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2387 dm._stmt.
Prepare(selectQuery.Count());
2394 .all = [pkValue]() -> FieldType::ReferencedRecordList {
2396 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2397 return detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pkValue));
2400 [pkValue](
auto const& each) {
2402 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2404 stmt.Prepare(selectQuery.All());
2405 auto cursor = stmt.Execute(pkValue);
2407 auto referencedRecord = ReferencedRecord {};
2408 dm.BindOutputColumns(referencedRecord, cursor);
2413 each(referencedRecord);
2414 dm.BindOutputColumns(referencedRecord, cursor);
2420 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
2422 using ReferencedRecord = FieldType::ReferencedRecord;
2423 using ThroughRecord = FieldType::ThroughRecord;
2428 .loadReference = [pkValue]() -> std::shared_ptr<ReferencedRecord> {
2430 return dm.LoadHasOneThroughByPK<ReferencedRecord, ThroughRecord, Record>(pkValue);
2434 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
2436 using ReferencedRecord = FieldType::ReferencedRecord;
2437 using ThroughRecord = FieldType::ThroughRecord;
2442 .count = [pkValue]() ->
size_t {
2446 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2448 dm._stmt.
Prepare(selectQuery.Count());
2455 .all = [pkValue]() -> FieldType::ReferencedRecordList {
2458 typename FieldType::ReferencedRecordList result;
2459 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2461 result = detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pk));
2466 [pkValue](
auto const& each) {
2469 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2472 stmt.Prepare(selectQuery.All());
2473 auto cursor = stmt.Execute(pk);
2474 auto referencedRecord = ReferencedRecord {};
2475 dm.BindOutputColumns(referencedRecord, cursor);
2480 each(referencedRecord);
2481 dm.BindOutputColumns(referencedRecord, cursor);
2489#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2490 constexpr auto ctx = std::meta::access_context::current();
2492 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2493 constexpr auto localctx = std::meta::access_context::current();
2494 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2495 using FieldType =
typename[:std::meta::type_of(members[I]):];
2496 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2499 Reflection::EnumerateMembers(record, callback);
2503template <
typename T>
2506 ZoneScopedN(
"DataMapper::Execute(string)");
2507 ZoneTextObject(sqlQueryString);