113 _stmt { _connection }
119 _connection { std::move(connection) },
120 _stmt { _connection }
125 explicit DataMapper(std::optional<SqlConnectionString> connectionString):
126 _connection { std::move(connectionString) },
127 _stmt { _connection }
136 _connection(std::move(other._connection)),
138 _relationLoadSource(std::move(other._relationLoadSource)),
139 _timestampClock(std::move(other._timestampClock))
150 _connection = std::move(other._connection);
153 _relationLoadSource = std::move(other._relationLoadSource);
154 _timestampClock = std::move(other._timestampClock);
185 _timestampClock = std::move(clock);
194 return _timestampClock();
196 return SqlDateTime { std::chrono::system_clock::now() };
199#if defined(BUILD_TESTS)
201 [[nodiscard]]
SqlStatement& Statement(
this auto&& self)
noexcept
209 template <
typename Record>
210 static std::string
Inspect(Record
const& record);
213 template <
typename Record>
217 template <
typename FirstRecord,
typename... MoreRecords>
221 template <
typename Record>
225 template <
typename FirstRecord,
typename... MoreRecords>
240 template <DataMapperOptions QueryOptions = {},
typename Record>
241 RecordPrimaryKeyType<Record>
Create(Record& record);
253 template <
typename Record>
254 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
279 template <std::ranges::range Records>
292 template <DataMapperOptions QueryOptions = {},
typename Record>
293 [[nodiscard]] RecordPrimaryKeyType<Record>
CreateCopyOf(Record
const& originalRecord);
318 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
319 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
329 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... InputParameters>
330 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
361 template <
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
362 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
402 template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
403 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
436 template <
typename First,
typename Second,
typename... Rest, DataMapperOptions QueryOptions = {}>
437 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
438 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
454 template <
typename Record, DataMapperOptions QueryOptions = {}>
483 *
this, BuildFullyQualifiedFieldList<Record>());
515 template <
typename Record>
516 requires HasPrimaryKey<Record>
517 void Update(Record& record);
540 template <std::ranges::range Records>
550 template <
typename Record>
551 std::size_t
Delete(Record
const& record);
556 return _connection.
Query(tableName);
564 template <
typename Record>
565 bool IsModified(Record
const& record)
const noexcept;
580 template <ModifiedState state,
typename Record>
587 template <
typename Record>
597 template <
typename Record>
606 template <
typename T>
607 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
627 [[nodiscard]] Async::Task<RecordPrimaryKeyType<Record>>
CreateAsync(Record& record);
634 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
635 [[nodiscard]] Async::Task<std::optional<Record>>
QuerySingleAsync(PrimaryKeyTypes... primaryKeys);
638 template <
typename Record>
639 [[nodiscard]] Async::Task<void>
UpdateAsync(Record& record);
642 template <
typename Record>
643 [[nodiscard]] Async::Task<std::size_t>
DeleteAsync(Record
const& record);
646 template <
typename Record>
652 template <
typename BuilderRecord,
typename Derived, DataMapperOptions BuilderQueryOptions>
653 friend class SqlCoreDataMapperQueryBuilder;
655 friend void detail::AdoptRelationLoadSource(
DataMapper& dataMapper,
656 std::shared_ptr<detail::RelationLoadSource> source)
noexcept;
666 [[nodiscard]] LIGHTWEIGHT_API std::shared_ptr<detail::RelationLoadSource>
const& RelationLoadSourceForLoaders();
676 template <
typename Record,
typename QueryText,
typename Callable,
typename... InputParameters>
677 void StreamRecords(QueryText
const& query, Callable
const& each, InputParameters
const&... inputParameters);
686 template <
typename Load>
687 static auto RunRelationLoad(detail::RelationLoadSource& source, Load
const& load)
688 -> RelationResult<std::invoke_result_t<Load const&, DataMapper&>>;
697 template <
typename Each,
typename Stream>
698 static RelationResult<void> RunRelationStream(detail::RelationLoadSource& source,
700 Stream
const& stream);
703 template <
typename Record>
704 static RelationResult<std::shared_ptr<Record>> NotFoundIfNull(std::shared_ptr<Record> record)
717 template <
typename Record>
718 [[nodiscard]]
static std::string BuildFullyQualifiedFieldList()
721 EnumerateRecordMembers<Record>([&fields]<
size_t I,
typename FieldType>() {
723 if constexpr (RecordColumnMember<FieldType>)
728 fields += RecordTableName<Record>;
730 fields += FieldNameAt<I, Record>;
743 template <
typename Record,
typename... Args>
744 std::optional<Record>
QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args);
746 template <
typename Record,
typename ValueType>
747 void SetId(Record& record, ValueType&&
id);
749 template <
typename Record,
size_t InitialOffset = 1>
750 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
752 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
753 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
755 template <
typename FieldType>
756 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
773 template <
typename FieldType>
774 std::shared_ptr<typename FieldType::ReferencedRecord> LoadCompositeForeignKeyRecord(
775 FieldType::OrderedValueType
const& keys);
781 template <
typename Record,
typename FieldType>
782 void LoadCompositeForeignKey(Record
const& record, FieldType& field);
784 template <
typename Record,
typename OtherRecord, auto InverseSelector>
785 void LoadHasMany(Record& record, HasMany<OtherRecord, InverseSelector>& field);
803 template <
typename Record,
size_t FieldIndex>
804 void PreloadRelation(std::span<Record* const> records);
808 template <
typename Record,
size_t FieldIndex>
809 void PreloadBelongsTo(std::span<Record* const> records);
814 template <
typename Record,
size_t FieldIndex>
815 void PreloadHasMany(std::span<Record* const> records);
828 template <
typename Record,
size_t FieldIndex,
auto... RestOfPath>
829 void PreloadRelationPath(std::span<Record* const> records);
842 template <
typename Record,
size_t Depth>
843 void PreloadAllRelations(std::span<Record* const> records);
852 template <
typename Record,
size_t FieldIndex>
853 [[nodiscard]]
static auto CollectRelationTargets(std::span<Record* const> records);
855 template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ThroughSelector>
856 void LoadHasOneThrough(Record& record,
857 HasOneThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ThroughSelector>& field);
859 template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ReferencedSelector>
860 void LoadHasManyThrough(Record& record,
861 HasManyThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ReferencedSelector>& field);
863 template <
typename Record,
typename OtherRecord, auto InverseSelector,
typename Callable>
864 void CallOnHasMany(Record& record, Callable
const& callback);
866 template <
typename OwnerRecord,
typename OtherRecord, auto InverseSelector>
867 SqlSelectQueryBuilder BuildHasManySelectQuery();
875 template <
typename Record>
876 SqlSelectQueryBuilder BuildRecordSelectQuery();
878 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
879 SqlSelectQueryBuilder BuildHasOneThroughSelectQuery();
881 template <
typename ReferencedRecord,
882 typename ThroughRecord,
885 auto ReferencedSelector>
886 SqlSelectQueryBuilder BuildHasManyThroughSelectQuery();
888 template <
typename ReferencedRecord,
889 typename ThroughRecord,
892 auto ReferencedSelector,
894 void CallOnHasManyThrough(Record& record, Callable
const& callback);
896 template <
typename ReferencedRecord,
897 typename ThroughRecord,
900 auto ReferencedSelector,
903 void CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback);
905 template <
typename ReferencedRecord,
906 typename ThroughRecord,
909 auto ThroughSelector,
911 std::shared_ptr<ReferencedRecord> LoadHasOneThroughByPK(PKValue
const& pkValue);
913 enum class PrimaryKeySource : std::uint8_t
920 enum class TimestampFill : std::uint8_t
933 template <TimestampFill Fill,
typename FieldType>
934 [[nodiscard]]
static constexpr bool FillsTimestamp(FieldType
const& field)
noexcept;
937 template <TimestampFill Fill,
typename Record>
938 static void StampTimestamps(Record& record, SqlDateTime
const& now);
941 template <
typename Record>
942 void StampTimestampsForUpdate(Record& record)
const;
945 template <TimestampFill Fill,
typename Record>
946 [[nodiscard]] std::vector<Record> CopyWithTimestamps(std::span<Record const> records)
const;
949 template <
typename Record>
950 void InsertBatch(std::span<Record const> records);
953 template <
typename Record>
954 void UpdateBatch(std::span<Record const> records);
956 template <
typename Record>
957 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record
const& record);
959 template <PrimaryKeySource UsePkOverr
ide, TimestampFill Fill,
typename Record>
960 RecordPrimaryKeyType<Record> CreateInternal(
961 Record
const& record,
962 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
963 pkOverride = std::nullopt);
965 SqlConnection _connection;
967 std::shared_ptr<detail::RelationLoadSource> _relationLoadSource;
971inline void detail::AdoptRelationLoadSource(DataMapper& dataMapper, std::shared_ptr<RelationLoadSource> source)
noexcept
973 dataMapper._relationLoadSource = std::move(source);
985 template <
typename T>
986 inline constexpr bool IsGrowableColumnType =
987 detail::OneOf<T, std::string, std::wstring, std::u16string, std::u32string, SqlBinary> || IsSqlDynamicString<T>
988 || IsSqlDynamicBinary<T>;
1003 template <
typename T>
1004 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
1006 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
1012 return !IsGrowableColumnType<T>;
1023 template <DataMapperRecord Record>
1024 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
1026 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
1030 EnumerateRecordMembers<Record>([&result]<
size_t I,
typename Field>() {
1031 if constexpr (IsField<Field>)
1032 if constexpr (IsGrowableColumnType<typename Field::ValueType>)
1039 template <
typename Record>
1040 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
1042 EnumerateRecordMembers(record, [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
1043 if constexpr (IsField<Field>)
1045 reader->BindOutputColumn(i++, &field.MutableValue());
1047 else if constexpr (IsBelongsTo<Field>)
1049 reader->BindOutputColumn(i++, &field.MutableValue());
1051 else if constexpr (SqlOutputColumnBinder<Field>)
1053 reader->BindOutputColumn(i++, &field);
1058 template <
typename Record>
1059 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
1061 BindAllOutputColumnsWithOffset(reader, record, 1);
1066 constexpr std::size_t kDefaultRowArrayFetchDepth = 1024;
1071 template <std::
size_t I>
1072 struct MutableFieldValueAccessor
1074 template <
typename Record>
1075 decltype(
auto)
operator()(Record& record)
const
1077 return GetRecordMemberAt<I>(record).MutableValue();
1083 template <
typename FieldType>
1084 using RowWiseColumnValueType = std::remove_cvref_t<decltype(std::declval<FieldType&>().MutableValue())>;
1088 template <
typename FieldType>
1089 constexpr bool RowWiseIsColumn()
1091 return IsField<FieldType> || IsBelongsTo<FieldType> || SqlOutputColumnBinder<FieldType>;
1098 template <
typename FieldType>
1099 constexpr bool RowWiseColumnAcceptable()
1101 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1102 return SqlRowWiseFetchableColumn<RowWiseColumnValueType<FieldType>>;
1103 else if constexpr (SqlOutputColumnBinder<FieldType>)
1109 template <
typename Record, std::size_t... Is>
1110 constexpr bool CanRowWiseFetchRecordImpl(std::index_sequence<Is...> )
1115 return (
sizeof(Record) %
alignof(SQLLEN) == 0) && (RowWiseColumnAcceptable<RecordMemberTypeOf<Is, Record>>() && ...)
1116 && (RowWiseIsColumn<RecordMemberTypeOf<Is, Record>>() || ...);
1123 template <
typename Record>
1124 constexpr bool CanRowWiseFetchRecord()
1126 return CanRowWiseFetchRecordImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
1131 template <std::
size_t I,
typename Record>
1132 auto MakeOutputColumnAccessor()
1134 using FieldType = RecordMemberTypeOf<I, Record>;
1135 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1136 return std::tuple<MutableFieldValueAccessor<I>> {};
1138 return std::tuple<> {};
1144 template <
typename Record>
1145 void ReadAllRowWise(SqlResultCursor& reader, std::vector<Record>* records)
1147 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
1149 [&](
auto const&... accessors) {
1150 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
1152 std::tuple_cat(MakeOutputColumnAccessor<Is, Record>()...));
1153 }(std::make_index_sequence<RecordMemberCount<Record>> {});
1159 template <
typename FieldType>
1160 constexpr bool ColumnIsNarrowFixedString()
1162 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1164 using V = RowWiseColumnValueType<FieldType>;
1165 if constexpr (SqlIsStdOptional<V>)
1166 return IsSqlFixedString<typename V::value_type>;
1168 return IsSqlFixedString<V>;
1174 template <
typename Record, std::size_t... Is>
1175 constexpr bool RecordHasNarrowFixedStringColumnImpl(std::index_sequence<Is...> )
1177 return (ColumnIsNarrowFixedString<RecordMemberTypeOf<Is, Record>>() || ...);
1183 template <
typename Record>
1184 constexpr bool RecordHasNarrowFixedStringColumn()
1186 return RecordHasNarrowFixedStringColumnImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
1193 template <
typename Record>
1194 bool CanRowWiseFetchOn(SqlServerType serverType)
1196 if constexpr (!CanRowWiseFetchRecord<Record>())
1200 && (!RecordHasNarrowFixedStringColumn<Record>()
1208 template <std::
size_t TupleIndex, std::
size_t I>
1209 struct MutableTupleFieldAccessor
1211 template <
typename TupleType>
1212 decltype(
auto)
operator()(TupleType& row)
const
1214 return GetRecordMemberAt<I>(std::get<TupleIndex>(row)).MutableValue();
1218 template <
typename First,
typename Second, std::size_t... Fs, std::size_t... Ss>
1219 constexpr bool CanRowWiseFetchTupleImpl(std::index_sequence<Fs...> ,
1220 std::index_sequence<Ss...> )
1222 return (
sizeof(std::tuple<First, Second>) %
alignof(SQLLEN) == 0)
1223 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Fs, First>>() && ...)
1224 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Ss, Second>>() && ...)
1225 && ((RowWiseIsColumn<RecordMemberTypeOf<Fs, First>>() || ...)
1226 || (RowWiseIsColumn<RecordMemberTypeOf<Ss, Second>>() || ...));
1232 template <
typename First,
typename Second>
1233 constexpr bool CanRowWiseFetchTuple()
1235 return CanRowWiseFetchTupleImpl<First, Second>(std::make_index_sequence<RecordMemberCount<First>> {},
1236 std::make_index_sequence<RecordMemberCount<Second>> {});
1242 template <
typename First,
typename Second>
1243 bool CanRowWiseFetchTupleOn(SqlServerType serverType)
1245 if constexpr (!CanRowWiseFetchTuple<First, Second>())
1249 && ((!RecordHasNarrowFixedStringColumn<First>() && !RecordHasNarrowFixedStringColumn<Second>())
1254 template <std::
size_t TupleIndex, std::
size_t I,
typename SubRecord>
1255 auto MakeTupleColumnAccessor()
1257 using FieldType = RecordMemberTypeOf<I, SubRecord>;
1258 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1259 return std::tuple<MutableTupleFieldAccessor<TupleIndex, I>> {};
1261 return std::tuple<> {};
1267 template <
typename First,
typename Second>
1268 void ReadAllRowWiseTuple(SqlResultCursor& reader, std::vector<std::tuple<First, Second>>* records)
1270 [&]<std::size_t... Fs, std::size_t... Ss>(std::index_sequence<Fs...>, std::index_sequence<Ss...>) {
1272 [&](
auto const&... accessors) {
1273 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
1275 std::tuple_cat(MakeTupleColumnAccessor<0, Fs, First>()..., MakeTupleColumnAccessor<1, Ss, Second>()...));
1276 }(std::make_index_sequence<RecordMemberCount<First>> {}, std::make_index_sequence<RecordMemberCount<Second>> {});
1282 template <
typename ElementMask,
typename Record>
1283 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
1285 EnumerateRecordMembers<ElementMask>(
1286 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
1295 static_assert(RecordColumnMember<Field> == (IsField<Field> || SqlGetColumnNativeType<Field>),
1296 "Record member is projected but not readable (or readable but not projected). "
1297 "A SqlDataBinder<T> used as a record member must provide both OutputColumn() "
1298 "and GetColumn().");
1299 if constexpr (IsField<Field>)
1303 field.MutableValue() =
1304 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
1306 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
1308 else if constexpr (SqlGetColumnNativeType<Field>)
1311 if constexpr (IsOptionalBelongsTo<Field>)
1312 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
1314 field = reader->GetColumn<Field>(indexFromQuery);
1319 template <
typename Record>
1320 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
1322 return GetAllColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record>(
1323 reader, record, indexFromQuery);
1326 template <
typename FirstRecord,
typename SecondRecord>
1328 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
1330 auto& [firstRecord, secondRecord] = record;
1335 GetAllColumns(reader, firstRecord, 0);
1336 GetAllColumns(reader, secondRecord,
static_cast<SQLUSMALLINT
>(RecordColumnCount<FirstRecord>));
1339 template <
typename Record>
1340 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
1342 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
1344 if (outputColumnsBound)
1345 BindAllOutputColumns(reader, record);
1347 if (!reader.FetchRow())
1350 if (!outputColumnsBound)
1351 GetAllColumns(reader, record);
1357template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1358template <
typename Finisher>
1359auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RunFinisher(Finisher finisher)
1361 if constexpr (Derived::QueryExecution == SqlQueryExecutionMode::Asynchronous)
1362 return Async::RunAsync(_dm.Connection().AsyncBackend(), std::move(finisher));
1367template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1369 DataMapper& dm, std::string fields)
noexcept:
1371 _formatter { dm.Connection().QueryFormatter() },
1372 _fields { std::move(fields) }
1374 this->_query.searchCondition.inputBindings = &_boundInputs;
1377template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1380 SqlBasicSelectQueryBuilder<Derived> { other },
1382 _formatter { other._formatter },
1383 _fields { other._fields },
1384 _boundInputs { other._boundInputs },
1385 _relationPreloaders { other._relationPreloaders }
1387 this->_query.searchCondition.inputBindings = &_boundInputs;
1390template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1394 SqlBasicSelectQueryBuilder<Derived> { std::move(
static_cast<SqlBasicSelectQueryBuilder<Derived>&
>(other)) },
1396 _formatter { other._formatter },
1397 _fields { std::move(other._fields) },
1398 _boundInputs { std::move(other._boundInputs) },
1399 _relationPreloaders { std::move(other._relationPreloaders) }
1401 this->_query.searchCondition.inputBindings = &_boundInputs;
1404template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1405size_t SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::CountImpl()
1407 auto stmt = SqlStatement { _dm.
Connection() };
1408 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
1409 RecordTableName<Record>,
1410 this->_query.searchCondition.tableAlias,
1411 this->_query.searchCondition.tableJoins,
1412 this->_query.searchCondition.condition,
1413 this->_query.groupBy));
1414 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1415 if (reader.FetchRow())
1416 return reader.template GetColumn<size_t>(1);
1420template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1421template <
auto... RelationPath>
1422 requires DataMapperRecord<Record> && (
sizeof...(RelationPath) >= 1)
1425 constexpr auto FirstField = detail::FirstOf<RelationPath...>;
1427#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1428 static_assert(std::same_as<
typename[:std::meta::parent_of(FirstField):], Record>,
1429 "The first relation named by With<>() must be a member of the record being queried");
1433 static_assert(std::same_as<MemberClassType<std::remove_cv_t<
decltype(FirstField)>>, Record>,
1434 "The first relation named by With<>() must be a member of the record being queried");
1439 _relationPreloaders.emplace_back(+[](
DataMapper& dm, std::span<Record* const> records) {
1440 [&]<
auto Head,
auto... Tail>() {
1441 dm.template PreloadRelationPath<Record, MemberIndexOf<Head>, Tail...>(records);
1442 }.template operator()<RelationPath...>();
1445 return static_cast<Derived&
>(*this);
1448template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1451 if constexpr (QueryOptions.eagerLoadDepth == 0)
1452 if (_relationPreloaders.empty())
1455 if (records.empty())
1458 auto pointers = std::vector<Record*> {};
1459 pointers.reserve(records.size());
1460 for (
auto& record: records)
1461 pointers.emplace_back(&record);
1462 auto const batch = std::span<Record* const> { pointers };
1466 for (
auto const preloader: _relationPreloaders)
1467 preloader(_dm, batch);
1469 if constexpr (QueryOptions.eagerLoadDepth > 0)
1470 _dm.template PreloadAllRelations<Record, QueryOptions.eagerLoadDepth>(batch);
1473template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1474bool SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::ExistImpl()
1476 auto stmt = SqlStatement { _dm.Connection() };
1478 auto const query = _formatter.SelectFirst(this->_query.distinct,
1480 RecordTableName<Record>,
1481 this->_query.searchCondition.tableAlias,
1482 this->_query.searchCondition.tableJoins,
1483 this->_query.searchCondition.condition,
1484 this->_query.orderBy,
1485 this->_query.groupBy,
1488 stmt.Prepare(query);
1489 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1494template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1495void SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::DeleteImpl()
1497 auto stmt = SqlStatement { _dm.Connection() };
1499 auto const query = _formatter.
Delete(RecordTableName<Record>,
1500 this->_query.searchCondition.tableAlias,
1501 this->_query.searchCondition.tableJoins,
1502 this->_query.searchCondition.condition);
1504 stmt.Prepare(query);
1505 [[maybe_unused]]
auto cursor = stmt.ExecuteWithVariants(_boundInputs);
1508template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1509std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl()
1512 auto records = std::vector<Record> {};
1513 auto stmt = SqlStatement { _dm.Connection() };
1514 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1516 RecordTableName<Record>,
1517 this->_query.searchCondition.tableAlias,
1518 this->_query.searchCondition.tableJoins,
1519 this->_query.searchCondition.condition,
1520 this->_query.orderBy,
1521 this->_query.groupBy));
1522 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1523 if constexpr (DataMapperRecord<Record>)
1528 if constexpr (QueryOptions.loadRelations)
1530 for (
auto& record: records)
1532 _dm.ConfigureRelationAutoLoading(record);
1535 RunRelationPreloaders(records);
1540template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1541template <auto Field>
1542#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1543 requires(is_aggregate_type(parent_of(Field)))
1545 requires std::is_member_object_pointer_v<
decltype(Field)>
1547auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<ReferencedFieldTypeOf<Field>>
1549 using value_type = ReferencedFieldTypeOf<Field>;
1550 auto result = std::vector<value_type> {};
1552 auto stmt = SqlStatement { _dm.Connection() };
1553 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1554 detail::FullyQualifiedNamesOf<Field>,
1555 RecordTableName<Record>,
1556 this->_query.searchCondition.tableAlias,
1557 this->_query.searchCondition.tableJoins,
1558 this->_query.searchCondition.condition,
1559 this->_query.orderBy,
1560 this->_query.groupBy));
1561 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1562 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
1565 auto& value = result.emplace_back();
1566 if (outputColumnsBound)
1567 reader.BindOutputColumn(1, &value);
1569 if (!reader.FetchRow())
1575 if (!outputColumnsBound)
1576 value = reader.template GetColumn<value_type>(1);
1582template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1583template <
auto... ReferencedFields>
1584 requires(
sizeof...(ReferencedFields) >= 2)
1585auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<Record>
1587 auto records = std::vector<Record> {};
1588 auto stmt = SqlStatement { _dm.Connection() };
1590 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1591 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1592 RecordTableName<Record>,
1593 this->_query.searchCondition.tableAlias,
1594 this->_query.searchCondition.tableJoins,
1595 this->_query.searchCondition.condition,
1596 this->_query.orderBy,
1597 this->_query.groupBy));
1599 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1600 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1603 auto& record = records.emplace_back();
1604 if (outputColumnsBound)
1605#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1606 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1608 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1610 if (!reader.FetchRow())
1615 if (!outputColumnsBound)
1617 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1618 detail::GetAllColumns<ElementMask>(reader, record);
1625template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1626std::optional<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl()
1628 std::optional<Record> record {};
1629 auto stmt = SqlStatement { _dm.Connection() };
1630 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1632 RecordTableName<Record>,
1633 this->_query.searchCondition.tableAlias,
1634 this->_query.searchCondition.tableJoins,
1635 this->_query.searchCondition.condition,
1636 this->_query.orderBy,
1637 this->_query.groupBy,
1639 Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record);
1640 if constexpr (QueryOptions.loadRelations)
1643 _dm.ConfigureRelationAutoLoading(record.value());
1645 if constexpr (DataMapperRecord<Record>)
1647 RunRelationPreloaders(std::span<Record> { &record.value(), 1 });
1651template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1652template <auto Field>
1653#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1654 requires(is_aggregate_type(parent_of(Field)))
1656 requires std::is_member_object_pointer_v<
decltype(Field)>
1658auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<ReferencedFieldTypeOf<Field>>
1660 auto constexpr count = 1;
1661 auto stmt = SqlStatement { _dm.Connection() };
1662 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1663 detail::FullyQualifiedNamesOf<Field>,
1664 RecordTableName<Record>,
1665 this->_query.searchCondition.tableAlias,
1666 this->_query.searchCondition.tableJoins,
1667 this->_query.searchCondition.condition,
1668 this->_query.orderBy,
1669 this->_query.groupBy,
1671 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1672 return reader.template GetColumn<ReferencedFieldTypeOf<Field>>(1);
1673 return std::nullopt;
1676template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1677template <
auto... ReferencedFields>
1678 requires(
sizeof...(ReferencedFields) >= 2)
1679auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<Record>
1681 auto optionalRecord = std::optional<Record> {};
1683 auto stmt = SqlStatement { _dm.Connection() };
1684 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1685 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1686 RecordTableName<Record>,
1687 this->_query.searchCondition.tableAlias,
1688 this->_query.searchCondition.tableJoins,
1689 this->_query.searchCondition.condition,
1690 this->_query.orderBy,
1691 this->_query.groupBy,
1694 auto& record = optionalRecord.emplace();
1695 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1696 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1697 if (outputColumnsBound)
1698#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1699 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1701 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1709 if (reader.FetchRow())
1711 if (!outputColumnsBound)
1713 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1714 detail::GetAllColumns<ElementMask>(reader, record);
1717 if constexpr (QueryOptions.loadRelations)
1718 _dm.ConfigureRelationAutoLoading(record);
1722 optionalRecord.reset();
1725 return optionalRecord;
1728template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1729std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1731 auto records = std::vector<Record> {};
1732 auto stmt = SqlStatement { _dm.Connection() };
1734 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1736 RecordTableName<Record>,
1737 this->_query.searchCondition.tableAlias,
1738 this->_query.searchCondition.tableJoins,
1739 this->_query.searchCondition.condition,
1740 this->_query.orderBy,
1741 this->_query.groupBy,
1743 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1745 if constexpr (QueryOptions.loadRelations)
1747 for (
auto& record: records)
1748 _dm.ConfigureRelationAutoLoading(record);
1750 if constexpr (DataMapperRecord<Record>)
1751 RunRelationPreloaders(records);
1755template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1756std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1758 auto records = std::vector<Record> {};
1759 auto stmt = SqlStatement { _dm.Connection() };
1760 records.reserve(limit);
1762 _formatter.SelectRange(this->_query.distinct,
1764 RecordTableName<Record>,
1765 this->_query.searchCondition.tableAlias,
1766 this->_query.searchCondition.tableJoins,
1767 this->_query.searchCondition.condition,
1768 !this->_query.orderBy.empty()
1769 ? this->_query.orderBy
1770 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1771 this->_query.groupBy,
1774 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1775 if constexpr (QueryOptions.loadRelations)
1777 for (
auto& record: records)
1778 _dm.ConfigureRelationAutoLoading(record);
1780 if constexpr (DataMapperRecord<Record>)
1781 RunRelationPreloaders(records);
1785template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1786template <
auto... ReferencedFields>
1787std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1789 auto records = std::vector<Record> {};
1790 auto stmt = SqlStatement { _dm.Connection() };
1791 records.reserve(limit);
1793 _formatter.SelectRange(this->_query.distinct,
1794 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1795 RecordTableName<Record>,
1796 this->_query.searchCondition.tableAlias,
1797 this->_query.searchCondition.tableJoins,
1798 this->_query.searchCondition.condition,
1799 !this->_query.orderBy.empty()
1800 ? this->_query.orderBy
1801 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1802 this->_query.groupBy,
1806 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1807 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1810 auto& record = records.emplace_back();
1811 if (outputColumnsBound)
1812#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1813 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1815 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1817 if (!reader.FetchRow())
1822 if (!outputColumnsBound)
1824 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1825 detail::GetAllColumns<ElementMask>(reader, record);
1829 if constexpr (QueryOptions.loadRelations)
1831 for (
auto& record: records)
1832 _dm.ConfigureRelationAutoLoading(record);
1838template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1839template <
auto... ReferencedFields>
1840[[nodiscard]] std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1842 auto records = std::vector<Record> {};
1843 auto stmt = SqlStatement { _dm.Connection() };
1845 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1846 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1847 RecordTableName<Record>,
1848 this->_query.searchCondition.tableAlias,
1849 this->_query.searchCondition.tableJoins,
1850 this->_query.searchCondition.condition,
1851 this->_query.orderBy,
1852 this->_query.groupBy,
1855 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1856 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1859 auto& record = records.emplace_back();
1860 if (outputColumnsBound)
1861#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1862 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1864 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1866 if (!reader.FetchRow())
1871 if (!outputColumnsBound)
1873 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1874 detail::GetAllColumns<ElementMask>(reader, record);
1878 if constexpr (QueryOptions.loadRelations)
1880 for (
auto& record: records)
1881 _dm.ConfigureRelationAutoLoading(record);
1887template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1888void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResults(SqlServerType sqlServerType,
1889 SqlResultCursor reader,
1890 std::vector<Record>* records)
1896 if constexpr (detail::CanRowWiseFetchRecord<Record>())
1898 if (detail::CanRowWiseFetchOn<Record>(sqlServerType))
1900 detail::ReadAllRowWise(reader, records);
1907 Record& record = records->emplace_back();
1908 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1910 records->pop_back();
1916template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1917void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResult(SqlServerType sqlServerType,
1918 SqlResultCursor reader,
1919 std::optional<Record>* optionalRecord)
1921 Record& record = optionalRecord->emplace();
1922 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1923 optionalRecord->reset();
1926template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1927void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution>::ReadResults(
1928 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1932 if constexpr (detail::CanRowWiseFetchTuple<FirstRecord, SecondRecord>())
1934 if (detail::CanRowWiseFetchTupleOn<FirstRecord, SecondRecord>(sqlServerType))
1936 detail::ReadAllRowWiseTuple<FirstRecord, SecondRecord>(reader, records);
1943 auto& record = records->emplace_back();
1944 auto& [firstRecord, secondRecord] = record;
1946 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1947 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1949 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1950 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1951 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1953 if (canSafelyBindAll)
1955 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1958 detail::BindAllOutputColumnsWithOffset(
1959 reader, secondRecord,
static_cast<SQLUSMALLINT
>(1 + RecordColumnCount<FirstRecord>));
1962 if (!reader.FetchRow())
1964 records->pop_back();
1968 if (!canSafelyBindAll)
1969 detail::GetAllColumns(reader, record);
1973template <
typename Record>
1979 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1985 if constexpr (Value::IsOptional)
1987 if (!value.Value().has_value())
1989 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1993 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1996 else if constexpr (IsBelongsTo<Value>)
1998 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
2000 else if constexpr (std::same_as<typename Value::ValueType, char>)
2005 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
2008 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>
2009 && !IsCompositeForeignKey<Value>)
2010 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
2012 return "{\n" + std::move(str) +
"\n}";
2015template <
typename Record>
2021 auto createTable = migration.
CreateTable(RecordTableName<Record>);
2022 detail::PopulateCreateTableBuilder<Record>(createTable);
2023 return migration.GetPlan().ToSql();
2026template <
typename FirstRecord,
typename... MoreRecords>
2029 std::vector<std::string> output;
2030 auto const append = [&output](
auto const& sql) {
2031 output.insert(output.end(), sql.begin(), sql.end());
2033 append(CreateTableString<FirstRecord>(serverType));
2034 (append(CreateTableString<MoreRecords>(serverType)), ...);
2038template <
typename Record>
2043 ZoneScopedN(
"DataMapper::CreateTable");
2044 ZoneTextObject(RecordTableName<Record>);
2046 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
2047 for (
auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
2051template <
typename FirstRecord,
typename... MoreRecords>
2054 CreateTable<FirstRecord>();
2055 (CreateTable<MoreRecords>(), ...);
2061 template <
typename Record>
2062 inline constexpr bool HasAutoTimestampFields =
2063 CheckFieldProperty<[]<typename FieldType>() {
return IsAutoTimestampField<FieldType>; }, Record>;
2066 template <
typename Record>
2067 inline constexpr bool HasUpdatedAtTimestampFields =
2072template <DataMapper::TimestampFill Fill,
typename FieldType>
2073constexpr bool DataMapper::FillsTimestamp(FieldType
const& field)
noexcept
2075 if constexpr (!IsAutoTimestampField<FieldType>)
2077 else if constexpr (Fill == TimestampFill::Unset)
2078 return detail::IsUnsetTimestamp(field.Value());
2079 else if constexpr (Fill == TimestampFill::All)
2081 else if constexpr (Fill == TimestampFill::UpdatedAt)
2087template <DataMapper::TimestampFill Fill,
typename Record>
2088void DataMapper::StampTimestamps(Record& record, SqlDateTime
const& now)
2091 using MemberType = RecordMemberTypeOf<I, Record>;
2092 if constexpr (IsAutoTimestampField<MemberType>)
2094 if (FillsTimestamp<Fill>(field))
2100template <
typename Record>
2101void DataMapper::StampTimestampsForUpdate(Record& record)
const
2103 if constexpr (detail::HasUpdatedAtTimestampFields<Record>)
2106 bool writesOtherColumn =
false;
2108 using MemberType = RecordMemberTypeOf<I, Record>;
2110 writesOtherColumn = writesOtherColumn || field.IsModified();
2113 auto const now = writesOtherColumn ? std::optional {
CurrentTimestamp() } : std::nullopt;
2115 using MemberType = RecordMemberTypeOf<I, Record>;
2119 if (field.IsModified() && !detail::IsUnsetTimestamp(field.Value()))
2121 if (now.has_value())
2124 field.SetModified(
false);
2130template <DataMapper::TimestampFill Fill,
typename Record>
2131std::vector<Record> DataMapper::CopyWithTimestamps(std::span<Record const> records)
const
2133 static_assert(std::copy_constructible<Record>,
2134 "Batch writes of a record with FieldTimestamp fields copy the batch to carry the generated "
2135 "timestamps, so the record type must be copy-constructible.");
2137 auto copies = std::vector<Record>(records.begin(), records.end());
2139 for (
auto& copy: copies)
2140 StampTimestamps<Fill>(copy, now);
2144template <
typename Record>
2145std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
2150 static_assert(detail::AutoAssignPrimaryKeyFieldCount<Record> <= 1,
2151 "A record may declare at most one auto-assigned primary key member. Auto-assignment yields a "
2152 "single value that would be written into every generated key member, so a composite key cannot "
2153 "be generated - declare its members PrimaryKey::Manual and set their values yourself before "
2154 "calling Create().");
2156 std::optional<RecordPrimaryKeyType<Record>> result;
2158 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2159 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
2160 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
2162 using ValueType = PrimaryKeyType::ValueType;
2163 if constexpr (std::same_as<ValueType, SqlGuid>)
2165 if (!primaryKeyField.Value())
2170 else if constexpr (
requires { ValueType {} + 1; })
2172 if (primaryKeyField.Value() == ValueType {})
2174 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
2175 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
2176 FieldNameAt<PrimaryKeyIndex, Record>,
2177 RecordTableName<Record>));
2178 result = maxId.value_or(ValueType {}) + 1;
2186template <DataMapper::PrimaryKeySource UsePkOverr
ide, DataMapper::TimestampFill Fill,
typename Record>
2187RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
2188 Record
const& record,
2189 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
2192 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2194 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
2196#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2197 constexpr auto ctx = std::meta::access_context::current();
2198 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2200 using FieldType =
typename[:std::meta::type_of(el):];
2201 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
2202 query.Set(FieldNameOf<el>, SqlWildcard);
2206 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
2207 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2216 Fill != TimestampFill::None && detail::HasAutoTimestampFields<Record> ?
CurrentTimestamp() : SqlDateTime {};
2218#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2220 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2222 using FieldType =
typename[:std::meta::type_of(el):];
2223 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
2226 if constexpr (detail::IsAutoAssignPrimaryKeyField<FieldType>::value
2227 && UsePkOverride == PrimaryKeySource::Override)
2229 else if (FillsTimestamp<Fill>(record.[:el:]))
2230 _stmt.BindInputParameter(i++, now, std::meta::identifier_of(el));
2236 Reflection::CallOnMembers(record,
2237 [
this, &pkOverride, &now, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
2238 Name
const& name, FieldType
const& field)
mutable {
2239 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
2242 if constexpr (detail::IsAutoAssignPrimaryKeyField<FieldType>::value
2243 && UsePkOverride == PrimaryKeySource::Override)
2245 else if (FillsTimestamp<Fill>(field))
2252 [[maybe_unused]]
auto cursor = _stmt.
Execute();
2254 if constexpr (HasAutoIncrementPrimaryKey<Record>)
2260 return static_cast<RecordPrimaryKeyType<Record>
>(_stmt.
LastInsertId(RecordTableName<Record>));
2261 else if constexpr (HasPrimaryKey<Record>)
2263 if constexpr (UsePkOverride == PrimaryKeySource::Override)
2266 return RecordPrimaryKeyOf(record).Value();
2272template <
typename Record>
2276 return CreateInternal<PrimaryKeySource::Record, TimestampFill::Unset>(record);
2285 template <
typename FieldType>
2286 constexpr bool IsBatchInsertColumn = SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>;
2289 template <
typename FieldType>
2293 template <
typename FieldType>
2294 constexpr bool IsBatchUpdateWhereColumn = IsPrimaryKey<FieldType>;
2298 template <std::
size_t I>
2299 struct FieldValueAccessor
2301 template <
typename Record>
2302 decltype(
auto)
operator()(Record
const& record)
const
2304 return GetRecordMemberAt<I>(record).Value();
2310 template <std::
size_t I,
typename Record>
2311 auto MakeCreateColumnAccessor()
2313 using FieldType = RecordMemberTypeOf<I, Record>;
2314 if constexpr (IsBatchInsertColumn<FieldType>)
2315 return std::tuple<FieldValueAccessor<I>> {};
2317 return std::tuple<> {};
2321 template <std::
size_t I,
typename Record>
2322 auto MakeUpdateSetAccessor()
2324 using FieldType = RecordMemberTypeOf<I, Record>;
2325 if constexpr (IsBatchUpdateSetColumn<FieldType>)
2326 return std::tuple<FieldValueAccessor<I>> {};
2328 return std::tuple<> {};
2332 template <std::
size_t I,
typename Record>
2333 auto MakeUpdateWhereAccessor()
2335 using FieldType = RecordMemberTypeOf<I, Record>;
2336 if constexpr (IsBatchUpdateWhereColumn<FieldType>)
2337 return std::tuple<FieldValueAccessor<I>> {};
2339 return std::tuple<> {};
2343template <std::ranges::range Records>
2346 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
2347 "CreateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
2348 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
2349 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
2352 ZoneScopedN(
"DataMapper::CreateAll");
2353 ZoneTextObject(RecordTableName<Record>);
2355 if (std::ranges::empty(records))
2358 auto const rows = std::span<Record const> { std::ranges::data(records), std::ranges::size(records) };
2359 if constexpr (detail::HasAutoTimestampFields<Record>)
2360 InsertBatch(std::span<Record const> { CopyWithTimestamps<TimestampFill::Unset>(rows) });
2365template <
typename Record>
2366void DataMapper::InsertBatch(std::span<Record const> records)
2369 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
2370 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2371 if constexpr (detail::IsBatchInsertColumn<FieldType>)
2372 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2377 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
2378 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
2379 std::tuple_cat(detail::MakeCreateColumnAccessor<Is, Record>()...));
2380 }(std::make_index_sequence<RecordMemberCount<Record>> {});
2383template <DataMapperOptions QueryOptions,
typename Record>
2387 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
2389 HasAutoIncrementPrimaryKey<Record>
2390 || detail::CheckFieldProperty<[]<typename Field>() {
return detail::IsAutoAssignPrimaryKeyField<Field>::value; },
2392 "CreateCopyOf() needs a primary key the mapper generates (AutoAssign or ServerSideAutoIncrement). A "
2393 "record keyed by PrimaryKey::Manual members only has no key to give the copy - set one on a copy "
2394 "of your own and call Create() instead.");
2396 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
2398 return CreateInternal<PrimaryKeySource::Override, TimestampFill::All>(originalRecord, generatedKey);
2400 if constexpr (HasAutoIncrementPrimaryKey<Record>)
2401 return CreateInternal<PrimaryKeySource::Record, TimestampFill::All>(originalRecord);
2403 return CreateInternal<PrimaryKeySource::Override, TimestampFill::All>(originalRecord, RecordPrimaryKeyType<Record> {});
2406template <DataMapperOptions QueryOptions,
typename Record>
2409 static_assert(!std::is_const_v<Record>);
2412 ZoneScopedN(
"DataMapper::Create");
2413 ZoneTextObject(RecordTableName<Record>);
2415 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
2417 SetId(record, *generatedKey);
2420 if constexpr (detail::HasAutoTimestampFields<Record>)
2423 auto pk = CreateInternal<PrimaryKeySource::Record, TimestampFill::None>(record);
2425 if constexpr (HasAutoIncrementPrimaryKey<Record>)
2428 SetModifiedState<ModifiedState::NotModified>(record);
2430 if constexpr (QueryOptions.loadRelations)
2433 if constexpr (HasPrimaryKey<Record>)
2437template <
typename Record>
2442 bool modified =
false;
2444#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2445 auto constexpr ctx = std::meta::access_context::current();
2446 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2448 if constexpr (
requires { record.[:el:].IsModified(); })
2450 modified = modified || record.[:el:].IsModified();
2454 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
2455 if constexpr (
requires { field.IsModified(); })
2457 modified = modified || field.IsModified();
2465template <
typename Record>
2466 requires HasPrimaryKey<Record>
2471 ZoneScopedN(
"DataMapper::Update");
2472 ZoneTextObject(RecordTableName<Record>);
2475 StampTimestampsForUpdate(record);
2477 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2482 bool anyFieldModified =
false;
2484#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2485 auto constexpr ctx = std::meta::access_context::current();
2486 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2488 using FieldType =
typename[:std::meta::type_of(el):];
2491 if (record.[:el:].IsModified())
2493 query.Set(FieldNameOf<el>, SqlWildcard);
2494 anyFieldModified =
true;
2496 if constexpr (IsPrimaryKey<FieldType>)
2497 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2501 EnumerateRecordMembers(record, [&query, &anyFieldModified]<
size_t I,
typename FieldType>(FieldType
const& field) {
2508 if (field.IsModified())
2510 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2511 anyFieldModified =
true;
2513 if constexpr (IsPrimaryKey<MemberType>)
2514 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2520 if (!anyFieldModified)
2527#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2528 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2530 using FieldType =
typename[:std::meta::type_of(el):];
2534 if (record.[:el:].IsModified())
2541 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2543 using FieldType =
typename[:std::meta::type_of(el):];
2546 if constexpr (FieldType::IsPrimaryKey)
2558 if (field.IsModified())
2565 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2570 [[maybe_unused]]
auto cursor = _stmt.
Execute();
2572 SetModifiedState<ModifiedState::NotModified>(record);
2575template <std::ranges::range Records>
2578 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
2579 "UpdateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
2580 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
2581 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
2583 static_assert(HasPrimaryKey<Record>,
"UpdateAll requires a record type with a primary key");
2585 ZoneScopedN(
"DataMapper::UpdateAll");
2586 ZoneTextObject(RecordTableName<Record>);
2588 if (std::ranges::empty(records))
2591 auto const rows = std::span<Record const> { std::ranges::data(records), std::ranges::size(records) };
2592 if constexpr (detail::HasUpdatedAtTimestampFields<Record>)
2593 UpdateBatch(std::span<Record const> { CopyWithTimestamps<TimestampFill::UpdatedAt>(rows) });
2598template <
typename Record>
2599void DataMapper::UpdateBatch(std::span<Record const> records)
2602 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2603 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2604 if constexpr (detail::IsBatchUpdateSetColumn<FieldType>)
2605 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2607 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2608 if constexpr (detail::IsBatchUpdateWhereColumn<FieldType>)
2609 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2614 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
2615 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
2616 std::tuple_cat(detail::MakeUpdateSetAccessor<Is, Record>()...,
2617 detail::MakeUpdateWhereAccessor<Is, Record>()...));
2618 }(std::make_index_sequence<RecordMemberCount<Record>> {});
2621template <
typename Record>
2626 ZoneScopedN(
"DataMapper::Delete");
2627 ZoneTextObject(RecordTableName<Record>);
2629 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
2631#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2632 auto constexpr ctx = std::meta::access_context::current();
2633 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2635 using FieldType =
typename[:std::meta::type_of(el):];
2638 if constexpr (FieldType::IsPrimaryKey)
2639 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2643 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2644 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2650#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2652 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2654 using FieldType =
typename[:std::meta::type_of(el):];
2657 if constexpr (FieldType::IsPrimaryKey)
2666 [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
2667 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2672 auto cursor = _stmt.
Execute();
2677template <
typename Record,
DataMapperOptions QueryOptions,
typename... PrimaryKeyTypes>
2682 ZoneScopedN(
"DataMapper::QuerySingle(PK)");
2683 ZoneTextObject(RecordTableName<Record>);
2689 auto selectStarter = _connection.
Query(RecordTableName<Record>).
Select();
2693 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2696 if (queryBuilder ==
nullptr)
2697 queryBuilder = &selectStarter.
Field(FieldNameAt<I, Record>);
2699 queryBuilder->
Field(FieldNameAt<I, Record>);
2703 if constexpr (FieldType::IsPrimaryKey)
2704 std::ignore = queryBuilder->
Where(FieldNameAt<I, Record>, SqlWildcard);
2710 auto reader = _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
2716 auto resultRecord = std::optional<Record> { Record {} };
2719 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2721 if constexpr (QueryOptions.loadRelations)
2726 resultRecord.reset();
2729 return resultRecord;
2732template <
typename Record,
typename... Args>
2737 ZoneScopedN(
"DataMapper::QuerySingle(Builder)");
2738 ZoneTextObject(RecordTableName<Record>);
2741 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2745 auto const composedSql = selectQuery.
First().ToSql();
2746 ZoneTextObject(composedSql);
2748 auto reader = _stmt.
Execute(std::forward<Args>(args)...);
2750 auto resultRecord = std::optional<Record> { Record {} };
2752 return std::nullopt;
2755 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2757 return resultRecord;
2763template <
typename Record, DataMapperOptions QueryOptions,
typename... InputParameters>
2765 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
2767 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
2769 ZoneScopedN(
"DataMapper::Query(ComposedQuery)");
2770 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
2773template <
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2774std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
2776 ZoneScopedN(
"DataMapper::Query(string)");
2777 ZoneTextObject(sqlQueryString);
2779 auto result = std::vector<Record> {};
2780 if constexpr (std::same_as<Record, SqlVariantRow>)
2782 _stmt.
Prepare(sqlQueryString);
2787 auto& record = result.emplace_back();
2788 record.reserve(numResultColumns);
2789 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
2797 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2799 _stmt.
Prepare(sqlQueryString);
2800 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2804 auto& record = result.emplace_back();
2806 if (canSafelyBindOutputColumns)
2807 BindOutputColumns(record, reader);
2809 if (!reader.FetchRow())
2812 if (!canSafelyBindOutputColumns)
2813 detail::GetAllColumns(reader, record);
2819 for (
auto& record: result)
2821 SetModifiedState<ModifiedState::NotModified>(record);
2822 if constexpr (QueryOptions.loadRelations)
2830template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
2832std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
2834 using value_type = std::tuple<First, Second, Rest...>;
2835 auto result = std::vector<value_type> {};
2837 ZoneScopedN(
"DataMapper::Query(ComposedQuery -> tuple)");
2838 auto const tupleSql = selectQuery.ToSql();
2839 ZoneTextObject(tupleSql);
2841 auto reader = _stmt.
Execute();
2846 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
2849 if constexpr (I > 0)
2851 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
2852 ((Indices < I ? (offset += RecordColumnCount<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
2853 }(std::make_index_sequence<I> {});
2858 auto const BindElements = [&](
auto& record) {
2859 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2860 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2861 auto& element = std::get<I>(record);
2862 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2863 this->BindOutputColumns<TupleElement, offset>(element, reader);
2867 auto const GetElements = [&](
auto& record) {
2868 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2869 auto& element = std::get<I>(record);
2870 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2871 detail::GetAllColumns(reader, element, offset - 1);
2875 bool const canSafelyBindOutputColumns = [&]() {
2877 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2878 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2886 auto& record = result.emplace_back();
2888 if (canSafelyBindOutputColumns)
2889 BindElements(record);
2891 if (!reader.FetchRow())
2894 if (!canSafelyBindOutputColumns)
2895 GetElements(record);
2901 for (
auto& record: result)
2903 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2904 auto& element = std::get<I>(record);
2905 SetModifiedState<ModifiedState::NotModified>(element);
2906 if constexpr (QueryOptions.loadRelations)
2916template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2918 InputParameters&&... inputParameters)
2922 ZoneScopedN(
"DataMapper::Query(ComposedQuery, ElementMask)");
2923 auto const maskedSql = selectQuery.ToSql();
2924 ZoneTextObject(maskedSql);
2927 auto records = std::vector<Record> {};
2930 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2932 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2936 auto& record = records.emplace_back();
2938 if (canSafelyBindOutputColumns)
2939 BindOutputColumns<ElementMask>(record, reader);
2941 if (!reader.FetchRow())
2944 if (!canSafelyBindOutputColumns)
2945 detail::GetAllColumns<ElementMask>(reader, record);
2951 for (
auto& record: records)
2953 SetModifiedState<ModifiedState::NotModified>(record);
2954 if constexpr (QueryOptions.loadRelations)
2961template <DataMapper::ModifiedState state,
typename Record>
2964 static_assert(!std::is_const_v<Record>);
2968 if constexpr (
requires { field.SetModified(
false); })
2970 if constexpr (state == ModifiedState::Modified)
2971 field.SetModified(
true);
2973 field.SetModified(
false);
2978template <
typename Record,
typename Callable>
2979inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
2984 if constexpr (IsField<FieldType>)
2986 if constexpr (FieldType::IsPrimaryKey)
2988 return callable.template operator()<I, FieldType>(field);
2994template <
typename Record,
typename Callable>
2995inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
2997 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2999 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
3000 if constexpr (IsField<FieldType>)
3002 if constexpr (FieldType::IsPrimaryKey)
3004 return callable.template operator()<I, FieldType>();
3010template <
typename Record,
typename Callable>
3011inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
3013 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3015 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
3016 if constexpr (IsBelongsTo<FieldType>)
3018 return callable.template operator()<I, FieldType>();
3023template <
typename FieldType>
3024std::shared_ptr<typename FieldType::ReferencedRecord> DataMapper::LoadCompositeForeignKeyRecord(
3025 FieldType::OrderedValueType
const& keys)
3027 using ReferencedRecord = FieldType::ReferencedRecord;
3030 std::apply([
this](
auto const&... key) {
return this->
template QuerySingle<ReferencedRecord>(key...); }, keys);
3033 return std::make_shared<ReferencedRecord>(std::move(*loaded));
3036template <
typename Record,
typename FieldType>
3037void DataMapper::LoadCompositeForeignKey(Record
const& record, FieldType& field)
3039 using ReferencedRecord = FieldType::ReferencedRecord;
3041 ZoneScopedN(
"DataMapper::LoadCompositeForeignKey");
3042 ZoneTextObject(RecordTableName<ReferencedRecord>);
3048 auto loaded = LoadCompositeForeignKeyRecord<FieldType>(FieldType::OrderedValuesOf(record));
3056 std::format(
"Loading composite foreign key failed for {}", RecordTableName<ReferencedRecord>));
3060 field.EmplaceRecord(std::move(loaded));
3063template <
typename FieldType>
3064std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
3066 using ReferencedRecord = FieldType::ReferencedRecord;
3068 ZoneScopedN(
"DataMapper::LoadBelongsTo");
3069 ZoneTextObject(RecordTableName<ReferencedRecord>);
3071 std::optional<ReferencedRecord> record { std::nullopt };
3074 if constexpr (FieldType::IsOptional)
3075 if (!value.has_value())
3078#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3079 auto constexpr ctx = std::meta::access_context::current();
3080 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
3082 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
3083 if constexpr (IsField<BelongsToFieldType>)
3084 if constexpr (BelongsToFieldType::IsPrimaryKey)
3086 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
3087 record = std::move(result);
3090 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
3094 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
3095 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
3096 record = std::move(result);
3099 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
3105template <
typename Record,
typename OtherRecord, auto InverseSelector,
typename Callable>
3106void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
3108 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3109 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
3111 using FieldType = HasMany<OtherRecord, InverseSelector>;
3112 using ReferencedRecord = FieldType::ReferencedRecord;
3114 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
3115 auto query = BuildHasManySelectQuery<Record, ReferencedRecord, InverseSelector>();
3116 callback(query, primaryKeyField);
3120template <
typename OwnerRecord,
typename OtherRecord, auto InverseSelector>
3121SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
3123 return BuildRecordSelectQuery<OtherRecord>()
3124 .Where(InverseBelongsToFieldNameOf<OwnerRecord, OtherRecord, InverseSelector>, SqlWildcard)
3125 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
3128template <
typename Record,
typename OtherRecord, auto InverseSelector>
3129void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord, InverseSelector>& field)
3131 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3132 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
3134 ZoneScopedN(
"DataMapper::LoadHasMany");
3135 ZoneTextObject(RecordTableName<OtherRecord>);
3137 CallOnHasMany<Record, OtherRecord, InverseSelector>(
3138 record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
3139 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
3154 template <
typename T,
typename Callable>
3155 void ForEachChunk(std::span<T const> values,
size_t chunkSize, Callable
const& callable)
3157 auto const stride = chunkSize == 0 ? values.size() : chunkSize;
3158 for (
size_t offset = 0; offset < values.size(); offset += stride)
3159 callable(values.subspan(offset, (std::min) (stride, values.size() - offset)));
3163template <
typename Record>
3164SqlSelectQueryBuilder DataMapper::BuildRecordSelectQuery()
3166 return _connection.
Query(RecordTableName<Record>).
Select().
Build([](
auto& query) {
3167 EnumerateRecordMembers<Record>([&query]<
size_t I,
typename FieldType>() {
3168 if constexpr (FieldWithStorage<FieldType>)
3169 query.Field(FieldNameAt<I, Record>);
3174template <
typename Record,
size_t FieldIndex>
3175void DataMapper::PreloadRelation(std::span<Record* const> records)
3177 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
3179 static_assert(IsBelongsTo<FieldType> || IsHasMany<FieldType>,
3180 "Eager loading via With<>() is supported for BelongsTo and HasMany relations. "
3181 "HasOneThrough, HasManyThrough and CompositeForeignKey still load on demand.");
3187 if (records.empty())
3190 if constexpr (IsBelongsTo<FieldType>)
3191 PreloadBelongsTo<Record, FieldIndex>(records);
3192 else if constexpr (IsHasMany<FieldType>)
3193 PreloadHasMany<Record, FieldIndex>(records);
3196template <
typename Record,
size_t FieldIndex>
3197auto DataMapper::CollectRelationTargets(std::span<Record* const> records)
3199 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
3200 using TargetRecord = FieldType::ReferencedRecord;
3202 auto targets = std::vector<TargetRecord*> {};
3203 targets.reserve(records.size());
3205 for (
auto* record: records)
3207 auto& field = GetRecordMemberAt<FieldIndex>(*record);
3208 if constexpr (IsBelongsTo<FieldType>)
3212 if (
auto* target = field.LoadedRecord(); target !=
nullptr)
3213 targets.emplace_back(target);
3215 else if constexpr (IsHasMany<FieldType>)
3217 if (
auto* loaded = field.LoadedRecords(); loaded !=
nullptr)
3218 for (
auto& element: *loaded)
3219 targets.emplace_back(element.get());
3226template <
typename Record,
size_t FieldIndex,
auto... RestOfPath>
3227void DataMapper::PreloadRelationPath(std::span<Record* const> records)
3229 PreloadRelation<Record, FieldIndex>(records);
3231 if constexpr (
sizeof...(RestOfPath) > 0)
3233 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
3234 using TargetRecord = FieldType::ReferencedRecord;
3236 auto targets = CollectRelationTargets<Record, FieldIndex>(records);
3237 if (targets.empty())
3248 [&]<
auto NextField,
auto... Rest>() {
3249#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3250 static_assert(std::same_as<
typename[:std::meta::parent_of(NextField):], TargetRecord>,
3251 "Each relation named after the first in With<>() must be a member of the "
3252 "preceding relation's referenced record");
3254 static_assert(std::same_as<MemberClassType<std::remove_cv_t<
decltype(NextField)>>, TargetRecord>,
3255 "Each relation named after the first in With<>() must be a member of the "
3256 "preceding relation's referenced record");
3258 PreloadRelationPath<TargetRecord, MemberIndexOf<NextField>, Rest...>(std::span<TargetRecord* const> { targets });
3259 }.template operator()<RestOfPath...>();
3263template <
typename Record,
size_t Depth>
3264void DataMapper::PreloadAllRelations(std::span<Record* const> records)
3266 if constexpr (Depth == 0)
3272 if (records.empty())
3275 Reflection::template_for<0, RecordMemberCount<Record>>([&]<
auto I>() {
3276 using FieldType = RecordMemberTypeOf<I, Record>;
3279 if constexpr (IsBelongsTo<FieldType> || IsHasMany<FieldType>)
3281 PreloadRelation<Record, I>(records);
3283 if constexpr (Depth > 1)
3285 using TargetRecord = FieldType::ReferencedRecord;
3286 auto targets = CollectRelationTargets<Record, I>(records);
3287 if (!targets.empty())
3288 PreloadAllRelations<TargetRecord, Depth - 1>(std::span<TargetRecord* const> { targets });
3295template <
typename Record,
size_t FieldIndex>
3296void DataMapper::PreloadBelongsTo(std::span<Record* const> records)
3298 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
3299 using ReferencedRecord = FieldType::ReferencedRecord;
3300 using KeyType = RecordPrimaryKeyType<ReferencedRecord>;
3302 static_assert(HasPrimaryKey<ReferencedRecord>,
3303 "Eager loading a BelongsTo requires the referenced record to have a primary key");
3305 ZoneScopedN(
"DataMapper::PreloadBelongsTo");
3306 ZoneTextObject(RecordTableName<ReferencedRecord>);
3308 auto const primaryKeyOf = [](ReferencedRecord
const& record) {
3316 auto keys = std::vector<KeyType> {};
3317 keys.reserve(records.size());
3318 for (
auto const* record: records)
3320 auto const& field = GetRecordMemberAt<FieldIndex>(*record);
3323 if (field.LoadedRecord() !=
nullptr)
3326 auto const& value = field.Value();
3327 if constexpr (FieldType::IsOptional)
3329 if (value.has_value())
3330 keys.emplace_back(*value);
3333 keys.emplace_back(value);
3335 std::ranges::sort(keys);
3336 keys.erase(std::ranges::unique(keys).begin(), keys.end());
3340 auto loaded = std::vector<ReferencedRecord> {};
3341 loaded.reserve(keys.size());
3342 detail::ForEachChunk(std::span<KeyType const> { keys },
3344 [&](std::span<KeyType const> chunk) {
3345 auto selectQuery = BuildRecordSelectQuery<ReferencedRecord>().WhereIn(
3346 FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>, chunk);
3347 auto rows = Query<ReferencedRecord>(selectQuery.All());
3349 loaded.end(), std::make_move_iterator(rows.begin()), std::make_move_iterator(rows.end()));
3352 std::ranges::sort(loaded, {}, primaryKeyOf);
3354 for (
auto* record: records)
3356 auto& field = GetRecordMemberAt<FieldIndex>(*record);
3361 if (field.LoadedRecord() !=
nullptr)
3364 auto const& value = field.Value();
3365 if constexpr (FieldType::IsOptional)
3366 if (!value.has_value())
3369 auto const key = [&] {
3370 if constexpr (FieldType::IsOptional)
3376 auto const it = std::ranges::lower_bound(loaded, key, {}, primaryKeyOf);
3380 if (it != loaded.end() && primaryKeyOf(*it) == key)
3381 field.AdoptFetchedRecord(*it);
3385template <
typename Record,
size_t FieldIndex>
3386void DataMapper::PreloadHasMany(std::span<Record* const> records)
3388 using FieldType = RecordMemberTypeOf<FieldIndex, Record>;
3389 using ReferencedRecord = FieldType::ReferencedRecord;
3390 using ReferencedRecordList = FieldType::ReferencedRecordList;
3391 using KeyType = RecordPrimaryKeyType<Record>;
3393 static_assert(HasPrimaryKey<Record>,
"Eager loading a HasMany requires the owning record to have a primary key");
3395 ZoneScopedN(
"DataMapper::PreloadHasMany");
3396 ZoneTextObject(RecordTableName<ReferencedRecord>);
3400 constexpr size_t InverseIndex = InverseBelongsToIndexOf<Record, ReferencedRecord, FieldType::InverseSelector>;
3402 auto keys = std::vector<KeyType> {};
3403 keys.reserve(records.size());
3404 for (
auto* record: records)
3408 std::ranges::sort(keys);
3409 keys.erase(std::ranges::unique(keys).begin(), keys.end());
3415 auto buckets = std::vector<ReferencedRecordList> {};
3416 buckets.resize(keys.size());
3418 detail::ForEachChunk(
3419 std::span<KeyType const> { keys },
3421 [&](std::span<KeyType const> chunk) {
3423 BuildRecordSelectQuery<ReferencedRecord>()
3424 .WhereIn(InverseBelongsToFieldNameOf<Record, ReferencedRecord, FieldType::InverseSelector>, chunk)
3425 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
3426 for (
auto& child:
Query<ReferencedRecord>(selectQuery.All()))
3430 std::optional<KeyType>
const ownerKey = GetRecordMemberAt<InverseIndex>(child).Value();
3431 if (!ownerKey.has_value())
3434 auto const it = std::ranges::lower_bound(keys, *ownerKey);
3435 if (it != keys.end() && *it == *ownerKey)
3436 buckets[
static_cast<size_t>(std::distance(keys.begin(), it))].emplace_back(
3437 std::make_shared<ReferencedRecord>(std::move(child)));
3441 for (
auto* record: records)
3443 auto& field = GetRecordMemberAt<FieldIndex>(*record);
3446 if (field.LoadedRecords() !=
nullptr)
3458 auto& bucket = buckets[
static_cast<size_t>(std::distance(keys.begin(), it))];
3459 field.Emplace(ReferencedRecordList { bucket });
3463template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
3464SqlSelectQueryBuilder DataMapper::BuildHasOneThroughSelectQuery()
3466 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3467 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
3470 constexpr size_t ThroughToOwnerIndex = InverseBelongsToIndexOf<Record, ThroughRecord, OwnerSelector>;
3473 constexpr size_t ReferencedToThroughIndex = InverseBelongsToIndexOf<ThroughRecord, ReferencedRecord, ThroughSelector>;
3478 return _connection.
Query(RecordTableName<ReferencedRecord>)
3480 .
Build([&](
auto& query) {
3481 EnumerateRecordMembers<ReferencedRecord>([&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
3482 if constexpr (FieldWithStorage<ReferencedFieldType>)
3484 query.Field(SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
3485 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
3489 .InnerJoin(RecordTableName<ThroughRecord>,
3490 FieldNameAt<RecordPrimaryKeyIndex<ThroughRecord>, ThroughRecord>,
3491 FieldNameAt<ReferencedToThroughIndex, ReferencedRecord>)
3493 SqlQualifiedTableColumnName {
3494 RecordTableName<ThroughRecord>,
3495 FieldNameAt<ThroughToOwnerIndex, ThroughRecord>,
3500template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ThroughSelector>
3501void DataMapper::LoadHasOneThrough(Record& record,
3502 HasOneThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ThroughSelector>& field)
3504 using ThroughRecord = ThroughRecordOf<ThroughSpec>;
3506 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3507 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
3509 ZoneScopedN(
"DataMapper::LoadHasOneThrough");
3510 ZoneTextObject(RecordTableName<ReferencedRecord>);
3512 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
3514 BuildHasOneThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ThroughSelector>();
3515 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
3516 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
3520template <
typename ReferencedRecord,
3521 typename ThroughRecord,
3524 auto ThroughSelector,
3526std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue
const& pkValue)
3528 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
3530 auto query = BuildHasOneThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ThroughSelector>();
3532 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
3533 return std::make_shared<ReferencedRecord>(std::move(*link));
3538template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ReferencedSelector>
3539SqlSelectQueryBuilder DataMapper::BuildHasManyThroughSelectQuery()
3541 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3542 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
3545 constexpr size_t ThroughToOwnerIndex = InverseBelongsToIndexOf<Record, ThroughRecord, OwnerSelector>;
3548 constexpr size_t ThroughToReferencedIndex = InverseBelongsToIndexOf<ReferencedRecord, ThroughRecord, ReferencedSelector>;
3550 return _connection.
Query(RecordTableName<ReferencedRecord>)
3552 .
Build([&](
auto& query) {
3553 EnumerateRecordMembers<ReferencedRecord>([&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
3554 if constexpr (FieldWithStorage<ReferencedFieldType>)
3556 query.Field(SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
3557 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
3561 .InnerJoin(RecordTableName<ThroughRecord>,
3562 FieldNameAt<ThroughToReferencedIndex, ThroughRecord>,
3563 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
3564 FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord> })
3566 SqlQualifiedTableColumnName {
3567 RecordTableName<ThroughRecord>,
3568 FieldNameAt<ThroughToOwnerIndex, ThroughRecord>,
3573template <
typename ReferencedRecord,
3574 typename ThroughRecord,
3577 auto ReferencedSelector,
3579void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
3581 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3583 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
3585 BuildHasManyThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>();
3586 callback(query, primaryKeyField);
3590template <
typename ReferencedRecord,
3591 typename ThroughRecord,
3594 auto ReferencedSelector,
3597void DataMapper::CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback)
3599 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3602 BuildHasManyThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>();
3603 callback(query, pkValue);
3606template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ReferencedSelector>
3607void DataMapper::LoadHasManyThrough(Record& record,
3608 HasManyThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ReferencedSelector>& field)
3610 using ThroughRecord = ThroughRecordOf<ThroughSpec>;
3612 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
3614 ZoneScopedN(
"DataMapper::LoadHasManyThrough");
3615 ZoneTextObject(RecordTableName<ReferencedRecord>);
3617 CallOnHasManyThrough<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>(
3618 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
3619 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
3623template <
typename Record>
3626 static_assert(!std::is_const_v<Record>);
3629 ZoneScopedN(
"DataMapper::LoadRelations");
3630 ZoneTextObject(RecordTableName<Record>);
3632#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3633 constexpr auto ctx = std::meta::access_context::current();
3634 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
3636 using FieldType =
typename[:std::meta::type_of(el):];
3637 if constexpr (IsBelongsTo<FieldType>)
3639 auto& field = record.[:el:];
3640 field.AdoptFetchedRecord(LoadBelongsTo<FieldType>(field.Value()));
3642 else if constexpr (IsCompositeForeignKey<FieldType>)
3644 LoadCompositeForeignKey(record, record.[:el:]);
3646 else if constexpr (IsHasMany<FieldType>)
3648 LoadHasMany(record, record.[:el:]);
3650 else if constexpr (IsHasOneThrough<FieldType>)
3652 LoadHasOneThrough(record, record.[:el:]);
3654 else if constexpr (IsHasManyThrough<FieldType>)
3656 LoadHasManyThrough(record, record.[:el:]);
3661 if constexpr (IsBelongsTo<FieldType>)
3663 field.AdoptFetchedRecord(LoadBelongsTo<FieldType>(field.Value()));
3665 else if constexpr (IsCompositeForeignKey<FieldType>)
3667 LoadCompositeForeignKey(record, field);
3669 else if constexpr (IsHasMany<FieldType>)
3671 LoadHasMany(record, field);
3673 else if constexpr (IsHasOneThrough<FieldType>)
3675 LoadHasOneThrough(record, field);
3677 else if constexpr (IsHasManyThrough<FieldType>)
3679 LoadHasManyThrough(record, field);
3686template <
typename Record,
typename ValueType>
3687inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
3692#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3694 auto constexpr ctx = std::meta::access_context::current();
3695 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
3697 using FieldType =
typename[:std::meta::type_of(el):];
3698 if constexpr (IsField<FieldType>)
3701 if constexpr (FieldType::IsAutoAssignPrimaryKey || FieldType::IsAutoIncrementPrimaryKey)
3703 record.[:el:] = std::forward<ValueType>(
id);
3709 if constexpr (IsField<FieldType>)
3712 if constexpr (FieldType::IsAutoAssignPrimaryKey || FieldType::IsAutoIncrementPrimaryKey)
3714 field = std::forward<FieldType>(
id);
3722template <
typename Record,
size_t InitialOffset>
3723inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
3726 return BindOutputColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record, InitialOffset>(record,
3730template <
typename ElementMask,
typename Record,
size_t InitialOffset>
3731Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
3734 static_assert(!std::is_const_v<Record>);
3736#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3737 auto constexpr ctx = std::meta::access_context::current();
3738 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
3739 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
3741 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
3742 using FieldType =
typename[:std::meta::type_of(el):];
3743 if constexpr (IsField<FieldType>)
3747 else if constexpr (SqlOutputColumnBinder<FieldType>)
3753 EnumerateRecordMembers<ElementMask>(
3754 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
3755 if constexpr (IsField<Field>)
3759 else if constexpr (SqlOutputColumnBinder<Field>)
3768template <
typename Load>
3769auto DataMapper::RunRelationLoad(detail::RelationLoadSource& source, Load
const& load)
3770 -> RelationResult<std::invoke_result_t<Load const&, DataMapper&>>
3772 using Result = std::invoke_result_t<Load const&, DataMapper&>;
3773 return source.Borrow().and_then([&load](std::shared_ptr<DataMapper>
const& borrowed) -> RelationResult<Result> {
3776 if constexpr (std::is_void_v<Result>)
3782 return load(*borrowed);
3784 catch (std::exception
const& error)
3792template <
typename Each,
typename Stream>
3793RelationResult<void> DataMapper::RunRelationStream(detail::RelationLoadSource& source,
3795 Stream
const& stream)
3797 auto callbackError = std::exception_ptr {};
3798 auto const guarded = [&each, &callbackError](
auto const& row) {
3805 callbackError = std::current_exception();
3809 auto result = RunRelationLoad(source, [&](
DataMapper& dm) { stream(dm, guarded); });
3811 std::rethrow_exception(callbackError);
3815template <
typename Record,
typename QueryText,
typename Callable,
typename... InputParameters>
3816void DataMapper::StreamRecords(QueryText
const& query, Callable
const& each, InputParameters
const&... inputParameters)
3819 auto cursor = _stmt.
Execute(inputParameters...);
3821 auto record = Record {};
3822 BindOutputColumns(record, cursor);
3835 BindOutputColumns(record, cursor);
3839template <
typename Record>
3848 auto const& source = RelationLoadSourceForLoaders();
3850 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
3851 if constexpr (IsBelongsTo<FieldType>)
3853 using ReferencedRecord = FieldType::ReferencedRecord;
3854 field.SetAutoLoader(
typename FieldType::Loader {
3856 return RunRelationLoad(*source, [&](
DataMapper& dm) {
return dm.LoadBelongsTo<FieldType>(value); })
3860 return std::move(*loaded);
3865 if constexpr (IsCompositeForeignKey<FieldType>)
3867 using ReferencedRecord = FieldType::ReferencedRecord;
3883 field.SetAutoLoader(
typename FieldType::Loader {
3884 .loadReference = [source, keys = FieldType::OrderedValuesOf(record)]()
3886 return RunRelationLoad(*source,
3887 [&](
DataMapper& dm) {
return dm.LoadCompositeForeignKeyRecord<FieldType>(keys); })
3888 .and_then(NotFoundIfNull<ReferencedRecord>);
3892 if constexpr (IsHasMany<FieldType>)
3894 if constexpr (HasPrimaryKey<Record>)
3896 using ReferencedRecord = FieldType::ReferencedRecord;
3902 return RunRelationLoad(*source, [&](
DataMapper& dm) {
3904 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3905 dm._stmt.
Prepare(selectQuery.Count());
3914 return RunRelationLoad(*source, [&](
DataMapper& dm) {
3916 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3917 return detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pkValue));
3921 return RunRelationStream(*source, each, [&](
DataMapper& dm,
auto const& guarded) {
3923 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3924 dm.StreamRecords<ReferencedRecord>(selectQuery.All(), guarded, pkValue);
3930 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
3932 using ReferencedRecord = FieldType::ReferencedRecord;
3933 using ThroughRecord = FieldType::ThroughRecord;
3936 field.SetAutoLoader(
typename FieldType::Loader {
3937 .loadReference = [source, pkValue]() ->
RelationResult<std::shared_ptr<ReferencedRecord>> {
3938 return RunRelationLoad(*source,
3940 return dm.LoadHasOneThroughByPK<ReferencedRecord,
3943 FieldType::OwnerSelector,
3944 FieldType::ThroughSelector>(pkValue);
3946 .and_then(NotFoundIfNull<ReferencedRecord>);
3950 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
3952 using ReferencedRecord = FieldType::ReferencedRecord;
3953 using ThroughRecord = FieldType::ThroughRecord;
3956 field.SetAutoLoader(
typename FieldType::Loader {
3958 return RunRelationLoad(*source, [&](
DataMapper& dm) {
3960 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3963 FieldType::OwnerSelector,
3964 FieldType::ReferencedSelector>(
3966 dm._stmt.
Prepare(selectQuery.Count());
3975 return RunRelationLoad(*source, [&](
DataMapper& dm) {
3976 typename FieldType::ReferencedRecordList result;
3977 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3980 FieldType::OwnerSelector,
3981 FieldType::ReferencedSelector>(
3983 result = detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pk));
3989 return RunRelationStream(*source, each, [&](
DataMapper& dm,
auto const& guarded) {
3990 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3993 FieldType::OwnerSelector,
3994 FieldType::ReferencedSelector>(
3996 dm.StreamRecords<ReferencedRecord>(selectQuery.All(), guarded, pk);
4004#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
4005 constexpr auto ctx = std::meta::access_context::current();
4007 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
4008 constexpr auto localctx = std::meta::access_context::current();
4009 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
4010 using FieldType =
typename[:std::meta::type_of(members[I]):];
4011 callback.template operator()<I, FieldType>(record.[:members[I]:]);
4018template <
typename T>
4021 ZoneScopedN(
"DataMapper::Execute(string)");
4022 ZoneTextObject(sqlQueryString);
4028#include "../Async/DataMapperAsync.hpp"