112 _connection { std::move(connectionString) },
113 _stmt { _connection }
118 [[nodiscard]]
static std::shared_ptr<DataMapper>
Create()
120 return std::make_shared<DataMapper>(PrivateTag {});
126 return std::make_shared<DataMapper>(std::move(connection), PrivateTag {});
132 return std::make_shared<DataMapper>(std::move(connectionString), PrivateTag {});
154 template <
typename Record>
155 static std::string
Inspect(Record
const& record);
158 template <
typename Record>
162 template <
typename FirstRecord,
typename... MoreRecords>
166 template <
typename Record>
170 template <
typename FirstRecord,
typename... MoreRecords>
178 template <
typename Record>
179 RecordPrimaryKeyType<Record>
Create(Record& record);
186 template <
typename Record>
187 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
193 template <
typename Record,
typename... PrimaryKeyTypes>
194 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
203 template <
typename Record,
typename... PrimaryKeyTypes>
207 template <
typename Record,
typename... InputParameters>
208 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
239 template <
typename Record,
typename... InputParameters>
240 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
266 template <
typename ElementMask,
typename Record,
typename... InputParameters>
267 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
293 template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions = {}>
295 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
298 template <
typename FirstRecord,
typename NextRecord,
DataMapperOptions QueryOptions = {}>
299 requires DataMapperRecord<FirstRecord> && DataMapperRecord<NextRecord>
300 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>, QueryOptions>
Query()
304 auto const emplaceRecordsFrom = [&fields]<
typename Record>() {
305 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
308 fields += std::format(R
"("{}"."{}")", RecordTableName<Record>, FieldNameAt<I, Record>);
312 emplaceRecordsFrom.template operator()<FirstRecord>();
313 emplaceRecordsFrom.template operator()<NextRecord>();
315 return SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>, QueryOptions>(*
this, std::move(fields));
330 template <
typename Record, DataMapperOptions QueryOptions = {}>
334 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
338 fields += RecordTableName<Record>;
340 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;
365 template <
typename Record>
369 template <
typename Record>
376 template <
typename Record>
386 template <
typename Record,
typename... Args>
389 template <
typename Record,
typename ValueType>
390 void SetId(Record& record, ValueType&&
id);
392 template <
typename Record,
size_t InitialOffset = 1>
393 Record& BindOutputColumns(Record& record);
395 template <
typename Record,
size_t InitialOffset = 1>
396 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
398 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
399 Record& BindOutputColumns(Record& record);
401 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
402 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
404 template <
typename FieldType>
405 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(
typename FieldType::ValueType value);
407 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
410 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
413 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
416 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
417 void CallOnHasMany(Record& record, Callable
const& callback);
419 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
420 void CallOnHasManyThrough(Record& record, Callable
const& callback);
430 template <
typename FieldType>
431 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
433 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
440 if constexpr (IsField<FieldType>)
442 if constexpr (detail::OneOf<
typename FieldType::ValueType,
448 || IsSqlDynamicString<typename FieldType::ValueType>
449 || IsSqlDynamicBinary<typename FieldType::ValueType>)
458 template <DataMapperRecord Record>
459 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
461 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
465 Reflection::EnumerateMembers<Record>([&result]<
size_t I,
typename Field>() {
466 if constexpr (IsField<Field>)
468 if constexpr (detail::OneOf<
typename Field::ValueType,
474 || IsSqlDynamicString<typename Field::ValueType>
475 || IsSqlDynamicBinary<typename Field::ValueType>)
485 template <
typename Record>
486 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLSMALLINT startOffset)
488 Reflection::EnumerateMembers(record,
489 [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
490 if constexpr (IsField<Field>)
492 reader->BindOutputColumn(i++, &field.MutableValue());
494 else if constexpr (IsBelongsTo<Field>)
496 reader->BindOutputColumn(i++, &field.MutableValue());
498 else if constexpr (SqlOutputColumnBinder<Field>)
500 reader->BindOutputColumn(i++, &field);
505 template <
typename Record>
506 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
508 BindAllOutputColumnsWithOffset(reader, record, 1);
514 template <
typename ElementMask,
typename Record>
515 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
517 Reflection::EnumerateMembers<ElementMask>(
518 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
520 if constexpr (IsField<Field>)
523 field.MutableValue() =
524 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
526 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
528 else if constexpr (SqlGetColumnNativeType<Field>)
530 if constexpr (IsOptionalBelongsTo<Field>)
531 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
533 field = reader->GetColumn<Field>(indexFromQuery);
538 template <
typename Record>
539 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
541 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(
542 reader, record, indexFromQuery);
545 template <
typename FirstRecord,
typename SecondRecord>
547 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
549 auto& [firstRecord, secondRecord] = record;
551 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
552 if constexpr (IsField<Field>)
555 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(I + 1);
557 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(I + 1);
559 else if constexpr (SqlGetColumnNativeType<Field>)
562 field = reader->GetNullableColumn<
typename Field::BaseType>(I + 1);
564 field = reader->GetColumn<Field>(I + 1);
568 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
569 if constexpr (IsField<Field>)
572 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(
573 Reflection::CountMembers<FirstRecord> + I + 1);
575 field.MutableValue() =
576 reader->GetColumn<
typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
578 else if constexpr (SqlGetColumnNativeType<Field>)
582 reader->GetNullableColumn<
typename Field::BaseType>(Reflection::CountMembers<FirstRecord> + I + 1);
584 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
589 template <
typename Record>
590 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
592 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
594 if (outputColumnsBound)
595 BindAllOutputColumns(reader, record);
597 if (!reader.FetchRow())
600 if (!outputColumnsBound)
601 GetAllColumns(reader, record);
607template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
608inline SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::SqlCoreDataMapperQueryBuilder(
609 DataMapper& dm, std::string fields)
noexcept:
611 _formatter { dm.Connection().QueryFormatter() },
612 _fields { std::move(fields) }
616template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
620 stmt.ExecuteDirect(_formatter.SelectCount(this->_query.distinct,
621 RecordTableName<Record>,
622 this->_query.searchCondition.tableAlias,
623 this->_query.searchCondition.tableJoins,
624 this->_query.searchCondition.condition));
625 auto reader = stmt.GetResultCursor();
626 if (reader.FetchRow())
627 return reader.GetColumn<
size_t>(1);
631template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
635 auto records = std::vector<Record> {};
637 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
639 RecordTableName<Record>,
640 this->_query.searchCondition.tableAlias,
641 this->_query.searchCondition.tableJoins,
642 this->_query.searchCondition.condition,
643 this->_query.orderBy,
644 this->_query.groupBy));
645 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
651 if constexpr (QueryOptions.loadRelations)
653 for (
auto& record: records)
655 _dm.ConfigureRelationAutoLoading(record);
662template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
664#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
665 requires(is_aggregate_type(parent_of(
Field)))
667 requires std::is_member_object_pointer_v<
decltype(
Field)>
672 auto result = std::vector<value_type> {};
675 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
676 FullyQualifiedNamesOf<Field>.string_view(),
677 RecordTableName<Record>,
678 this->_query.searchCondition.tableAlias,
679 this->_query.searchCondition.tableJoins,
680 this->_query.searchCondition.condition,
681 this->_query.orderBy,
682 this->_query.groupBy));
684 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
687 auto& value = result.emplace_back();
688 if (outputColumnsBound)
689 reader.BindOutputColumn(1, &value);
697 if (!outputColumnsBound)
704template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
705template <
auto... ReferencedFields>
706 requires(
sizeof...(ReferencedFields) >= 2)
709 auto records = std::vector<Record> {};
710 auto stmt = SqlStatement { _dm.Connection() };
712 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
713 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
714 RecordTableName<Record>,
715 this->_query.searchCondition.tableAlias,
716 this->_query.searchCondition.tableJoins,
717 this->_query.searchCondition.condition,
718 this->_query.orderBy,
719 this->_query.groupBy));
721 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
722 SqlResultCursor reader = stmt.GetResultCursor();
725 auto& record = records.emplace_back();
726 if (outputColumnsBound)
727#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
728 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
730 reader.BindOutputColumns(&(record.*ReferencedFields)...);
732 if (!reader.FetchRow())
737 if (!outputColumnsBound)
739 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
740 detail::GetAllColumns<ElementMask>(reader, record);
747template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
750 std::optional<Record> record {};
752 stmt.ExecuteDirect(_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,
760 Derived::ReadResult(stmt.Connection().ServerType(), stmt.GetResultCursor(), &record);
761 if constexpr (QueryOptions.loadRelations)
764 _dm.ConfigureRelationAutoLoading(record.value());
769template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
771#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
772 requires(is_aggregate_type(parent_of(
Field)))
774 requires std::is_member_object_pointer_v<
decltype(
Field)>
778 auto constexpr count = 1;
780 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
781 FullyQualifiedNamesOf<Field>.string_view(),
782 RecordTableName<Record>,
783 this->_query.searchCondition.tableAlias,
784 this->_query.searchCondition.tableJoins,
785 this->_query.searchCondition.condition,
786 this->_query.orderBy,
788 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
793template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
794template <
auto... ReferencedFields>
795 requires(
sizeof...(ReferencedFields) >= 2)
798 auto optionalRecord = std::optional<Record> {};
800 auto stmt = SqlStatement { _dm.Connection() };
801 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
802 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
803 RecordTableName<Record>,
804 this->_query.searchCondition.tableAlias,
805 this->_query.searchCondition.tableJoins,
806 this->_query.searchCondition.condition,
807 this->_query.orderBy,
810 auto& record = optionalRecord.emplace();
811 SqlResultCursor reader = stmt.GetResultCursor();
812 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
813 if (outputColumnsBound)
814#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
815 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
817 reader.BindOutputColumns(&(record.*ReferencedFields)...);
819 if (!reader.FetchRow())
821 if (!outputColumnsBound)
823 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
824 detail::GetAllColumns<ElementMask>(reader, record);
827 if constexpr (QueryOptions.loadRelations)
828 _dm.ConfigureRelationAutoLoading(record);
830 return optionalRecord;
833template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
836 auto records = std::vector<Record> {};
839 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
841 RecordTableName<Record>,
842 this->_query.searchCondition.tableAlias,
843 this->_query.searchCondition.tableJoins,
844 this->_query.searchCondition.condition,
845 this->_query.orderBy,
847 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
849 if constexpr (QueryOptions.loadRelations)
851 for (
auto& record: records)
852 _dm.ConfigureRelationAutoLoading(record);
857template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
860 auto records = std::vector<Record> {};
862 records.reserve(limit);
864 _formatter.SelectRange(this->_query.distinct,
866 RecordTableName<Record>,
867 this->_query.searchCondition.tableAlias,
868 this->_query.searchCondition.tableJoins,
869 this->_query.searchCondition.condition,
870 !this->_query.orderBy.empty()
871 ? this->_query.orderBy
872 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
873 this->_query.groupBy,
876 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
877 if constexpr (QueryOptions.loadRelations)
879 for (
auto& record: records)
880 _dm.ConfigureRelationAutoLoading(record);
885template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
886template <
auto... ReferencedFields>
889 auto records = std::vector<Record> {};
890 auto stmt = SqlStatement { _dm.Connection() };
891 records.reserve(limit);
893 _formatter.SelectRange(this->_query.distinct,
894 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
895 RecordTableName<Record>,
896 this->_query.searchCondition.tableAlias,
897 this->_query.searchCondition.tableJoins,
898 this->_query.searchCondition.condition,
899 !this->_query.orderBy.empty()
900 ? this->_query.orderBy
901 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
902 this->_query.groupBy,
906 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
907 SqlResultCursor reader = stmt.GetResultCursor();
910 auto& record = records.emplace_back();
911 if (outputColumnsBound)
912#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
913 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
915 reader.BindOutputColumns(&(record.*ReferencedFields)...);
917 if (!reader.FetchRow())
922 if (!outputColumnsBound)
924 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
925 detail::GetAllColumns<ElementMask>(reader, record);
929 if constexpr (QueryOptions.loadRelations)
931 for (
auto& record: records)
932 _dm.ConfigureRelationAutoLoading(record);
938template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
939template <
auto... ReferencedFields>
942 auto records = std::vector<Record> {};
943 auto stmt = SqlStatement { _dm.Connection() };
945 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
946 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
947 RecordTableName<Record>,
948 this->_query.searchCondition.tableAlias,
949 this->_query.searchCondition.tableJoins,
950 this->_query.searchCondition.condition,
951 this->_query.orderBy,
954 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
955 SqlResultCursor reader = stmt.GetResultCursor();
958 auto& record = records.emplace_back();
959 if (outputColumnsBound)
960#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
961 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
963 reader.BindOutputColumns(&(record.*ReferencedFields)...);
965 if (!reader.FetchRow())
970 if (!outputColumnsBound)
972 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
973 detail::GetAllColumns<ElementMask>(reader, record);
977 if constexpr (QueryOptions.loadRelations)
979 for (
auto& record: records)
980 _dm.ConfigureRelationAutoLoading(record);
986template <
typename Record, DataMapperOptions QueryOptions>
987void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResults(SqlServerType sqlServerType,
988 SqlResultCursor reader,
989 std::vector<Record>* records)
993 Record& record = records->emplace_back();
994 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1002template <
typename Record, DataMapperOptions QueryOptions>
1003void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1004 SqlResultCursor reader,
1005 std::optional<Record>* optionalRecord)
1007 Record& record = optionalRecord->emplace();
1008 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1009 optionalRecord->reset();
1012template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions>
1013void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1014 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1018 auto& record = records->emplace_back();
1019 auto& [firstRecord, secondRecord] = record;
1021 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1022 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1024 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1025 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1026 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1028 if (canSafelyBindAll)
1030 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1031 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1034 if (!reader.FetchRow())
1036 records->pop_back();
1040 if (!canSafelyBindAll)
1041 detail::GetAllColumns(reader, record);
1045template <
typename Record>
1051 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1057 if constexpr (Value::IsOptional)
1059 if (!value.Value().has_value())
1061 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1065 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1068 else if constexpr (IsBelongsTo<Value>)
1070 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1072 else if constexpr (std::same_as<typename Value::ValueType, char>)
1077 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1080 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1081 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1083 return "{\n" + std::move(str) +
"\n}";
1086template <
typename Record>
1092 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1093#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1094 constexpr auto ctx = std::meta::access_context::current();
1095 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1097 using FieldType =
typename[:std::meta::type_of(el):];
1100 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1101 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameOf<el>),
1102 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1103 else if constexpr (FieldType::IsPrimaryKey)
1104 createTable.PrimaryKey(std::string(FieldNameOf<el>),
1105 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1106 else if constexpr (IsBelongsTo<FieldType>)
1108 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
1109 auto index = size_t(-1);
1110 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1111 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
1112 if constexpr (IsField<ReferencedFieldType>)
1113 if constexpr (ReferencedFieldType::IsPrimaryKey)
1118 createTable.ForeignKey(
1119 std::string(FieldNameOf<el>),
1120 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1122 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1123 .columnName = std::string { FieldNameOf<FieldType::ReferencedField> } });
1125 else if constexpr (FieldType::IsMandatory)
1126 createTable.RequiredColumn(std::string(FieldNameOf<el>),
1127 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1129 createTable.Column(std::string(FieldNameOf<el>), SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1134 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1137 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1138 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
1139 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1140 else if constexpr (FieldType::IsPrimaryKey)
1141 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
1142 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1143 else if constexpr (IsBelongsTo<FieldType>)
1145 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
1146 auto index = size_t(-1);
1147 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1148 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
1149 if constexpr (IsField<ReferencedFieldType>)
1150 if constexpr (ReferencedFieldType::IsPrimaryKey)
1155 createTable.ForeignKey(
1156 std::string(FieldNameAt<I, Record>),
1157 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1159 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1161 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
1163 else if constexpr (FieldType::IsMandatory)
1164 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
1165 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1167 createTable.Column(std::string(FieldNameAt<I, Record>),
1168 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1172 return migration.GetPlan().ToSql();
1175template <
typename FirstRecord,
typename... MoreRecords>
1178 std::vector<std::string> output;
1179 auto const append = [&output](
auto const& sql) {
1180 output.insert(output.end(), sql.begin(), sql.end());
1182 append(CreateTableString<FirstRecord>(serverType));
1183 (append(CreateTableString<MoreRecords>(serverType)), ...);
1187template <
typename Record>
1192 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1193 for (
auto const& sqlQueryString: sqlQueryStrings)
1197template <
typename FirstRecord,
typename... MoreRecords>
1200 CreateTable<FirstRecord>();
1201 (CreateTable<MoreRecords>(), ...);
1204template <
typename Record>
1209 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1211#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1212 constexpr auto ctx = std::meta::access_context::current();
1213 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1215 using FieldType =
typename[:std::meta::type_of(el):];
1216 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1217 query.Set(FieldNameOf<el>, SqlWildcard);
1220 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1221 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1222 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1228#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1230 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1232 using FieldType =
typename[:std::meta::type_of(el):];
1233 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1234 _stmt.BindInputParameter(i++, record.[:el:], std::meta::identifier_of(el));
1237 Reflection::CallOnMembers(
1239 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
1240 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1241 _stmt.BindInputParameter(i++, field, name);
1246 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1247 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1248 else if constexpr (HasPrimaryKey<Record>)
1250 RecordPrimaryKeyType<Record>
const* primaryKey =
nullptr;
1251#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1252 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1254 using FieldType =
typename[:std::meta::type_of(el):];
1255 if constexpr (IsField<FieldType>)
1257 if constexpr (FieldType::IsPrimaryKey)
1259 primaryKey = &record.[:el:].Value();
1264 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1265 if constexpr (IsField<FieldType>)
1267 if constexpr (FieldType::IsPrimaryKey)
1269 primaryKey = &field.Value();
1278template <
typename Record>
1281 static_assert(!std::is_const_v<Record>);
1286#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1287 constexpr auto ctx = std::meta::access_context::current();
1288 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1290 using FieldType =
typename[:std::meta::type_of(el):];
1291 if constexpr (IsField<FieldType>)
1292 if constexpr (FieldType::IsPrimaryKey)
1293 if constexpr (FieldType::IsAutoAssignPrimaryKey)
1295 if (!record.[:el:].IsModified())
1297 using ValueType =
typename FieldType::ValueType;
1298 if constexpr (std::same_as<ValueType, SqlGuid>)
1302 else if constexpr (
requires { ValueType {} + 1; })
1304 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(std::format(
1305 R
"sql(SELECT MAX("{}") FROM "{}")sql", FieldNameOf<el>, RecordTableName<Record>));
1306 record.[:el:] = maxId.value_or(ValueType {}) + 1;
1312 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
1313 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
1315 if (!primaryKeyField.IsModified())
1317 using ValueType =
typename PrimaryKeyType::ValueType;
1318 if constexpr (std::same_as<ValueType, SqlGuid>)
1322 else if constexpr (
requires { ValueType {} + 1; })
1324 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1325 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1326 FieldNameAt<PrimaryKeyIndex, Record>,
1327 RecordTableName<Record>));
1328 primaryKeyField = maxId.value_or(ValueType {}) + 1;
1337 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1338 SetId(record, _stmt.
LastInsertId(RecordTableName<Record>));
1343 if constexpr (HasPrimaryKey<Record>)
1347template <
typename Record>
1352 bool modified =
false;
1354#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1355 auto constexpr ctx = std::meta::access_context::current();
1356 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1358 if constexpr (
requires { record.[:el:].IsModified(); })
1360 modified = modified || record.[:el:].IsModified();
1364 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1365 if constexpr (
requires { field.IsModified(); })
1367 modified = modified || field.IsModified();
1375template <
typename Record>
1380 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1382#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1383 auto constexpr ctx = std::meta::access_context::current();
1384 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1386 using FieldType =
typename[:std::meta::type_of(el):];
1389 if (record.[:el:].IsModified())
1390 query.Set(FieldNameOf<el>, SqlWildcard);
1391 if constexpr (IsPrimaryKey<FieldType>)
1392 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1396 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1397 if (field.IsModified())
1398 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1401 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1402 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1409#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1410 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1412 if (record.[:el:].IsModified())
1414 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1418 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1420 using FieldType =
typename[:std::meta::type_of(el):];
1421 if constexpr (FieldType::IsPrimaryKey)
1423 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1428 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1429 if (field.IsModified())
1430 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1434 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1435 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1436 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1445template <
typename Record>
1450 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1452#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1453 auto constexpr ctx = std::meta::access_context::current();
1454 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1456 using FieldType =
typename[:std::meta::type_of(el):];
1457 if constexpr (FieldType::IsPrimaryKey)
1458 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1461 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& ) {
1462 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1463 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1469#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1471 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1473 using FieldType =
typename[:std::meta::type_of(el):];
1474 if constexpr (FieldType::IsPrimaryKey)
1476 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1481 Reflection::CallOnMembersWithoutName(
1482 record, [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
1483 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1484 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1493template <
typename Record,
typename... PrimaryKeyTypes>
1498 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1500 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1503 queryBuilder.Field(FieldNameAt<I, Record>);
1505 if constexpr (FieldType::IsPrimaryKey)
1506 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1510 _stmt.
Prepare(queryBuilder.First());
1511 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1513 auto resultRecord = std::optional<Record> { Record {} };
1516 return std::nullopt;
1518 return resultRecord;
1521template <
typename Record,
typename... PrimaryKeyTypes>
1524 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1530template <
typename Record,
typename... Args>
1535 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1537 selectQuery.
Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
1540 _stmt.
Execute(std::forward<Args>(args)...);
1542 auto resultRecord = std::optional<Record> { Record {} };
1545 return std::nullopt;
1546 return resultRecord;
1551template <
typename Record,
typename... InputParameters>
1553 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1555 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1557 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1560template <
typename Record,
typename... InputParameters>
1561std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1563 auto result = std::vector<Record> {};
1564 if constexpr (std::same_as<Record, SqlVariantRow>)
1566 _stmt.
Prepare(sqlQueryString);
1567 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1571 auto& record = result.emplace_back();
1572 record.reserve(numResultColumns);
1573 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1581 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1583 _stmt.
Prepare(sqlQueryString);
1584 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1590 auto& record = result.emplace_back();
1592 if (canSafelyBindOutputColumns)
1593 BindOutputColumns(record);
1595 if (!reader.FetchRow())
1598 if (!canSafelyBindOutputColumns)
1599 detail::GetAllColumns(reader, record);
1605 for (
auto& record: result)
1612template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
1614std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
1616 using value_type = std::tuple<First, Second, Rest...>;
1617 auto result = std::vector<value_type> {};
1619 _stmt.
Prepare(selectQuery.ToSql());
1623 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1626 if constexpr (I > 0)
1628 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1629 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1630 }(std::make_index_sequence<I> {});
1635 auto const BindElements = [&](
auto& record) {
1636 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1637 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1638 auto& element = std::get<I>(record);
1639 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1640 this->BindOutputColumns<TupleElement, offset>(element);
1644 auto const GetElements = [&](
auto& record) {
1645 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1646 auto& element = std::get<I>(record);
1647 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1648 detail::GetAllColumns(reader, element, offset - 1);
1652 bool const canSafelyBindOutputColumns = [&]() {
1654 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1655 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1663 auto& record = result.emplace_back();
1665 if (canSafelyBindOutputColumns)
1666 BindElements(record);
1668 if (!reader.FetchRow())
1671 if (!canSafelyBindOutputColumns)
1672 GetElements(record);
1678 if constexpr (QueryOptions.loadRelations)
1681 for (
auto& record: result)
1683 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1684 auto& element = std::get<I>(record);
1693template <
typename ElementMask,
typename Record,
typename... InputParameters>
1695 InputParameters&&... inputParameters)
1699 _stmt.
Prepare(selectQuery.ToSql());
1700 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1702 auto records = std::vector<Record> {};
1705 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1711 auto& record = records.emplace_back();
1713 if (canSafelyBindOutputColumns)
1714 BindOutputColumns<ElementMask>(record);
1716 if (!reader.FetchRow())
1719 if (!canSafelyBindOutputColumns)
1720 detail::GetAllColumns<ElementMask>(reader, record);
1726 for (
auto& record: records)
1732template <
typename Record>
1735 static_assert(!std::is_const_v<Record>);
1738 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1739 if constexpr (
requires { field.SetModified(
false); })
1741 field.SetModified(
false);
1746template <
typename Record,
typename Callable>
1747inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1751 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1752 if constexpr (IsField<FieldType>)
1754 if constexpr (FieldType::IsPrimaryKey)
1756 return callable.template operator()<I, FieldType>(field);
1762template <
typename Record,
typename Callable>
1763inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1765 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1767 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1768 if constexpr (IsField<FieldType>)
1770 if constexpr (FieldType::IsPrimaryKey)
1772 return callable.template operator()<I, FieldType>();
1778template <
typename Record,
typename Callable>
1779inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1781 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1783 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1784 if constexpr (IsBelongsTo<FieldType>)
1786 return callable.template operator()<I, FieldType>();
1791template <
typename FieldType>
1792std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(
typename FieldType::ValueType value)
1794 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1796 std::optional<ReferencedRecord> record { std::nullopt };
1798#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1799 auto constexpr ctx = std::meta::access_context::current();
1800 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1802 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1803 if constexpr (IsField<BelongsToFieldType>)
1804 if constexpr (BelongsToFieldType::IsPrimaryKey)
1806 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1807 record = std::move(result);
1810 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1814 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1815 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1816 record = std::move(result);
1819 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1825template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1826void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1828 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1829 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1831 using FieldType = HasMany<OtherRecord>;
1832 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1834 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1835 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1837 .Build([&](
auto& query) {
1838 Reflection::EnumerateMembers<ReferencedRecord>(
1839 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1840 if constexpr (FieldWithStorage<ReferencedFieldType>)
1842 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1846 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1847 callback(query, primaryKeyField);
1851template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1852void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1854 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1855 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1857 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1858 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1862template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1863void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1865 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1866 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
1869 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1871 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1873 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1875 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1880 _connection.
Query(RecordTableName<ReferencedRecord>)
1882 .Build([&](
auto& query) {
1883 Reflection::EnumerateMembers<ReferencedRecord>(
1884 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1885 if constexpr (FieldWithStorage<ReferencedFieldType>)
1887 query.Field(SqlQualifiedTableColumnName {
1888 RecordTableName<ReferencedRecord>,
1889 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1893 .InnerJoin(RecordTableName<ThroughRecord>,
1894 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1895 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1896 .InnerJoin(RecordTableName<Record>,
1897 FieldNameAt<PrimaryKeyIndex, Record>,
1898 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1899 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1901 SqlQualifiedTableColumnName {
1902 RecordTableName<Record>,
1903 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1906 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1908 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1916template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1917void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1919 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1922 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1924 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1925 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1926 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1929 CallOnBelongsTo<ThroughRecord>(
1930 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
1931 using ThroughBelongsToReferenceRecordFieldType =
1932 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1933 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1936 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1938 .Build([&](
auto& query) {
1939 Reflection::EnumerateMembers<ReferencedRecord>(
1940 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1941 if constexpr (FieldWithStorage<ReferencedFieldType>)
1943 query.Field(SqlQualifiedTableColumnName {
1944 RecordTableName<ReferencedRecord>,
1945 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1949 .InnerJoin(RecordTableName<ThroughRecord>,
1950 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1951 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1952 FieldNameAt<PrimaryKeyIndex, Record> })
1954 SqlQualifiedTableColumnName {
1955 RecordTableName<ThroughRecord>,
1956 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1959 callback(query, primaryKeyField);
1967template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1968void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
1970 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1972 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1973 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
1974 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
1978template <
typename Record>
1981 static_assert(!std::is_const_v<Record>);
1984#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1985 constexpr auto ctx = std::meta::access_context::current();
1986 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1988 using FieldType =
typename[:std::meta::type_of(el):];
1989 if constexpr (IsBelongsTo<FieldType>)
1991 auto& field = record.[:el:];
1992 field = LoadBelongsTo<FieldType>(field.Value());
1994 else if constexpr (IsHasMany<FieldType>)
1996 LoadHasMany<el>(record, record.[:el:]);
1998 else if constexpr (IsHasOneThrough<FieldType>)
2000 LoadHasOneThrough(record, record.[:el:]);
2002 else if constexpr (IsHasManyThrough<FieldType>)
2004 LoadHasManyThrough(record, record.[:el:]);
2008 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2009 if constexpr (IsBelongsTo<FieldType>)
2011 field = LoadBelongsTo<FieldType>(field.Value());
2013 else if constexpr (IsHasMany<FieldType>)
2015 LoadHasMany<FieldIndex>(record, field);
2017 else if constexpr (IsHasOneThrough<FieldType>)
2019 LoadHasOneThrough(record, field);
2021 else if constexpr (IsHasManyThrough<FieldType>)
2023 LoadHasManyThrough(record, field);
2029template <
typename Record,
typename ValueType>
2030inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2035#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2037 auto constexpr ctx = std::meta::access_context::current();
2038 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2040 using FieldType =
typename[:std::meta::type_of(el):];
2041 if constexpr (IsField<FieldType>)
2043 if constexpr (FieldType::IsPrimaryKey)
2045 record.[:el:] = std::forward<ValueType>(
id);
2050 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
2051 if constexpr (IsField<FieldType>)
2053 if constexpr (FieldType::IsPrimaryKey)
2055 field = std::forward<FieldType>(
id);
2062template <
typename Record,
size_t InitialOffset>
2063inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2065 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2066 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
2070template <
typename Record,
size_t InitialOffset>
2071Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2073 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2077template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2078inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2080 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2081 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
2084template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2085Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2087 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2088 static_assert(!std::is_const_v<Record>);
2089 assert(stmt !=
nullptr);
2091#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2092 auto constexpr ctx = std::meta::access_context::current();
2093 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2094 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2096 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2097 using FieldType =
typename[:std::meta::type_of(el):];
2098 if constexpr (IsField<FieldType>)
2100 stmt->BindOutputColumn(i++, &record.[:el:].MutableValue());
2102 else if constexpr (SqlOutputColumnBinder<FieldType>)
2104 stmt->BindOutputColumn(i++, &record.[:el:]);
2108 Reflection::EnumerateMembers<ElementMask>(
2109 record, [stmt, i = SQLSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2110 if constexpr (IsField<Field>)
2112 stmt->BindOutputColumn(i++, &field.MutableValue());
2114 else if constexpr (SqlOutputColumnBinder<Field>)
2116 stmt->BindOutputColumn(i++, &field);
2124template <
typename Record>
2129 auto self = shared_from_this();
2130 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2131 if constexpr (IsBelongsTo<FieldType>)
2133 field.SetAutoLoader(
typename FieldType::Loader {
2134 .loadReference = [self, value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2135 return self->LoadBelongsTo<FieldType>(value);
2139 else if constexpr (IsHasMany<FieldType>)
2141 using ReferencedRecord =
typename FieldType::ReferencedRecord;
2144 .count = [self, &record]() ->
size_t {
2146 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2148 self->_stmt.Prepare(selectQuery.Count());
2149 self->_stmt.Execute(primaryKeyField.Value());
2150 if (self->_stmt.FetchRow())
2151 count = self->_stmt.GetColumn<
size_t>(1);
2152 self->_stmt.CloseCursor();
2156 .all = [self, &record, &hasMany]() { self->LoadHasMany<FieldIndex>(record, hasMany); },
2158 [self, &record](
auto const& each) {
2159 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2162 stmt.
Prepare(selectQuery.All());
2163 stmt.Execute(primaryKeyField.Value());
2165 auto referencedRecord = ReferencedRecord {};
2166 self->BindOutputColumns(referencedRecord, &stmt);
2167 self->ConfigureRelationAutoLoading(referencedRecord);
2169 while (stmt.FetchRow())
2171 each(referencedRecord);
2172 self->BindOutputColumns(referencedRecord, &stmt);
2178 else if constexpr (IsHasOneThrough<FieldType>)
2180 using ReferencedRecord =
typename FieldType::ReferencedRecord;
2181 using ThroughRecord =
typename FieldType::ThroughRecord;
2185 [self, &record, &hasOneThrough]() {
2186 self->LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
2190 else if constexpr (IsHasManyThrough<FieldType>)
2192 using ReferencedRecord =
typename FieldType::ReferencedRecord;
2193 using ThroughRecord =
typename FieldType::ThroughRecord;
2196 .count = [self, &record]() ->
size_t {
2199 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2201 self->_stmt.Prepare(selectQuery.Count());
2202 self->_stmt.Execute(primaryKeyField.Value());
2203 if (self->_stmt.FetchRow())
2204 count = self->_stmt.GetColumn<
size_t>(1);
2205 self->_stmt.CloseCursor();
2210 [self, &record, &hasManyThrough]() {
2212 self->LoadHasManyThrough(record, hasManyThrough);
2215 [self, &record](
auto const& each) {
2217 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2220 stmt.
Prepare(selectQuery.All());
2221 stmt.Execute(primaryKeyField.Value());
2223 auto referencedRecord = ReferencedRecord {};
2224 self->BindOutputColumns(referencedRecord, &stmt);
2225 self->ConfigureRelationAutoLoading(referencedRecord);
2227 while (stmt.FetchRow())
2229 each(referencedRecord);
2230 self->BindOutputColumns(referencedRecord, &stmt);
2238#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2239 constexpr auto ctx = std::meta::access_context::current();
2241 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2242 constexpr auto localctx = std::meta::access_context::current();
2243 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2244 using FieldType =
typename[:std::meta::type_of(members[I]):];
2245 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2248 Reflection::EnumerateMembers(record, callback);