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 detail::FullyQualifiedNamesOf<Field>,
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 detail::FullyQualifiedNamesOf<ReferencedFields...>,
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 detail::FullyQualifiedNamesOf<Field>,
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 detail::FullyQualifiedNamesOf<ReferencedFields...>,
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 detail::FullyQualifiedNamesOf<ReferencedFields...>,
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 detail::FullyQualifiedNamesOf<ReferencedFields...>,
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 ZoneScopedN(
"DataMapper::CreateTable");
1266 ZoneTextObject(RecordTableName<Record>);
1268 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1269 for (
auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
1273template <
typename FirstRecord,
typename... MoreRecords>
1276 CreateTable<FirstRecord>();
1277 (CreateTable<MoreRecords>(), ...);
1280template <
typename Record>
1281std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
1283 std::optional<RecordPrimaryKeyType<Record>> result;
1284 Reflection::EnumerateMembers(
1285 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1286 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1287 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1289 using ValueType = PrimaryKeyType::ValueType;
1290 if constexpr (std::same_as<ValueType, SqlGuid>)
1292 if (!primaryKeyField.Value())
1297 else if constexpr (
requires { ValueType {} + 1; })
1299 if (primaryKeyField.Value() == ValueType {})
1301 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1302 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1303 FieldNameAt<PrimaryKeyIndex, Record>,
1304 RecordTableName<Record>));
1305 result = maxId.value_or(ValueType {}) + 1;
1313template <DataMapper::PrimaryKeySource UsePkOverr
ide,
typename Record>
1314RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
1315 Record
const& record,
1316 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
1319 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1321 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1323#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1324 constexpr auto ctx = std::meta::access_context::current();
1325 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1327 using FieldType =
typename[:std::meta::type_of(el):];
1328 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1329 query.Set(FieldNameOf<el>, SqlWildcard);
1332 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1333 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1334 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1340#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1342 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1344 using FieldType =
typename[:std::meta::type_of(el):];
1345 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1347 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1354 Reflection::CallOnMembers(record,
1355 [
this, &pkOverride, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
1356 Name
const& name, FieldType
const& field)
mutable {
1357 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1359 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1366 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1368 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1369 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1370 else if constexpr (HasPrimaryKey<Record>)
1372 if constexpr (UsePkOverride == PrimaryKeySource::Override)
1375 return RecordPrimaryKeyOf(record).Value();
1381template <
typename Record>
1385 return CreateInternal<PrimaryKeySource::Record>(record);
1388template <DataMapperOptions QueryOptions,
typename Record>
1392 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
1394 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
1396 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
1398 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1399 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
1401 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
1404template <DataMapperOptions QueryOptions,
typename Record>
1407 static_assert(!std::is_const_v<Record>);
1410 ZoneScopedN(
"DataMapper::Create");
1411 ZoneTextObject(RecordTableName<Record>);
1413 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
1415 SetId(record, *generatedKey);
1417 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
1419 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1422 SetModifiedState<ModifiedState::NotModified>(record);
1424 if constexpr (QueryOptions.loadRelations)
1427 if constexpr (HasPrimaryKey<Record>)
1431template <
typename Record>
1436 bool modified =
false;
1438#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1439 auto constexpr ctx = std::meta::access_context::current();
1440 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1442 if constexpr (
requires { record.[:el:].IsModified(); })
1444 modified = modified || record.[:el:].IsModified();
1448 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1449 if constexpr (
requires { field.IsModified(); })
1451 modified = modified || field.IsModified();
1459template <
typename Record>
1464 ZoneScopedN(
"DataMapper::Update");
1465 ZoneTextObject(RecordTableName<Record>);
1467 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1469#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1470 auto constexpr ctx = std::meta::access_context::current();
1471 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1473 using FieldType =
typename[:std::meta::type_of(el):];
1476 if (record.[:el:].IsModified())
1477 query.Set(FieldNameOf<el>, SqlWildcard);
1478 if constexpr (IsPrimaryKey<FieldType>)
1479 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1483 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1484 if (field.IsModified())
1485 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1488 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1489 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1496#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1497 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1499 if (record.[:el:].IsModified())
1505 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1507 using FieldType =
typename[:std::meta::type_of(el):];
1508 if constexpr (FieldType::IsPrimaryKey)
1515 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1516 if (field.IsModified())
1521 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1522 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1527 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1529 SetModifiedState<ModifiedState::NotModified>(record);
1532template <
typename Record>
1537 ZoneScopedN(
"DataMapper::Delete");
1538 ZoneTextObject(RecordTableName<Record>);
1540 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1542#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1543 auto constexpr ctx = std::meta::access_context::current();
1544 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1546 using FieldType =
typename[:std::meta::type_of(el):];
1547 if constexpr (FieldType::IsPrimaryKey)
1548 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1551 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& ) {
1552 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1553 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1559#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1561 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1563 using FieldType =
typename[:std::meta::type_of(el):];
1564 if constexpr (FieldType::IsPrimaryKey)
1571 Reflection::CallOnMembersWithoutName(
1572 record, [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
1573 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1578 auto cursor = _stmt.
Execute();
1583template <
typename Record,
DataMapperOptions QueryOptions,
typename... PrimaryKeyTypes>
1588 ZoneScopedN(
"DataMapper::QuerySingle(PK)");
1589 ZoneTextObject(RecordTableName<Record>);
1595 auto selectStarter = _connection.
Query(RecordTableName<Record>).
Select();
1597 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1600 if (queryBuilder ==
nullptr)
1601 queryBuilder = &selectStarter.
Field(FieldNameAt<I, Record>);
1603 queryBuilder->
Field(FieldNameAt<I, Record>);
1605 if constexpr (FieldType::IsPrimaryKey)
1606 std::ignore = queryBuilder->
Where(FieldNameAt<I, Record>, SqlWildcard);
1611 auto reader = _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1613 auto resultRecord = std::optional<Record> { Record {} };
1615 return std::nullopt;
1618 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1620 if constexpr (QueryOptions.loadRelations)
1626 return resultRecord;
1629template <
typename Record,
typename... Args>
1634 ZoneScopedN(
"DataMapper::QuerySingle(Builder)");
1635 ZoneTextObject(RecordTableName<Record>);
1637 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1641 auto const composedSql = selectQuery.
First().ToSql();
1642 ZoneTextObject(composedSql);
1644 auto reader = _stmt.
Execute(std::forward<Args>(args)...);
1646 auto resultRecord = std::optional<Record> { Record {} };
1648 return std::nullopt;
1651 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1653 return resultRecord;
1659template <
typename Record, DataMapperOptions QueryOptions,
typename... InputParameters>
1661 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1663 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1665 ZoneScopedN(
"DataMapper::Query(ComposedQuery)");
1666 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1669template <
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
1670std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1672 ZoneScopedN(
"DataMapper::Query(string)");
1673 ZoneTextObject(sqlQueryString);
1675 auto result = std::vector<Record> {};
1676 if constexpr (std::same_as<Record, SqlVariantRow>)
1678 _stmt.
Prepare(sqlQueryString);
1683 auto& record = result.emplace_back();
1684 record.reserve(numResultColumns);
1685 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1693 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1695 _stmt.
Prepare(sqlQueryString);
1696 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1700 auto& record = result.emplace_back();
1702 if (canSafelyBindOutputColumns)
1703 BindOutputColumns(record, reader);
1705 if (!reader.FetchRow())
1708 if (!canSafelyBindOutputColumns)
1709 detail::GetAllColumns(reader, record);
1715 for (
auto& record: result)
1717 SetModifiedState<ModifiedState::NotModified>(record);
1718 if constexpr (QueryOptions.loadRelations)
1726template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
1728std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
1730 using value_type = std::tuple<First, Second, Rest...>;
1731 auto result = std::vector<value_type> {};
1733 ZoneScopedN(
"DataMapper::Query(ComposedQuery -> tuple)");
1734 auto const tupleSql = selectQuery.ToSql();
1735 ZoneTextObject(tupleSql);
1737 auto reader = _stmt.
Execute();
1739 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1742 if constexpr (I > 0)
1744 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1745 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1746 }(std::make_index_sequence<I> {});
1751 auto const BindElements = [&](
auto& record) {
1752 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1753 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1754 auto& element = std::get<I>(record);
1755 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1756 this->BindOutputColumns<TupleElement, offset>(element, reader);
1760 auto const GetElements = [&](
auto& record) {
1761 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1762 auto& element = std::get<I>(record);
1763 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1764 detail::GetAllColumns(reader, element, offset - 1);
1768 bool const canSafelyBindOutputColumns = [&]() {
1770 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1771 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1779 auto& record = result.emplace_back();
1781 if (canSafelyBindOutputColumns)
1782 BindElements(record);
1784 if (!reader.FetchRow())
1787 if (!canSafelyBindOutputColumns)
1788 GetElements(record);
1794 for (
auto& record: result)
1796 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1797 auto& element = std::get<I>(record);
1798 SetModifiedState<ModifiedState::NotModified>(element);
1799 if constexpr (QueryOptions.loadRelations)
1809template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
1811 InputParameters&&... inputParameters)
1815 ZoneScopedN(
"DataMapper::Query(ComposedQuery, ElementMask)");
1816 auto const maskedSql = selectQuery.ToSql();
1817 ZoneTextObject(maskedSql);
1820 auto records = std::vector<Record> {};
1823 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1825 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1829 auto& record = records.emplace_back();
1831 if (canSafelyBindOutputColumns)
1832 BindOutputColumns<ElementMask>(record, reader);
1834 if (!reader.FetchRow())
1837 if (!canSafelyBindOutputColumns)
1838 detail::GetAllColumns<ElementMask>(reader, record);
1844 for (
auto& record: records)
1846 SetModifiedState<ModifiedState::NotModified>(record);
1847 if constexpr (QueryOptions.loadRelations)
1854template <DataMapper::ModifiedState state,
typename Record>
1857 static_assert(!std::is_const_v<Record>);
1860 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1861 if constexpr (
requires { field.SetModified(
false); })
1863 if constexpr (state == ModifiedState::Modified)
1864 field.SetModified(
true);
1866 field.SetModified(
false);
1871template <
typename Record,
typename Callable>
1872inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1876 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1877 if constexpr (IsField<FieldType>)
1879 if constexpr (FieldType::IsPrimaryKey)
1881 return callable.template operator()<I, FieldType>(field);
1887template <
typename Record,
typename Callable>
1888inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1890 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1892 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1893 if constexpr (IsField<FieldType>)
1895 if constexpr (FieldType::IsPrimaryKey)
1897 return callable.template operator()<I, FieldType>();
1903template <
typename Record,
typename Callable>
1904inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1906 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1908 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1909 if constexpr (IsBelongsTo<FieldType>)
1911 return callable.template operator()<I, FieldType>();
1916template <
typename FieldType>
1917std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1919 using ReferencedRecord = FieldType::ReferencedRecord;
1921 ZoneScopedN(
"DataMapper::LoadBelongsTo");
1922 ZoneTextObject(RecordTableName<ReferencedRecord>);
1924 std::optional<ReferencedRecord> record { std::nullopt };
1926#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1927 auto constexpr ctx = std::meta::access_context::current();
1928 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1930 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1931 if constexpr (IsField<BelongsToFieldType>)
1932 if constexpr (BelongsToFieldType::IsPrimaryKey)
1934 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1935 record = std::move(result);
1938 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1942 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1943 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1944 record = std::move(result);
1947 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1953template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1954void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1956 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1957 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1959 using FieldType = HasMany<OtherRecord>;
1960 using ReferencedRecord = FieldType::ReferencedRecord;
1962 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1963 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1965 .Build([&](
auto& query) {
1966 Reflection::EnumerateMembers<ReferencedRecord>(
1967 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1968 if constexpr (FieldWithStorage<ReferencedFieldType>)
1970 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1974 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard)
1975 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
1976 callback(query, primaryKeyField);
1980template <
size_t FieldIndex,
typename OtherRecord>
1981SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
1983 return _connection.
Query(RecordTableName<OtherRecord>)
1985 .
Build([](
auto& q) {
1986 Reflection::EnumerateMembers<OtherRecord>([&]<
size_t I,
typename F>() {
1987 if constexpr (FieldWithStorage<F>)
1988 q.Field(FieldNameAt<I, OtherRecord>);
1991 .Where(FieldNameAt<FieldIndex, OtherRecord>, SqlWildcard)
1992 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
1995template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1996void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1998 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1999 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2001 ZoneScopedN(
"DataMapper::LoadHasMany");
2002 ZoneTextObject(RecordTableName<OtherRecord>);
2004 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
2005 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
2009template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2010void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
2012 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2013 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2015 ZoneScopedN(
"DataMapper::LoadHasOneThrough");
2016 ZoneTextObject(RecordTableName<ReferencedRecord>);
2019 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2021 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
2023 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
2025 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
2030 _connection.
Query(RecordTableName<ReferencedRecord>)
2032 .Build([&](
auto& query) {
2033 Reflection::EnumerateMembers<ReferencedRecord>(
2034 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2035 if constexpr (FieldWithStorage<ReferencedFieldType>)
2037 query.Field(SqlQualifiedTableColumnName {
2038 RecordTableName<ReferencedRecord>,
2039 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2043 .InnerJoin(RecordTableName<ThroughRecord>,
2044 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
2045 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2046 .InnerJoin(RecordTableName<Record>,
2047 FieldNameAt<PrimaryKeyIndex, Record>,
2048 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2049 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2051 SqlQualifiedTableColumnName {
2052 RecordTableName<Record>,
2053 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2056 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
2058 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
2066template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue>
2067std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue
const& pkValue)
2069 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2071 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2072 std::shared_ptr<ReferencedRecord> result;
2075 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
2077 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
2079 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
2081 _connection.
Query(RecordTableName<ReferencedRecord>)
2083 .Build([&](
auto& query) {
2084 Reflection::EnumerateMembers<ReferencedRecord>(
2085 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2086 if constexpr (FieldWithStorage<ReferencedFieldType>)
2088 query.Field(SqlQualifiedTableColumnName {
2089 RecordTableName<ReferencedRecord>,
2090 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2094 .InnerJoin(RecordTableName<ThroughRecord>,
2095 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
2096 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2097 .InnerJoin(RecordTableName<Record>,
2098 FieldNameAt<PrimaryKeyIndex, Record>,
2099 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2100 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2102 SqlQualifiedTableColumnName {
2103 RecordTableName<Record>,
2104 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2107 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
2108 result = std::make_shared<ReferencedRecord>(std::move(*link));
2116template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
2117void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
2119 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2122 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2124 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
2125 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2126 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2129 CallOnBelongsTo<ThroughRecord>(
2130 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
2131 using ThroughBelongsToReferenceRecordFieldType =
2132 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2133 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2136 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2138 .
Build([&](
auto& query) {
2139 Reflection::EnumerateMembers<ReferencedRecord>(
2140 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2141 if constexpr (FieldWithStorage<ReferencedFieldType>)
2143 query.Field(SqlQualifiedTableColumnName {
2144 RecordTableName<ReferencedRecord>,
2145 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2149 .InnerJoin(RecordTableName<ThroughRecord>,
2150 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2151 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2152 FieldNameAt<PrimaryKeyIndex, Record> })
2154 SqlQualifiedTableColumnName {
2155 RecordTableName<ThroughRecord>,
2156 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2159 callback(query, primaryKeyField);
2167template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue,
typename Callable>
2168void DataMapper::CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback)
2170 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2172 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2175 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
2176 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2177 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2180 CallOnBelongsTo<ThroughRecord>(
2181 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
2182 using ThroughBelongsToReferenceRecordFieldType =
2183 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2184 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2187 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2189 .
Build([&](
auto& query) {
2190 Reflection::EnumerateMembers<ReferencedRecord>(
2191 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2192 if constexpr (FieldWithStorage<ReferencedFieldType>)
2194 query.Field(SqlQualifiedTableColumnName {
2195 RecordTableName<ReferencedRecord>,
2196 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2200 .InnerJoin(RecordTableName<ThroughRecord>,
2201 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2202 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2203 FieldNameAt<PrimaryKeyIndex, Record> })
2205 SqlQualifiedTableColumnName {
2206 RecordTableName<ThroughRecord>,
2207 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2210 callback(query, pkValue);
2217template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2218void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2220 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2222 ZoneScopedN(
"DataMapper::LoadHasManyThrough");
2223 ZoneTextObject(RecordTableName<ReferencedRecord>);
2225 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2226 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2227 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2231template <
typename Record>
2234 static_assert(!std::is_const_v<Record>);
2237 ZoneScopedN(
"DataMapper::LoadRelations");
2238 ZoneTextObject(RecordTableName<Record>);
2240#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2241 constexpr auto ctx = std::meta::access_context::current();
2242 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2244 using FieldType =
typename[:std::meta::type_of(el):];
2245 if constexpr (IsBelongsTo<FieldType>)
2247 auto& field = record.[:el:];
2248 field = LoadBelongsTo<FieldType>(field.Value());
2250 else if constexpr (IsHasMany<FieldType>)
2252 LoadHasMany<el>(record, record.[:el:]);
2254 else if constexpr (IsHasOneThrough<FieldType>)
2256 LoadHasOneThrough(record, record.[:el:]);
2258 else if constexpr (IsHasManyThrough<FieldType>)
2260 LoadHasManyThrough(record, record.[:el:]);
2264 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2265 if constexpr (IsBelongsTo<FieldType>)
2267 field = LoadBelongsTo<FieldType>(field.Value());
2269 else if constexpr (IsHasMany<FieldType>)
2271 LoadHasMany<FieldIndex>(record, field);
2273 else if constexpr (IsHasOneThrough<FieldType>)
2275 LoadHasOneThrough(record, field);
2277 else if constexpr (IsHasManyThrough<FieldType>)
2279 LoadHasManyThrough(record, field);
2286template <
typename Record,
typename ValueType>
2287inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2292#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2294 auto constexpr ctx = std::meta::access_context::current();
2295 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2297 using FieldType =
typename[:std::meta::type_of(el):];
2298 if constexpr (IsField<FieldType>)
2300 if constexpr (FieldType::IsPrimaryKey)
2302 record.[:el:] = std::forward<ValueType>(
id);
2307 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
2308 if constexpr (IsField<FieldType>)
2310 if constexpr (FieldType::IsPrimaryKey)
2312 field = std::forward<FieldType>(
id);
2320template <
typename Record,
size_t InitialOffset>
2321inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2324 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2328template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2329Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2332 static_assert(!std::is_const_v<Record>);
2334#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2335 auto constexpr ctx = std::meta::access_context::current();
2336 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2337 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2339 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2340 using FieldType =
typename[:std::meta::type_of(el):];
2341 if constexpr (IsField<FieldType>)
2345 else if constexpr (SqlOutputColumnBinder<FieldType>)
2351 Reflection::EnumerateMembers<ElementMask>(
2352 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2353 if constexpr (IsField<Field>)
2357 else if constexpr (SqlOutputColumnBinder<Field>)
2366template <
typename Record>
2372 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2373 if constexpr (IsBelongsTo<FieldType>)
2375 field.SetAutoLoader(
typename FieldType::Loader {
2376 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2378 return dm.LoadBelongsTo<FieldType>(value);
2382 if constexpr (IsHasMany<FieldType>)
2384 if constexpr (HasPrimaryKey<Record>)
2386 using ReferencedRecord = FieldType::ReferencedRecord;
2391 .count = [pkValue]() ->
size_t {
2393 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2394 dm._stmt.
Prepare(selectQuery.Count());
2401 .all = [pkValue]() -> FieldType::ReferencedRecordList {
2403 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2404 return detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pkValue));
2407 [pkValue](
auto const& each) {
2409 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2411 stmt.Prepare(selectQuery.All());
2412 auto cursor = stmt.Execute(pkValue);
2414 auto referencedRecord = ReferencedRecord {};
2415 dm.BindOutputColumns(referencedRecord, cursor);
2420 each(referencedRecord);
2421 dm.BindOutputColumns(referencedRecord, cursor);
2427 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
2429 using ReferencedRecord = FieldType::ReferencedRecord;
2430 using ThroughRecord = FieldType::ThroughRecord;
2435 .loadReference = [pkValue]() -> std::shared_ptr<ReferencedRecord> {
2437 return dm.LoadHasOneThroughByPK<ReferencedRecord, ThroughRecord, Record>(pkValue);
2441 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
2443 using ReferencedRecord = FieldType::ReferencedRecord;
2444 using ThroughRecord = FieldType::ThroughRecord;
2449 .count = [pkValue]() ->
size_t {
2453 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2455 dm._stmt.
Prepare(selectQuery.Count());
2462 .all = [pkValue]() -> FieldType::ReferencedRecordList {
2465 typename FieldType::ReferencedRecordList result;
2466 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2468 result = detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pk));
2473 [pkValue](
auto const& each) {
2476 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2479 stmt.Prepare(selectQuery.All());
2480 auto cursor = stmt.Execute(pk);
2481 auto referencedRecord = ReferencedRecord {};
2482 dm.BindOutputColumns(referencedRecord, cursor);
2487 each(referencedRecord);
2488 dm.BindOutputColumns(referencedRecord, cursor);
2496#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2497 constexpr auto ctx = std::meta::access_context::current();
2499 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2500 constexpr auto localctx = std::meta::access_context::current();
2501 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2502 using FieldType =
typename[:std::meta::type_of(members[I]):];
2503 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2506 Reflection::EnumerateMembers(record, callback);
2510template <
typename T>
2513 ZoneScopedN(
"DataMapper::Execute(string)");
2514 ZoneTextObject(sqlQueryString);