153#if defined(BUILD_TESTS)
155 [[nodiscard]]
SqlStatement& Statement(
this auto&& self)
noexcept
163 template <
typename Record>
164 static std::string
Inspect(Record
const& record);
167 template <
typename Record>
171 template <
typename FirstRecord,
typename... MoreRecords>
175 template <
typename Record>
179 template <
typename FirstRecord,
typename... MoreRecords>
190 template <DataMapperOptions QueryOptions = {},
typename Record>
191 RecordPrimaryKeyType<Record>
Create(Record& record);
200 template <
typename Record>
201 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
211 template <DataMapperOptions QueryOptions = {},
typename Record>
212 [[nodiscard]] RecordPrimaryKeyType<Record>
CreateCopyOf(Record
const& originalRecord);
237 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
238 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
248 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... InputParameters>
249 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
280 template <
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
281 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
321 template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
322 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
355 template <
typename First,
typename Second,
typename... Rest, DataMapperOptions QueryOptions = {}>
356 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
357 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
373 template <
typename Record, DataMapperOptions QueryOptions = {}>
377 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
381 fields += RecordTableName<Record>;
383 fields += FieldNameAt<I, Record>;
407 template <
typename Record>
408 void Update(Record& record);
417 template <
typename Record>
418 std::size_t
Delete(Record
const& record);
423 return _connection.
Query(tableName);
431 template <
typename Record>
432 bool IsModified(Record
const& record)
const noexcept;
447 template <ModifiedState state,
typename Record>
454 template <
typename Record>
464 template <
typename Record>
473 template <
typename T>
474 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
483 template <
typename Record,
typename... Args>
486 template <
typename Record,
typename ValueType>
487 void SetId(Record& record, ValueType&&
id);
489 template <
typename Record,
size_t InitialOffset = 1>
492 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
495 template <
typename FieldType>
496 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
498 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
501 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
504 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
507 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
508 void CallOnHasMany(Record& record, Callable
const& callback);
510 template <
size_t FieldIndex,
typename OtherRecord>
513 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
514 void CallOnHasManyThrough(Record& record, Callable
const& callback);
516 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue,
typename Callable>
517 void CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback);
519 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue>
520 std::shared_ptr<ReferencedRecord> LoadHasOneThroughByPK(PKValue
const& pkValue);
522 enum class PrimaryKeySource : std::uint8_t
528 template <
typename Record>
529 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record
const& record);
531 template <PrimaryKeySource UsePkOverr
ide,
typename Record>
532 RecordPrimaryKeyType<Record> CreateInternal(
533 Record
const& record,
534 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
535 pkOverride = std::nullopt);
537 SqlConnection _connection;
545 template <
typename FieldType>
546 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
548 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
555 if constexpr (IsField<FieldType>)
557 if constexpr (detail::OneOf<
typename FieldType::ValueType,
563 || IsSqlDynamicString<typename FieldType::ValueType>
564 || IsSqlDynamicBinary<typename FieldType::ValueType>)
573 template <DataMapperRecord Record>
574 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
576 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
580 Reflection::EnumerateMembers<Record>([&result]<
size_t I,
typename Field>() {
581 if constexpr (IsField<Field>)
589 || IsSqlDynamicString<typename Field::ValueType>
590 || IsSqlDynamicBinary<typename Field::ValueType>)
600 template <
typename Record>
601 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
603 Reflection::EnumerateMembers(record,
604 [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
605 if constexpr (IsField<Field>)
607 reader->BindOutputColumn(i++, &field.MutableValue());
609 else if constexpr (IsBelongsTo<Field>)
611 reader->BindOutputColumn(i++, &field.MutableValue());
613 else if constexpr (SqlOutputColumnBinder<Field>)
615 reader->BindOutputColumn(i++, &field);
620 template <
typename Record>
621 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
623 BindAllOutputColumnsWithOffset(reader, record, 1);
629 template <
typename ElementMask,
typename Record>
630 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
632 Reflection::EnumerateMembers<ElementMask>(
633 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
635 if constexpr (IsField<Field>)
638 field.MutableValue() =
639 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
641 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
643 else if constexpr (SqlGetColumnNativeType<Field>)
645 if constexpr (IsOptionalBelongsTo<Field>)
646 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
648 field = reader->GetColumn<Field>(indexFromQuery);
653 template <
typename Record>
654 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
656 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(
657 reader, record, indexFromQuery);
660 template <
typename FirstRecord,
typename SecondRecord>
662 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
664 auto& [firstRecord, secondRecord] = record;
666 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
667 if constexpr (IsField<Field>)
670 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(I + 1);
674 else if constexpr (SqlGetColumnNativeType<Field>)
677 field = reader->GetNullableColumn<
typename Field::BaseType>(I + 1);
679 field = reader->GetColumn<Field>(I + 1);
683 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
684 if constexpr (IsField<Field>)
687 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(
688 Reflection::CountMembers<FirstRecord> + I + 1);
690 field.MutableValue() =
691 reader->GetColumn<
typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
693 else if constexpr (SqlGetColumnNativeType<Field>)
697 reader->GetNullableColumn<
typename Field::BaseType>(Reflection::CountMembers<FirstRecord> + I + 1);
699 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
704 template <
typename Record>
705 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
707 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
709 if (outputColumnsBound)
710 BindAllOutputColumns(reader, record);
712 if (!reader.FetchRow())
715 if (!outputColumnsBound)
716 GetAllColumns(reader, record);
722template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
726 _formatter { dm.Connection().QueryFormatter() },
727 _fields { std::move(fields) }
729 this->_query.searchCondition.inputBindings = &_boundInputs;
732template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
736 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
737 RecordTableName<Record>,
738 this->_query.searchCondition.tableAlias,
739 this->_query.searchCondition.tableJoins,
740 this->_query.searchCondition.condition));
741 auto reader = stmt.ExecuteWithVariants(_boundInputs);
742 if (reader.FetchRow())
743 return reader.GetColumn<
size_t>(1);
747template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
752 auto const query = _formatter.SelectFirst(this->_query.distinct,
754 RecordTableName<Record>,
755 this->_query.searchCondition.tableAlias,
756 this->_query.searchCondition.tableJoins,
757 this->_query.searchCondition.condition,
758 this->_query.orderBy,
762 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
767template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
772 auto const query = _formatter.Delete(RecordTableName<Record>,
773 this->_query.searchCondition.tableAlias,
774 this->_query.searchCondition.tableJoins,
775 this->_query.searchCondition.condition);
779 [[maybe_unused]]
auto cursor = stmt.ExecuteWithVariants(_boundInputs);
782template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
786 auto records = std::vector<Record> {};
788 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
790 RecordTableName<Record>,
791 this->_query.searchCondition.tableAlias,
792 this->_query.searchCondition.tableJoins,
793 this->_query.searchCondition.condition,
794 this->_query.orderBy,
795 this->_query.groupBy));
796 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
802 if constexpr (QueryOptions.loadRelations)
804 for (
auto& record: records)
806 _dm.ConfigureRelationAutoLoading(record);
813template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
815#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
816 requires(is_aggregate_type(parent_of(
Field)))
818 requires std::is_member_object_pointer_v<
decltype(
Field)>
823 auto result = std::vector<value_type> {};
826 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
827 FullyQualifiedNamesOf<Field>.string_view(),
828 RecordTableName<Record>,
829 this->_query.searchCondition.tableAlias,
830 this->_query.searchCondition.tableJoins,
831 this->_query.searchCondition.condition,
832 this->_query.orderBy,
833 this->_query.groupBy));
834 auto reader = stmt.ExecuteWithVariants(_boundInputs);
835 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
838 auto& value = result.emplace_back();
839 if (outputColumnsBound)
840 reader.BindOutputColumn(1, &value);
842 if (!reader.FetchRow())
848 if (!outputColumnsBound)
849 value = reader.GetColumn<value_type>(1);
855template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
856template <
auto... ReferencedFields>
857 requires(
sizeof...(ReferencedFields) >= 2)
860 auto records = std::vector<Record> {};
861 auto stmt = SqlStatement { _dm.Connection() };
863 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
864 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
865 RecordTableName<Record>,
866 this->_query.searchCondition.tableAlias,
867 this->_query.searchCondition.tableJoins,
868 this->_query.searchCondition.condition,
869 this->_query.orderBy,
870 this->_query.groupBy));
872 auto reader = stmt.ExecuteWithVariants(_boundInputs);
873 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
876 auto& record = records.emplace_back();
877 if (outputColumnsBound)
878#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
879 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
881 reader.BindOutputColumns(&(record.*ReferencedFields)...);
883 if (!reader.FetchRow())
888 if (!outputColumnsBound)
890 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
891 detail::GetAllColumns<ElementMask>(reader, record);
898template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
901 std::optional<Record> record {};
903 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
905 RecordTableName<Record>,
906 this->_query.searchCondition.tableAlias,
907 this->_query.searchCondition.tableJoins,
908 this->_query.searchCondition.condition,
909 this->_query.orderBy,
911 Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record);
912 if constexpr (QueryOptions.loadRelations)
915 _dm.ConfigureRelationAutoLoading(record.value());
920template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
922#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
923 requires(is_aggregate_type(parent_of(
Field)))
925 requires std::is_member_object_pointer_v<
decltype(
Field)>
929 auto constexpr count = 1;
931 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
932 FullyQualifiedNamesOf<Field>.string_view(),
933 RecordTableName<Record>,
934 this->_query.searchCondition.tableAlias,
935 this->_query.searchCondition.tableJoins,
936 this->_query.searchCondition.condition,
937 this->_query.orderBy,
939 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
944template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
945template <
auto... ReferencedFields>
946 requires(
sizeof...(ReferencedFields) >= 2)
949 auto optionalRecord = std::optional<Record> {};
951 auto stmt = SqlStatement { _dm.Connection() };
952 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
953 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
954 RecordTableName<Record>,
955 this->_query.searchCondition.tableAlias,
956 this->_query.searchCondition.tableJoins,
957 this->_query.searchCondition.condition,
958 this->_query.orderBy,
961 auto& record = optionalRecord.emplace();
962 auto reader = stmt.ExecuteWithVariants(_boundInputs);
963 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
964 if (outputColumnsBound)
965#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
966 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
968 reader.BindOutputColumns(&(record.*ReferencedFields)...);
970 if (!reader.FetchRow())
972 if (!outputColumnsBound)
974 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
975 detail::GetAllColumns<ElementMask>(reader, record);
978 if constexpr (QueryOptions.loadRelations)
979 _dm.ConfigureRelationAutoLoading(record);
981 return optionalRecord;
984template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
987 auto records = std::vector<Record> {};
990 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
992 RecordTableName<Record>,
993 this->_query.searchCondition.tableAlias,
994 this->_query.searchCondition.tableJoins,
995 this->_query.searchCondition.condition,
996 this->_query.orderBy,
998 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1000 if constexpr (QueryOptions.loadRelations)
1002 for (
auto& record: records)
1003 _dm.ConfigureRelationAutoLoading(record);
1008template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1011 auto records = std::vector<Record> {};
1013 records.reserve(limit);
1015 _formatter.SelectRange(this->_query.distinct,
1017 RecordTableName<Record>,
1018 this->_query.searchCondition.tableAlias,
1019 this->_query.searchCondition.tableJoins,
1020 this->_query.searchCondition.condition,
1021 !this->_query.orderBy.empty()
1022 ? this->_query.orderBy
1023 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1024 this->_query.groupBy,
1027 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1028 if constexpr (QueryOptions.loadRelations)
1030 for (
auto& record: records)
1031 _dm.ConfigureRelationAutoLoading(record);
1036template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1037template <
auto... ReferencedFields>
1040 auto records = std::vector<Record> {};
1042 records.reserve(limit);
1044 _formatter.SelectRange(this->_query.distinct,
1045 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
1046 RecordTableName<Record>,
1047 this->_query.searchCondition.tableAlias,
1048 this->_query.searchCondition.tableJoins,
1049 this->_query.searchCondition.condition,
1050 !this->_query.orderBy.empty()
1051 ? this->_query.orderBy
1052 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1053 this->_query.groupBy,
1057 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1058 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1061 auto& record = records.emplace_back();
1062 if (outputColumnsBound)
1063#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1064 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1066 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1068 if (!reader.FetchRow())
1073 if (!outputColumnsBound)
1075 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1076 detail::GetAllColumns<ElementMask>(reader, record);
1080 if constexpr (QueryOptions.loadRelations)
1082 for (
auto& record: records)
1083 _dm.ConfigureRelationAutoLoading(record);
1089template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1090template <
auto... ReferencedFields>
1093 auto records = std::vector<Record> {};
1096 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1097 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
1098 RecordTableName<Record>,
1099 this->_query.searchCondition.tableAlias,
1100 this->_query.searchCondition.tableJoins,
1101 this->_query.searchCondition.condition,
1102 this->_query.orderBy,
1105 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1106 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1109 auto& record = records.emplace_back();
1110 if (outputColumnsBound)
1111#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1112 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1114 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1116 if (!reader.FetchRow())
1121 if (!outputColumnsBound)
1123 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1124 detail::GetAllColumns<ElementMask>(reader, record);
1128 if constexpr (QueryOptions.loadRelations)
1130 for (
auto& record: records)
1131 _dm.ConfigureRelationAutoLoading(record);
1137template <
typename Record, DataMapperOptions QueryOptions>
1140 std::vector<Record>* records)
1144 Record& record = records->emplace_back();
1145 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1147 records->pop_back();
1153template <
typename Record, DataMapperOptions QueryOptions>
1154void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1155 SqlResultCursor reader,
1156 std::optional<Record>* optionalRecord)
1158 Record& record = optionalRecord->emplace();
1159 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1160 optionalRecord->reset();
1163template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions>
1164void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1165 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1169 auto& record = records->emplace_back();
1170 auto& [firstRecord, secondRecord] = record;
1172 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1173 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1175 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1176 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1177 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1179 if (canSafelyBindAll)
1181 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1182 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1185 if (!reader.FetchRow())
1187 records->pop_back();
1191 if (!canSafelyBindAll)
1192 detail::GetAllColumns(reader, record);
1196template <
typename Record>
1202 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1208 if constexpr (Value::IsOptional)
1210 if (!value.Value().has_value())
1212 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1216 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1219 else if constexpr (IsBelongsTo<Value>)
1221 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1223 else if constexpr (std::same_as<typename Value::ValueType, char>)
1228 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1231 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1232 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1234 return "{\n" + std::move(str) +
"\n}";
1237template <
typename Record>
1243 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1244 detail::PopulateCreateTableBuilder<Record>(createTable);
1245 return migration.GetPlan().ToSql();
1248template <
typename FirstRecord,
typename... MoreRecords>
1251 std::vector<std::string> output;
1252 auto const append = [&output](
auto const& sql) {
1253 output.insert(output.end(), sql.begin(), sql.end());
1255 append(CreateTableString<FirstRecord>(serverType));
1256 (append(CreateTableString<MoreRecords>(serverType)), ...);
1260template <
typename Record>
1265 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1266 for (
auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
1270template <
typename FirstRecord,
typename... MoreRecords>
1273 CreateTable<FirstRecord>();
1274 (CreateTable<MoreRecords>(), ...);
1277template <
typename Record>
1278std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
1280 std::optional<RecordPrimaryKeyType<Record>> result;
1281 Reflection::EnumerateMembers(
1282 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1283 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1284 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1286 using ValueType =
typename PrimaryKeyType::ValueType;
1287 if constexpr (std::same_as<ValueType, SqlGuid>)
1289 if (!primaryKeyField.Value())
1294 else if constexpr (
requires { ValueType {} + 1; })
1296 if (primaryKeyField.Value() == ValueType {})
1298 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1299 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1300 FieldNameAt<PrimaryKeyIndex, Record>,
1301 RecordTableName<Record>));
1302 result = maxId.value_or(ValueType {}) + 1;
1310template <DataMapper::PrimaryKeySource UsePkOverr
ide,
typename Record>
1311RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
1312 Record
const& record,
1313 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
1316 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1318 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1320#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1321 constexpr auto ctx = std::meta::access_context::current();
1322 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1324 using FieldType =
typename[:std::meta::type_of(el):];
1325 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1326 query.Set(FieldNameOf<el>, SqlWildcard);
1329 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1330 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1331 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1337#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
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 (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1344 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1351 Reflection::CallOnMembers(record,
1352 [
this, &pkOverride, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
1353 Name
const& name, FieldType
const& field)
mutable {
1354 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1356 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1363 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1365 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1366 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1367 else if constexpr (HasPrimaryKey<Record>)
1369 if constexpr (UsePkOverride == PrimaryKeySource::Override)
1372 return RecordPrimaryKeyOf(record).Value();
1378template <
typename Record>
1382 return CreateInternal<PrimaryKeySource::Record>(record);
1385template <DataMapperOptions QueryOptions,
typename Record>
1389 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
1391 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
1393 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
1395 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1396 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
1398 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
1401template <DataMapperOptions QueryOptions,
typename Record>
1404 static_assert(!std::is_const_v<Record>);
1407 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
1409 SetId(record, *generatedKey);
1411 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
1413 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1416 SetModifiedState<ModifiedState::NotModified>(record);
1418 if constexpr (QueryOptions.loadRelations)
1421 if constexpr (HasPrimaryKey<Record>)
1425template <
typename Record>
1430 bool modified =
false;
1432#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1433 auto constexpr ctx = std::meta::access_context::current();
1434 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1436 if constexpr (
requires { record.[:el:].IsModified(); })
1438 modified = modified || record.[:el:].IsModified();
1442 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1443 if constexpr (
requires { field.IsModified(); })
1445 modified = modified || field.IsModified();
1453template <
typename Record>
1458 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1460#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1461 auto constexpr ctx = std::meta::access_context::current();
1462 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1464 using FieldType =
typename[:std::meta::type_of(el):];
1467 if (record.[:el:].IsModified())
1468 query.Set(FieldNameOf<el>, SqlWildcard);
1469 if constexpr (IsPrimaryKey<FieldType>)
1470 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1474 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1475 if (field.IsModified())
1476 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1479 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1480 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1487#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1488 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1490 if (record.[:el:].IsModified())
1496 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1498 using FieldType =
typename[:std::meta::type_of(el):];
1499 if constexpr (FieldType::IsPrimaryKey)
1506 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1507 if (field.IsModified())
1512 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1513 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1518 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1520 SetModifiedState<ModifiedState::NotModified>(record);
1523template <
typename Record>
1528 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1530#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1531 auto constexpr ctx = std::meta::access_context::current();
1532 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1534 using FieldType =
typename[:std::meta::type_of(el):];
1535 if constexpr (FieldType::IsPrimaryKey)
1536 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1539 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& ) {
1540 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1541 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1547#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1549 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1551 using FieldType =
typename[:std::meta::type_of(el):];
1552 if constexpr (FieldType::IsPrimaryKey)
1559 Reflection::CallOnMembersWithoutName(
1560 record, [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
1561 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1566 auto cursor = _stmt.
Execute();
1571template <
typename Record,
DataMapperOptions QueryOptions,
typename... PrimaryKeyTypes>
1576 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1578 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1581 queryBuilder.Field(FieldNameAt<I, Record>);
1583 if constexpr (FieldType::IsPrimaryKey)
1584 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1588 _stmt.
Prepare(queryBuilder.First());
1589 auto reader = _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1591 auto resultRecord = std::optional<Record> { Record {} };
1593 return std::nullopt;
1596 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1598 if constexpr (QueryOptions.loadRelations)
1604 return resultRecord;
1607template <
typename Record,
typename... Args>
1612 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1617 auto reader = _stmt.
Execute(std::forward<Args>(args)...);
1619 auto resultRecord = std::optional<Record> { Record {} };
1621 return std::nullopt;
1624 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1626 return resultRecord;
1632template <
typename Record, DataMapperOptions QueryOptions,
typename... InputParameters>
1634 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1636 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1638 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1641template <
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
1642std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1644 auto result = std::vector<Record> {};
1645 if constexpr (std::same_as<Record, SqlVariantRow>)
1647 _stmt.
Prepare(sqlQueryString);
1652 auto& record = result.emplace_back();
1653 record.reserve(numResultColumns);
1654 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1662 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1664 _stmt.
Prepare(sqlQueryString);
1665 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1669 auto& record = result.emplace_back();
1671 if (canSafelyBindOutputColumns)
1672 BindOutputColumns(record, reader);
1674 if (!reader.FetchRow())
1677 if (!canSafelyBindOutputColumns)
1678 detail::GetAllColumns(reader, record);
1684 for (
auto& record: result)
1686 SetModifiedState<ModifiedState::NotModified>(record);
1687 if constexpr (QueryOptions.loadRelations)
1695template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
1697std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
1699 using value_type = std::tuple<First, Second, Rest...>;
1700 auto result = std::vector<value_type> {};
1702 _stmt.
Prepare(selectQuery.ToSql());
1703 auto reader = _stmt.
Execute();
1705 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1708 if constexpr (I > 0)
1710 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1711 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1712 }(std::make_index_sequence<I> {});
1717 auto const BindElements = [&](
auto& record) {
1718 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1719 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1720 auto& element = std::get<I>(record);
1721 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1722 this->BindOutputColumns<TupleElement, offset>(element, reader);
1726 auto const GetElements = [&](
auto& record) {
1727 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1728 auto& element = std::get<I>(record);
1729 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1730 detail::GetAllColumns(reader, element, offset - 1);
1734 bool const canSafelyBindOutputColumns = [&]() {
1736 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1737 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1745 auto& record = result.emplace_back();
1747 if (canSafelyBindOutputColumns)
1748 BindElements(record);
1750 if (!reader.FetchRow())
1753 if (!canSafelyBindOutputColumns)
1754 GetElements(record);
1760 for (
auto& record: result)
1762 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1763 auto& element = std::get<I>(record);
1764 SetModifiedState<ModifiedState::NotModified>(element);
1765 if constexpr (QueryOptions.loadRelations)
1775template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
1777 InputParameters&&... inputParameters)
1781 _stmt.
Prepare(selectQuery.ToSql());
1783 auto records = std::vector<Record> {};
1786 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1788 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1792 auto& record = records.emplace_back();
1794 if (canSafelyBindOutputColumns)
1795 BindOutputColumns<ElementMask>(record, reader);
1797 if (!reader.FetchRow())
1800 if (!canSafelyBindOutputColumns)
1801 detail::GetAllColumns<ElementMask>(reader, record);
1807 for (
auto& record: records)
1809 SetModifiedState<ModifiedState::NotModified>(record);
1810 if constexpr (QueryOptions.loadRelations)
1817template <DataMapper::ModifiedState state,
typename Record>
1820 static_assert(!std::is_const_v<Record>);
1823 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1824 if constexpr (
requires { field.SetModified(
false); })
1826 if constexpr (state == ModifiedState::Modified)
1827 field.SetModified(
true);
1829 field.SetModified(
false);
1834template <
typename Record,
typename Callable>
1835inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1839 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1840 if constexpr (IsField<FieldType>)
1842 if constexpr (FieldType::IsPrimaryKey)
1844 return callable.template operator()<I, FieldType>(field);
1850template <
typename Record,
typename Callable>
1851inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1853 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1855 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1856 if constexpr (IsField<FieldType>)
1858 if constexpr (FieldType::IsPrimaryKey)
1860 return callable.template operator()<I, FieldType>();
1866template <
typename Record,
typename Callable>
1867inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1869 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1871 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1872 if constexpr (IsBelongsTo<FieldType>)
1874 return callable.template operator()<I, FieldType>();
1879template <
typename FieldType>
1880std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1882 using ReferencedRecord = FieldType::ReferencedRecord;
1884 std::optional<ReferencedRecord> record { std::nullopt };
1886#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1887 auto constexpr ctx = std::meta::access_context::current();
1888 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1890 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1891 if constexpr (IsField<BelongsToFieldType>)
1892 if constexpr (BelongsToFieldType::IsPrimaryKey)
1894 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1895 record = std::move(result);
1898 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1902 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1903 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1904 record = std::move(result);
1907 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1913template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1914void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1916 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1917 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1919 using FieldType = HasMany<OtherRecord>;
1920 using ReferencedRecord = FieldType::ReferencedRecord;
1922 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1923 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1925 .Build([&](
auto& query) {
1926 Reflection::EnumerateMembers<ReferencedRecord>(
1927 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1928 if constexpr (FieldWithStorage<ReferencedFieldType>)
1930 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1934 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard)
1935 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
1936 callback(query, primaryKeyField);
1940template <
size_t FieldIndex,
typename OtherRecord>
1941SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
1943 return _connection.
Query(RecordTableName<OtherRecord>)
1945 .
Build([](
auto& q) {
1946 Reflection::EnumerateMembers<OtherRecord>([&]<
size_t I,
typename F>() {
1947 if constexpr (FieldWithStorage<F>)
1948 q.Field(FieldNameAt<I, OtherRecord>);
1951 .Where(FieldNameAt<FieldIndex, OtherRecord>, SqlWildcard)
1952 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
1955template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1956void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1958 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1959 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1961 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1962 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1966template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1967void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1969 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1970 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
1973 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1975 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1977 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1979 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1984 _connection.
Query(RecordTableName<ReferencedRecord>)
1986 .Build([&](
auto& query) {
1987 Reflection::EnumerateMembers<ReferencedRecord>(
1988 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1989 if constexpr (FieldWithStorage<ReferencedFieldType>)
1991 query.Field(SqlQualifiedTableColumnName {
1992 RecordTableName<ReferencedRecord>,
1993 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1997 .InnerJoin(RecordTableName<ThroughRecord>,
1998 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1999 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2000 .InnerJoin(RecordTableName<Record>,
2001 FieldNameAt<PrimaryKeyIndex, Record>,
2002 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2003 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2005 SqlQualifiedTableColumnName {
2006 RecordTableName<Record>,
2007 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2010 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
2012 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
2020template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue>
2021std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue
const& pkValue)
2023 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2025 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2026 std::shared_ptr<ReferencedRecord> result;
2029 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
2031 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
2033 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
2035 _connection.
Query(RecordTableName<ReferencedRecord>)
2037 .Build([&](
auto& query) {
2038 Reflection::EnumerateMembers<ReferencedRecord>(
2039 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2040 if constexpr (FieldWithStorage<ReferencedFieldType>)
2042 query.Field(SqlQualifiedTableColumnName {
2043 RecordTableName<ReferencedRecord>,
2044 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2048 .InnerJoin(RecordTableName<ThroughRecord>,
2049 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
2050 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2051 .InnerJoin(RecordTableName<Record>,
2052 FieldNameAt<PrimaryKeyIndex, Record>,
2053 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2054 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2056 SqlQualifiedTableColumnName {
2057 RecordTableName<Record>,
2058 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2061 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
2062 result = std::make_shared<ReferencedRecord>(std::move(*link));
2070template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
2071void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
2073 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2076 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2078 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
2079 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2080 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2083 CallOnBelongsTo<ThroughRecord>(
2084 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
2085 using ThroughBelongsToReferenceRecordFieldType =
2086 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2087 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2090 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2092 .
Build([&](
auto& query) {
2093 Reflection::EnumerateMembers<ReferencedRecord>(
2094 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2095 if constexpr (FieldWithStorage<ReferencedFieldType>)
2097 query.Field(SqlQualifiedTableColumnName {
2098 RecordTableName<ReferencedRecord>,
2099 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2103 .InnerJoin(RecordTableName<ThroughRecord>,
2104 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2105 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2106 FieldNameAt<PrimaryKeyIndex, Record> })
2108 SqlQualifiedTableColumnName {
2109 RecordTableName<ThroughRecord>,
2110 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2113 callback(query, primaryKeyField);
2121template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue,
typename Callable>
2122void DataMapper::CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback)
2124 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2126 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2129 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
2130 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2131 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2134 CallOnBelongsTo<ThroughRecord>(
2135 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
2136 using ThroughBelongsToReferenceRecordFieldType =
2137 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2138 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2141 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2143 .
Build([&](
auto& query) {
2144 Reflection::EnumerateMembers<ReferencedRecord>(
2145 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2146 if constexpr (FieldWithStorage<ReferencedFieldType>)
2148 query.Field(SqlQualifiedTableColumnName {
2149 RecordTableName<ReferencedRecord>,
2150 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2154 .InnerJoin(RecordTableName<ThroughRecord>,
2155 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2156 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2157 FieldNameAt<PrimaryKeyIndex, Record> })
2159 SqlQualifiedTableColumnName {
2160 RecordTableName<ThroughRecord>,
2161 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2164 callback(query, pkValue);
2171template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2172void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2174 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2176 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2177 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2178 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2182template <
typename Record>
2185 static_assert(!std::is_const_v<Record>);
2188#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2189 constexpr auto ctx = std::meta::access_context::current();
2190 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2192 using FieldType =
typename[:std::meta::type_of(el):];
2193 if constexpr (IsBelongsTo<FieldType>)
2195 auto& field = record.[:el:];
2196 field = LoadBelongsTo<FieldType>(field.Value());
2198 else if constexpr (IsHasMany<FieldType>)
2200 LoadHasMany<el>(record, record.[:el:]);
2202 else if constexpr (IsHasOneThrough<FieldType>)
2204 LoadHasOneThrough(record, record.[:el:]);
2206 else if constexpr (IsHasManyThrough<FieldType>)
2208 LoadHasManyThrough(record, record.[:el:]);
2212 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2213 if constexpr (IsBelongsTo<FieldType>)
2215 field = LoadBelongsTo<FieldType>(field.Value());
2217 else if constexpr (IsHasMany<FieldType>)
2219 LoadHasMany<FieldIndex>(record, field);
2221 else if constexpr (IsHasOneThrough<FieldType>)
2223 LoadHasOneThrough(record, field);
2225 else if constexpr (IsHasManyThrough<FieldType>)
2227 LoadHasManyThrough(record, field);
2234template <
typename Record,
typename ValueType>
2235inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2240#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2242 auto constexpr ctx = std::meta::access_context::current();
2243 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2245 using FieldType =
typename[:std::meta::type_of(el):];
2246 if constexpr (IsField<FieldType>)
2248 if constexpr (FieldType::IsPrimaryKey)
2250 record.[:el:] = std::forward<ValueType>(
id);
2255 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
2256 if constexpr (IsField<FieldType>)
2258 if constexpr (FieldType::IsPrimaryKey)
2260 field = std::forward<FieldType>(
id);
2268template <
typename Record,
size_t InitialOffset>
2269inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2272 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2276template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2277Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2280 static_assert(!std::is_const_v<Record>);
2282#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2283 auto constexpr ctx = std::meta::access_context::current();
2284 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2285 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2287 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2288 using FieldType =
typename[:std::meta::type_of(el):];
2289 if constexpr (IsField<FieldType>)
2293 else if constexpr (SqlOutputColumnBinder<FieldType>)
2299 Reflection::EnumerateMembers<ElementMask>(
2300 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2301 if constexpr (IsField<Field>)
2305 else if constexpr (SqlOutputColumnBinder<Field>)
2314template <
typename Record>
2320 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2321 if constexpr (IsBelongsTo<FieldType>)
2323 field.SetAutoLoader(
typename FieldType::Loader {
2324 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2326 return dm.LoadBelongsTo<FieldType>(value);
2330 if constexpr (IsHasMany<FieldType>)
2332 if constexpr (HasPrimaryKey<Record>)
2334 using ReferencedRecord = FieldType::ReferencedRecord;
2339 .count = [pkValue]() ->
size_t {
2341 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2342 dm._stmt.
Prepare(selectQuery.Count());
2349 .all = [pkValue]() ->
typename FieldType::ReferencedRecordList {
2351 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2352 return detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pkValue));
2355 [pkValue](
auto const& each) {
2357 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2359 stmt.Prepare(selectQuery.All());
2360 auto cursor = stmt.Execute(pkValue);
2362 auto referencedRecord = ReferencedRecord {};
2363 dm.BindOutputColumns(referencedRecord, cursor);
2368 each(referencedRecord);
2369 dm.BindOutputColumns(referencedRecord, cursor);
2375 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
2377 using ReferencedRecord = FieldType::ReferencedRecord;
2378 using ThroughRecord = FieldType::ThroughRecord;
2383 .loadReference = [pkValue]() -> std::shared_ptr<ReferencedRecord> {
2385 return dm.LoadHasOneThroughByPK<ReferencedRecord, ThroughRecord, Record>(pkValue);
2389 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
2391 using ReferencedRecord = FieldType::ReferencedRecord;
2392 using ThroughRecord = FieldType::ThroughRecord;
2397 .count = [pkValue]() ->
size_t {
2401 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2403 dm._stmt.
Prepare(selectQuery.Count());
2410 .all = [pkValue]() ->
typename FieldType::ReferencedRecordList {
2413 typename FieldType::ReferencedRecordList result;
2414 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2416 result = detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pk));
2421 [pkValue](
auto const& each) {
2424 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2427 stmt.Prepare(selectQuery.All());
2428 auto cursor = stmt.Execute(pk);
2429 auto referencedRecord = ReferencedRecord {};
2430 dm.BindOutputColumns(referencedRecord, cursor);
2435 each(referencedRecord);
2436 dm.BindOutputColumns(referencedRecord, cursor);
2444#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2445 constexpr auto ctx = std::meta::access_context::current();
2447 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2448 constexpr auto localctx = std::meta::access_context::current();
2449 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2450 using FieldType =
typename[:std::meta::type_of(members[I]):];
2451 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2454 Reflection::EnumerateMembers(record, callback);
2458template <
typename T>