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>;
356 template <
typename Record>
357 void Update(Record& record);
360 template <
typename Record>
361 std::size_t
Delete(Record
const& record);
366 return _connection.
Query(tableName);
370 template <
typename Record>
371 bool IsModified(Record
const& record)
const noexcept;
374 template <
typename Record>
378 template <
typename Record>
385 template <
typename Record>
392 template <
typename T>
393 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
402 template <
typename Record,
typename... Args>
405 template <
typename Record,
typename ValueType>
406 void SetId(Record& record, ValueType&&
id);
408 template <
typename Record,
size_t InitialOffset = 1>
409 Record& BindOutputColumns(Record& record);
411 template <
typename Record,
size_t InitialOffset = 1>
412 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
414 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
415 Record& BindOutputColumns(Record& record);
417 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
418 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
420 template <
typename FieldType>
421 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
423 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
426 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
429 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
432 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
433 void CallOnHasMany(Record& record, Callable
const& callback);
435 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
436 void CallOnHasManyThrough(Record& record, Callable
const& callback);
446 template <
typename FieldType>
447 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
449 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
456 if constexpr (IsField<FieldType>)
458 if constexpr (detail::OneOf<
typename FieldType::ValueType,
464 || IsSqlDynamicString<typename FieldType::ValueType>
465 || IsSqlDynamicBinary<typename FieldType::ValueType>)
474 template <DataMapperRecord Record>
475 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
477 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
481 Reflection::EnumerateMembers<Record>([&result]<
size_t I,
typename Field>() {
482 if constexpr (IsField<Field>)
484 if constexpr (detail::OneOf<
typename Field::ValueType,
490 || IsSqlDynamicString<typename Field::ValueType>
491 || IsSqlDynamicBinary<typename Field::ValueType>)
501 template <
typename Record>
502 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLSMALLINT startOffset)
504 Reflection::EnumerateMembers(record,
505 [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
506 if constexpr (IsField<Field>)
508 reader->BindOutputColumn(i++, &field.MutableValue());
510 else if constexpr (IsBelongsTo<Field>)
512 reader->BindOutputColumn(i++, &field.MutableValue());
514 else if constexpr (SqlOutputColumnBinder<Field>)
516 reader->BindOutputColumn(i++, &field);
521 template <
typename Record>
522 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
524 BindAllOutputColumnsWithOffset(reader, record, 1);
530 template <
typename ElementMask,
typename Record>
531 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
533 Reflection::EnumerateMembers<ElementMask>(
534 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
536 if constexpr (IsField<Field>)
539 field.MutableValue() =
540 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
542 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
544 else if constexpr (SqlGetColumnNativeType<Field>)
546 if constexpr (IsOptionalBelongsTo<Field>)
547 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
549 field = reader->GetColumn<Field>(indexFromQuery);
554 template <
typename Record>
555 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
557 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(
558 reader, record, indexFromQuery);
561 template <
typename FirstRecord,
typename SecondRecord>
563 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
565 auto& [firstRecord, secondRecord] = record;
567 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
568 if constexpr (IsField<Field>)
571 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(I + 1);
573 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(I + 1);
575 else if constexpr (SqlGetColumnNativeType<Field>)
578 field = reader->GetNullableColumn<
typename Field::BaseType>(I + 1);
580 field = reader->GetColumn<Field>(I + 1);
584 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
585 if constexpr (IsField<Field>)
588 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(
589 Reflection::CountMembers<FirstRecord> + I + 1);
591 field.MutableValue() =
592 reader->GetColumn<
typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
594 else if constexpr (SqlGetColumnNativeType<Field>)
598 reader->GetNullableColumn<
typename Field::BaseType>(Reflection::CountMembers<FirstRecord> + I + 1);
600 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
605 template <
typename Record>
606 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
608 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
610 if (outputColumnsBound)
611 BindAllOutputColumns(reader, record);
613 if (!reader.FetchRow())
616 if (!outputColumnsBound)
617 GetAllColumns(reader, record);
623template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
624inline SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::SqlCoreDataMapperQueryBuilder(
625 DataMapper& dm, std::string fields)
noexcept:
627 _formatter { dm.Connection().QueryFormatter() },
628 _fields { std::move(fields) }
632template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
636 stmt.ExecuteDirect(_formatter.SelectCount(this->_query.distinct,
637 RecordTableName<Record>,
638 this->_query.searchCondition.tableAlias,
639 this->_query.searchCondition.tableJoins,
640 this->_query.searchCondition.condition));
641 auto reader = stmt.GetResultCursor();
642 if (reader.FetchRow())
643 return reader.GetColumn<
size_t>(1);
647template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
652 auto const query = _formatter.SelectFirst(this->_query.distinct,
654 RecordTableName<Record>,
655 this->_query.searchCondition.tableAlias,
656 this->_query.searchCondition.tableJoins,
657 this->_query.searchCondition.condition,
658 this->_query.orderBy,
661 stmt.ExecuteDirect(query);
662 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
667template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
672 auto const query = _formatter.Delete(RecordTableName<Record>,
673 this->_query.searchCondition.tableAlias,
674 this->_query.searchCondition.tableJoins,
675 this->_query.searchCondition.condition);
682template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
686 auto records = std::vector<Record> {};
688 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
690 RecordTableName<Record>,
691 this->_query.searchCondition.tableAlias,
692 this->_query.searchCondition.tableJoins,
693 this->_query.searchCondition.condition,
694 this->_query.orderBy,
695 this->_query.groupBy));
696 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
702 if constexpr (QueryOptions.loadRelations)
704 for (
auto& record: records)
706 _dm.ConfigureRelationAutoLoading(record);
713template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
715#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
716 requires(is_aggregate_type(parent_of(
Field)))
718 requires std::is_member_object_pointer_v<
decltype(
Field)>
723 auto result = std::vector<value_type> {};
726 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
727 FullyQualifiedNamesOf<Field>.string_view(),
728 RecordTableName<Record>,
729 this->_query.searchCondition.tableAlias,
730 this->_query.searchCondition.tableJoins,
731 this->_query.searchCondition.condition,
732 this->_query.orderBy,
733 this->_query.groupBy));
735 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
738 auto& value = result.emplace_back();
739 if (outputColumnsBound)
740 reader.BindOutputColumn(1, &value);
748 if (!outputColumnsBound)
755template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
756template <
auto... ReferencedFields>
757 requires(
sizeof...(ReferencedFields) >= 2)
760 auto records = std::vector<Record> {};
761 auto stmt = SqlStatement { _dm.Connection() };
763 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
764 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
765 RecordTableName<Record>,
766 this->_query.searchCondition.tableAlias,
767 this->_query.searchCondition.tableJoins,
768 this->_query.searchCondition.condition,
769 this->_query.orderBy,
770 this->_query.groupBy));
772 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
773 SqlResultCursor reader = stmt.GetResultCursor();
776 auto& record = records.emplace_back();
777 if (outputColumnsBound)
778#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
779 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
781 reader.BindOutputColumns(&(record.*ReferencedFields)...);
783 if (!reader.FetchRow())
788 if (!outputColumnsBound)
790 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
791 detail::GetAllColumns<ElementMask>(reader, record);
798template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
801 std::optional<Record> record {};
803 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
805 RecordTableName<Record>,
806 this->_query.searchCondition.tableAlias,
807 this->_query.searchCondition.tableJoins,
808 this->_query.searchCondition.condition,
809 this->_query.orderBy,
811 Derived::ReadResult(stmt.Connection().ServerType(), stmt.GetResultCursor(), &record);
812 if constexpr (QueryOptions.loadRelations)
815 _dm.ConfigureRelationAutoLoading(record.value());
820template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
822#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
823 requires(is_aggregate_type(parent_of(
Field)))
825 requires std::is_member_object_pointer_v<
decltype(
Field)>
829 auto constexpr count = 1;
831 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
832 FullyQualifiedNamesOf<Field>.string_view(),
833 RecordTableName<Record>,
834 this->_query.searchCondition.tableAlias,
835 this->_query.searchCondition.tableJoins,
836 this->_query.searchCondition.condition,
837 this->_query.orderBy,
839 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
844template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
845template <
auto... ReferencedFields>
846 requires(
sizeof...(ReferencedFields) >= 2)
849 auto optionalRecord = std::optional<Record> {};
851 auto stmt = SqlStatement { _dm.Connection() };
852 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
853 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
854 RecordTableName<Record>,
855 this->_query.searchCondition.tableAlias,
856 this->_query.searchCondition.tableJoins,
857 this->_query.searchCondition.condition,
858 this->_query.orderBy,
861 auto& record = optionalRecord.emplace();
862 SqlResultCursor reader = stmt.GetResultCursor();
863 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
864 if (outputColumnsBound)
865#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
866 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
868 reader.BindOutputColumns(&(record.*ReferencedFields)...);
870 if (!reader.FetchRow())
872 if (!outputColumnsBound)
874 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
875 detail::GetAllColumns<ElementMask>(reader, record);
878 if constexpr (QueryOptions.loadRelations)
879 _dm.ConfigureRelationAutoLoading(record);
881 return optionalRecord;
884template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
887 auto records = std::vector<Record> {};
890 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
892 RecordTableName<Record>,
893 this->_query.searchCondition.tableAlias,
894 this->_query.searchCondition.tableJoins,
895 this->_query.searchCondition.condition,
896 this->_query.orderBy,
898 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
900 if constexpr (QueryOptions.loadRelations)
902 for (
auto& record: records)
903 _dm.ConfigureRelationAutoLoading(record);
908template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
911 auto records = std::vector<Record> {};
913 records.reserve(limit);
915 _formatter.SelectRange(this->_query.distinct,
917 RecordTableName<Record>,
918 this->_query.searchCondition.tableAlias,
919 this->_query.searchCondition.tableJoins,
920 this->_query.searchCondition.condition,
921 !this->_query.orderBy.empty()
922 ? this->_query.orderBy
923 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
924 this->_query.groupBy,
927 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
928 if constexpr (QueryOptions.loadRelations)
930 for (
auto& record: records)
931 _dm.ConfigureRelationAutoLoading(record);
936template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
937template <
auto... ReferencedFields>
940 auto records = std::vector<Record> {};
941 auto stmt = SqlStatement { _dm.Connection() };
942 records.reserve(limit);
944 _formatter.SelectRange(this->_query.distinct,
945 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
946 RecordTableName<Record>,
947 this->_query.searchCondition.tableAlias,
948 this->_query.searchCondition.tableJoins,
949 this->_query.searchCondition.condition,
950 !this->_query.orderBy.empty()
951 ? this->_query.orderBy
952 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
953 this->_query.groupBy,
957 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
958 SqlResultCursor reader = stmt.GetResultCursor();
961 auto& record = records.emplace_back();
962 if (outputColumnsBound)
963#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
964 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
966 reader.BindOutputColumns(&(record.*ReferencedFields)...);
968 if (!reader.FetchRow())
973 if (!outputColumnsBound)
975 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
976 detail::GetAllColumns<ElementMask>(reader, record);
980 if constexpr (QueryOptions.loadRelations)
982 for (
auto& record: records)
983 _dm.ConfigureRelationAutoLoading(record);
989template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
990template <
auto... ReferencedFields>
993 auto records = std::vector<Record> {};
994 auto stmt = SqlStatement { _dm.Connection() };
996 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
997 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
998 RecordTableName<Record>,
999 this->_query.searchCondition.tableAlias,
1000 this->_query.searchCondition.tableJoins,
1001 this->_query.searchCondition.condition,
1002 this->_query.orderBy,
1005 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1006 SqlResultCursor reader = stmt.GetResultCursor();
1009 auto& record = records.emplace_back();
1010 if (outputColumnsBound)
1011#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1012 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1014 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1016 if (!reader.FetchRow())
1021 if (!outputColumnsBound)
1023 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1024 detail::GetAllColumns<ElementMask>(reader, record);
1028 if constexpr (QueryOptions.loadRelations)
1030 for (
auto& record: records)
1031 _dm.ConfigureRelationAutoLoading(record);
1037template <
typename Record, DataMapperOptions QueryOptions>
1038void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResults(SqlServerType sqlServerType,
1039 SqlResultCursor reader,
1040 std::vector<Record>* records)
1044 Record& record = records->emplace_back();
1045 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1047 records->pop_back();
1053template <
typename Record, DataMapperOptions QueryOptions>
1054void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1055 SqlResultCursor reader,
1056 std::optional<Record>* optionalRecord)
1058 Record& record = optionalRecord->emplace();
1059 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1060 optionalRecord->reset();
1063template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions>
1064void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1065 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1069 auto& record = records->emplace_back();
1070 auto& [firstRecord, secondRecord] = record;
1072 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1073 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1075 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1076 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1077 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1079 if (canSafelyBindAll)
1081 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1082 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1085 if (!reader.FetchRow())
1087 records->pop_back();
1091 if (!canSafelyBindAll)
1092 detail::GetAllColumns(reader, record);
1096template <
typename Record>
1102 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1108 if constexpr (Value::IsOptional)
1110 if (!value.Value().has_value())
1112 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1116 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1119 else if constexpr (IsBelongsTo<Value>)
1121 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1123 else if constexpr (std::same_as<typename Value::ValueType, char>)
1128 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1131 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1132 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1134 return "{\n" + std::move(str) +
"\n}";
1137template <
typename Record>
1143 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1144#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1145 constexpr auto ctx = std::meta::access_context::current();
1146 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1148 using FieldType =
typename[:std::meta::type_of(el):];
1151 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1152 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameOf<el>),
1153 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1154 else if constexpr (FieldType::IsPrimaryKey)
1155 createTable.PrimaryKey(std::string(FieldNameOf<el>),
1156 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1157 else if constexpr (IsBelongsTo<FieldType>)
1159 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
1160 auto index = size_t(-1);
1161 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1162 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
1163 if constexpr (IsField<ReferencedFieldType>)
1164 if constexpr (ReferencedFieldType::IsPrimaryKey)
1169 createTable.ForeignKey(
1170 std::string(FieldNameOf<el>),
1171 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1173 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1174 .columnName = std::string { FieldNameOf<FieldType::ReferencedField> } });
1176 else if constexpr (FieldType::IsMandatory)
1177 createTable.RequiredColumn(std::string(FieldNameOf<el>),
1178 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1180 createTable.Column(std::string(FieldNameOf<el>), SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1185 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1188 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1189 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
1190 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1191 else if constexpr (FieldType::IsPrimaryKey)
1192 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
1193 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1194 else if constexpr (IsBelongsTo<FieldType>)
1196 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
1197 auto index = size_t(-1);
1198 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1199 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
1200 if constexpr (IsField<ReferencedFieldType>)
1201 if constexpr (ReferencedFieldType::IsPrimaryKey)
1206 createTable.ForeignKey(
1207 std::string(FieldNameAt<I, Record>),
1208 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1210 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1212 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
1214 else if constexpr (FieldType::IsMandatory)
1215 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
1216 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1218 createTable.Column(std::string(FieldNameAt<I, Record>),
1219 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1223 return migration.GetPlan().ToSql();
1226template <
typename FirstRecord,
typename... MoreRecords>
1229 std::vector<std::string> output;
1230 auto const append = [&output](
auto const& sql) {
1231 output.insert(output.end(), sql.begin(), sql.end());
1233 append(CreateTableString<FirstRecord>(serverType));
1234 (append(CreateTableString<MoreRecords>(serverType)), ...);
1238template <
typename Record>
1243 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1244 for (
auto const& sqlQueryString: sqlQueryStrings)
1248template <
typename FirstRecord,
typename... MoreRecords>
1251 CreateTable<FirstRecord>();
1252 (CreateTable<MoreRecords>(), ...);
1255template <
typename Record>
1260 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1262#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1263 constexpr auto ctx = std::meta::access_context::current();
1264 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1266 using FieldType =
typename[:std::meta::type_of(el):];
1267 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1268 query.Set(FieldNameOf<el>, SqlWildcard);
1271 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1272 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1273 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1279#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1281 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1283 using FieldType =
typename[:std::meta::type_of(el):];
1284 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1285 _stmt.BindInputParameter(i++, record.[:el:], std::meta::identifier_of(el));
1288 Reflection::CallOnMembers(
1290 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
1291 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1292 _stmt.BindInputParameter(i++, field, name);
1297 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1298 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1299 else if constexpr (HasPrimaryKey<Record>)
1301 RecordPrimaryKeyType<Record>
const* primaryKey =
nullptr;
1302#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1303 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1305 using FieldType =
typename[:std::meta::type_of(el):];
1306 if constexpr (IsField<FieldType>)
1308 if constexpr (FieldType::IsPrimaryKey)
1310 primaryKey = &record.[:el:].Value();
1315 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1316 if constexpr (IsField<FieldType>)
1318 if constexpr (FieldType::IsPrimaryKey)
1320 primaryKey = &field.Value();
1329template <
typename Record>
1332 static_assert(!std::is_const_v<Record>);
1337#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1338 constexpr auto ctx = std::meta::access_context::current();
1339 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1341 using FieldType =
typename[:std::meta::type_of(el):];
1342 if constexpr (IsField<FieldType>)
1343 if constexpr (FieldType::IsPrimaryKey)
1344 if constexpr (FieldType::IsAutoAssignPrimaryKey)
1346 if (!record.[:el:].IsModified())
1348 using ValueType =
typename FieldType::ValueType;
1349 if constexpr (std::same_as<ValueType, SqlGuid>)
1351 if (!record.[:el:].Value())
1354 else if constexpr (
requires { ValueType {} + 1; })
1356 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(std::format(
1357 R
"sql(SELECT MAX("{}") FROM "{}")sql", FieldNameOf<el>, RecordTableName<Record>));
1358 record.[:el:] = maxId.value_or(ValueType {}) + 1;
1364 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
1365 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
1367 if (!primaryKeyField.IsModified())
1369 using ValueType = PrimaryKeyType::ValueType;
1370 if constexpr (std::same_as<ValueType, SqlGuid>)
1372 if (!primaryKeyField.Value())
1375 else if constexpr (
requires { ValueType {} + 1; })
1377 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1378 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1379 FieldNameAt<PrimaryKeyIndex, Record>,
1380 RecordTableName<Record>));
1381 primaryKeyField = maxId.value_or(ValueType {}) + 1;
1390 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1391 SetId(record, _stmt.
LastInsertId(RecordTableName<Record>));
1396 if constexpr (HasPrimaryKey<Record>)
1400template <
typename Record>
1405 bool modified =
false;
1407#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1408 auto constexpr ctx = std::meta::access_context::current();
1409 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1411 if constexpr (
requires { record.[:el:].IsModified(); })
1413 modified = modified || record.[:el:].IsModified();
1417 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1418 if constexpr (
requires { field.IsModified(); })
1420 modified = modified || field.IsModified();
1428template <
typename Record>
1433 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1435#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1436 auto constexpr ctx = std::meta::access_context::current();
1437 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1439 using FieldType =
typename[:std::meta::type_of(el):];
1442 if (record.[:el:].IsModified())
1443 query.Set(FieldNameOf<el>, SqlWildcard);
1444 if constexpr (IsPrimaryKey<FieldType>)
1445 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1449 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1450 if (field.IsModified())
1451 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1454 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1455 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1462#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1463 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1465 if (record.[:el:].IsModified())
1467 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
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(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1482 if (field.IsModified())
1483 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1487 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1488 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1489 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1498template <
typename Record>
1503 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1505#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1506 auto constexpr ctx = std::meta::access_context::current();
1507 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1509 using FieldType =
typename[:std::meta::type_of(el):];
1510 if constexpr (FieldType::IsPrimaryKey)
1511 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1514 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& ) {
1515 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1516 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1522#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1524 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1526 using FieldType =
typename[:std::meta::type_of(el):];
1527 if constexpr (FieldType::IsPrimaryKey)
1529 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1534 Reflection::CallOnMembersWithoutName(
1535 record, [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
1536 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1537 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1546template <
typename Record,
typename... PrimaryKeyTypes>
1551 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1553 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1556 queryBuilder.Field(FieldNameAt<I, Record>);
1558 if constexpr (FieldType::IsPrimaryKey)
1559 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1563 _stmt.
Prepare(queryBuilder.First());
1564 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1566 auto resultRecord = std::optional<Record> { Record {} };
1569 return std::nullopt;
1571 return resultRecord;
1574template <
typename Record,
typename... PrimaryKeyTypes>
1577 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1583template <
typename Record,
typename... Args>
1588 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1590 selectQuery.
Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
1593 _stmt.
Execute(std::forward<Args>(args)...);
1595 auto resultRecord = std::optional<Record> { Record {} };
1598 return std::nullopt;
1599 return resultRecord;
1604template <
typename Record,
typename... InputParameters>
1606 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1608 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1610 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1613template <
typename Record,
typename... InputParameters>
1614std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1616 auto result = std::vector<Record> {};
1617 if constexpr (std::same_as<Record, SqlVariantRow>)
1619 _stmt.
Prepare(sqlQueryString);
1620 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1624 auto& record = result.emplace_back();
1625 record.reserve(numResultColumns);
1626 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1634 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1636 _stmt.
Prepare(sqlQueryString);
1637 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1643 auto& record = result.emplace_back();
1645 if (canSafelyBindOutputColumns)
1646 BindOutputColumns(record);
1648 if (!reader.FetchRow())
1651 if (!canSafelyBindOutputColumns)
1652 detail::GetAllColumns(reader, record);
1658 for (
auto& record: result)
1665template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
1667std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
1669 using value_type = std::tuple<First, Second, Rest...>;
1670 auto result = std::vector<value_type> {};
1672 _stmt.
Prepare(selectQuery.ToSql());
1676 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1679 if constexpr (I > 0)
1681 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1682 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1683 }(std::make_index_sequence<I> {});
1688 auto const BindElements = [&](
auto& record) {
1689 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1690 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1691 auto& element = std::get<I>(record);
1692 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1693 this->BindOutputColumns<TupleElement, offset>(element);
1697 auto const GetElements = [&](
auto& record) {
1698 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1699 auto& element = std::get<I>(record);
1700 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1701 detail::GetAllColumns(reader, element, offset - 1);
1705 bool const canSafelyBindOutputColumns = [&]() {
1707 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1708 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1716 auto& record = result.emplace_back();
1718 if (canSafelyBindOutputColumns)
1719 BindElements(record);
1721 if (!reader.FetchRow())
1724 if (!canSafelyBindOutputColumns)
1725 GetElements(record);
1731 if constexpr (QueryOptions.loadRelations)
1734 for (
auto& record: result)
1736 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1737 auto& element = std::get<I>(record);
1746template <
typename ElementMask,
typename Record,
typename... InputParameters>
1748 InputParameters&&... inputParameters)
1752 _stmt.
Prepare(selectQuery.ToSql());
1753 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1755 auto records = std::vector<Record> {};
1758 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1764 auto& record = records.emplace_back();
1766 if (canSafelyBindOutputColumns)
1767 BindOutputColumns<ElementMask>(record);
1769 if (!reader.FetchRow())
1772 if (!canSafelyBindOutputColumns)
1773 detail::GetAllColumns<ElementMask>(reader, record);
1779 for (
auto& record: records)
1785template <
typename Record>
1788 static_assert(!std::is_const_v<Record>);
1791 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1792 if constexpr (
requires { field.SetModified(
false); })
1794 field.SetModified(
false);
1799template <
typename Record,
typename Callable>
1800inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1804 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1805 if constexpr (IsField<FieldType>)
1807 if constexpr (FieldType::IsPrimaryKey)
1809 return callable.template operator()<I, FieldType>(field);
1815template <
typename Record,
typename Callable>
1816inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1818 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1820 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1821 if constexpr (IsField<FieldType>)
1823 if constexpr (FieldType::IsPrimaryKey)
1825 return callable.template operator()<I, FieldType>();
1831template <
typename Record,
typename Callable>
1832inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1834 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1836 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1837 if constexpr (IsBelongsTo<FieldType>)
1839 return callable.template operator()<I, FieldType>();
1844template <
typename FieldType>
1845std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1847 using ReferencedRecord = FieldType::ReferencedRecord;
1849 std::optional<ReferencedRecord> record { std::nullopt };
1851#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1852 auto constexpr ctx = std::meta::access_context::current();
1853 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1855 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1856 if constexpr (IsField<BelongsToFieldType>)
1857 if constexpr (BelongsToFieldType::IsPrimaryKey)
1859 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1860 record = std::move(result);
1863 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1867 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1868 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1869 record = std::move(result);
1872 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1878template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1879void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1881 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1882 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1884 using FieldType = HasMany<OtherRecord>;
1885 using ReferencedRecord = FieldType::ReferencedRecord;
1887 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1888 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1890 .Build([&](
auto& query) {
1891 Reflection::EnumerateMembers<ReferencedRecord>(
1892 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1893 if constexpr (FieldWithStorage<ReferencedFieldType>)
1895 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1899 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1900 callback(query, primaryKeyField);
1904template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1905void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1907 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1908 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1910 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1911 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1915template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1916void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1918 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1919 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
1922 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1924 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1926 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1928 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1933 _connection.
Query(RecordTableName<ReferencedRecord>)
1935 .Build([&](
auto& query) {
1936 Reflection::EnumerateMembers<ReferencedRecord>(
1937 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1938 if constexpr (FieldWithStorage<ReferencedFieldType>)
1940 query.Field(SqlQualifiedTableColumnName {
1941 RecordTableName<ReferencedRecord>,
1942 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1946 .InnerJoin(RecordTableName<ThroughRecord>,
1947 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1948 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1949 .InnerJoin(RecordTableName<Record>,
1950 FieldNameAt<PrimaryKeyIndex, Record>,
1951 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1952 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1954 SqlQualifiedTableColumnName {
1955 RecordTableName<Record>,
1956 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1959 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1961 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1969template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1970void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1972 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1975 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1977 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1978 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1979 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1982 CallOnBelongsTo<ThroughRecord>(
1983 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
1984 using ThroughBelongsToReferenceRecordFieldType =
1985 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1986 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1989 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1991 .Build([&](
auto& query) {
1992 Reflection::EnumerateMembers<ReferencedRecord>(
1993 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1994 if constexpr (FieldWithStorage<ReferencedFieldType>)
1996 query.Field(SqlQualifiedTableColumnName {
1997 RecordTableName<ReferencedRecord>,
1998 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2002 .InnerJoin(RecordTableName<ThroughRecord>,
2003 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2004 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2005 FieldNameAt<PrimaryKeyIndex, Record> })
2007 SqlQualifiedTableColumnName {
2008 RecordTableName<ThroughRecord>,
2009 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2012 callback(query, primaryKeyField);
2020template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2021void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2023 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2025 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2026 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2027 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2031template <
typename Record>
2034 static_assert(!std::is_const_v<Record>);
2037#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2038 constexpr auto ctx = std::meta::access_context::current();
2039 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2041 using FieldType =
typename[:std::meta::type_of(el):];
2042 if constexpr (IsBelongsTo<FieldType>)
2044 auto& field = record.[:el:];
2045 field = LoadBelongsTo<FieldType>(field.Value());
2047 else if constexpr (IsHasMany<FieldType>)
2049 LoadHasMany<el>(record, record.[:el:]);
2051 else if constexpr (IsHasOneThrough<FieldType>)
2053 LoadHasOneThrough(record, record.[:el:]);
2055 else if constexpr (IsHasManyThrough<FieldType>)
2057 LoadHasManyThrough(record, record.[:el:]);
2061 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2062 if constexpr (IsBelongsTo<FieldType>)
2064 field = LoadBelongsTo<FieldType>(field.Value());
2066 else if constexpr (IsHasMany<FieldType>)
2068 LoadHasMany<FieldIndex>(record, field);
2070 else if constexpr (IsHasOneThrough<FieldType>)
2072 LoadHasOneThrough(record, field);
2074 else if constexpr (IsHasManyThrough<FieldType>)
2076 LoadHasManyThrough(record, field);
2082template <
typename Record,
typename ValueType>
2083inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2088#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2090 auto constexpr ctx = std::meta::access_context::current();
2091 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2093 using FieldType =
typename[:std::meta::type_of(el):];
2094 if constexpr (IsField<FieldType>)
2096 if constexpr (FieldType::IsPrimaryKey)
2098 record.[:el:] = std::forward<ValueType>(
id);
2103 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
2104 if constexpr (IsField<FieldType>)
2106 if constexpr (FieldType::IsPrimaryKey)
2108 field = std::forward<FieldType>(
id);
2115template <
typename Record,
size_t InitialOffset>
2116inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2118 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2119 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
2123template <
typename Record,
size_t InitialOffset>
2124Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2126 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2130template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2131inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2133 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2134 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
2137template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2138Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2140 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2141 static_assert(!std::is_const_v<Record>);
2142 assert(stmt !=
nullptr);
2144#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2145 auto constexpr ctx = std::meta::access_context::current();
2146 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2147 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2149 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2150 using FieldType =
typename[:std::meta::type_of(el):];
2151 if constexpr (IsField<FieldType>)
2153 stmt->BindOutputColumn(i++, &record.[:el:].MutableValue());
2155 else if constexpr (SqlOutputColumnBinder<FieldType>)
2157 stmt->BindOutputColumn(i++, &record.[:el:]);
2161 Reflection::EnumerateMembers<ElementMask>(
2162 record, [stmt, i = SQLSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2163 if constexpr (IsField<Field>)
2165 stmt->BindOutputColumn(i++, &field.MutableValue());
2167 else if constexpr (SqlOutputColumnBinder<Field>)
2169 stmt->BindOutputColumn(i++, &field);
2177template <
typename Record>
2182 auto self = shared_from_this();
2183 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2184 if constexpr (IsBelongsTo<FieldType>)
2186 field.SetAutoLoader(
typename FieldType::Loader {
2187 .loadReference = [self, value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2188 return self->LoadBelongsTo<FieldType>(value);
2192 else if constexpr (IsHasMany<FieldType>)
2194 using ReferencedRecord = FieldType::ReferencedRecord;
2197 .count = [self, &record]() ->
size_t {
2199 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
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();
2209 .all = [self, &record, &hasMany]() { self->LoadHasMany<FieldIndex>(record, hasMany); },
2211 [self, &record](
auto const& each) {
2212 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2215 stmt.
Prepare(selectQuery.All());
2216 stmt.Execute(primaryKeyField.Value());
2218 auto referencedRecord = ReferencedRecord {};
2219 self->BindOutputColumns(referencedRecord, &stmt);
2220 self->ConfigureRelationAutoLoading(referencedRecord);
2222 while (stmt.FetchRow())
2224 each(referencedRecord);
2225 self->BindOutputColumns(referencedRecord, &stmt);
2231 else if constexpr (IsHasOneThrough<FieldType>)
2233 using ReferencedRecord = FieldType::ReferencedRecord;
2234 using ThroughRecord = FieldType::ThroughRecord;
2238 [self, &record, &hasOneThrough]() {
2239 self->LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
2243 else if constexpr (IsHasManyThrough<FieldType>)
2245 using ReferencedRecord = FieldType::ReferencedRecord;
2246 using ThroughRecord = FieldType::ThroughRecord;
2249 .count = [self, &record]() ->
size_t {
2252 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2254 self->_stmt.Prepare(selectQuery.Count());
2255 self->_stmt.Execute(primaryKeyField.Value());
2256 if (self->_stmt.FetchRow())
2257 count = self->_stmt.GetColumn<
size_t>(1);
2258 self->_stmt.CloseCursor();
2263 [self, &record, &hasManyThrough]() {
2265 self->LoadHasManyThrough(record, hasManyThrough);
2268 [self, &record](
auto const& each) {
2270 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2273 stmt.
Prepare(selectQuery.All());
2274 stmt.Execute(primaryKeyField.Value());
2276 auto referencedRecord = ReferencedRecord {};
2277 self->BindOutputColumns(referencedRecord, &stmt);
2278 self->ConfigureRelationAutoLoading(referencedRecord);
2280 while (stmt.FetchRow())
2282 each(referencedRecord);
2283 self->BindOutputColumns(referencedRecord, &stmt);
2291#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2292 constexpr auto ctx = std::meta::access_context::current();
2294 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2295 constexpr auto localctx = std::meta::access_context::current();
2296 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2297 using FieldType =
typename[:std::meta::type_of(members[I]):];
2298 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2301 Reflection::EnumerateMembers(record, callback);
2305template <
typename T>