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(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>
636 auto const query = _formatter.SelectFirst(this->_query.distinct,
638 RecordTableName<Record>,
639 this->_query.searchCondition.tableAlias,
640 this->_query.searchCondition.tableJoins,
641 this->_query.searchCondition.condition,
642 this->_query.orderBy,
645 stmt.ExecuteDirect(query);
646 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
651template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
656 auto const query = _formatter.Delete(RecordTableName<Record>,
657 this->_query.searchCondition.tableAlias,
658 this->_query.searchCondition.tableJoins,
659 this->_query.searchCondition.condition);
666template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
670 auto records = std::vector<Record> {};
672 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
674 RecordTableName<Record>,
675 this->_query.searchCondition.tableAlias,
676 this->_query.searchCondition.tableJoins,
677 this->_query.searchCondition.condition,
678 this->_query.orderBy,
679 this->_query.groupBy));
680 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
686 if constexpr (QueryOptions.loadRelations)
688 for (
auto& record: records)
690 _dm.ConfigureRelationAutoLoading(record);
697template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
699#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
700 requires(is_aggregate_type(parent_of(
Field)))
702 requires std::is_member_object_pointer_v<
decltype(
Field)>
707 auto result = std::vector<value_type> {};
710 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
711 FullyQualifiedNamesOf<Field>.string_view(),
712 RecordTableName<Record>,
713 this->_query.searchCondition.tableAlias,
714 this->_query.searchCondition.tableJoins,
715 this->_query.searchCondition.condition,
716 this->_query.orderBy,
717 this->_query.groupBy));
719 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
722 auto& value = result.emplace_back();
723 if (outputColumnsBound)
724 reader.BindOutputColumn(1, &value);
732 if (!outputColumnsBound)
739template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
740template <
auto... ReferencedFields>
741 requires(
sizeof...(ReferencedFields) >= 2)
744 auto records = std::vector<Record> {};
745 auto stmt = SqlStatement { _dm.Connection() };
747 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
748 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
749 RecordTableName<Record>,
750 this->_query.searchCondition.tableAlias,
751 this->_query.searchCondition.tableJoins,
752 this->_query.searchCondition.condition,
753 this->_query.orderBy,
754 this->_query.groupBy));
756 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
757 SqlResultCursor reader = stmt.GetResultCursor();
760 auto& record = records.emplace_back();
761 if (outputColumnsBound)
762#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
763 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
765 reader.BindOutputColumns(&(record.*ReferencedFields)...);
767 if (!reader.FetchRow())
772 if (!outputColumnsBound)
774 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
775 detail::GetAllColumns<ElementMask>(reader, record);
782template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
785 std::optional<Record> record {};
787 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
789 RecordTableName<Record>,
790 this->_query.searchCondition.tableAlias,
791 this->_query.searchCondition.tableJoins,
792 this->_query.searchCondition.condition,
793 this->_query.orderBy,
795 Derived::ReadResult(stmt.Connection().ServerType(), stmt.GetResultCursor(), &record);
796 if constexpr (QueryOptions.loadRelations)
799 _dm.ConfigureRelationAutoLoading(record.value());
804template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
806#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
807 requires(is_aggregate_type(parent_of(
Field)))
809 requires std::is_member_object_pointer_v<
decltype(
Field)>
813 auto constexpr count = 1;
815 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
816 FullyQualifiedNamesOf<Field>.string_view(),
817 RecordTableName<Record>,
818 this->_query.searchCondition.tableAlias,
819 this->_query.searchCondition.tableJoins,
820 this->_query.searchCondition.condition,
821 this->_query.orderBy,
823 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
828template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
829template <
auto... ReferencedFields>
830 requires(
sizeof...(ReferencedFields) >= 2)
833 auto optionalRecord = std::optional<Record> {};
835 auto stmt = SqlStatement { _dm.Connection() };
836 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
837 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
838 RecordTableName<Record>,
839 this->_query.searchCondition.tableAlias,
840 this->_query.searchCondition.tableJoins,
841 this->_query.searchCondition.condition,
842 this->_query.orderBy,
845 auto& record = optionalRecord.emplace();
846 SqlResultCursor reader = stmt.GetResultCursor();
847 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
848 if (outputColumnsBound)
849#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
850 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
852 reader.BindOutputColumns(&(record.*ReferencedFields)...);
854 if (!reader.FetchRow())
856 if (!outputColumnsBound)
858 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
859 detail::GetAllColumns<ElementMask>(reader, record);
862 if constexpr (QueryOptions.loadRelations)
863 _dm.ConfigureRelationAutoLoading(record);
865 return optionalRecord;
868template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
871 auto records = std::vector<Record> {};
874 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
876 RecordTableName<Record>,
877 this->_query.searchCondition.tableAlias,
878 this->_query.searchCondition.tableJoins,
879 this->_query.searchCondition.condition,
880 this->_query.orderBy,
882 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
884 if constexpr (QueryOptions.loadRelations)
886 for (
auto& record: records)
887 _dm.ConfigureRelationAutoLoading(record);
892template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
895 auto records = std::vector<Record> {};
897 records.reserve(limit);
899 _formatter.SelectRange(this->_query.distinct,
901 RecordTableName<Record>,
902 this->_query.searchCondition.tableAlias,
903 this->_query.searchCondition.tableJoins,
904 this->_query.searchCondition.condition,
905 !this->_query.orderBy.empty()
906 ? this->_query.orderBy
907 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
908 this->_query.groupBy,
911 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
912 if constexpr (QueryOptions.loadRelations)
914 for (
auto& record: records)
915 _dm.ConfigureRelationAutoLoading(record);
920template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
921template <
auto... ReferencedFields>
924 auto records = std::vector<Record> {};
925 auto stmt = SqlStatement { _dm.Connection() };
926 records.reserve(limit);
928 _formatter.SelectRange(this->_query.distinct,
929 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
930 RecordTableName<Record>,
931 this->_query.searchCondition.tableAlias,
932 this->_query.searchCondition.tableJoins,
933 this->_query.searchCondition.condition,
934 !this->_query.orderBy.empty()
935 ? this->_query.orderBy
936 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
937 this->_query.groupBy,
941 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
942 SqlResultCursor reader = stmt.GetResultCursor();
945 auto& record = records.emplace_back();
946 if (outputColumnsBound)
947#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
948 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
950 reader.BindOutputColumns(&(record.*ReferencedFields)...);
952 if (!reader.FetchRow())
957 if (!outputColumnsBound)
959 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
960 detail::GetAllColumns<ElementMask>(reader, record);
964 if constexpr (QueryOptions.loadRelations)
966 for (
auto& record: records)
967 _dm.ConfigureRelationAutoLoading(record);
973template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
974template <
auto... ReferencedFields>
977 auto records = std::vector<Record> {};
978 auto stmt = SqlStatement { _dm.Connection() };
980 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
981 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
982 RecordTableName<Record>,
983 this->_query.searchCondition.tableAlias,
984 this->_query.searchCondition.tableJoins,
985 this->_query.searchCondition.condition,
986 this->_query.orderBy,
989 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
990 SqlResultCursor reader = stmt.GetResultCursor();
993 auto& record = records.emplace_back();
994 if (outputColumnsBound)
995#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
996 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
998 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1000 if (!reader.FetchRow())
1005 if (!outputColumnsBound)
1007 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1008 detail::GetAllColumns<ElementMask>(reader, record);
1012 if constexpr (QueryOptions.loadRelations)
1014 for (
auto& record: records)
1015 _dm.ConfigureRelationAutoLoading(record);
1021template <
typename Record, DataMapperOptions QueryOptions>
1022void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResults(SqlServerType sqlServerType,
1023 SqlResultCursor reader,
1024 std::vector<Record>* records)
1028 Record& record = records->emplace_back();
1029 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1031 records->pop_back();
1037template <
typename Record, DataMapperOptions QueryOptions>
1038void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1039 SqlResultCursor reader,
1040 std::optional<Record>* optionalRecord)
1042 Record& record = optionalRecord->emplace();
1043 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1044 optionalRecord->reset();
1047template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions>
1048void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1049 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1053 auto& record = records->emplace_back();
1054 auto& [firstRecord, secondRecord] = record;
1056 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1057 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1059 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1060 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1061 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1063 if (canSafelyBindAll)
1065 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1066 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1069 if (!reader.FetchRow())
1071 records->pop_back();
1075 if (!canSafelyBindAll)
1076 detail::GetAllColumns(reader, record);
1080template <
typename Record>
1086 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1092 if constexpr (Value::IsOptional)
1094 if (!value.Value().has_value())
1096 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1100 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1103 else if constexpr (IsBelongsTo<Value>)
1105 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1107 else if constexpr (std::same_as<typename Value::ValueType, char>)
1112 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1115 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1116 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1118 return "{\n" + std::move(str) +
"\n}";
1121template <
typename Record>
1127 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1128#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1129 constexpr auto ctx = std::meta::access_context::current();
1130 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1132 using FieldType =
typename[:std::meta::type_of(el):];
1135 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1136 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameOf<el>),
1137 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1138 else if constexpr (FieldType::IsPrimaryKey)
1139 createTable.PrimaryKey(std::string(FieldNameOf<el>),
1140 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1141 else if constexpr (IsBelongsTo<FieldType>)
1143 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
1144 auto index = size_t(-1);
1145 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1146 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
1147 if constexpr (IsField<ReferencedFieldType>)
1148 if constexpr (ReferencedFieldType::IsPrimaryKey)
1153 createTable.ForeignKey(
1154 std::string(FieldNameOf<el>),
1155 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1157 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1158 .columnName = std::string { FieldNameOf<FieldType::ReferencedField> } });
1160 else if constexpr (FieldType::IsMandatory)
1161 createTable.RequiredColumn(std::string(FieldNameOf<el>),
1162 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1164 createTable.Column(std::string(FieldNameOf<el>), SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1169 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1172 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1173 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
1174 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1175 else if constexpr (FieldType::IsPrimaryKey)
1176 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
1177 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1178 else if constexpr (IsBelongsTo<FieldType>)
1180 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
1181 auto index = size_t(-1);
1182 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1183 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
1184 if constexpr (IsField<ReferencedFieldType>)
1185 if constexpr (ReferencedFieldType::IsPrimaryKey)
1190 createTable.ForeignKey(
1191 std::string(FieldNameAt<I, Record>),
1192 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1194 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1196 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
1198 else if constexpr (FieldType::IsMandatory)
1199 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
1200 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1202 createTable.Column(std::string(FieldNameAt<I, Record>),
1203 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1207 return migration.GetPlan().ToSql();
1210template <
typename FirstRecord,
typename... MoreRecords>
1213 std::vector<std::string> output;
1214 auto const append = [&output](
auto const& sql) {
1215 output.insert(output.end(), sql.begin(), sql.end());
1217 append(CreateTableString<FirstRecord>(serverType));
1218 (append(CreateTableString<MoreRecords>(serverType)), ...);
1222template <
typename Record>
1227 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1228 for (
auto const& sqlQueryString: sqlQueryStrings)
1232template <
typename FirstRecord,
typename... MoreRecords>
1235 CreateTable<FirstRecord>();
1236 (CreateTable<MoreRecords>(), ...);
1239template <
typename Record>
1244 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1246#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1247 constexpr auto ctx = std::meta::access_context::current();
1248 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1250 using FieldType =
typename[:std::meta::type_of(el):];
1251 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1252 query.Set(FieldNameOf<el>, SqlWildcard);
1255 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1256 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1257 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1263#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1265 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1267 using FieldType =
typename[:std::meta::type_of(el):];
1268 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1269 _stmt.BindInputParameter(i++, record.[:el:], std::meta::identifier_of(el));
1272 Reflection::CallOnMembers(
1274 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
1275 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1276 _stmt.BindInputParameter(i++, field, name);
1281 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1282 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1283 else if constexpr (HasPrimaryKey<Record>)
1285 RecordPrimaryKeyType<Record>
const* primaryKey =
nullptr;
1286#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1287 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1289 using FieldType =
typename[:std::meta::type_of(el):];
1290 if constexpr (IsField<FieldType>)
1292 if constexpr (FieldType::IsPrimaryKey)
1294 primaryKey = &record.[:el:].Value();
1299 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1300 if constexpr (IsField<FieldType>)
1302 if constexpr (FieldType::IsPrimaryKey)
1304 primaryKey = &field.Value();
1313template <
typename Record>
1316 static_assert(!std::is_const_v<Record>);
1321#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1322 constexpr auto ctx = std::meta::access_context::current();
1323 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1325 using FieldType =
typename[:std::meta::type_of(el):];
1326 if constexpr (IsField<FieldType>)
1327 if constexpr (FieldType::IsPrimaryKey)
1328 if constexpr (FieldType::IsAutoAssignPrimaryKey)
1330 if (!record.[:el:].IsModified())
1332 using ValueType =
typename FieldType::ValueType;
1333 if constexpr (std::same_as<ValueType, SqlGuid>)
1335 if (!record.[:el:].Value())
1338 else if constexpr (
requires { ValueType {} + 1; })
1340 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(std::format(
1341 R
"sql(SELECT MAX("{}") FROM "{}")sql", FieldNameOf<el>, RecordTableName<Record>));
1342 record.[:el:] = maxId.value_or(ValueType {}) + 1;
1348 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
1349 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
1351 if (!primaryKeyField.IsModified())
1353 using ValueType = PrimaryKeyType::ValueType;
1354 if constexpr (std::same_as<ValueType, SqlGuid>)
1356 if (!primaryKeyField.Value())
1359 else if constexpr (
requires { ValueType {} + 1; })
1361 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1362 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1363 FieldNameAt<PrimaryKeyIndex, Record>,
1364 RecordTableName<Record>));
1365 primaryKeyField = maxId.value_or(ValueType {}) + 1;
1374 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1375 SetId(record, _stmt.
LastInsertId(RecordTableName<Record>));
1380 if constexpr (HasPrimaryKey<Record>)
1384template <
typename Record>
1389 bool modified =
false;
1391#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1392 auto constexpr ctx = std::meta::access_context::current();
1393 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1395 if constexpr (
requires { record.[:el:].IsModified(); })
1397 modified = modified || record.[:el:].IsModified();
1401 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1402 if constexpr (
requires { field.IsModified(); })
1404 modified = modified || field.IsModified();
1412template <
typename Record>
1417 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1419#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1420 auto constexpr ctx = std::meta::access_context::current();
1421 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1423 using FieldType =
typename[:std::meta::type_of(el):];
1426 if (record.[:el:].IsModified())
1427 query.Set(FieldNameOf<el>, SqlWildcard);
1428 if constexpr (IsPrimaryKey<FieldType>)
1429 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1433 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1434 if (field.IsModified())
1435 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1438 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1439 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1446#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1447 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1449 if (record.[:el:].IsModified())
1451 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1455 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1457 using FieldType =
typename[:std::meta::type_of(el):];
1458 if constexpr (FieldType::IsPrimaryKey)
1460 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1465 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1466 if (field.IsModified())
1467 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1471 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1472 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1473 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1482template <
typename Record>
1487 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1489#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1490 auto constexpr ctx = std::meta::access_context::current();
1491 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1493 using FieldType =
typename[:std::meta::type_of(el):];
1494 if constexpr (FieldType::IsPrimaryKey)
1495 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1498 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& ) {
1499 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1500 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1506#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1508 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1510 using FieldType =
typename[:std::meta::type_of(el):];
1511 if constexpr (FieldType::IsPrimaryKey)
1513 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1518 Reflection::CallOnMembersWithoutName(
1519 record, [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
1520 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1521 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1530template <
typename Record,
typename... PrimaryKeyTypes>
1535 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1537 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1540 queryBuilder.Field(FieldNameAt<I, Record>);
1542 if constexpr (FieldType::IsPrimaryKey)
1543 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1547 _stmt.
Prepare(queryBuilder.First());
1548 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1550 auto resultRecord = std::optional<Record> { Record {} };
1553 return std::nullopt;
1555 return resultRecord;
1558template <
typename Record,
typename... PrimaryKeyTypes>
1561 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1567template <
typename Record,
typename... Args>
1572 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1574 selectQuery.
Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
1577 _stmt.
Execute(std::forward<Args>(args)...);
1579 auto resultRecord = std::optional<Record> { Record {} };
1582 return std::nullopt;
1583 return resultRecord;
1588template <
typename Record,
typename... InputParameters>
1590 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1592 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1594 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1597template <
typename Record,
typename... InputParameters>
1598std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1600 auto result = std::vector<Record> {};
1601 if constexpr (std::same_as<Record, SqlVariantRow>)
1603 _stmt.
Prepare(sqlQueryString);
1604 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1608 auto& record = result.emplace_back();
1609 record.reserve(numResultColumns);
1610 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1618 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1620 _stmt.
Prepare(sqlQueryString);
1621 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1627 auto& record = result.emplace_back();
1629 if (canSafelyBindOutputColumns)
1630 BindOutputColumns(record);
1632 if (!reader.FetchRow())
1635 if (!canSafelyBindOutputColumns)
1636 detail::GetAllColumns(reader, record);
1642 for (
auto& record: result)
1649template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
1651std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
1653 using value_type = std::tuple<First, Second, Rest...>;
1654 auto result = std::vector<value_type> {};
1656 _stmt.
Prepare(selectQuery.ToSql());
1660 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1663 if constexpr (I > 0)
1665 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1666 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1667 }(std::make_index_sequence<I> {});
1672 auto const BindElements = [&](
auto& record) {
1673 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1674 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1675 auto& element = std::get<I>(record);
1676 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1677 this->BindOutputColumns<TupleElement, offset>(element);
1681 auto const GetElements = [&](
auto& record) {
1682 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1683 auto& element = std::get<I>(record);
1684 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1685 detail::GetAllColumns(reader, element, offset - 1);
1689 bool const canSafelyBindOutputColumns = [&]() {
1691 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1692 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1700 auto& record = result.emplace_back();
1702 if (canSafelyBindOutputColumns)
1703 BindElements(record);
1705 if (!reader.FetchRow())
1708 if (!canSafelyBindOutputColumns)
1709 GetElements(record);
1715 if constexpr (QueryOptions.loadRelations)
1718 for (
auto& record: result)
1720 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1721 auto& element = std::get<I>(record);
1730template <
typename ElementMask,
typename Record,
typename... InputParameters>
1732 InputParameters&&... inputParameters)
1736 _stmt.
Prepare(selectQuery.ToSql());
1737 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1739 auto records = std::vector<Record> {};
1742 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1748 auto& record = records.emplace_back();
1750 if (canSafelyBindOutputColumns)
1751 BindOutputColumns<ElementMask>(record);
1753 if (!reader.FetchRow())
1756 if (!canSafelyBindOutputColumns)
1757 detail::GetAllColumns<ElementMask>(reader, record);
1763 for (
auto& record: records)
1769template <
typename Record>
1772 static_assert(!std::is_const_v<Record>);
1775 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1776 if constexpr (
requires { field.SetModified(
false); })
1778 field.SetModified(
false);
1783template <
typename Record,
typename Callable>
1784inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1788 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1789 if constexpr (IsField<FieldType>)
1791 if constexpr (FieldType::IsPrimaryKey)
1793 return callable.template operator()<I, FieldType>(field);
1799template <
typename Record,
typename Callable>
1800inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1802 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1804 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1805 if constexpr (IsField<FieldType>)
1807 if constexpr (FieldType::IsPrimaryKey)
1809 return callable.template operator()<I, FieldType>();
1815template <
typename Record,
typename Callable>
1816inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1818 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1820 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1821 if constexpr (IsBelongsTo<FieldType>)
1823 return callable.template operator()<I, FieldType>();
1828template <
typename FieldType>
1829std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1831 using ReferencedRecord = FieldType::ReferencedRecord;
1833 std::optional<ReferencedRecord> record { std::nullopt };
1835#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1836 auto constexpr ctx = std::meta::access_context::current();
1837 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1839 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1840 if constexpr (IsField<BelongsToFieldType>)
1841 if constexpr (BelongsToFieldType::IsPrimaryKey)
1843 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1844 record = std::move(result);
1847 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1851 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1852 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1853 record = std::move(result);
1856 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1862template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1863void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1865 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1866 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1868 using FieldType = HasMany<OtherRecord>;
1869 using ReferencedRecord = FieldType::ReferencedRecord;
1871 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1872 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1874 .Build([&](
auto& query) {
1875 Reflection::EnumerateMembers<ReferencedRecord>(
1876 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1877 if constexpr (FieldWithStorage<ReferencedFieldType>)
1879 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1883 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1884 callback(query, primaryKeyField);
1888template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1889void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1891 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1892 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1894 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1895 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1899template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1900void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1902 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1903 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
1906 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1908 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1910 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1912 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1917 _connection.
Query(RecordTableName<ReferencedRecord>)
1919 .Build([&](
auto& query) {
1920 Reflection::EnumerateMembers<ReferencedRecord>(
1921 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1922 if constexpr (FieldWithStorage<ReferencedFieldType>)
1924 query.Field(SqlQualifiedTableColumnName {
1925 RecordTableName<ReferencedRecord>,
1926 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1930 .InnerJoin(RecordTableName<ThroughRecord>,
1931 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1932 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1933 .InnerJoin(RecordTableName<Record>,
1934 FieldNameAt<PrimaryKeyIndex, Record>,
1935 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1936 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1938 SqlQualifiedTableColumnName {
1939 RecordTableName<Record>,
1940 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1943 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1945 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1953template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1954void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1956 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1959 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1961 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1962 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1963 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1966 CallOnBelongsTo<ThroughRecord>(
1967 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
1968 using ThroughBelongsToReferenceRecordFieldType =
1969 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1970 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1973 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1975 .Build([&](
auto& query) {
1976 Reflection::EnumerateMembers<ReferencedRecord>(
1977 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1978 if constexpr (FieldWithStorage<ReferencedFieldType>)
1980 query.Field(SqlQualifiedTableColumnName {
1981 RecordTableName<ReferencedRecord>,
1982 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1986 .InnerJoin(RecordTableName<ThroughRecord>,
1987 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1988 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1989 FieldNameAt<PrimaryKeyIndex, Record> })
1991 SqlQualifiedTableColumnName {
1992 RecordTableName<ThroughRecord>,
1993 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1996 callback(query, primaryKeyField);
2004template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2005void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2007 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2009 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2010 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2011 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2015template <
typename Record>
2018 static_assert(!std::is_const_v<Record>);
2021#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2022 constexpr auto ctx = std::meta::access_context::current();
2023 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2025 using FieldType =
typename[:std::meta::type_of(el):];
2026 if constexpr (IsBelongsTo<FieldType>)
2028 auto& field = record.[:el:];
2029 field = LoadBelongsTo<FieldType>(field.Value());
2031 else if constexpr (IsHasMany<FieldType>)
2033 LoadHasMany<el>(record, record.[:el:]);
2035 else if constexpr (IsHasOneThrough<FieldType>)
2037 LoadHasOneThrough(record, record.[:el:]);
2039 else if constexpr (IsHasManyThrough<FieldType>)
2041 LoadHasManyThrough(record, record.[:el:]);
2045 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2046 if constexpr (IsBelongsTo<FieldType>)
2048 field = LoadBelongsTo<FieldType>(field.Value());
2050 else if constexpr (IsHasMany<FieldType>)
2052 LoadHasMany<FieldIndex>(record, field);
2054 else if constexpr (IsHasOneThrough<FieldType>)
2056 LoadHasOneThrough(record, field);
2058 else if constexpr (IsHasManyThrough<FieldType>)
2060 LoadHasManyThrough(record, field);
2066template <
typename Record,
typename ValueType>
2067inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2072#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2074 auto constexpr ctx = std::meta::access_context::current();
2075 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2077 using FieldType =
typename[:std::meta::type_of(el):];
2078 if constexpr (IsField<FieldType>)
2080 if constexpr (FieldType::IsPrimaryKey)
2082 record.[:el:] = std::forward<ValueType>(
id);
2087 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
2088 if constexpr (IsField<FieldType>)
2090 if constexpr (FieldType::IsPrimaryKey)
2092 field = std::forward<FieldType>(
id);
2099template <
typename Record,
size_t InitialOffset>
2100inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2102 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2103 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
2107template <
typename Record,
size_t InitialOffset>
2108Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2110 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2114template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2115inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2117 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2118 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
2121template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2122Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2124 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2125 static_assert(!std::is_const_v<Record>);
2126 assert(stmt !=
nullptr);
2128#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2129 auto constexpr ctx = std::meta::access_context::current();
2130 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2131 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2133 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2134 using FieldType =
typename[:std::meta::type_of(el):];
2135 if constexpr (IsField<FieldType>)
2137 stmt->BindOutputColumn(i++, &record.[:el:].MutableValue());
2139 else if constexpr (SqlOutputColumnBinder<FieldType>)
2141 stmt->BindOutputColumn(i++, &record.[:el:]);
2145 Reflection::EnumerateMembers<ElementMask>(
2146 record, [stmt, i = SQLSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2147 if constexpr (IsField<Field>)
2149 stmt->BindOutputColumn(i++, &field.MutableValue());
2151 else if constexpr (SqlOutputColumnBinder<Field>)
2153 stmt->BindOutputColumn(i++, &field);
2161template <
typename Record>
2166 auto self = shared_from_this();
2167 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2168 if constexpr (IsBelongsTo<FieldType>)
2170 field.SetAutoLoader(
typename FieldType::Loader {
2171 .loadReference = [self, value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2172 return self->LoadBelongsTo<FieldType>(value);
2176 else if constexpr (IsHasMany<FieldType>)
2178 using ReferencedRecord = FieldType::ReferencedRecord;
2181 .count = [self, &record]() ->
size_t {
2183 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2185 self->_stmt.Prepare(selectQuery.Count());
2186 self->_stmt.Execute(primaryKeyField.Value());
2187 if (self->_stmt.FetchRow())
2188 count = self->_stmt.GetColumn<
size_t>(1);
2189 self->_stmt.CloseCursor();
2193 .all = [self, &record, &hasMany]() { self->LoadHasMany<FieldIndex>(record, hasMany); },
2195 [self, &record](
auto const& each) {
2196 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2199 stmt.
Prepare(selectQuery.All());
2200 stmt.Execute(primaryKeyField.Value());
2202 auto referencedRecord = ReferencedRecord {};
2203 self->BindOutputColumns(referencedRecord, &stmt);
2204 self->ConfigureRelationAutoLoading(referencedRecord);
2206 while (stmt.FetchRow())
2208 each(referencedRecord);
2209 self->BindOutputColumns(referencedRecord, &stmt);
2215 else if constexpr (IsHasOneThrough<FieldType>)
2217 using ReferencedRecord = FieldType::ReferencedRecord;
2218 using ThroughRecord = FieldType::ThroughRecord;
2222 [self, &record, &hasOneThrough]() {
2223 self->LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
2227 else if constexpr (IsHasManyThrough<FieldType>)
2229 using ReferencedRecord = FieldType::ReferencedRecord;
2230 using ThroughRecord = FieldType::ThroughRecord;
2233 .count = [self, &record]() ->
size_t {
2236 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2238 self->_stmt.Prepare(selectQuery.Count());
2239 self->_stmt.Execute(primaryKeyField.Value());
2240 if (self->_stmt.FetchRow())
2241 count = self->_stmt.GetColumn<
size_t>(1);
2242 self->_stmt.CloseCursor();
2247 [self, &record, &hasManyThrough]() {
2249 self->LoadHasManyThrough(record, hasManyThrough);
2252 [self, &record](
auto const& each) {
2254 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2257 stmt.
Prepare(selectQuery.All());
2258 stmt.Execute(primaryKeyField.Value());
2260 auto referencedRecord = ReferencedRecord {};
2261 self->BindOutputColumns(referencedRecord, &stmt);
2262 self->ConfigureRelationAutoLoading(referencedRecord);
2264 while (stmt.FetchRow())
2266 each(referencedRecord);
2267 self->BindOutputColumns(referencedRecord, &stmt);
2275#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2276 constexpr auto ctx = std::meta::access_context::current();
2278 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2279 constexpr auto localctx = std::meta::access_context::current();
2280 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2281 using FieldType =
typename[:std::meta::type_of(members[I]):];
2282 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2285 Reflection::EnumerateMembers(record, callback);