134 template <
typename Record>
135 static std::string
Inspect(Record
const& record);
138 template <
typename Record>
142 template <
typename FirstRecord,
typename... MoreRecords>
146 template <
typename Record>
150 template <
typename FirstRecord,
typename... MoreRecords>
159 RecordPrimaryKeyType<Record>
Create(Record& record);
166 template <
typename Record>
167 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
178 [[nodiscard]] RecordPrimaryKeyType<Record>
CreateCopyOf(Record
const& originalRecord);
184 template <
typename Record,
typename... PrimaryKeyTypes>
185 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
194 template <
typename Record,
typename... PrimaryKeyTypes>
198 template <
typename Record,
typename... InputParameters>
199 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
230 template <
typename Record,
typename... InputParameters>
231 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
257 template <
typename ElementMask,
typename Record,
typename... InputParameters>
258 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
284 template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions = {}>
286 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
289 template <
typename FirstRecord,
typename NextRecord,
DataMapperOptions QueryOptions = {}>
290 requires DataMapperRecord<FirstRecord> && DataMapperRecord<NextRecord>
291 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>, QueryOptions>
Query()
295 auto const emplaceRecordsFrom = [&fields]<
typename Record>() {
296 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
299 fields += std::format(R
"("{}"."{}")", RecordTableName<Record>, FieldNameAt<I, Record>);
303 emplaceRecordsFrom.template operator()<FirstRecord>();
304 emplaceRecordsFrom.template operator()<NextRecord>();
306 return SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>, QueryOptions>(*
this, std::move(fields));
321 template <
typename Record, DataMapperOptions QueryOptions = {}>
325 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
329 fields += RecordTableName<Record>;
331 fields += FieldNameAt<I, Record>;
347 template <
typename Record>
348 void Update(Record& record);
351 template <
typename Record>
352 std::size_t
Delete(Record
const& record);
357 return _connection.
Query(tableName);
361 template <
typename Record>
362 bool IsModified(Record
const& record)
const noexcept;
373 template <ModifiedState state,
typename Record>
377 template <
typename Record>
384 template <
typename Record>
391 template <
typename T>
392 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
401 template <
typename Record,
typename... Args>
404 template <
typename Record,
typename ValueType>
405 void SetId(Record& record, ValueType&&
id);
407 template <
typename Record,
size_t InitialOffset = 1>
408 Record& BindOutputColumns(Record& record);
410 template <
typename Record,
size_t InitialOffset = 1>
411 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
413 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
414 Record& BindOutputColumns(Record& record);
416 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
417 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
419 template <
typename FieldType>
420 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
422 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
425 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
428 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
431 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
432 void CallOnHasMany(Record& record, Callable
const& callback);
434 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
435 void CallOnHasManyThrough(Record& record, Callable
const& callback);
437 enum class PrimaryKeySource : std::uint8_t
443 template <
typename Record>
444 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record
const& record);
446 template <PrimaryKeySource UsePkOverr
ide,
typename Record>
447 RecordPrimaryKeyType<Record> CreateInternal(
448 Record
const& record,
449 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
450 pkOverride = std::nullopt);
452 SqlConnection _connection;
460 template <
typename FieldType>
461 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
463 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
470 if constexpr (IsField<FieldType>)
472 if constexpr (detail::OneOf<
typename FieldType::ValueType,
478 || IsSqlDynamicString<typename FieldType::ValueType>
479 || IsSqlDynamicBinary<typename FieldType::ValueType>)
488 template <DataMapperRecord Record>
489 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
491 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
495 Reflection::EnumerateMembers<Record>([&result]<
size_t I,
typename Field>() {
496 if constexpr (IsField<Field>)
498 if constexpr (detail::OneOf<
typename Field::ValueType,
504 || IsSqlDynamicString<typename Field::ValueType>
505 || IsSqlDynamicBinary<typename Field::ValueType>)
515 template <
typename Record>
516 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
518 Reflection::EnumerateMembers(record,
519 [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
520 if constexpr (IsField<Field>)
522 reader->BindOutputColumn(i++, &field.MutableValue());
524 else if constexpr (IsBelongsTo<Field>)
526 reader->BindOutputColumn(i++, &field.MutableValue());
528 else if constexpr (SqlOutputColumnBinder<Field>)
530 reader->BindOutputColumn(i++, &field);
535 template <
typename Record>
536 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
538 BindAllOutputColumnsWithOffset(reader, record, 1);
544 template <
typename ElementMask,
typename Record>
545 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
547 Reflection::EnumerateMembers<ElementMask>(
548 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
550 if constexpr (IsField<Field>)
553 field.MutableValue() =
554 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
556 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
558 else if constexpr (SqlGetColumnNativeType<Field>)
560 if constexpr (IsOptionalBelongsTo<Field>)
561 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
563 field = reader->GetColumn<Field>(indexFromQuery);
568 template <
typename Record>
569 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
571 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(
572 reader, record, indexFromQuery);
575 template <
typename FirstRecord,
typename SecondRecord>
577 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
579 auto& [firstRecord, secondRecord] = record;
581 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
582 if constexpr (IsField<Field>)
585 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(I + 1);
587 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(I + 1);
589 else if constexpr (SqlGetColumnNativeType<Field>)
592 field = reader->GetNullableColumn<
typename Field::BaseType>(I + 1);
594 field = reader->GetColumn<Field>(I + 1);
598 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
599 if constexpr (IsField<Field>)
602 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(
603 Reflection::CountMembers<FirstRecord> + I + 1);
605 field.MutableValue() =
606 reader->GetColumn<
typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
608 else if constexpr (SqlGetColumnNativeType<Field>)
612 reader->GetNullableColumn<
typename Field::BaseType>(Reflection::CountMembers<FirstRecord> + I + 1);
614 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
619 template <
typename Record>
620 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
622 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
624 if (outputColumnsBound)
625 BindAllOutputColumns(reader, record);
627 if (!reader.FetchRow())
630 if (!outputColumnsBound)
631 GetAllColumns(reader, record);
637template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
638inline SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::SqlCoreDataMapperQueryBuilder(
639 DataMapper& dm, std::string fields)
noexcept:
641 _formatter { dm.Connection().QueryFormatter() },
642 _fields { std::move(fields) }
644 this->_query.searchCondition.inputBindings = &_boundInputs;
647template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
651 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
652 RecordTableName<Record>,
653 this->_query.searchCondition.tableAlias,
654 this->_query.searchCondition.tableJoins,
655 this->_query.searchCondition.condition));
656 stmt.ExecuteWithVariants(_boundInputs);
657 auto reader = stmt.GetResultCursor();
658 if (reader.FetchRow())
659 return reader.GetColumn<
size_t>(1);
663template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
668 auto const query = _formatter.SelectFirst(this->_query.distinct,
670 RecordTableName<Record>,
671 this->_query.searchCondition.tableAlias,
672 this->_query.searchCondition.tableJoins,
673 this->_query.searchCondition.condition,
674 this->_query.orderBy,
678 stmt.ExecuteWithVariants(_boundInputs);
679 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
684template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
689 auto const query = _formatter.Delete(RecordTableName<Record>,
690 this->_query.searchCondition.tableAlias,
691 this->_query.searchCondition.tableJoins,
692 this->_query.searchCondition.condition);
696 stmt.ExecuteWithVariants(_boundInputs);
700template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
704 auto records = std::vector<Record> {};
706 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
708 RecordTableName<Record>,
709 this->_query.searchCondition.tableAlias,
710 this->_query.searchCondition.tableJoins,
711 this->_query.searchCondition.condition,
712 this->_query.orderBy,
713 this->_query.groupBy));
714 stmt.ExecuteWithVariants(_boundInputs);
715 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
721 if constexpr (QueryOptions.loadRelations)
723 for (
auto& record: records)
725 _dm.ConfigureRelationAutoLoading(record);
732template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
734#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
735 requires(is_aggregate_type(parent_of(
Field)))
737 requires std::is_member_object_pointer_v<
decltype(
Field)>
742 auto result = std::vector<value_type> {};
745 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
746 FullyQualifiedNamesOf<Field>.string_view(),
747 RecordTableName<Record>,
748 this->_query.searchCondition.tableAlias,
749 this->_query.searchCondition.tableJoins,
750 this->_query.searchCondition.condition,
751 this->_query.orderBy,
752 this->_query.groupBy));
753 stmt.ExecuteWithVariants(_boundInputs);
755 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
758 auto& value = result.emplace_back();
759 if (outputColumnsBound)
760 reader.BindOutputColumn(1, &value);
768 if (!outputColumnsBound)
775template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
776template <
auto... ReferencedFields>
777 requires(
sizeof...(ReferencedFields) >= 2)
780 auto records = std::vector<Record> {};
781 auto stmt = SqlStatement { _dm.Connection() };
783 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
784 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
785 RecordTableName<Record>,
786 this->_query.searchCondition.tableAlias,
787 this->_query.searchCondition.tableJoins,
788 this->_query.searchCondition.condition,
789 this->_query.orderBy,
790 this->_query.groupBy));
791 stmt.ExecuteWithVariants(_boundInputs);
793 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
794 SqlResultCursor reader = stmt.GetResultCursor();
797 auto& record = records.emplace_back();
798 if (outputColumnsBound)
799#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
800 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
802 reader.BindOutputColumns(&(record.*ReferencedFields)...);
804 if (!reader.FetchRow())
809 if (!outputColumnsBound)
811 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
812 detail::GetAllColumns<ElementMask>(reader, record);
819template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
822 std::optional<Record> record {};
824 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
826 RecordTableName<Record>,
827 this->_query.searchCondition.tableAlias,
828 this->_query.searchCondition.tableJoins,
829 this->_query.searchCondition.condition,
830 this->_query.orderBy,
832 stmt.ExecuteWithVariants(_boundInputs);
833 Derived::ReadResult(stmt.Connection().ServerType(), stmt.GetResultCursor(), &record);
834 if constexpr (QueryOptions.loadRelations)
837 _dm.ConfigureRelationAutoLoading(record.value());
842template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
844#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
845 requires(is_aggregate_type(parent_of(
Field)))
847 requires std::is_member_object_pointer_v<
decltype(
Field)>
851 auto constexpr count = 1;
853 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
854 FullyQualifiedNamesOf<Field>.string_view(),
855 RecordTableName<Record>,
856 this->_query.searchCondition.tableAlias,
857 this->_query.searchCondition.tableJoins,
858 this->_query.searchCondition.condition,
859 this->_query.orderBy,
861 stmt.ExecuteWithVariants(_boundInputs);
862 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
867template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
868template <
auto... ReferencedFields>
869 requires(
sizeof...(ReferencedFields) >= 2)
872 auto optionalRecord = std::optional<Record> {};
874 auto stmt = SqlStatement { _dm.Connection() };
875 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
876 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
877 RecordTableName<Record>,
878 this->_query.searchCondition.tableAlias,
879 this->_query.searchCondition.tableJoins,
880 this->_query.searchCondition.condition,
881 this->_query.orderBy,
883 stmt.ExecuteWithVariants(_boundInputs);
885 auto& record = optionalRecord.emplace();
886 SqlResultCursor reader = stmt.GetResultCursor();
887 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
888 if (outputColumnsBound)
889#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
890 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
892 reader.BindOutputColumns(&(record.*ReferencedFields)...);
894 if (!reader.FetchRow())
896 if (!outputColumnsBound)
898 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
899 detail::GetAllColumns<ElementMask>(reader, record);
902 if constexpr (QueryOptions.loadRelations)
903 _dm.ConfigureRelationAutoLoading(record);
905 return optionalRecord;
908template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
911 auto records = std::vector<Record> {};
914 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
916 RecordTableName<Record>,
917 this->_query.searchCondition.tableAlias,
918 this->_query.searchCondition.tableJoins,
919 this->_query.searchCondition.condition,
920 this->_query.orderBy,
922 stmt.ExecuteWithVariants(_boundInputs);
923 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
925 if constexpr (QueryOptions.loadRelations)
927 for (
auto& record: records)
928 _dm.ConfigureRelationAutoLoading(record);
933template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
936 auto records = std::vector<Record> {};
938 records.reserve(limit);
940 _formatter.SelectRange(this->_query.distinct,
942 RecordTableName<Record>,
943 this->_query.searchCondition.tableAlias,
944 this->_query.searchCondition.tableJoins,
945 this->_query.searchCondition.condition,
946 !this->_query.orderBy.empty()
947 ? this->_query.orderBy
948 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
949 this->_query.groupBy,
952 stmt.ExecuteWithVariants(_boundInputs);
953 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
954 if constexpr (QueryOptions.loadRelations)
956 for (
auto& record: records)
957 _dm.ConfigureRelationAutoLoading(record);
962template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
963template <
auto... ReferencedFields>
966 auto records = std::vector<Record> {};
967 auto stmt = SqlStatement { _dm.Connection() };
968 records.reserve(limit);
970 _formatter.SelectRange(this->_query.distinct,
971 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
972 RecordTableName<Record>,
973 this->_query.searchCondition.tableAlias,
974 this->_query.searchCondition.tableJoins,
975 this->_query.searchCondition.condition,
976 !this->_query.orderBy.empty()
977 ? this->_query.orderBy
978 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
979 this->_query.groupBy,
982 stmt.ExecuteWithVariants(_boundInputs);
984 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
985 SqlResultCursor reader = stmt.GetResultCursor();
988 auto& record = records.emplace_back();
989 if (outputColumnsBound)
990#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
991 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
993 reader.BindOutputColumns(&(record.*ReferencedFields)...);
995 if (!reader.FetchRow())
1000 if (!outputColumnsBound)
1002 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1003 detail::GetAllColumns<ElementMask>(reader, record);
1007 if constexpr (QueryOptions.loadRelations)
1009 for (
auto& record: records)
1010 _dm.ConfigureRelationAutoLoading(record);
1016template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1017template <
auto... ReferencedFields>
1020 auto records = std::vector<Record> {};
1021 auto stmt = SqlStatement { _dm.Connection() };
1023 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1024 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
1025 RecordTableName<Record>,
1026 this->_query.searchCondition.tableAlias,
1027 this->_query.searchCondition.tableJoins,
1028 this->_query.searchCondition.condition,
1029 this->_query.orderBy,
1031 stmt.ExecuteWithVariants(_boundInputs);
1033 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1034 SqlResultCursor reader = stmt.GetResultCursor();
1037 auto& record = records.emplace_back();
1038 if (outputColumnsBound)
1039#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1040 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1042 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1044 if (!reader.FetchRow())
1049 if (!outputColumnsBound)
1051 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1052 detail::GetAllColumns<ElementMask>(reader, record);
1056 if constexpr (QueryOptions.loadRelations)
1058 for (
auto& record: records)
1059 _dm.ConfigureRelationAutoLoading(record);
1065template <
typename Record, DataMapperOptions QueryOptions>
1066void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResults(SqlServerType sqlServerType,
1067 SqlResultCursor reader,
1068 std::vector<Record>* records)
1072 Record& record = records->emplace_back();
1073 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1075 records->pop_back();
1081template <
typename Record, DataMapperOptions QueryOptions>
1082void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1083 SqlResultCursor reader,
1084 std::optional<Record>* optionalRecord)
1086 Record& record = optionalRecord->emplace();
1087 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1088 optionalRecord->reset();
1091template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions>
1092void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1093 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1097 auto& record = records->emplace_back();
1098 auto& [firstRecord, secondRecord] = record;
1100 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1101 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1103 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1104 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1105 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1107 if (canSafelyBindAll)
1109 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1110 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1113 if (!reader.FetchRow())
1115 records->pop_back();
1119 if (!canSafelyBindAll)
1120 detail::GetAllColumns(reader, record);
1124template <
typename Record>
1130 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1136 if constexpr (Value::IsOptional)
1138 if (!value.Value().has_value())
1140 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1144 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1147 else if constexpr (IsBelongsTo<Value>)
1149 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1151 else if constexpr (std::same_as<typename Value::ValueType, char>)
1156 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1159 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1160 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1162 return "{\n" + std::move(str) +
"\n}";
1165template <
typename Record>
1171 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1172 detail::PopulateCreateTableBuilder<Record>(createTable);
1173 return migration.GetPlan().ToSql();
1176template <
typename FirstRecord,
typename... MoreRecords>
1179 std::vector<std::string> output;
1180 auto const append = [&output](
auto const& sql) {
1181 output.insert(output.end(), sql.begin(), sql.end());
1183 append(CreateTableString<FirstRecord>(serverType));
1184 (append(CreateTableString<MoreRecords>(serverType)), ...);
1188template <
typename Record>
1193 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1194 for (
auto const& sqlQueryString: sqlQueryStrings)
1198template <
typename FirstRecord,
typename... MoreRecords>
1201 CreateTable<FirstRecord>();
1202 (CreateTable<MoreRecords>(), ...);
1205template <
typename Record>
1206std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
1208 std::optional<RecordPrimaryKeyType<Record>> result;
1209 Reflection::EnumerateMembers(
1210 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1211 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1212 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1214 using ValueType =
typename PrimaryKeyType::ValueType;
1215 if constexpr (std::same_as<ValueType, SqlGuid>)
1217 if (!primaryKeyField.Value())
1222 else if constexpr (
requires { ValueType {} + 1; })
1224 if (primaryKeyField.Value() == ValueType {})
1226 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1227 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1228 FieldNameAt<PrimaryKeyIndex, Record>,
1229 RecordTableName<Record>));
1230 result = maxId.value_or(ValueType {}) + 1;
1238template <DataMapper::PrimaryKeySource UsePkOverr
ide,
typename Record>
1239RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
1240 Record
const& record,
1241 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
1244 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1246 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1248#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1249 constexpr auto ctx = std::meta::access_context::current();
1250 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1252 using FieldType =
typename[:std::meta::type_of(el):];
1253 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1254 query.Set(FieldNameOf<el>, SqlWildcard);
1257 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1258 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1259 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1265#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1267 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1269 using FieldType =
typename[:std::meta::type_of(el):];
1270 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1272 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1273 _stmt.BindInputParameter(i++, *pkOverride, std::meta::identifier_of(el));
1275 _stmt.BindInputParameter(i++, record.[:el:], std::meta::identifier_of(el));
1279 Reflection::CallOnMembers(record,
1280 [
this, &pkOverride, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
1281 Name
const& name, FieldType
const& field)
mutable {
1282 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1284 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1285 _stmt.BindInputParameter(i++, *pkOverride, name);
1287 _stmt.BindInputParameter(i++, field, name);
1293 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1294 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1295 else if constexpr (HasPrimaryKey<Record>)
1297 if constexpr (UsePkOverride == PrimaryKeySource::Override)
1300 return RecordPrimaryKeyOf(record).Value();
1304template <
typename Record>
1308 return CreateInternal<PrimaryKeySource::Record>(record);
1311template <DataMapperOptions QueryOptions,
typename Record>
1315 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
1317 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
1319 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
1321 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1322 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
1324 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
1327template <DataMapperOptions QueryOptions,
typename Record>
1330 static_assert(!std::is_const_v<Record>);
1333 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
1335 SetId(record, *generatedKey);
1337 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
1339 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1342 SetModifiedState<ModifiedState::NotModified>(record);
1344 if constexpr (QueryOptions.loadRelations)
1347 if constexpr (HasPrimaryKey<Record>)
1351template <
typename Record>
1356 bool modified =
false;
1358#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1359 auto constexpr ctx = std::meta::access_context::current();
1360 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1362 if constexpr (
requires { record.[:el:].IsModified(); })
1364 modified = modified || record.[:el:].IsModified();
1368 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1369 if constexpr (
requires { field.IsModified(); })
1371 modified = modified || field.IsModified();
1379template <
typename Record>
1384 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1386#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1387 auto constexpr ctx = std::meta::access_context::current();
1388 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1390 using FieldType =
typename[:std::meta::type_of(el):];
1393 if (record.[:el:].IsModified())
1394 query.Set(FieldNameOf<el>, SqlWildcard);
1395 if constexpr (IsPrimaryKey<FieldType>)
1396 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1400 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1401 if (field.IsModified())
1402 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1405 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1406 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1413#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1414 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1416 if (record.[:el:].IsModified())
1418 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1422 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1424 using FieldType =
typename[:std::meta::type_of(el):];
1425 if constexpr (FieldType::IsPrimaryKey)
1427 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1432 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1433 if (field.IsModified())
1434 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1438 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1439 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1440 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1446 SetModifiedState<ModifiedState::NotModified>(record);
1449template <
typename Record>
1454 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1456#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1457 auto constexpr ctx = std::meta::access_context::current();
1458 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1460 using FieldType =
typename[:std::meta::type_of(el):];
1461 if constexpr (FieldType::IsPrimaryKey)
1462 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1465 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& ) {
1466 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1467 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1473#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1475 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1477 using FieldType =
typename[:std::meta::type_of(el):];
1478 if constexpr (FieldType::IsPrimaryKey)
1480 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1485 Reflection::CallOnMembersWithoutName(
1486 record, [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
1487 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1488 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1497template <
typename Record,
typename... PrimaryKeyTypes>
1502 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1504 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1507 queryBuilder.Field(FieldNameAt<I, Record>);
1509 if constexpr (FieldType::IsPrimaryKey)
1510 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1514 _stmt.
Prepare(queryBuilder.First());
1515 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1517 auto resultRecord = std::optional<Record> { Record {} };
1520 return std::nullopt;
1523 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1525 return resultRecord;
1528template <
typename Record,
typename... PrimaryKeyTypes>
1531 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1539template <
typename Record,
typename... Args>
1544 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1546 selectQuery.
Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
1549 _stmt.
Execute(std::forward<Args>(args)...);
1551 auto resultRecord = std::optional<Record> { Record {} };
1554 return std::nullopt;
1557 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1559 return resultRecord;
1564template <
typename Record,
typename... InputParameters>
1566 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1568 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1570 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1573template <
typename Record,
typename... InputParameters>
1574std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1576 auto result = std::vector<Record> {};
1577 if constexpr (std::same_as<Record, SqlVariantRow>)
1579 _stmt.
Prepare(sqlQueryString);
1580 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1584 auto& record = result.emplace_back();
1585 record.reserve(numResultColumns);
1586 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1594 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1596 _stmt.
Prepare(sqlQueryString);
1597 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1603 auto& record = result.emplace_back();
1605 if (canSafelyBindOutputColumns)
1606 BindOutputColumns(record);
1608 if (!reader.FetchRow())
1611 if (!canSafelyBindOutputColumns)
1612 detail::GetAllColumns(reader, record);
1618 for (
auto& record: result)
1620 SetModifiedState<ModifiedState::NotModified>(record);
1628template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
1630std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
1632 using value_type = std::tuple<First, Second, Rest...>;
1633 auto result = std::vector<value_type> {};
1635 _stmt.
Prepare(selectQuery.ToSql());
1639 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1642 if constexpr (I > 0)
1644 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1645 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1646 }(std::make_index_sequence<I> {});
1651 auto const BindElements = [&](
auto& record) {
1652 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1653 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1654 auto& element = std::get<I>(record);
1655 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1656 this->BindOutputColumns<TupleElement, offset>(element);
1660 auto const GetElements = [&](
auto& record) {
1661 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1662 auto& element = std::get<I>(record);
1663 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1664 detail::GetAllColumns(reader, element, offset - 1);
1668 bool const canSafelyBindOutputColumns = [&]() {
1670 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1671 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1679 auto& record = result.emplace_back();
1681 if (canSafelyBindOutputColumns)
1682 BindElements(record);
1684 if (!reader.FetchRow())
1687 if (!canSafelyBindOutputColumns)
1688 GetElements(record);
1694 for (
auto& record: result)
1696 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1697 auto& element = std::get<I>(record);
1698 SetModifiedState<ModifiedState::NotModified>(element);
1699 if constexpr (QueryOptions.loadRelations)
1709template <
typename ElementMask,
typename Record,
typename... InputParameters>
1711 InputParameters&&... inputParameters)
1715 _stmt.
Prepare(selectQuery.ToSql());
1716 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1718 auto records = std::vector<Record> {};
1721 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1727 auto& record = records.emplace_back();
1729 if (canSafelyBindOutputColumns)
1730 BindOutputColumns<ElementMask>(record);
1732 if (!reader.FetchRow())
1735 if (!canSafelyBindOutputColumns)
1736 detail::GetAllColumns<ElementMask>(reader, record);
1742 for (
auto& record: records)
1744 SetModifiedState<ModifiedState::NotModified>(record);
1751template <DataMapper::ModifiedState state,
typename Record>
1754 static_assert(!std::is_const_v<Record>);
1757 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1758 if constexpr (
requires { field.SetModified(
false); })
1760 if constexpr (state == ModifiedState::Modified)
1761 field.SetModified(
true);
1763 field.SetModified(
false);
1768template <
typename Record,
typename Callable>
1769inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1773 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1774 if constexpr (IsField<FieldType>)
1776 if constexpr (FieldType::IsPrimaryKey)
1778 return callable.template operator()<I, FieldType>(field);
1784template <
typename Record,
typename Callable>
1785inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1787 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1789 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1790 if constexpr (IsField<FieldType>)
1792 if constexpr (FieldType::IsPrimaryKey)
1794 return callable.template operator()<I, FieldType>();
1800template <
typename Record,
typename Callable>
1801inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1803 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1805 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1806 if constexpr (IsBelongsTo<FieldType>)
1808 return callable.template operator()<I, FieldType>();
1813template <
typename FieldType>
1814std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1816 using ReferencedRecord = FieldType::ReferencedRecord;
1818 std::optional<ReferencedRecord> record { std::nullopt };
1820#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1821 auto constexpr ctx = std::meta::access_context::current();
1822 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1824 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1825 if constexpr (IsField<BelongsToFieldType>)
1826 if constexpr (BelongsToFieldType::IsPrimaryKey)
1828 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1829 record = std::move(result);
1832 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1836 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1837 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1838 record = std::move(result);
1841 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1847template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1848void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1850 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1851 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1853 using FieldType = HasMany<OtherRecord>;
1854 using ReferencedRecord = FieldType::ReferencedRecord;
1856 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1857 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1859 .Build([&](
auto& query) {
1860 Reflection::EnumerateMembers<ReferencedRecord>(
1861 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1862 if constexpr (FieldWithStorage<ReferencedFieldType>)
1864 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1868 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1869 callback(query, primaryKeyField);
1873template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1874void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1876 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1877 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1879 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1880 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1884template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1885void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1887 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1888 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
1891 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1893 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1895 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1897 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1902 _connection.
Query(RecordTableName<ReferencedRecord>)
1904 .Build([&](
auto& query) {
1905 Reflection::EnumerateMembers<ReferencedRecord>(
1906 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1907 if constexpr (FieldWithStorage<ReferencedFieldType>)
1909 query.Field(SqlQualifiedTableColumnName {
1910 RecordTableName<ReferencedRecord>,
1911 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1915 .InnerJoin(RecordTableName<ThroughRecord>,
1916 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1917 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1918 .InnerJoin(RecordTableName<Record>,
1919 FieldNameAt<PrimaryKeyIndex, Record>,
1920 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1921 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1923 SqlQualifiedTableColumnName {
1924 RecordTableName<Record>,
1925 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1928 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1930 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1938template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1939void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1941 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1944 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1946 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1947 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1948 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1951 CallOnBelongsTo<ThroughRecord>(
1952 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
1953 using ThroughBelongsToReferenceRecordFieldType =
1954 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1955 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1958 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1960 .Build([&](
auto& query) {
1961 Reflection::EnumerateMembers<ReferencedRecord>(
1962 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1963 if constexpr (FieldWithStorage<ReferencedFieldType>)
1965 query.Field(SqlQualifiedTableColumnName {
1966 RecordTableName<ReferencedRecord>,
1967 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1971 .InnerJoin(RecordTableName<ThroughRecord>,
1972 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1973 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1974 FieldNameAt<PrimaryKeyIndex, Record> })
1976 SqlQualifiedTableColumnName {
1977 RecordTableName<ThroughRecord>,
1978 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1981 callback(query, primaryKeyField);
1989template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1990void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
1992 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1994 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1995 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
1996 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2000template <
typename Record>
2003 static_assert(!std::is_const_v<Record>);
2006#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2007 constexpr auto ctx = std::meta::access_context::current();
2008 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2010 using FieldType =
typename[:std::meta::type_of(el):];
2011 if constexpr (IsBelongsTo<FieldType>)
2013 auto& field = record.[:el:];
2014 field = LoadBelongsTo<FieldType>(field.Value());
2016 else if constexpr (IsHasMany<FieldType>)
2018 LoadHasMany<el>(record, record.[:el:]);
2020 else if constexpr (IsHasOneThrough<FieldType>)
2022 LoadHasOneThrough(record, record.[:el:]);
2024 else if constexpr (IsHasManyThrough<FieldType>)
2026 LoadHasManyThrough(record, record.[:el:]);
2030 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2031 if constexpr (IsBelongsTo<FieldType>)
2033 field = LoadBelongsTo<FieldType>(field.Value());
2035 else if constexpr (IsHasMany<FieldType>)
2037 LoadHasMany<FieldIndex>(record, field);
2039 else if constexpr (IsHasOneThrough<FieldType>)
2041 LoadHasOneThrough(record, field);
2043 else if constexpr (IsHasManyThrough<FieldType>)
2045 LoadHasManyThrough(record, field);
2051template <
typename Record,
typename ValueType>
2052inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2057#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2059 auto constexpr ctx = std::meta::access_context::current();
2060 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2062 using FieldType =
typename[:std::meta::type_of(el):];
2063 if constexpr (IsField<FieldType>)
2065 if constexpr (FieldType::IsPrimaryKey)
2067 record.[:el:] = std::forward<ValueType>(
id);
2072 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
2073 if constexpr (IsField<FieldType>)
2075 if constexpr (FieldType::IsPrimaryKey)
2077 field = std::forward<FieldType>(
id);
2084template <
typename Record,
size_t InitialOffset>
2085inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2087 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2088 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
2092template <
typename Record,
size_t InitialOffset>
2093Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2095 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2099template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2100inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2102 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2103 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
2106template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2107Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2109 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2110 static_assert(!std::is_const_v<Record>);
2111 assert(stmt !=
nullptr);
2113#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2114 auto constexpr ctx = std::meta::access_context::current();
2115 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2116 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2118 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2119 using FieldType =
typename[:std::meta::type_of(el):];
2120 if constexpr (IsField<FieldType>)
2122 stmt->BindOutputColumn(i++, &record.[:el:].MutableValue());
2124 else if constexpr (SqlOutputColumnBinder<FieldType>)
2126 stmt->BindOutputColumn(i++, &record.[:el:]);
2130 Reflection::EnumerateMembers<ElementMask>(
2131 record, [stmt, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2132 if constexpr (IsField<Field>)
2134 stmt->BindOutputColumn(i++, &field.MutableValue());
2136 else if constexpr (SqlOutputColumnBinder<Field>)
2138 stmt->BindOutputColumn(i++, &field);
2146template <
typename Record>
2151 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2152 if constexpr (IsBelongsTo<FieldType>)
2154 field.SetAutoLoader(
typename FieldType::Loader {
2155 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2157 return dm.LoadBelongsTo<FieldType>(value);
2161 else if constexpr (IsHasMany<FieldType>)
2163 using ReferencedRecord = FieldType::ReferencedRecord;
2166 .count = [&record]() ->
size_t {
2169 dm.CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2171 dm._stmt.
Prepare(selectQuery.Count());
2172 dm._stmt.
Execute(primaryKeyField.Value());
2180 [&record, &hasMany]() {
2182 dm.LoadHasMany<FieldIndex>(record, hasMany);
2185 [&record](
auto const& each) {
2187 dm.CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2190 stmt.Prepare(selectQuery.All());
2191 stmt.Execute(primaryKeyField.Value());
2193 auto referencedRecord = ReferencedRecord {};
2194 dm.BindOutputColumns(referencedRecord, &stmt);
2197 while (stmt.FetchRow())
2199 each(referencedRecord);
2200 dm.BindOutputColumns(referencedRecord, &stmt);
2206 else if constexpr (IsHasOneThrough<FieldType>)
2208 using ReferencedRecord = FieldType::ReferencedRecord;
2209 using ThroughRecord = FieldType::ThroughRecord;
2213 [&record, &hasOneThrough]() {
2215 dm.LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
2219 else if constexpr (IsHasManyThrough<FieldType>)
2221 using ReferencedRecord = FieldType::ReferencedRecord;
2222 using ThroughRecord = FieldType::ThroughRecord;
2225 .count = [&record]() ->
size_t {
2229 dm.CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2231 dm._stmt.
Prepare(selectQuery.Count());
2232 dm._stmt.
Execute(primaryKeyField.Value());
2240 [&record, &hasManyThrough]() {
2243 dm.LoadHasManyThrough(record, hasManyThrough);
2246 [&record](
auto const& each) {
2249 dm.CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2252 stmt.Prepare(selectQuery.All());
2253 stmt.Execute(primaryKeyField.Value());
2255 auto referencedRecord = ReferencedRecord {};
2256 dm.BindOutputColumns(referencedRecord, &stmt);
2259 while (stmt.FetchRow())
2261 each(referencedRecord);
2262 dm.BindOutputColumns(referencedRecord, &stmt);
2270#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2271 constexpr auto ctx = std::meta::access_context::current();
2273 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2274 constexpr auto localctx = std::meta::access_context::current();
2275 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2276 using FieldType =
typename[:std::meta::type_of(members[I]):];
2277 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2280 Reflection::EnumerateMembers(record, callback);
2284template <
typename T>