117 _connection { std::move(connection) },
118 _stmt { _connection }
123 explicit DataMapper(std::optional<SqlConnectionString> connectionString):
124 _connection { std::move(connectionString) },
125 _stmt { _connection }
134 _connection(std::move(other._connection)),
136 _relationLoadSource(std::move(other._relationLoadSource))
147 _connection = std::move(other._connection);
150 _relationLoadSource = std::move(other._relationLoadSource);
169#if defined(BUILD_TESTS)
171 [[nodiscard]]
SqlStatement& Statement(
this auto&& self)
noexcept
179 template <
typename Record>
180 static std::string
Inspect(Record
const& record);
183 template <
typename Record>
187 template <
typename FirstRecord,
typename... MoreRecords>
191 template <
typename Record>
195 template <
typename FirstRecord,
typename... MoreRecords>
207 RecordPrimaryKeyType<Record>
Create(Record& record);
216 template <
typename Record>
217 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
238 template <std::ranges::range Records>
250 [[nodiscard]] RecordPrimaryKeyType<Record>
CreateCopyOf(Record
const& originalRecord);
275 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
276 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
286 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... InputParameters>
287 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
318 template <
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
319 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
359 template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
360 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
393 template <
typename First,
typename Second,
typename... Rest, DataMapperOptions QueryOptions = {}>
394 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
395 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
411 template <
typename Record, DataMapperOptions QueryOptions = {}>
440 *
this, BuildFullyQualifiedFieldList<Record>());
468 template <
typename Record>
469 requires HasPrimaryKey<Record>
470 void Update(Record& record);
488 template <std::ranges::range Records>
498 template <
typename Record>
499 std::size_t
Delete(Record
const& record);
504 return _connection.
Query(tableName);
512 template <
typename Record>
513 bool IsModified(Record
const& record)
const noexcept;
528 template <ModifiedState state,
typename Record>
535 template <
typename Record>
545 template <
typename Record>
554 template <
typename T>
555 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
575 [[nodiscard]] Async::Task<RecordPrimaryKeyType<Record>>
CreateAsync(Record& record);
582 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
583 [[nodiscard]] Async::Task<std::optional<Record>>
QuerySingleAsync(PrimaryKeyTypes... primaryKeys);
586 template <
typename Record>
587 [[nodiscard]] Async::Task<void>
UpdateAsync(Record& record);
590 template <
typename Record>
591 [[nodiscard]] Async::Task<std::size_t>
DeleteAsync(Record
const& record);
594 template <
typename Record>
600 template <
typename BuilderRecord,
typename Derived, DataMapperOptions BuilderQueryOptions>
601 friend class SqlCoreDataMapperQueryBuilder;
603 friend void detail::AdoptRelationLoadSource(
DataMapper& dataMapper,
604 std::shared_ptr<detail::RelationLoadSource> source)
noexcept;
614 [[nodiscard]] LIGHTWEIGHT_API std::shared_ptr<detail::RelationLoadSource>
const& RelationLoadSourceForLoaders();
624 template <
typename Record,
typename QueryText,
typename Callable,
typename... InputParameters>
625 void StreamRecords(QueryText
const& query, Callable
const& each, InputParameters
const&... inputParameters);
634 template <
typename Load>
635 static auto RunRelationLoad(detail::RelationLoadSource& source, Load
const& load)
636 -> RelationResult<std::invoke_result_t<Load const&, DataMapper&>>;
645 template <
typename Each,
typename Stream>
646 static RelationResult<void> RunRelationStream(detail::RelationLoadSource& source,
648 Stream
const& stream);
651 template <
typename Record>
652 static RelationResult<std::shared_ptr<Record>> NotFoundIfNull(std::shared_ptr<Record> record)
665 template <
typename Record>
666 [[nodiscard]]
static std::string BuildFullyQualifiedFieldList()
669 EnumerateRecordMembers<Record>([&fields]<
size_t I,
typename FieldType>() {
671 if constexpr (RecordColumnMember<FieldType>)
676 fields += RecordTableName<Record>;
678 fields += FieldNameAt<I, Record>;
691 template <
typename Record,
typename... Args>
692 std::optional<Record>
QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args);
694 template <
typename Record,
typename ValueType>
695 void SetId(Record& record, ValueType&&
id);
697 template <
typename Record,
size_t InitialOffset = 1>
698 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
700 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
701 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
703 template <
typename FieldType>
704 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
721 template <
typename FieldType>
722 std::shared_ptr<typename FieldType::ReferencedRecord> LoadCompositeForeignKeyRecord(
723 FieldType::OrderedValueType
const& keys);
729 template <
typename Record,
typename FieldType>
730 void LoadCompositeForeignKey(Record
const& record, FieldType& field);
732 template <
typename Record,
typename OtherRecord, auto InverseSelector>
733 void LoadHasMany(Record& record, HasMany<OtherRecord, InverseSelector>& field);
751 template <
typename Record,
size_t FieldIndex>
752 void PreloadRelation(std::span<Record* const> records);
756 template <
typename Record,
size_t FieldIndex>
757 void PreloadBelongsTo(std::span<Record* const> records);
762 template <
typename Record,
size_t FieldIndex>
763 void PreloadHasMany(std::span<Record* const> records);
776 template <
typename Record,
size_t FieldIndex,
auto... RestOfPath>
777 void PreloadRelationPath(std::span<Record* const> records);
790 template <
typename Record,
size_t Depth>
791 void PreloadAllRelations(std::span<Record* const> records);
800 template <
typename Record,
size_t FieldIndex>
801 [[nodiscard]]
static auto CollectRelationTargets(std::span<Record* const> records);
803 template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ThroughSelector>
804 void LoadHasOneThrough(Record& record,
805 HasOneThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ThroughSelector>& field);
807 template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ReferencedSelector>
808 void LoadHasManyThrough(Record& record,
809 HasManyThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ReferencedSelector>& field);
811 template <
typename Record,
typename OtherRecord, auto InverseSelector,
typename Callable>
812 void CallOnHasMany(Record& record, Callable
const& callback);
814 template <
typename OwnerRecord,
typename OtherRecord, auto InverseSelector>
815 SqlSelectQueryBuilder BuildHasManySelectQuery();
823 template <
typename Record>
824 SqlSelectQueryBuilder BuildRecordSelectQuery();
826 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
827 SqlSelectQueryBuilder BuildHasOneThroughSelectQuery();
829 template <
typename ReferencedRecord,
830 typename ThroughRecord,
833 auto ReferencedSelector>
834 SqlSelectQueryBuilder BuildHasManyThroughSelectQuery();
836 template <
typename ReferencedRecord,
837 typename ThroughRecord,
840 auto ReferencedSelector,
842 void CallOnHasManyThrough(Record& record, Callable
const& callback);
844 template <
typename ReferencedRecord,
845 typename ThroughRecord,
848 auto ReferencedSelector,
851 void CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback);
853 template <
typename ReferencedRecord,
854 typename ThroughRecord,
857 auto ThroughSelector,
859 std::shared_ptr<ReferencedRecord> LoadHasOneThroughByPK(PKValue
const& pkValue);
861 enum class PrimaryKeySource : std::uint8_t
867 template <
typename Record>
868 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record
const& record);
870 template <PrimaryKeySource UsePkOverr
ide,
typename Record>
871 RecordPrimaryKeyType<Record> CreateInternal(
872 Record
const& record,
873 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
874 pkOverride = std::nullopt);
876 SqlConnection _connection;
878 std::shared_ptr<detail::RelationLoadSource> _relationLoadSource;
881inline void detail::AdoptRelationLoadSource(DataMapper& dataMapper, std::shared_ptr<RelationLoadSource> source)
noexcept
883 dataMapper._relationLoadSource = std::move(source);
895 template <
typename T>
896 inline constexpr bool IsGrowableColumnType =
897 detail::OneOf<T, std::string, std::wstring, std::u16string, std::u32string, SqlBinary> || IsSqlDynamicString<T>
898 || IsSqlDynamicBinary<T>;
913 template <
typename T>
914 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
916 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
922 return !IsGrowableColumnType<T>;
933 template <DataMapperRecord Record>
934 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
936 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
940 EnumerateRecordMembers<Record>([&result]<
size_t I,
typename Field>() {
941 if constexpr (IsField<Field>)
942 if constexpr (IsGrowableColumnType<typename Field::ValueType>)
949 template <
typename Record>
950 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
952 EnumerateRecordMembers(record, [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
953 if constexpr (IsField<Field>)
955 reader->BindOutputColumn(i++, &field.MutableValue());
957 else if constexpr (IsBelongsTo<Field>)
959 reader->BindOutputColumn(i++, &field.MutableValue());
961 else if constexpr (SqlOutputColumnBinder<Field>)
963 reader->BindOutputColumn(i++, &field);
968 template <
typename Record>
969 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
971 BindAllOutputColumnsWithOffset(reader, record, 1);
976 constexpr std::size_t kDefaultRowArrayFetchDepth = 1024;
981 template <std::
size_t I>
982 struct MutableFieldValueAccessor
984 template <
typename Record>
985 decltype(
auto)
operator()(Record& record)
const
987 return GetRecordMemberAt<I>(record).MutableValue();
993 template <
typename FieldType>
994 using RowWiseColumnValueType = std::remove_cvref_t<decltype(std::declval<FieldType&>().MutableValue())>;
998 template <
typename FieldType>
999 constexpr bool RowWiseIsColumn()
1001 return IsField<FieldType> || IsBelongsTo<FieldType> || SqlOutputColumnBinder<FieldType>;
1008 template <
typename FieldType>
1009 constexpr bool RowWiseColumnAcceptable()
1011 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1012 return SqlRowWiseFetchableColumn<RowWiseColumnValueType<FieldType>>;
1013 else if constexpr (SqlOutputColumnBinder<FieldType>)
1019 template <
typename Record, std::size_t... Is>
1020 constexpr bool CanRowWiseFetchRecordImpl(std::index_sequence<Is...> )
1025 return (
sizeof(Record) %
alignof(SQLLEN) == 0) && (RowWiseColumnAcceptable<RecordMemberTypeOf<Is, Record>>() && ...)
1026 && (RowWiseIsColumn<RecordMemberTypeOf<Is, Record>>() || ...);
1033 template <
typename Record>
1034 constexpr bool CanRowWiseFetchRecord()
1036 return CanRowWiseFetchRecordImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
1041 template <std::
size_t I,
typename Record>
1042 auto MakeOutputColumnAccessor()
1044 using FieldType = RecordMemberTypeOf<I, Record>;
1045 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1046 return std::tuple<MutableFieldValueAccessor<I>> {};
1048 return std::tuple<> {};
1054 template <
typename Record>
1055 void ReadAllRowWise(SqlResultCursor& reader, std::vector<Record>* records)
1057 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
1059 [&](
auto const&... accessors) {
1060 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
1062 std::tuple_cat(MakeOutputColumnAccessor<Is, Record>()...));
1063 }(std::make_index_sequence<RecordMemberCount<Record>> {});
1069 template <
typename FieldType>
1070 constexpr bool ColumnIsNarrowFixedString()
1072 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1074 using V = RowWiseColumnValueType<FieldType>;
1075 if constexpr (SqlIsStdOptional<V>)
1076 return IsSqlFixedString<typename V::value_type>;
1078 return IsSqlFixedString<V>;
1084 template <
typename Record, std::size_t... Is>
1085 constexpr bool RecordHasNarrowFixedStringColumnImpl(std::index_sequence<Is...> )
1087 return (ColumnIsNarrowFixedString<RecordMemberTypeOf<Is, Record>>() || ...);
1093 template <
typename Record>
1094 constexpr bool RecordHasNarrowFixedStringColumn()
1096 return RecordHasNarrowFixedStringColumnImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
1103 template <
typename Record>
1104 bool CanRowWiseFetchOn(SqlServerType serverType)
1106 if constexpr (!CanRowWiseFetchRecord<Record>())
1110 && (!RecordHasNarrowFixedStringColumn<Record>()
1118 template <std::
size_t TupleIndex, std::
size_t I>
1119 struct MutableTupleFieldAccessor
1121 template <
typename TupleType>
1122 decltype(
auto)
operator()(TupleType& row)
const
1124 return GetRecordMemberAt<I>(std::get<TupleIndex>(row)).MutableValue();
1128 template <
typename First,
typename Second, std::size_t... Fs, std::size_t... Ss>
1129 constexpr bool CanRowWiseFetchTupleImpl(std::index_sequence<Fs...> ,
1130 std::index_sequence<Ss...> )
1132 return (
sizeof(std::tuple<First, Second>) %
alignof(SQLLEN) == 0)
1133 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Fs, First>>() && ...)
1134 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Ss, Second>>() && ...)
1135 && ((RowWiseIsColumn<RecordMemberTypeOf<Fs, First>>() || ...)
1136 || (RowWiseIsColumn<RecordMemberTypeOf<Ss, Second>>() || ...));
1142 template <
typename First,
typename Second>
1143 constexpr bool CanRowWiseFetchTuple()
1145 return CanRowWiseFetchTupleImpl<First, Second>(std::make_index_sequence<RecordMemberCount<First>> {},
1146 std::make_index_sequence<RecordMemberCount<Second>> {});
1152 template <
typename First,
typename Second>
1153 bool CanRowWiseFetchTupleOn(SqlServerType serverType)
1155 if constexpr (!CanRowWiseFetchTuple<First, Second>())
1159 && ((!RecordHasNarrowFixedStringColumn<First>() && !RecordHasNarrowFixedStringColumn<Second>())
1164 template <std::
size_t TupleIndex, std::
size_t I,
typename SubRecord>
1165 auto MakeTupleColumnAccessor()
1167 using FieldType = RecordMemberTypeOf<I, SubRecord>;
1168 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1169 return std::tuple<MutableTupleFieldAccessor<TupleIndex, I>> {};
1171 return std::tuple<> {};
1177 template <
typename First,
typename Second>
1178 void ReadAllRowWiseTuple(SqlResultCursor& reader, std::vector<std::tuple<First, Second>>* records)
1180 [&]<std::size_t... Fs, std::size_t... Ss>(std::index_sequence<Fs...>, std::index_sequence<Ss...>) {
1182 [&](
auto const&... accessors) {
1183 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
1185 std::tuple_cat(MakeTupleColumnAccessor<0, Fs, First>()..., MakeTupleColumnAccessor<1, Ss, Second>()...));
1186 }(std::make_index_sequence<RecordMemberCount<First>> {}, std::make_index_sequence<RecordMemberCount<Second>> {});
1192 template <
typename ElementMask,
typename Record>
1193 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
1195 EnumerateRecordMembers<ElementMask>(
1196 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
1205 static_assert(RecordColumnMember<Field> == (IsField<Field> || SqlGetColumnNativeType<Field>),
1206 "Record member is projected but not readable (or readable but not projected). "
1207 "A SqlDataBinder<T> used as a record member must provide both OutputColumn() "
1208 "and GetColumn().");
1209 if constexpr (IsField<Field>)
1213 field.MutableValue() =
1214 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
1216 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
1218 else if constexpr (SqlGetColumnNativeType<Field>)
1221 if constexpr (IsOptionalBelongsTo<Field>)
1222 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
1224 field = reader->GetColumn<Field>(indexFromQuery);
1229 template <
typename Record>
1230 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
1232 return GetAllColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record>(
1233 reader, record, indexFromQuery);
1236 template <
typename FirstRecord,
typename SecondRecord>
1238 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
1240 auto& [firstRecord, secondRecord] = record;
1245 GetAllColumns(reader, firstRecord, 0);
1246 GetAllColumns(reader, secondRecord,
static_cast<SQLUSMALLINT
>(RecordColumnCount<FirstRecord>));
1249 template <
typename Record>
1250 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
1252 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
1254 if (outputColumnsBound)
1255 BindAllOutputColumns(reader, record);
1257 if (!reader.FetchRow())
1260 if (!outputColumnsBound)
1261 GetAllColumns(reader, record);
1267template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1268template <
typename Finisher>
1269auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RunFinisher(Finisher finisher)
1271 if constexpr (Derived::QueryExecution == SqlQueryExecutionMode::Asynchronous)
1272 return Async::RunAsync(_dm.Connection().AsyncBackend(), std::move(finisher));
1277template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1279 DataMapper& dm, std::string fields)
noexcept:
1281 _formatter { dm.Connection().QueryFormatter() },
1282 _fields { std::move(fields) }
1284 this->_query.searchCondition.inputBindings = &_boundInputs;
1287template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1290 SqlBasicSelectQueryBuilder<Derived> { other },
1292 _formatter { other._formatter },
1293 _fields { other._fields },
1294 _boundInputs { other._boundInputs },
1295 _relationPreloaders { other._relationPreloaders }
1297 this->_query.searchCondition.inputBindings = &_boundInputs;
1300template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1304 SqlBasicSelectQueryBuilder<Derived> { std::move(
static_cast<SqlBasicSelectQueryBuilder<Derived>&
>(other)) },
1306 _formatter { other._formatter },
1307 _fields { std::move(other._fields) },
1308 _boundInputs { std::move(other._boundInputs) },
1309 _relationPreloaders { std::move(other._relationPreloaders) }
1311 this->_query.searchCondition.inputBindings = &_boundInputs;
1314template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1315size_t SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::CountImpl()
1317 auto stmt = SqlStatement { _dm.
Connection() };
1318 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
1319 RecordTableName<Record>,
1320 this->_query.searchCondition.tableAlias,
1321 this->_query.searchCondition.tableJoins,
1322 this->_query.searchCondition.condition,
1323 this->_query.groupBy));
1324 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1325 if (reader.FetchRow())
1326 return reader.template GetColumn<size_t>(1);
1330template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1331template <
auto... RelationPath>
1332 requires DataMapperRecord<Record> && (
sizeof...(RelationPath) >= 1)
1335 constexpr auto FirstField = detail::FirstOf<RelationPath...>;
1337#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1338 static_assert(std::same_as<
typename[:std::meta::parent_of(FirstField):], Record>,
1339 "The first relation named by With<>() must be a member of the record being queried");
1343 static_assert(std::same_as<MemberClassType<std::remove_cv_t<
decltype(FirstField)>>, Record>,
1344 "The first relation named by With<>() must be a member of the record being queried");
1349 _relationPreloaders.emplace_back(+[](
DataMapper& dm, std::span<Record* const> records) {
1350 [&]<
auto Head,
auto... Tail>() {
1351 dm.template PreloadRelationPath<Record, MemberIndexOf<Head>, Tail...>(records);
1352 }.template operator()<RelationPath...>();
1355 return static_cast<Derived&
>(*this);
1358template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1362 if (_relationPreloaders.empty())
1365 if (records.empty())
1368 auto pointers = std::vector<Record*> {};
1369 pointers.reserve(records.size());
1370 for (
auto& record: records)
1371 pointers.emplace_back(&record);
1372 auto const batch = std::span<Record* const> { pointers };
1376 for (
auto const preloader: _relationPreloaders)
1377 preloader(_dm, batch);
1380 _dm.template PreloadAllRelations<Record, QueryOptions.eagerLoadDepth>(batch);
1383template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1384bool SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::ExistImpl()
1386 auto stmt = SqlStatement { _dm.Connection() };
1388 auto const query = _formatter.SelectFirst(this->_query.distinct,
1390 RecordTableName<Record>,
1391 this->_query.searchCondition.tableAlias,
1392 this->_query.searchCondition.tableJoins,
1393 this->_query.searchCondition.condition,
1394 this->_query.orderBy,
1395 this->_query.groupBy,
1398 stmt.Prepare(query);
1399 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1404template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1405void SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::DeleteImpl()
1407 auto stmt = SqlStatement { _dm.Connection() };
1409 auto const query = _formatter.
Delete(RecordTableName<Record>,
1410 this->_query.searchCondition.tableAlias,
1411 this->_query.searchCondition.tableJoins,
1412 this->_query.searchCondition.condition);
1414 stmt.Prepare(query);
1415 [[maybe_unused]]
auto cursor = stmt.ExecuteWithVariants(_boundInputs);
1418template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1419std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl()
1422 auto records = std::vector<Record> {};
1423 auto stmt = SqlStatement { _dm.Connection() };
1424 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1426 RecordTableName<Record>,
1427 this->_query.searchCondition.tableAlias,
1428 this->_query.searchCondition.tableJoins,
1429 this->_query.searchCondition.condition,
1430 this->_query.orderBy,
1431 this->_query.groupBy));
1432 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1433 if constexpr (DataMapperRecord<Record>)
1440 for (
auto& record: records)
1442 _dm.ConfigureRelationAutoLoading(record);
1445 RunRelationPreloaders(records);
1450template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1451template <auto Field>
1452#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1453 requires(is_aggregate_type(parent_of(Field)))
1455 requires std::is_member_object_pointer_v<
decltype(Field)>
1457auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<ReferencedFieldTypeOf<Field>>
1459 using value_type = ReferencedFieldTypeOf<Field>;
1460 auto result = std::vector<value_type> {};
1462 auto stmt = SqlStatement { _dm.Connection() };
1463 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1464 detail::FullyQualifiedNamesOf<Field>,
1465 RecordTableName<Record>,
1466 this->_query.searchCondition.tableAlias,
1467 this->_query.searchCondition.tableJoins,
1468 this->_query.searchCondition.condition,
1469 this->_query.orderBy,
1470 this->_query.groupBy));
1471 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1472 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
1475 auto& value = result.emplace_back();
1476 if (outputColumnsBound)
1477 reader.BindOutputColumn(1, &value);
1479 if (!reader.FetchRow())
1485 if (!outputColumnsBound)
1486 value = reader.template GetColumn<value_type>(1);
1492template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1493template <
auto... ReferencedFields>
1494 requires(
sizeof...(ReferencedFields) >= 2)
1495auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<Record>
1497 auto records = std::vector<Record> {};
1498 auto stmt = SqlStatement { _dm.Connection() };
1500 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1501 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1502 RecordTableName<Record>,
1503 this->_query.searchCondition.tableAlias,
1504 this->_query.searchCondition.tableJoins,
1505 this->_query.searchCondition.condition,
1506 this->_query.orderBy,
1507 this->_query.groupBy));
1509 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1510 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1513 auto& record = records.emplace_back();
1514 if (outputColumnsBound)
1515#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1516 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1518 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1520 if (!reader.FetchRow())
1525 if (!outputColumnsBound)
1527 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1528 detail::GetAllColumns<ElementMask>(reader, record);
1535template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1536std::optional<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl()
1538 std::optional<Record> record {};
1539 auto stmt = SqlStatement { _dm.Connection() };
1540 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1542 RecordTableName<Record>,
1543 this->_query.searchCondition.tableAlias,
1544 this->_query.searchCondition.tableJoins,
1545 this->_query.searchCondition.condition,
1546 this->_query.orderBy,
1547 this->_query.groupBy,
1549 Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record);
1553 _dm.ConfigureRelationAutoLoading(record.value());
1555 if constexpr (DataMapperRecord<Record>)
1557 RunRelationPreloaders(std::span<Record> { &record.value(), 1 });
1561template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1562template <auto Field>
1563#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1564 requires(is_aggregate_type(parent_of(Field)))
1566 requires std::is_member_object_pointer_v<
decltype(Field)>
1568auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<ReferencedFieldTypeOf<Field>>
1570 auto constexpr count = 1;
1571 auto stmt = SqlStatement { _dm.Connection() };
1572 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1573 detail::FullyQualifiedNamesOf<Field>,
1574 RecordTableName<Record>,
1575 this->_query.searchCondition.tableAlias,
1576 this->_query.searchCondition.tableJoins,
1577 this->_query.searchCondition.condition,
1578 this->_query.orderBy,
1579 this->_query.groupBy,
1581 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1582 return reader.template GetColumn<ReferencedFieldTypeOf<Field>>(1);
1583 return std::nullopt;
1586template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1587template <
auto... ReferencedFields>
1588 requires(
sizeof...(ReferencedFields) >= 2)
1589auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<Record>
1591 auto optionalRecord = std::optional<Record> {};
1593 auto stmt = SqlStatement { _dm.Connection() };
1594 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1595 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1596 RecordTableName<Record>,
1597 this->_query.searchCondition.tableAlias,
1598 this->_query.searchCondition.tableJoins,
1599 this->_query.searchCondition.condition,
1600 this->_query.orderBy,
1601 this->_query.groupBy,
1604 auto& record = optionalRecord.emplace();
1605 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1606 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1607 if (outputColumnsBound)
1608#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1609 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1611 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1619 if (reader.FetchRow())
1621 if (!outputColumnsBound)
1623 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1624 detail::GetAllColumns<ElementMask>(reader, record);
1628 _dm.ConfigureRelationAutoLoading(record);
1632 optionalRecord.reset();
1635 return optionalRecord;
1638template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1639std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1641 auto records = std::vector<Record> {};
1642 auto stmt = SqlStatement { _dm.Connection() };
1644 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1646 RecordTableName<Record>,
1647 this->_query.searchCondition.tableAlias,
1648 this->_query.searchCondition.tableJoins,
1649 this->_query.searchCondition.condition,
1650 this->_query.orderBy,
1651 this->_query.groupBy,
1653 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1657 for (
auto& record: records)
1658 _dm.ConfigureRelationAutoLoading(record);
1660 if constexpr (DataMapperRecord<Record>)
1661 RunRelationPreloaders(records);
1665template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1666std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1668 auto records = std::vector<Record> {};
1669 auto stmt = SqlStatement { _dm.Connection() };
1670 records.reserve(limit);
1672 _formatter.SelectRange(this->_query.distinct,
1674 RecordTableName<Record>,
1675 this->_query.searchCondition.tableAlias,
1676 this->_query.searchCondition.tableJoins,
1677 this->_query.searchCondition.condition,
1678 !this->_query.orderBy.empty()
1679 ? this->_query.orderBy
1680 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1681 this->_query.groupBy,
1684 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1687 for (
auto& record: records)
1688 _dm.ConfigureRelationAutoLoading(record);
1690 if constexpr (DataMapperRecord<Record>)
1691 RunRelationPreloaders(records);
1695template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1696template <
auto... ReferencedFields>
1697std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1699 auto records = std::vector<Record> {};
1700 auto stmt = SqlStatement { _dm.Connection() };
1701 records.reserve(limit);
1703 _formatter.SelectRange(this->_query.distinct,
1704 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1705 RecordTableName<Record>,
1706 this->_query.searchCondition.tableAlias,
1707 this->_query.searchCondition.tableJoins,
1708 this->_query.searchCondition.condition,
1709 !this->_query.orderBy.empty()
1710 ? this->_query.orderBy
1711 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1712 this->_query.groupBy,
1716 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1717 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1720 auto& record = records.emplace_back();
1721 if (outputColumnsBound)
1722#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1723 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1725 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1727 if (!reader.FetchRow())
1732 if (!outputColumnsBound)
1734 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1735 detail::GetAllColumns<ElementMask>(reader, record);
1741 for (
auto& record: records)
1742 _dm.ConfigureRelationAutoLoading(record);
1748template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1749template <
auto... ReferencedFields>
1750[[nodiscard]] std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1752 auto records = std::vector<Record> {};
1753 auto stmt = SqlStatement { _dm.Connection() };
1755 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1756 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1757 RecordTableName<Record>,
1758 this->_query.searchCondition.tableAlias,
1759 this->_query.searchCondition.tableJoins,
1760 this->_query.searchCondition.condition,
1761 this->_query.orderBy,
1762 this->_query.groupBy,
1765 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1766 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1769 auto& record = records.emplace_back();
1770 if (outputColumnsBound)
1771#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1772 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1774 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1776 if (!reader.FetchRow())
1781 if (!outputColumnsBound)
1783 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1784 detail::GetAllColumns<ElementMask>(reader, record);
1790 for (
auto& record: records)
1791 _dm.ConfigureRelationAutoLoading(record);
1797template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1798void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResults(SqlServerType sqlServerType,
1799 SqlResultCursor reader,
1800 std::vector<Record>* records)
1806 if constexpr (detail::CanRowWiseFetchRecord<Record>())
1808 if (detail::CanRowWiseFetchOn<Record>(sqlServerType))
1810 detail::ReadAllRowWise(reader, records);
1817 Record& record = records->emplace_back();
1818 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1820 records->pop_back();
1826template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1827void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResult(SqlServerType sqlServerType,
1828 SqlResultCursor reader,
1829 std::optional<Record>* optionalRecord)
1831 Record& record = optionalRecord->emplace();
1832 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1833 optionalRecord->reset();
1836template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1837void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution>::ReadResults(
1838 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1842 if constexpr (detail::CanRowWiseFetchTuple<FirstRecord, SecondRecord>())
1844 if (detail::CanRowWiseFetchTupleOn<FirstRecord, SecondRecord>(sqlServerType))
1846 detail::ReadAllRowWiseTuple<FirstRecord, SecondRecord>(reader, records);
1853 auto& record = records->emplace_back();
1854 auto& [firstRecord, secondRecord] = record;
1856 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1857 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1859 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1860 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1861 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1863 if (canSafelyBindAll)
1865 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1868 detail::BindAllOutputColumnsWithOffset(
1869 reader, secondRecord,
static_cast<SQLUSMALLINT
>(1 + RecordColumnCount<FirstRecord>));
1872 if (!reader.FetchRow())
1874 records->pop_back();
1878 if (!canSafelyBindAll)
1879 detail::GetAllColumns(reader, record);
1883template <
typename Record>
1889 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1895 if constexpr (Value::IsOptional)
1897 if (!value.Value().has_value())
1899 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1903 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1906 else if constexpr (IsBelongsTo<Value>)
1908 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1910 else if constexpr (std::same_as<typename Value::ValueType, char>)
1915 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1918 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>
1919 && !IsCompositeForeignKey<Value>)
1920 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1922 return "{\n" + std::move(str) +
"\n}";
1925template <
typename Record>
1931 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1932 detail::PopulateCreateTableBuilder<Record>(createTable);
1933 return migration.GetPlan().ToSql();
1936template <
typename FirstRecord,
typename... MoreRecords>
1939 std::vector<std::string> output;
1940 auto const append = [&output](
auto const& sql) {
1941 output.insert(output.end(), sql.begin(), sql.end());
1943 append(CreateTableString<FirstRecord>(serverType));
1944 (append(CreateTableString<MoreRecords>(serverType)), ...);
1948template <
typename Record>
1953 ZoneScopedN(
"DataMapper::CreateTable");
1954 ZoneTextObject(RecordTableName<Record>);
1956 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1957 for (
auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
1961template <
typename FirstRecord,
typename... MoreRecords>
1964 CreateTable<FirstRecord>();
1965 (CreateTable<MoreRecords>(), ...);
1968template <
typename Record>
1969std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
1974 static_assert(detail::AutoAssignPrimaryKeyFieldCount<Record> <= 1,
1975 "A record may declare at most one auto-assigned primary key member. Auto-assignment yields a "
1976 "single value that would be written into every generated key member, so a composite key cannot "
1977 "be generated - declare its members PrimaryKey::Manual and set their values yourself before "
1978 "calling Create().");
1980 std::optional<RecordPrimaryKeyType<Record>> result;
1982 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1983 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1984 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1986 using ValueType = PrimaryKeyType::ValueType;
1987 if constexpr (std::same_as<ValueType, SqlGuid>)
1989 if (!primaryKeyField.Value())
1994 else if constexpr (
requires { ValueType {} + 1; })
1996 if (primaryKeyField.Value() == ValueType {})
1998 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1999 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
2000 FieldNameAt<PrimaryKeyIndex, Record>,
2001 RecordTableName<Record>));
2002 result = maxId.value_or(ValueType {}) + 1;
2010template <DataMapper::PrimaryKeySource UsePkOverr
ide,
typename Record>
2011RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
2012 Record
const& record,
2013 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
2016 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2018 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
2020#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2021 constexpr auto ctx = std::meta::access_context::current();
2022 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2024 using FieldType =
typename[:std::meta::type_of(el):];
2025 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
2026 query.Set(FieldNameOf<el>, SqlWildcard);
2030 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
2031 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2037#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2039 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2041 using FieldType =
typename[:std::meta::type_of(el):];
2042 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
2045 if constexpr (detail::IsAutoAssignPrimaryKeyField<FieldType>::value
2046 && UsePkOverride == PrimaryKeySource::Override)
2053 Reflection::CallOnMembers(record,
2054 [
this, &pkOverride, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
2055 Name
const& name, FieldType
const& field)
mutable {
2056 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
2059 if constexpr (detail::IsAutoAssignPrimaryKeyField<FieldType>::value
2060 && UsePkOverride == PrimaryKeySource::Override)
2067 [[maybe_unused]]
auto cursor = _stmt.
Execute();
2069 if constexpr (HasAutoIncrementPrimaryKey<Record>)
2075 return static_cast<RecordPrimaryKeyType<Record>
>(_stmt.
LastInsertId(RecordTableName<Record>));
2076 else if constexpr (HasPrimaryKey<Record>)
2078 if constexpr (UsePkOverride == PrimaryKeySource::Override)
2081 return RecordPrimaryKeyOf(record).Value();
2087template <
typename Record>
2091 return CreateInternal<PrimaryKeySource::Record>(record);
2100 template <
typename FieldType>
2101 constexpr bool IsBatchInsertColumn = SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>;
2104 template <
typename FieldType>
2108 template <
typename FieldType>
2109 constexpr bool IsBatchUpdateWhereColumn = IsPrimaryKey<FieldType>;
2113 template <std::
size_t I>
2114 struct FieldValueAccessor
2116 template <
typename Record>
2117 decltype(
auto)
operator()(Record
const& record)
const
2119 return GetRecordMemberAt<I>(record).Value();
2125 template <std::
size_t I,
typename Record>
2126 auto MakeCreateColumnAccessor()
2128 using FieldType = RecordMemberTypeOf<I, Record>;
2129 if constexpr (IsBatchInsertColumn<FieldType>)
2130 return std::tuple<FieldValueAccessor<I>> {};
2132 return std::tuple<> {};
2136 template <std::
size_t I,
typename Record>
2137 auto MakeUpdateSetAccessor()
2139 using FieldType = RecordMemberTypeOf<I, Record>;
2140 if constexpr (IsBatchUpdateSetColumn<FieldType>)
2141 return std::tuple<FieldValueAccessor<I>> {};
2143 return std::tuple<> {};
2147 template <std::
size_t I,
typename Record>
2148 auto MakeUpdateWhereAccessor()
2150 using FieldType = RecordMemberTypeOf<I, Record>;
2151 if constexpr (IsBatchUpdateWhereColumn<FieldType>)
2152 return std::tuple<FieldValueAccessor<I>> {};
2154 return std::tuple<> {};
2158template <std::ranges::range Records>
2161 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
2162 "CreateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
2163 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
2164 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
2167 ZoneScopedN(
"DataMapper::CreateAll");
2168 ZoneTextObject(RecordTableName<Record>);
2170 if (std::ranges::empty(records))
2174 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
2175 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2176 if constexpr (detail::IsBatchInsertColumn<FieldType>)
2177 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2182 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
2183 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
2184 std::tuple_cat(detail::MakeCreateColumnAccessor<Is, Record>()...));
2185 }(std::make_index_sequence<RecordMemberCount<Record>> {});
2188template <DataMapperOptions QueryOptions,
typename Record>
2192 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
2194 HasAutoIncrementPrimaryKey<Record>
2195 || detail::CheckFieldProperty<[]<typename Field>() {
return detail::IsAutoAssignPrimaryKeyField<Field>::value; },
2197 "CreateCopyOf() needs a primary key the mapper generates (AutoAssign or ServerSideAutoIncrement). A "
2198 "record keyed by PrimaryKey::Manual members only has no key to give the copy - set one on a copy "
2199 "of your own and call Create() instead.");
2201 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
2203 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
2205 if constexpr (HasAutoIncrementPrimaryKey<Record>)
2206 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
2208 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
2211template <DataMapperOptions QueryOptions,
typename Record>
2214 static_assert(!std::is_const_v<Record>);
2217 ZoneScopedN(
"DataMapper::Create");
2218 ZoneTextObject(RecordTableName<Record>);
2220 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
2222 SetId(record, *generatedKey);
2224 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
2226 if constexpr (HasAutoIncrementPrimaryKey<Record>)
2229 SetModifiedState<ModifiedState::NotModified>(record);
2231 if constexpr (QueryOptions.loadRelations)
2234 if constexpr (HasPrimaryKey<Record>)
2238template <
typename Record>
2243 bool modified =
false;
2245#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2246 auto constexpr ctx = std::meta::access_context::current();
2247 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2249 if constexpr (
requires { record.[:el:].IsModified(); })
2251 modified = modified || record.[:el:].IsModified();
2255 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
2256 if constexpr (
requires { field.IsModified(); })
2258 modified = modified || field.IsModified();
2266template <
typename Record>
2267 requires HasPrimaryKey<Record>
2272 ZoneScopedN(
"DataMapper::Update");
2273 ZoneTextObject(RecordTableName<Record>);
2275 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2280 bool anyFieldModified =
false;
2282#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2283 auto constexpr ctx = std::meta::access_context::current();
2284 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2286 using FieldType =
typename[:std::meta::type_of(el):];
2289 if (record.[:el:].IsModified())
2291 query.Set(FieldNameOf<el>, SqlWildcard);
2292 anyFieldModified =
true;
2294 if constexpr (IsPrimaryKey<FieldType>)
2295 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2299 EnumerateRecordMembers(record, [&query, &anyFieldModified]<
size_t I,
typename FieldType>(FieldType
const& field) {
2306 if (field.IsModified())
2308 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2309 anyFieldModified =
true;
2311 if constexpr (IsPrimaryKey<MemberType>)
2312 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2318 if (!anyFieldModified)
2325#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2326 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2328 using FieldType =
typename[:std::meta::type_of(el):];
2332 if (record.[:el:].IsModified())
2339 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2341 using FieldType =
typename[:std::meta::type_of(el):];
2344 if constexpr (FieldType::IsPrimaryKey)
2356 if (field.IsModified())
2363 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2368 [[maybe_unused]]
auto cursor = _stmt.
Execute();
2370 SetModifiedState<ModifiedState::NotModified>(record);
2373template <std::ranges::range Records>
2376 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
2377 "UpdateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
2378 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
2379 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
2381 static_assert(HasPrimaryKey<Record>,
"UpdateAll requires a record type with a primary key");
2383 ZoneScopedN(
"DataMapper::UpdateAll");
2384 ZoneTextObject(RecordTableName<Record>);
2386 if (std::ranges::empty(records))
2390 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2391 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2392 if constexpr (detail::IsBatchUpdateSetColumn<FieldType>)
2393 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2395 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2396 if constexpr (detail::IsBatchUpdateWhereColumn<FieldType>)
2397 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2402 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
2403 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
2404 std::tuple_cat(detail::MakeUpdateSetAccessor<Is, Record>()...,
2405 detail::MakeUpdateWhereAccessor<Is, Record>()...));
2406 }(std::make_index_sequence<RecordMemberCount<Record>> {});
2409template <
typename Record>
2414 ZoneScopedN(
"DataMapper::Delete");
2415 ZoneTextObject(RecordTableName<Record>);
2417 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
2419#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2420 auto constexpr ctx = std::meta::access_context::current();
2421 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2423 using FieldType =
typename[:std::meta::type_of(el):];
2426 if constexpr (FieldType::IsPrimaryKey)
2427 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2431 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2432 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2438#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2440 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2442 using FieldType =
typename[:std::meta::type_of(el):];
2445 if constexpr (FieldType::IsPrimaryKey)
2454 [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
2455 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2460 auto cursor = _stmt.
Execute();
2465template <
typename Record,
DataMapperOptions QueryOptions,
typename... PrimaryKeyTypes>
2470 ZoneScopedN(
"DataMapper::QuerySingle(PK)");
2471 ZoneTextObject(RecordTableName<Record>);
2477 auto selectStarter = _connection.
Query(RecordTableName<Record>).
Select();
2481 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2484 if (queryBuilder ==
nullptr)
2485 queryBuilder = &selectStarter.
Field(FieldNameAt<I, Record>);
2487 queryBuilder->
Field(FieldNameAt<I, Record>);
2491 if constexpr (FieldType::IsPrimaryKey)
2492 std::ignore = queryBuilder->
Where(FieldNameAt<I, Record>, SqlWildcard);
2498 auto reader = _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
2504 auto resultRecord = std::optional<Record> { Record {} };
2507 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2509 if constexpr (QueryOptions.loadRelations)
2514 resultRecord.reset();
2517 return resultRecord;
2520template <
typename Record,
typename... Args>
2525 ZoneScopedN(
"DataMapper::QuerySingle(Builder)");
2526 ZoneTextObject(RecordTableName<Record>);
2529 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2533 auto const composedSql = selectQuery.
First().ToSql();
2534 ZoneTextObject(composedSql);
2536 auto reader = _stmt.
Execute(std::forward<Args>(args)...);
2538 auto resultRecord = std::optional<Record> { Record {} };
2540 return std::nullopt;
2543 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2545 return resultRecord;
2551template <
typename Record, DataMapperOptions QueryOptions,
typename... InputParameters>
2553 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
2555 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
2557 ZoneScopedN(
"DataMapper::Query(ComposedQuery)");
2558 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
2561template <
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2562std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
2564 ZoneScopedN(
"DataMapper::Query(string)");
2565 ZoneTextObject(sqlQueryString);
2567 auto result = std::vector<Record> {};
2568 if constexpr (std::same_as<Record, SqlVariantRow>)
2570 _stmt.
Prepare(sqlQueryString);
2575 auto& record = result.emplace_back();
2576 record.reserve(numResultColumns);
2577 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
2585 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2587 _stmt.
Prepare(sqlQueryString);
2588 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2592 auto& record = result.emplace_back();
2594 if (canSafelyBindOutputColumns)
2595 BindOutputColumns(record, reader);
2597 if (!reader.FetchRow())
2600 if (!canSafelyBindOutputColumns)
2601 detail::GetAllColumns(reader, record);
2607 for (
auto& record: result)
2609 SetModifiedState<ModifiedState::NotModified>(record);
2610 if constexpr (QueryOptions.loadRelations)
2618template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
2620std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
2622 using value_type = std::tuple<First, Second, Rest...>;
2623 auto result = std::vector<value_type> {};
2625 ZoneScopedN(
"DataMapper::Query(ComposedQuery -> tuple)");
2626 auto const tupleSql = selectQuery.ToSql();
2627 ZoneTextObject(tupleSql);
2629 auto reader = _stmt.
Execute();
2634 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
2637 if constexpr (I > 0)
2639 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
2640 ((Indices < I ? (offset += RecordColumnCount<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
2641 }(std::make_index_sequence<I> {});
2646 auto const BindElements = [&](
auto& record) {
2647 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2648 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2649 auto& element = std::get<I>(record);
2650 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2651 this->BindOutputColumns<TupleElement, offset>(element, reader);
2655 auto const GetElements = [&](
auto& record) {
2656 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2657 auto& element = std::get<I>(record);
2658 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2659 detail::GetAllColumns(reader, element, offset - 1);
2663 bool const canSafelyBindOutputColumns = [&]() {
2665 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2666 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2674 auto& record = result.emplace_back();
2676 if (canSafelyBindOutputColumns)
2677 BindElements(record);
2679 if (!reader.FetchRow())
2682 if (!canSafelyBindOutputColumns)
2683 GetElements(record);
2689 for (
auto& record: result)
2691 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2692 auto& element = std::get<I>(record);
2693 SetModifiedState<ModifiedState::NotModified>(element);
2694 if constexpr (QueryOptions.loadRelations)
2704template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2706 InputParameters&&... inputParameters)
2710 ZoneScopedN(
"DataMapper::Query(ComposedQuery, ElementMask)");
2711 auto const maskedSql = selectQuery.ToSql();
2712 ZoneTextObject(maskedSql);
2715 auto records = std::vector<Record> {};
2718 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2720 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2724 auto& record = records.emplace_back();
2726 if (canSafelyBindOutputColumns)
2727 BindOutputColumns<ElementMask>(record, reader);
2729 if (!reader.FetchRow())
2732 if (!canSafelyBindOutputColumns)
2733 detail::GetAllColumns<ElementMask>(reader, record);
2739 for (
auto& record: records)
2741 SetModifiedState<ModifiedState::NotModified>(record);
2742 if constexpr (QueryOptions.loadRelations)
2749template <DataMapper::ModifiedState state,
typename Record>
2752 static_assert(!std::is_const_v<Record>);
2756 if constexpr (
requires { field.SetModified(
false); })
2758 if constexpr (state == ModifiedState::Modified)
2759 field.SetModified(
true);
2761 field.SetModified(
false);
2766template <
typename Record,
typename Callable>
2767inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
2772 if constexpr (IsField<FieldType>)
2774 if constexpr (FieldType::IsPrimaryKey)
2776 return callable.template operator()<I, FieldType>(field);
2782template <
typename Record,
typename Callable>
2783inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
2785 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2787 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2788 if constexpr (IsField<FieldType>)
2790 if constexpr (FieldType::IsPrimaryKey)
2792 return callable.template operator()<I, FieldType>();
2798template <
typename Record,
typename Callable>
2799inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
2801 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2803 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2804 if constexpr (IsBelongsTo<FieldType>)
2806 return callable.template operator()<I, FieldType>();
2811template <
typename FieldType>
2812std::shared_ptr<typename FieldType::ReferencedRecord> DataMapper::LoadCompositeForeignKeyRecord(
2813 FieldType::OrderedValueType
const& keys)
2815 using ReferencedRecord = FieldType::ReferencedRecord;
2818 std::apply([
this](
auto const&... key) {
return this->
template QuerySingle<ReferencedRecord>(key...); }, keys);
2821 return std::make_shared<ReferencedRecord>(std::move(*loaded));
2824template <
typename Record,
typename FieldType>
2825void DataMapper::LoadCompositeForeignKey(Record
const& record, FieldType& field)
2827 using ReferencedRecord = FieldType::ReferencedRecord;
2829 ZoneScopedN(
"DataMapper::LoadCompositeForeignKey");
2830 ZoneTextObject(RecordTableName<ReferencedRecord>);
2836 auto loaded = LoadCompositeForeignKeyRecord<FieldType>(FieldType::OrderedValuesOf(record));
2844 std::format(
"Loading composite foreign key failed for {}", RecordTableName<ReferencedRecord>));
2848 field.EmplaceRecord(std::move(loaded));
2851template <
typename FieldType>
2852std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
2854 using ReferencedRecord = FieldType::ReferencedRecord;
2856 ZoneScopedN(
"DataMapper::LoadBelongsTo");
2857 ZoneTextObject(RecordTableName<ReferencedRecord>);
2859 std::optional<ReferencedRecord> record { std::nullopt };
2862 if constexpr (FieldType::IsOptional)
2863 if (!value.has_value())
2866#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2867 auto constexpr ctx = std::meta::access_context::current();
2868 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
2870 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
2871 if constexpr (IsField<BelongsToFieldType>)
2872 if constexpr (BelongsToFieldType::IsPrimaryKey)
2874 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
2875 record = std::move(result);
2878 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
2882 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
2883 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
2884 record = std::move(result);
2887 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
2893template <
typename Record,
typename OtherRecord, auto InverseSelector,
typename Callable>
2894void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
2896 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2897 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2899 using FieldType = HasMany<OtherRecord, InverseSelector>;
2900 using ReferencedRecord = FieldType::ReferencedRecord;
2902 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2903 auto query = BuildHasManySelectQuery<Record, ReferencedRecord, InverseSelector>();
2904 callback(query, primaryKeyField);
2908template <
typename OwnerRecord,
typename OtherRecord, auto InverseSelector>
2909SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
2911 return BuildRecordSelectQuery<OtherRecord>()
2912 .Where(InverseBelongsToFieldNameOf<OwnerRecord, OtherRecord, InverseSelector>, SqlWildcard)
2913 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
2916template <
typename Record,
typename OtherRecord, auto InverseSelector>
2917void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord, InverseSelector>& field)
2919 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2920 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2922 ZoneScopedN(
"DataMapper::LoadHasMany");
2923 ZoneTextObject(RecordTableName<OtherRecord>);
2925 CallOnHasMany<Record, OtherRecord, InverseSelector>(
2926 record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
2927 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
2942 template <
typename T,
typename Callable>
2943 void ForEachChunk(std::span<T const> values,
size_t chunkSize, Callable
const& callable)
2945 auto const stride = chunkSize == 0 ? values.size() : chunkSize;
2946 for (
size_t offset = 0; offset < values.size(); offset += stride)
2947 callable(values.subspan(offset, (std::min) (stride, values.size() - offset)));
2951template <
typename Record>
2952SqlSelectQueryBuilder DataMapper::BuildRecordSelectQuery()
2954 return _connection.
Query(RecordTableName<Record>).
Select().
Build([](
auto& query) {
2955 EnumerateRecordMembers<Record>([&query]<
size_t I,
typename FieldType>() {
2956 if constexpr (FieldWithStorage<FieldType>)
2957 query.Field(FieldNameAt<I, Record>);
2962template <
typename Record,
size_t FieldIndex>
2963void DataMapper::PreloadRelation(std::span<Record* const> records)
2965 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
2967 static_assert(IsBelongsTo<FieldType> || IsHasMany<FieldType>,
2968 "Eager loading via With<>() is supported for BelongsTo and HasMany relations. "
2969 "HasOneThrough, HasManyThrough and CompositeForeignKey still load on demand.");
2975 if (records.empty())
2978 if constexpr (IsBelongsTo<FieldType>)
2979 PreloadBelongsTo<Record, FieldIndex>(records);
2980 else if constexpr (IsHasMany<FieldType>)
2981 PreloadHasMany<Record, FieldIndex>(records);
2984template <
typename Record,
size_t FieldIndex>
2985auto DataMapper::CollectRelationTargets(std::span<Record* const> records)
2987 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
2988 using TargetRecord = FieldType::ReferencedRecord;
2990 auto targets = std::vector<TargetRecord*> {};
2991 targets.reserve(records.size());
2993 for (
auto* record: records)
2995 auto& field = GetRecordMemberAt<FieldIndex>(*record);
2996 if constexpr (IsBelongsTo<FieldType>)
3000 if (
auto* target = field.LoadedRecord(); target !=
nullptr)
3001 targets.emplace_back(target);
3003 else if constexpr (IsHasMany<FieldType>)
3005 if (
auto* loaded = field.LoadedRecords(); loaded !=
nullptr)
3006 for (
auto& element: *loaded)
3007 targets.emplace_back(element.get());
3014template <
typename Record,
size_t FieldIndex,
auto... RestOfPath>
3015void DataMapper::PreloadRelationPath(std::span<Record* const> records)
3017 PreloadRelation<Record, FieldIndex>(records);
3019 if constexpr (
sizeof...(RestOfPath) > 0)
3021 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
3022 using TargetRecord = FieldType::ReferencedRecord;
3024 auto targets = CollectRelationTargets<Record, FieldIndex>(records);
3025 if (targets.empty())
3036 [&]<
auto NextField,
auto... Rest>() {
3037#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3038 static_assert(std::same_as<
typename[:std::meta::parent_of(NextField):], TargetRecord>,
3039 "Each relation named after the first in With<>() must be a member of the "
3040 "preceding relation's referenced record");
3042 static_assert(std::same_as<MemberClassType<std::remove_cv_t<
decltype(NextField)>>, TargetRecord>,
3043 "Each relation named after the first in With<>() must be a member of the "
3044 "preceding relation's referenced record");
3046 PreloadRelationPath<TargetRecord, MemberIndexOf<NextField>, Rest...>(std::span<TargetRecord* const> { targets });
3047 }.template operator()<RestOfPath...>();
3051template <
typename Record,
size_t Depth>
3052void DataMapper::PreloadAllRelations(std::span<Record* const> records)
3054 if constexpr (Depth == 0)
3060 if (records.empty())
3063 Reflection::template_for<0, RecordMemberCount<Record>>([&]<
auto I>() {
3064 using FieldType = RecordMemberTypeOf<I, Record>;
3067 if constexpr (IsBelongsTo<FieldType> || IsHasMany<FieldType>)
3069 PreloadRelation<Record, I>(records);
3071 if constexpr (Depth > 1)
3073 using TargetRecord = FieldType::ReferencedRecord;
3074 auto targets = CollectRelationTargets<Record, I>(records);
3075 if (!targets.empty())
3076 PreloadAllRelations<TargetRecord, Depth - 1>(std::span<TargetRecord* const> { targets });
3083template <
typename Record,
size_t FieldIndex>
3084void DataMapper::PreloadBelongsTo(std::span<Record* const> records)
3086 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
3087 using ReferencedRecord = FieldType::ReferencedRecord;
3088 using KeyType = RecordPrimaryKeyType<ReferencedRecord>;
3090 static_assert(HasPrimaryKey<ReferencedRecord>,
3091 "Eager loading a BelongsTo requires the referenced record to have a primary key");
3093 ZoneScopedN(
"DataMapper::PreloadBelongsTo");
3094 ZoneTextObject(RecordTableName<ReferencedRecord>);
3096 auto const primaryKeyOf = [](ReferencedRecord
const& record) {
3104 auto keys = std::vector<KeyType> {};
3105 keys.reserve(records.size());
3106 for (
auto const* record: records)
3108 auto const& field = GetRecordMemberAt<FieldIndex>(*record);
3111 if (field.LoadedRecord() !=
nullptr)
3114 auto const& value = field.Value();
3115 if constexpr (FieldType::IsOptional)
3117 if (value.has_value())
3118 keys.emplace_back(*value);
3121 keys.emplace_back(value);
3123 std::ranges::sort(keys);
3124 keys.erase(std::ranges::unique(keys).begin(), keys.end());
3128 auto loaded = std::vector<ReferencedRecord> {};
3129 loaded.reserve(keys.size());
3130 detail::ForEachChunk(std::span<KeyType const> { keys },
3132 [&](std::span<KeyType const> chunk) {
3133 auto selectQuery = BuildRecordSelectQuery<ReferencedRecord>().WhereIn(
3134 FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>, chunk);
3135 auto rows = Query<ReferencedRecord>(selectQuery.All());
3137 loaded.end(), std::make_move_iterator(rows.begin()), std::make_move_iterator(rows.end()));
3140 std::ranges::sort(loaded, {}, primaryKeyOf);
3142 for (
auto* record: records)
3144 auto& field = GetRecordMemberAt<FieldIndex>(*record);
3149 if (field.LoadedRecord() !=
nullptr)
3152 auto const& value = field.Value();
3153 if constexpr (FieldType::IsOptional)
3154 if (!value.has_value())
3157 auto const key = [&] {
3158 if constexpr (FieldType::IsOptional)
3164 auto const it = std::ranges::lower_bound(loaded, key, {}, primaryKeyOf);
3168 if (it != loaded.end() && primaryKeyOf(*it) == key)
3169 field.AdoptFetchedRecord(*it);
3173template <
typename Record,
size_t FieldIndex>
3174void DataMapper::PreloadHasMany(std::span<Record* const> records)
3176 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
3177 using ReferencedRecord = FieldType::ReferencedRecord;
3178 using ReferencedRecordList = FieldType::ReferencedRecordList;
3179 using KeyType = RecordPrimaryKeyType<Record>;
3181 static_assert(HasPrimaryKey<Record>,
"Eager loading a HasMany requires the owning record to have a primary key");
3183 ZoneScopedN(
"DataMapper::PreloadHasMany");
3184 ZoneTextObject(RecordTableName<ReferencedRecord>);
3188 constexpr size_t InverseIndex = InverseBelongsToIndexOf<Record, ReferencedRecord, FieldType::InverseSelector>;
3190 auto keys = std::vector<KeyType> {};
3191 keys.reserve(records.size());
3192 for (
auto* record: records)
3196 std::ranges::sort(keys);
3197 keys.erase(std::ranges::unique(keys).begin(), keys.end());
3203 auto buckets = std::vector<ReferencedRecordList> {};
3204 buckets.resize(keys.size());
3206 detail::ForEachChunk(
3207 std::span<KeyType const> { keys },
3209 [&](std::span<KeyType const> chunk) {
3211 BuildRecordSelectQuery<ReferencedRecord>()
3212 .WhereIn(InverseBelongsToFieldNameOf<Record, ReferencedRecord, FieldType::InverseSelector>, chunk)
3213 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
3214 for (
auto& child:
Query<ReferencedRecord>(selectQuery.All()))
3218 std::optional<KeyType>
const ownerKey = GetRecordMemberAt<InverseIndex>(child).Value();
3219 if (!ownerKey.has_value())
3222 auto const it = std::ranges::lower_bound(keys, *ownerKey);
3223 if (it != keys.end() && *it == *ownerKey)
3224 buckets[
static_cast<size_t>(std::distance(keys.begin(), it))].emplace_back(
3225 std::make_shared<ReferencedRecord>(std::move(child)));
3229 for (
auto* record: records)
3231 auto& field = GetRecordMemberAt<FieldIndex>(*record);
3234 if (field.LoadedRecords() !=
nullptr)
3246 auto& bucket = buckets[
static_cast<size_t>(std::distance(keys.begin(), it))];
3247 field.Emplace(ReferencedRecordList { bucket });
3251template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
3252SqlSelectQueryBuilder DataMapper::BuildHasOneThroughSelectQuery()
3254 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3255 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
3258 constexpr size_t ThroughToOwnerIndex = InverseBelongsToIndexOf<Record, ThroughRecord, OwnerSelector>;
3261 constexpr size_t ReferencedToThroughIndex = InverseBelongsToIndexOf<ThroughRecord, ReferencedRecord, ThroughSelector>;
3266 return _connection.
Query(RecordTableName<ReferencedRecord>)
3268 .
Build([&](
auto& query) {
3269 EnumerateRecordMembers<ReferencedRecord>([&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
3270 if constexpr (FieldWithStorage<ReferencedFieldType>)
3272 query.Field(SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
3273 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
3277 .InnerJoin(RecordTableName<ThroughRecord>,
3278 FieldNameAt<RecordPrimaryKeyIndex<ThroughRecord>, ThroughRecord>,
3279 FieldNameAt<ReferencedToThroughIndex, ReferencedRecord>)
3281 SqlQualifiedTableColumnName {
3282 RecordTableName<ThroughRecord>,
3283 FieldNameAt<ThroughToOwnerIndex, ThroughRecord>,
3288template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ThroughSelector>
3289void DataMapper::LoadHasOneThrough(Record& record,
3290 HasOneThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ThroughSelector>& field)
3292 using ThroughRecord = ThroughRecordOf<ThroughSpec>;
3294 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3295 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
3297 ZoneScopedN(
"DataMapper::LoadHasOneThrough");
3298 ZoneTextObject(RecordTableName<ReferencedRecord>);
3300 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
3302 BuildHasOneThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ThroughSelector>();
3303 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
3304 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
3308template <
typename ReferencedRecord,
3309 typename ThroughRecord,
3312 auto ThroughSelector,
3314std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue
const& pkValue)
3316 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
3318 auto query = BuildHasOneThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ThroughSelector>();
3320 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
3321 return std::make_shared<ReferencedRecord>(std::move(*link));
3326template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ReferencedSelector>
3327SqlSelectQueryBuilder DataMapper::BuildHasManyThroughSelectQuery()
3329 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3330 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
3333 constexpr size_t ThroughToOwnerIndex = InverseBelongsToIndexOf<Record, ThroughRecord, OwnerSelector>;
3336 constexpr size_t ThroughToReferencedIndex = InverseBelongsToIndexOf<ReferencedRecord, ThroughRecord, ReferencedSelector>;
3338 return _connection.
Query(RecordTableName<ReferencedRecord>)
3340 .
Build([&](
auto& query) {
3341 EnumerateRecordMembers<ReferencedRecord>([&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
3342 if constexpr (FieldWithStorage<ReferencedFieldType>)
3344 query.Field(SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
3345 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
3349 .InnerJoin(RecordTableName<ThroughRecord>,
3350 FieldNameAt<ThroughToReferencedIndex, ThroughRecord>,
3351 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
3352 FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord> })
3354 SqlQualifiedTableColumnName {
3355 RecordTableName<ThroughRecord>,
3356 FieldNameAt<ThroughToOwnerIndex, ThroughRecord>,
3361template <
typename ReferencedRecord,
3362 typename ThroughRecord,
3365 auto ReferencedSelector,
3367void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
3369 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3371 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
3373 BuildHasManyThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>();
3374 callback(query, primaryKeyField);
3378template <
typename ReferencedRecord,
3379 typename ThroughRecord,
3382 auto ReferencedSelector,
3385void DataMapper::CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback)
3387 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3390 BuildHasManyThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>();
3391 callback(query, pkValue);
3394template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ReferencedSelector>
3395void DataMapper::LoadHasManyThrough(Record& record,
3396 HasManyThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ReferencedSelector>& field)
3398 using ThroughRecord = ThroughRecordOf<ThroughSpec>;
3400 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3402 ZoneScopedN(
"DataMapper::LoadHasManyThrough");
3403 ZoneTextObject(RecordTableName<ReferencedRecord>);
3405 CallOnHasManyThrough<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>(
3406 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
3407 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
3411template <
typename Record>
3414 static_assert(!std::is_const_v<Record>);
3417 ZoneScopedN(
"DataMapper::LoadRelations");
3418 ZoneTextObject(RecordTableName<Record>);
3420#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3421 constexpr auto ctx = std::meta::access_context::current();
3422 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
3424 using FieldType =
typename[:std::meta::type_of(el):];
3425 if constexpr (IsBelongsTo<FieldType>)
3427 auto& field = record.[:el:];
3428 field.AdoptFetchedRecord(LoadBelongsTo<FieldType>(field.Value()));
3430 else if constexpr (IsCompositeForeignKey<FieldType>)
3432 LoadCompositeForeignKey(record, record.[:el:]);
3434 else if constexpr (IsHasMany<FieldType>)
3436 LoadHasMany(record, record.[:el:]);
3438 else if constexpr (IsHasOneThrough<FieldType>)
3440 LoadHasOneThrough(record, record.[:el:]);
3442 else if constexpr (IsHasManyThrough<FieldType>)
3444 LoadHasManyThrough(record, record.[:el:]);
3449 if constexpr (IsBelongsTo<FieldType>)
3451 field.AdoptFetchedRecord(LoadBelongsTo<FieldType>(field.Value()));
3453 else if constexpr (IsCompositeForeignKey<FieldType>)
3455 LoadCompositeForeignKey(record, field);
3457 else if constexpr (IsHasMany<FieldType>)
3459 LoadHasMany(record, field);
3461 else if constexpr (IsHasOneThrough<FieldType>)
3463 LoadHasOneThrough(record, field);
3465 else if constexpr (IsHasManyThrough<FieldType>)
3467 LoadHasManyThrough(record, field);
3474template <
typename Record,
typename ValueType>
3475inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
3480#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3482 auto constexpr ctx = std::meta::access_context::current();
3483 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
3485 using FieldType =
typename[:std::meta::type_of(el):];
3486 if constexpr (IsField<FieldType>)
3489 if constexpr (FieldType::IsAutoAssignPrimaryKey || FieldType::IsAutoIncrementPrimaryKey)
3491 record.[:el:] = std::forward<ValueType>(
id);
3497 if constexpr (IsField<FieldType>)
3500 if constexpr (FieldType::IsAutoAssignPrimaryKey || FieldType::IsAutoIncrementPrimaryKey)
3502 field = std::forward<FieldType>(
id);
3510template <
typename Record,
size_t InitialOffset>
3511inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
3514 return BindOutputColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record, InitialOffset>(record,
3518template <
typename ElementMask,
typename Record,
size_t InitialOffset>
3519Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
3522 static_assert(!std::is_const_v<Record>);
3524#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3525 auto constexpr ctx = std::meta::access_context::current();
3526 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
3527 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
3529 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
3530 using FieldType =
typename[:std::meta::type_of(el):];
3531 if constexpr (IsField<FieldType>)
3535 else if constexpr (SqlOutputColumnBinder<FieldType>)
3541 EnumerateRecordMembers<ElementMask>(
3542 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
3543 if constexpr (IsField<Field>)
3547 else if constexpr (SqlOutputColumnBinder<Field>)
3556template <
typename Load>
3557auto DataMapper::RunRelationLoad(detail::RelationLoadSource& source, Load
const& load)
3558 -> RelationResult<std::invoke_result_t<Load const&, DataMapper&>>
3560 using Result = std::invoke_result_t<Load const&, DataMapper&>;
3561 return source.Borrow().and_then([&load](std::shared_ptr<DataMapper>
const& borrowed) -> RelationResult<Result> {
3564 if constexpr (std::is_void_v<Result>)
3570 return load(*borrowed);
3572 catch (std::exception
const& error)
3580template <
typename Each,
typename Stream>
3581RelationResult<void> DataMapper::RunRelationStream(detail::RelationLoadSource& source,
3583 Stream
const& stream)
3585 auto callbackError = std::exception_ptr {};
3586 auto const guarded = [&each, &callbackError](
auto const& row) {
3593 callbackError = std::current_exception();
3597 auto result = RunRelationLoad(source, [&](
DataMapper& dm) { stream(dm, guarded); });
3599 std::rethrow_exception(callbackError);
3603template <
typename Record,
typename QueryText,
typename Callable,
typename... InputParameters>
3604void DataMapper::StreamRecords(QueryText
const& query, Callable
const& each, InputParameters
const&... inputParameters)
3607 auto cursor = _stmt.
Execute(inputParameters...);
3609 auto record = Record {};
3610 BindOutputColumns(record, cursor);
3623 BindOutputColumns(record, cursor);
3627template <
typename Record>
3636 auto const& source = RelationLoadSourceForLoaders();
3638 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
3639 if constexpr (IsBelongsTo<FieldType>)
3641 using ReferencedRecord = FieldType::ReferencedRecord;
3642 field.SetAutoLoader(
typename FieldType::Loader {
3644 return RunRelationLoad(*source, [&](
DataMapper& dm) {
return dm.LoadBelongsTo<FieldType>(value); })
3648 return std::move(*loaded);
3653 if constexpr (IsCompositeForeignKey<FieldType>)
3655 using ReferencedRecord = FieldType::ReferencedRecord;
3671 field.SetAutoLoader(
typename FieldType::Loader {
3672 .loadReference = [source, keys = FieldType::OrderedValuesOf(record)]()
3674 return RunRelationLoad(*source,
3675 [&](
DataMapper& dm) {
return dm.LoadCompositeForeignKeyRecord<FieldType>(keys); })
3676 .and_then(NotFoundIfNull<ReferencedRecord>);
3680 if constexpr (IsHasMany<FieldType>)
3682 if constexpr (HasPrimaryKey<Record>)
3684 using ReferencedRecord = FieldType::ReferencedRecord;
3690 return RunRelationLoad(*source, [&](
DataMapper& dm) {
3692 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3693 dm._stmt.
Prepare(selectQuery.Count());
3702 return RunRelationLoad(*source, [&](
DataMapper& dm) {
3704 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3705 return detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pkValue));
3709 return RunRelationStream(*source, each, [&](
DataMapper& dm,
auto const& guarded) {
3711 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3712 dm.StreamRecords<ReferencedRecord>(selectQuery.All(), guarded, pkValue);
3718 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
3720 using ReferencedRecord = FieldType::ReferencedRecord;
3721 using ThroughRecord = FieldType::ThroughRecord;
3724 field.SetAutoLoader(
typename FieldType::Loader {
3725 .loadReference = [source, pkValue]() ->
RelationResult<std::shared_ptr<ReferencedRecord>> {
3726 return RunRelationLoad(*source,
3728 return dm.LoadHasOneThroughByPK<ReferencedRecord,
3731 FieldType::OwnerSelector,
3732 FieldType::ThroughSelector>(pkValue);
3734 .and_then(NotFoundIfNull<ReferencedRecord>);
3738 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
3740 using ReferencedRecord = FieldType::ReferencedRecord;
3741 using ThroughRecord = FieldType::ThroughRecord;
3744 field.SetAutoLoader(
typename FieldType::Loader {
3746 return RunRelationLoad(*source, [&](
DataMapper& dm) {
3748 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3751 FieldType::OwnerSelector,
3752 FieldType::ReferencedSelector>(
3754 dm._stmt.
Prepare(selectQuery.Count());
3763 return RunRelationLoad(*source, [&](
DataMapper& dm) {
3764 typename FieldType::ReferencedRecordList result;
3765 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3768 FieldType::OwnerSelector,
3769 FieldType::ReferencedSelector>(
3771 result = detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pk));
3777 return RunRelationStream(*source, each, [&](
DataMapper& dm,
auto const& guarded) {
3778 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3781 FieldType::OwnerSelector,
3782 FieldType::ReferencedSelector>(
3784 dm.StreamRecords<ReferencedRecord>(selectQuery.All(), guarded, pk);
3792#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3793 constexpr auto ctx = std::meta::access_context::current();
3795 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
3796 constexpr auto localctx = std::meta::access_context::current();
3797 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
3798 using FieldType =
typename[:std::meta::type_of(members[I]):];
3799 callback.template operator()<I, FieldType>(record.[:members[I]:]);
3806template <
typename T>
3809 ZoneScopedN(
"DataMapper::Execute(string)");
3810 ZoneTextObject(sqlQueryString);
3816#include "../Async/DataMapperAsync.hpp"