Lightweight 0.20260303.0
Loading...
Searching...
No Matches
DataMapper.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../SqlConnection.hpp"
5#include "../SqlDataBinder.hpp"
6#include "../SqlLogger.hpp"
7#include "../SqlRealName.hpp"
8#include "../SqlStatement.hpp"
9#include "../Utils.hpp"
10#include "BelongsTo.hpp"
11#include "CollectDifferences.hpp"
12#include "Field.hpp"
13#include "HasMany.hpp"
14#include "HasManyThrough.hpp"
15#include "HasOneThrough.hpp"
16#include "QueryBuilders.hpp"
17#include "Record.hpp"
18
19#include <reflection-cpp/reflection.hpp>
20
21#include <cassert>
22#include <concepts>
23#include <memory>
24#include <tuple>
25#include <type_traits>
26#include <utility>
27
28namespace Lightweight
29{
30
31/// @defgroup DataMapper Data Mapper
32///
33/// @brief The data mapper is a high level API for mapping records to and from the database using high level C++ syntax.
34
35namespace detail
36{
37 // Converts a container of T to a container of std::shared_ptr<T>.
38 template <template <typename> class Allocator, template <typename, typename> class Container, typename Object>
39 auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
40 {
41 using SharedPtrRecord = std::shared_ptr<Object>;
42 auto sharedPtrContainer = Container<SharedPtrRecord, Allocator<SharedPtrRecord>> {};
43 for (auto& object: container)
44 sharedPtrContainer.emplace_back(std::make_shared<Object>(std::move(object)));
45 return sharedPtrContainer;
46 }
47} // namespace detail
48
49/// @brief Main API for mapping records to and from the database using high level C++ syntax.
50///
51/// A DataMapper instances operates on a single SQL connection and provides methods to
52/// create, read, update and delete records in the database.
53///
54/// @see Field, BelongsTo, HasMany, HasManyThrough, HasOneThrough
55/// @ingroup DataMapper
56///
57/// @code
58/// struct Person
59/// {
60/// Field<SqlGuid, PrimaryKey::AutoAssign> id;
61/// Field<SqlAnsiString<30>> name;
62/// Field<SqlAnsiString<40>> email;
63/// };
64///
65/// auto dm = DataMapper {};
66///
67/// // Create a new person record
68/// auto person = Person { .id = SqlGuid::Create(), .name = "John Doe", .email = "johnt@doe.com" };
69///
70/// // Create the record in the database and set the primary key on the record
71/// auto const personId = dm.Create(person);
72///
73/// // Query the person record from the database
74/// auto const queriedPerson = dm.Query<Person>(personId)
75/// .Where(FieldNameOf<&Person::id>, "=", personId)
76/// .First();
77///
78/// if (queriedPerson.has_value())
79/// std::println("Queried Person: {}", DataMapper::Inspect(queriedPerson.value()));
80///
81/// // Update the person record in the database
82/// person.email = "alt@doe.com";
83/// dm.Update(person);
84///
85/// // Delete the person record from the database
86/// dm.Delete(person);
87/// @endcode
89{
90 public:
91 /// Acquires a thread-local DataMapper instance that is safe for reuse within that thread.
92 LIGHTWEIGHT_API static DataMapper& AcquireThreadLocal();
93
94 /// Constructs a new data mapper, using the default connection.
96 _connection {},
97 _stmt { _connection }
98 {
99 }
100
101 /// Constructs a new data mapper, using the given connection.
102 explicit DataMapper(SqlConnection&& connection):
103 _connection { std::move(connection) },
104 _stmt { _connection }
105 {
106 }
107
108 /// Constructs a new data mapper, using the given connection string.
109 explicit DataMapper(std::optional<SqlConnectionString> connectionString):
110 _connection { std::move(connectionString) },
111 _stmt { _connection }
112 {
113 }
114
115 DataMapper(DataMapper const&) = delete;
116 DataMapper& operator=(DataMapper const&) = delete;
117
118 /// Move constructor.
119 DataMapper(DataMapper&& other) noexcept:
120 _connection(std::move(other._connection)),
121 _stmt(_connection)
122 {
123 other._stmt = SqlStatement(std::nullopt);
124 }
125
126 /// Move assignment operator.
127 DataMapper& operator=(DataMapper&& other) noexcept
128 {
129 if (this == &other)
130 return *this;
131
132 _connection = std::move(other._connection);
133 _stmt = SqlStatement(_connection);
134 other._stmt = SqlStatement(std::nullopt);
135
136 return *this;
138
139 ~DataMapper() = default;
141 /// Returns the connection reference used by this data mapper.
142 [[nodiscard]] SqlConnection const& Connection() const noexcept
143 {
144 return _connection;
145 }
146
147 /// Returns the mutable connection reference used by this data mapper.
148 [[nodiscard]] SqlConnection& Connection() noexcept
149 {
150 return _connection;
151 }
152
153#if defined(BUILD_TESTS)
154
155 [[nodiscard]] SqlStatement& Statement(this auto&& self) noexcept
156 {
157 return self._stmt;
158 }
159
160#endif
161
162 /// Constructs a human readable string representation of the given record.
163 template <typename Record>
164 static std::string Inspect(Record const& record);
165
166 /// Constructs a string list of SQL queries to create the table for the given record type.
167 template <typename Record>
168 std::vector<std::string> CreateTableString(SqlServerType serverType);
169
170 /// Constructs a string list of SQL queries to create the tables for the given record types.
171 template <typename FirstRecord, typename... MoreRecords>
172 std::vector<std::string> CreateTablesString(SqlServerType serverType);
173
174 /// Creates the table for the given record type.
175 template <typename Record>
176 void CreateTable();
177
178 /// Creates the tables for the given record types.
179 template <typename FirstRecord, typename... MoreRecords>
180 void CreateTables();
181
182 /// @brief Creates a new record in the database.
183 ///
184 /// The record is inserted into the database and the primary key is set on this record.
185 ///
186 /// @tparam QueryOptions A specialization of DataMapperOptions that controls query behavior.
187 /// @tparam Record The record type to insert.
188 /// @param record The record to insert. The primary key field is updated in-place after the insert.
189 /// @return The primary key of the newly created record.
190 template <DataMapperOptions QueryOptions = {}, typename Record>
191 RecordPrimaryKeyType<Record> Create(Record& record);
192
193 /// @brief Creates a new record in the database.
194 ///
195 /// @note This is a variation of the Create() method and does not update the record's primary key.
196 ///
197 /// @tparam Record The record type to insert.
198 /// @param record The record to insert. Unlike Create(), the primary key field is NOT updated in-place.
199 /// @return The primary key of the newly created record.
200 template <typename Record>
201 RecordPrimaryKeyType<Record> CreateExplicit(Record const& record);
202
203 /// @brief Creates a copy of an existing record in the database.
204 ///
205 /// This method is useful for duplicating a database record while assigning a new primary key.
206 /// All fields except primary key(s) are copied from the original record.
207 /// The primary key is automatically generated (auto-incremented or auto-assigned).
208 ///
209 /// @param originalRecord The record to copy.
210 /// @return The primary key of the newly created record.
211 template <DataMapperOptions QueryOptions = {}, typename Record>
212 [[nodiscard]] RecordPrimaryKeyType<Record> CreateCopyOf(Record const& originalRecord);
213
214 /// @brief Queries a single record (based on primary key) from the database.
215 ///
216 /// The primary key(s) are used to identify the record to load.
217 /// If the record is not found, std::nullopt is returned.
218 ///
219 /// @tparam Record The record type to query and materialize.
220 /// @tparam QueryOptions A specialization of DataMapperOptions that controls query behavior,
221 /// such as whether related records should be auto-loaded. For example,
222 /// set the relation loading option to false to disable auto-loading of
223 /// relations when reading a single record.
224 /// @tparam PrimaryKeyTypes The type(s) of the primary key value(s) used to look up the record.
225 /// @param primaryKeys The primary key value(s) identifying the record to load.
226 /// @return An initialized Record if found; otherwise std::nullopt.
227 ///
228 /// @code
229 /// // Example: disable auto-loading of relations when querying a single record
230 /// auto result = dataMapper
231 /// .QuerySingle<MyRecord, DataMapperOptions{ .loadRelations = false }>(primaryKeyValue);
232 /// if (result)
233 /// {
234 /// // use *result; relations have not been auto-loaded
235 /// }
236 /// @endcode
237 template <typename Record, DataMapperOptions QueryOptions = {}, typename... PrimaryKeyTypes>
238 std::optional<Record> QuerySingle(PrimaryKeyTypes&&... primaryKeys);
239
240 /// Queries multiple records from the database, based on the given query.
241 ///
242 /// @tparam Record The record type to query and materialize.
243 /// @tparam QueryOptions A specialization of DataMapperOptions that controls query behavior.
244 /// @tparam InputParameters The types of the input parameters to bind before executing the query.
245 /// @param selectQuery The composed SQL select query to execute.
246 /// @param inputParameters Zero or more values to bind as positional parameters in the query.
247 /// @return A vector of records populated from the query results.
248 template <typename Record, DataMapperOptions QueryOptions = {}, typename... InputParameters>
249 std::vector<Record> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters);
250
251 /// Queries multiple records from the database, based on the given query.
252 ///
253 /// @param sqlQueryString The SQL query string to execute.
254 /// @param inputParameters The input parameters for the query to be bound before executing.
255 /// @return A vector of records of the given type that were found via the query.
256 ///
257 /// example:
258 /// @code
259 /// struct Person
260 /// {
261 /// int id;
262 /// std::string name;
263 /// std::string email;
264 /// std::string phone;
265 /// std::string address;
266 /// std::string city;
267 /// std::string country;
268 /// };
269 ///
270 /// void example(DataMapper& dm)
271 /// {
272 /// auto const sqlQueryString = R"(SELECT * FROM "Person" WHERE "city" = ? AND "country" = ?)";
273 /// auto const records = dm.Query<Person>(sqlQueryString, "Berlin", "Germany");
274 /// for (auto const& record: records)
275 /// {
276 /// std::println("Person: {}", DataMapper::Inspect(record));
277 /// }
278 /// }
279 /// @endcode
280 template <typename Record, DataMapperOptions QueryOptions = {}, typename... InputParameters>
281 std::vector<Record> Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
282
283 /// Queries records from the database, based on the given query and can be used to retrieve only part of the record
284 /// by specifying the ElementMask.
285 ///
286 /// @tparam ElementMask A SqlElements<Idx...> specialization specifying the zero-based field indices to populate.
287 /// @tparam Record The record type to query and materialize.
288 /// @tparam QueryOptions A specialization of DataMapperOptions that controls query behavior.
289 /// @tparam InputParameters The types of the input parameters to bind before executing the query.
290 /// @param selectQuery The composed SQL select query to execute. Only the columns listed in the SELECT clause
291 /// are bound; the remaining fields of Record are left at their default values.
292 /// @param inputParameters Zero or more values to bind as positional parameters in the query.
293 /// @return A vector of partially populated records; only fields at the specified indices are filled in.
294 ///
295 /// @code
296 ///
297 /// struct Person
298 /// {
299 /// Field<int> id;
300 /// Field<std::string> name; // index 1
301 /// Field<std::string> email;
302 /// Field<std::string> phone;
303 /// Field<std::string> address;
304 /// Field<std::string> city; // index 5
305 /// Field<std::string> country;
306 /// };
307 ///
308 /// void example(DataMapper& dm)
309 /// {
310 /// auto const query = dm.FromTable(RecordTableName<Person>)
311 /// .Select()
312 /// .Fields({ "name"sv, "city"sv })
313 /// .All();
314 /// auto const infos = dm.Query<SqlElements<1, 5>, Person>(query);
315 /// for (auto const& info : infos)
316 /// {
317 /// // only info.name and info.city are populated
318 /// }
319 /// }
320 /// @endcode
321 template <typename ElementMask, typename Record, DataMapperOptions QueryOptions = {}, typename... InputParameters>
322 std::vector<Record> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters);
323
324 /// Queries records of different types from the database, based on the given query.
325 /// User can constructed query that selects columns from the multiple tables
326 /// this function is used to get result of the query
327 ///
328 /// @tparam First The first record type to materialize from each result row.
329 /// @tparam Second The second record type to materialize from each result row.
330 /// @tparam Rest Zero or more additional record types to materialize from each result row.
331 /// @tparam QueryOptions A specialization of DataMapperOptions that controls query behavior.
332 /// @param selectQuery The composed SQL select query whose column list covers all fields of First, Second, and Rest.
333 /// @return A vector of tuples, each containing one instance of every requested record type per result row.
334 ///
335 /// @code
336 ///
337 /// struct JointA{};
338 /// struct JointB{};
339 /// struct JointC{};
340 ///
341 /// // the following query will construct statement to fetch all elements of JointA and JointC types
342 /// auto dm = DataMapper {};
343 /// auto const query = dm.FromTable(RecordTableName<JoinTestA>)
344 /// .Select()
345 /// .Fields<JointA, JointC>()
346 /// .InnerJoin<&JointB::a_id, &JointA::id>()
347 /// .InnerJoin<&JointC::id, &JointB::c_id>()
348 /// .All();
349 /// auto const records = dm.Query<JointA, JointC>(query);
350 /// for(const auto [elementA, elementC] : records)
351 /// {
352 /// // do something with elementA and elementC
353 /// }
354 /// @endcode
355 template <typename First, typename Second, typename... Rest, DataMapperOptions QueryOptions = {}>
356 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
357 std::vector<std::tuple<First, Second, Rest...>> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery);
358
359 /// Queries records of given Record type.
360 ///
361 /// The query builder can be used to further refine the query.
362 /// The query builder will execute the query when a method like All(), First(n), etc. is called.
363 ///
364 /// @tparam Record The record type to query and materialize.
365 /// @tparam QueryOptions A specialization of DataMapperOptions that controls query behavior.
366 /// @return A query builder for the given Record type.
367 ///
368 /// @code
369 /// auto const records = dm.Query<Person>()
370 /// .Where(FieldNameOf<&Person::is_active>, "=", true)
371 /// .All();
372 /// @endcode
373 template <typename Record, DataMapperOptions QueryOptions = {}>
375 {
376 std::string fields;
377 Reflection::EnumerateMembers<Record>([&fields]<size_t I, typename Field>() {
378 if (!fields.empty())
379 fields += ", ";
380 fields += '"';
381 fields += RecordTableName<Record>;
382 fields += "\".\"";
383 fields += FieldNameAt<I, Record>;
384 fields += '"';
385 });
386 return SqlAllFieldsQueryBuilder<Record, QueryOptions>(*this, std::move(fields));
387 }
388
389 /// Returns a SqlQueryBuilder using the default query formatter.
390 ///
391 /// This can be used to build custom queries separately from the DataMapper
392 /// and execute them via the DataMapper's typed Query() overloads that accept a SqlSelectQueryBuilder.
393 ///
394 /// @return A SqlQueryBuilder bound to the connection's query formatter.
396 {
397 return SqlQueryBuilder(_connection.QueryFormatter());
398 }
399
400 /// Updates the record in the database.
401 ///
402 /// Only fields that have been modified since the record was last loaded or saved are written.
403 /// Fields that were not changed are excluded from the UPDATE statement.
404 ///
405 /// @tparam Record The record type to update.
406 /// @param record The record to update. Only its modified fields are written to the database.
407 template <typename Record>
408 void Update(Record& record);
409
410 /// Deletes the record from the database.
411 ///
412 /// The record is identified by its primary key(s). The row is removed from the backing table.
413 ///
414 /// @tparam Record The record type to delete.
415 /// @param record The record to delete. Its primary key field(s) identify the row to remove.
416 /// @return The number of rows deleted (typically 1 if the record was found, 0 otherwise).
417 template <typename Record>
418 std::size_t Delete(Record const& record);
419
420 /// Constructs an SQL query builder for the given table name.
421 SqlQueryBuilder FromTable(std::string_view tableName)
422 {
423 return _connection.Query(tableName);
424 }
425
426 /// Checks if the record has any modified fields.
427 ///
428 /// @tparam Record The record type to inspect.
429 /// @param record The record to check.
430 /// @return True if at least one field has been modified since the record was last loaded or saved.
431 template <typename Record>
432 bool IsModified(Record const& record) const noexcept;
433
434 /// Enum to set the modified state of a record.
435 enum class ModifiedState : uint8_t
436 {
437 Modified,
438 NotModified
439 };
440
441 /// Sets the modified state of the record after receiving from the database.
442 /// This marks all fields as not modified.
443 ///
444 /// @tparam state The target modified state for all fields (Modified or NotModified).
445 /// @tparam Record The record type whose fields are to be updated.
446 /// @param record The record whose field modification flags are set to @p state.
447 template <ModifiedState state, typename Record>
448 void SetModifiedState(Record& record) noexcept;
449
450 /// Loads all direct relations to this record.
451 ///
452 /// @tparam Record The record type whose relation fields are to be populated.
453 /// @param record The record whose BelongsTo, HasMany, HasOneThrough, and HasManyThrough fields are loaded.
454 template <typename Record>
455 void LoadRelations(Record& record);
456
457 /// Configures the auto loading of relations for the given record.
458 ///
459 /// This means, that no explicit loading of relations is required.
460 /// The relations are automatically loaded when accessed.
461 ///
462 /// @tparam Record The record type to configure auto-loading for.
463 /// @param record The record whose relation fields are set up to load lazily on first access.
464 template <typename Record>
465 void ConfigureRelationAutoLoading(Record& record);
466
467 /// Helper function that allow to execute query directly via data mapper
468 /// and get scalar result without need to create SqlStatement manually
469 ///
470 /// @tparam T The scalar type of the expected result value.
471 /// @param sqlQueryString The SQL query string to execute.
472 /// @return The first column of the first result row cast to T, or std::nullopt if the query returns no rows.
473 template <typename T>
474 [[nodiscard]] std::optional<T> Execute(std::string_view sqlQueryString);
475
476 private:
477 /// @brief Queries a single record from the database based on the given query.
478 ///
479 /// @param selectQuery The SQL select query to execute.
480 /// @param args The input parameters for the query.
481 ///
482 /// @return The record if found, otherwise std::nullopt.
483 template <typename Record, typename... Args>
484 std::optional<Record> QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args);
485
486 template <typename Record, typename ValueType>
487 void SetId(Record& record, ValueType&& id);
488
489 template <typename Record, size_t InitialOffset = 1>
490 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
491
492 template <typename ElementMask, typename Record, size_t InitialOffset = 1>
493 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
494
495 template <typename FieldType>
496 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
497
498 template <size_t FieldIndex, typename Record, typename OtherRecord>
499 void LoadHasMany(Record& record, HasMany<OtherRecord>& field);
500
501 template <typename ReferencedRecord, typename ThroughRecord, typename Record>
502 void LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field);
503
504 template <typename ReferencedRecord, typename ThroughRecord, typename Record>
505 void LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field);
506
507 template <size_t FieldIndex, typename Record, typename OtherRecord, typename Callable>
508 void CallOnHasMany(Record& record, Callable const& callback);
509
510 template <size_t FieldIndex, typename OtherRecord>
511 SqlSelectQueryBuilder BuildHasManySelectQuery();
512
513 template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename Callable>
514 void CallOnHasManyThrough(Record& record, Callable const& callback);
515
516 template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename PKValue, typename Callable>
517 void CallOnHasManyThroughByPK(PKValue const& pkValue, Callable const& callback);
518
519 template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename PKValue>
520 std::shared_ptr<ReferencedRecord> LoadHasOneThroughByPK(PKValue const& pkValue);
521
522 enum class PrimaryKeySource : std::uint8_t
523 {
524 Record,
525 Override,
526 };
527
528 template <typename Record>
529 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record const& record);
530
531 template <PrimaryKeySource UsePkOverride, typename Record>
532 RecordPrimaryKeyType<Record> CreateInternal(
533 Record const& record,
534 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>, int, RecordPrimaryKeyType<Record>>>
535 pkOverride = std::nullopt);
536
537 SqlConnection _connection;
538 SqlStatement _stmt;
539};
540
541// ------------------------------------------------------------------------------------------------
542
543namespace detail
544{
545 template <typename FieldType>
546 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType) noexcept
547 {
548 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
549 return true;
550
551 // Test if we have some columns that might not be sufficient to store the result (e.g. string truncation),
552 // then don't call BindOutputColumn but SQLFetch to get the result, because
553 // regrowing previously bound columns is not supported in MS-SQL's ODBC driver, so it seems.
554 bool result = true;
555 if constexpr (IsField<FieldType>)
556 {
557 if constexpr (detail::OneOf<typename FieldType::ValueType,
558 std::string,
559 std::wstring,
560 std::u16string,
561 std::u32string,
562 SqlBinary>
563 || IsSqlDynamicString<typename FieldType::ValueType>
564 || IsSqlDynamicBinary<typename FieldType::ValueType>)
565 {
566 // Known types that MAY require growing due to truncation.
567 result = false;
568 }
569 }
570 return result;
571 }
572
573 template <DataMapperRecord Record>
574 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType) noexcept
575 {
576 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
577 return true;
578
579 bool result = true;
580 Reflection::EnumerateMembers<Record>([&result]<size_t I, typename Field>() {
581 if constexpr (IsField<Field>)
582 {
583 if constexpr (detail::OneOf<typename Field::ValueType,
584 std::string,
585 std::wstring,
586 std::u16string,
587 std::u32string,
588 SqlBinary>
589 || IsSqlDynamicString<typename Field::ValueType>
590 || IsSqlDynamicBinary<typename Field::ValueType>)
591 {
592 // Known types that MAY require growing due to truncation.
593 result = false;
594 }
595 }
596 });
597 return result;
598 }
599
600 template <typename Record>
601 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
602 {
603 Reflection::EnumerateMembers(record,
604 [reader = &reader, i = startOffset]<size_t I, typename Field>(Field& field) mutable {
605 if constexpr (IsField<Field>)
606 {
607 reader->BindOutputColumn(i++, &field.MutableValue());
608 }
609 else if constexpr (IsBelongsTo<Field>)
610 {
611 reader->BindOutputColumn(i++, &field.MutableValue());
612 }
613 else if constexpr (SqlOutputColumnBinder<Field>)
614 {
615 reader->BindOutputColumn(i++, &field);
616 }
617 });
618 }
619
620 template <typename Record>
621 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
622 {
623 BindAllOutputColumnsWithOffset(reader, record, 1);
624 }
625
626 // when we iterate over all columns using element mask
627 // indexes of the mask corresponds to the indexe of the field
628 // inside the structure, not inside the SQL result set
629 template <typename ElementMask, typename Record>
630 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
631 {
632 Reflection::EnumerateMembers<ElementMask>(
633 record, [reader = &reader, &indexFromQuery]<size_t I, typename Field>(Field& field) mutable {
634 ++indexFromQuery;
635 if constexpr (IsField<Field>)
636 {
637 if constexpr (Field::IsOptional)
638 field.MutableValue() =
639 reader->GetNullableColumn<typename Field::ValueType::value_type>(indexFromQuery);
640 else
641 field.MutableValue() = reader->GetColumn<typename Field::ValueType>(indexFromQuery);
642 }
643 else if constexpr (SqlGetColumnNativeType<Field>)
644 {
645 if constexpr (IsOptionalBelongsTo<Field>)
646 field = reader->GetNullableColumn<typename Field::BaseType>(indexFromQuery);
647 else
648 field = reader->GetColumn<Field>(indexFromQuery);
649 }
650 });
651 }
652
653 template <typename Record>
654 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
655 {
656 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(
657 reader, record, indexFromQuery);
658 }
659
660 template <typename FirstRecord, typename SecondRecord>
661 // TODO we need to remove this at some points and provide generic bindings for tuples
662 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
663 {
664 auto& [firstRecord, secondRecord] = record;
665
666 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<size_t I, typename Field>(Field& field) mutable {
667 if constexpr (IsField<Field>)
668 {
669 if constexpr (Field::IsOptional)
670 field.MutableValue() = reader->GetNullableColumn<typename Field::ValueType::value_type>(I + 1);
671 else
672 field.MutableValue() = reader->GetColumn<typename Field::ValueType>(I + 1);
673 }
674 else if constexpr (SqlGetColumnNativeType<Field>)
675 {
676 if constexpr (Field::IsOptional)
677 field = reader->GetNullableColumn<typename Field::BaseType>(I + 1);
678 else
679 field = reader->GetColumn<Field>(I + 1);
680 }
681 });
682
683 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<size_t I, typename Field>(Field& field) mutable {
684 if constexpr (IsField<Field>)
685 {
686 if constexpr (Field::IsOptional)
687 field.MutableValue() = reader->GetNullableColumn<typename Field::ValueType::value_type>(
688 Reflection::CountMembers<FirstRecord> + I + 1);
689 else
690 field.MutableValue() =
691 reader->GetColumn<typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
692 }
693 else if constexpr (SqlGetColumnNativeType<Field>)
694 {
695 if constexpr (Field::IsOptional)
696 field =
697 reader->GetNullableColumn<typename Field::BaseType>(Reflection::CountMembers<FirstRecord> + I + 1);
698 else
699 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
700 }
701 });
702 }
703
704 template <typename Record>
705 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
706 {
707 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
708
709 if (outputColumnsBound)
710 BindAllOutputColumns(reader, record);
711
712 if (!reader.FetchRow())
713 return false;
714
715 if (!outputColumnsBound)
716 GetAllColumns(reader, record);
717
718 return true;
719 }
720} // namespace detail
721
722template <typename Record, typename Derived, DataMapperOptions QueryOptions>
724 DataMapper& dm, std::string fields) noexcept:
725 _dm { dm },
726 _formatter { dm.Connection().QueryFormatter() },
727 _fields { std::move(fields) }
728{
729 this->_query.searchCondition.inputBindings = &_boundInputs;
730}
731
732template <typename Record, typename Derived, DataMapperOptions QueryOptions>
734{
735 auto stmt = SqlStatement { _dm.Connection() };
736 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
737 RecordTableName<Record>,
738 this->_query.searchCondition.tableAlias,
739 this->_query.searchCondition.tableJoins,
740 this->_query.searchCondition.condition));
741 auto reader = stmt.ExecuteWithVariants(_boundInputs);
742 if (reader.FetchRow())
743 return reader.GetColumn<size_t>(1);
744 return 0;
745}
746
747template <typename Record, typename Derived, DataMapperOptions QueryOptions>
749{
750 auto stmt = SqlStatement { _dm.Connection() };
751
752 auto const query = _formatter.SelectFirst(this->_query.distinct,
753 _fields,
754 RecordTableName<Record>,
755 this->_query.searchCondition.tableAlias,
756 this->_query.searchCondition.tableJoins,
757 this->_query.searchCondition.condition,
758 this->_query.orderBy,
759 1);
760
761 stmt.Prepare(query);
762 if (auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
763 return true;
764 return false;
765}
766
767template <typename Record, typename Derived, DataMapperOptions QueryOptions>
769{
770 auto stmt = SqlStatement { _dm.Connection() };
771
772 auto const query = _formatter.Delete(RecordTableName<Record>,
773 this->_query.searchCondition.tableAlias,
774 this->_query.searchCondition.tableJoins,
775 this->_query.searchCondition.condition);
776
777 stmt.Prepare(query);
778 stmt.Prepare(query);
779 [[maybe_unused]] auto cursor = stmt.ExecuteWithVariants(_boundInputs);
780}
781
782template <typename Record, typename Derived, DataMapperOptions QueryOptions>
784{
785
786 auto records = std::vector<Record> {};
787 auto stmt = SqlStatement { _dm.Connection() };
788 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
789 _fields,
790 RecordTableName<Record>,
791 this->_query.searchCondition.tableAlias,
792 this->_query.searchCondition.tableJoins,
793 this->_query.searchCondition.condition,
794 this->_query.orderBy,
795 this->_query.groupBy));
796 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
797 if constexpr (DataMapperRecord<Record>)
798 {
799 // This can be called when record type is not plain aggregate type
800 // but more complex tuple, like std::tuple<A, B>
801 // for now we do not unwrap this type and just skip auto-loading configuration
802 if constexpr (QueryOptions.loadRelations)
803 {
804 for (auto& record: records)
805 {
806 _dm.ConfigureRelationAutoLoading(record);
807 }
808 }
809 }
810 return records;
811}
812
813template <typename Record, typename Derived, DataMapperOptions QueryOptions>
814template <auto Field>
815#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
816 requires(is_aggregate_type(parent_of(Field)))
817#else
818 requires std::is_member_object_pointer_v<decltype(Field)>
819#endif
820auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::All() -> std::vector<ReferencedFieldTypeOf<Field>>
821{
822 using value_type = ReferencedFieldTypeOf<Field>;
823 auto result = std::vector<value_type> {};
824
825 auto stmt = SqlStatement { _dm.Connection() };
826 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
827 FullyQualifiedNamesOf<Field>.string_view(),
828 RecordTableName<Record>,
829 this->_query.searchCondition.tableAlias,
830 this->_query.searchCondition.tableJoins,
831 this->_query.searchCondition.condition,
832 this->_query.orderBy,
833 this->_query.groupBy));
834 auto reader = stmt.ExecuteWithVariants(_boundInputs);
835 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
836 while (true)
837 {
838 auto& value = result.emplace_back();
839 if (outputColumnsBound)
840 reader.BindOutputColumn(1, &value);
841
842 if (!reader.FetchRow())
843 {
844 result.pop_back();
845 break;
846 }
847
848 if (!outputColumnsBound)
849 value = reader.GetColumn<value_type>(1);
850 }
851
852 return result;
853}
854
855template <typename Record, typename Derived, DataMapperOptions QueryOptions>
856template <auto... ReferencedFields>
857 requires(sizeof...(ReferencedFields) >= 2)
858auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::All() -> std::vector<Record>
859{
860 auto records = std::vector<Record> {};
861 auto stmt = SqlStatement { _dm.Connection() };
862
863 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
864 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
865 RecordTableName<Record>,
866 this->_query.searchCondition.tableAlias,
867 this->_query.searchCondition.tableJoins,
868 this->_query.searchCondition.condition,
869 this->_query.orderBy,
870 this->_query.groupBy));
871
872 auto reader = stmt.ExecuteWithVariants(_boundInputs);
873 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
874 while (true)
875 {
876 auto& record = records.emplace_back();
877 if (outputColumnsBound)
878#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
879 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
880#else
881 reader.BindOutputColumns(&(record.*ReferencedFields)...);
882#endif
883 if (!reader.FetchRow())
884 {
885 records.pop_back();
886 break;
887 }
888 if (!outputColumnsBound)
889 {
890 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
891 detail::GetAllColumns<ElementMask>(reader, record);
892 }
893 }
894
895 return records;
896}
897
898template <typename Record, typename Derived, DataMapperOptions QueryOptions>
900{
901 std::optional<Record> record {};
902 auto stmt = SqlStatement { _dm.Connection() };
903 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
904 _fields,
905 RecordTableName<Record>,
906 this->_query.searchCondition.tableAlias,
907 this->_query.searchCondition.tableJoins,
908 this->_query.searchCondition.condition,
909 this->_query.orderBy,
910 1));
911 Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record);
912 if constexpr (QueryOptions.loadRelations)
913 {
914 if (record)
915 _dm.ConfigureRelationAutoLoading(record.value());
916 }
917 return record;
918}
919
920template <typename Record, typename Derived, DataMapperOptions QueryOptions>
921template <auto Field>
922#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
923 requires(is_aggregate_type(parent_of(Field)))
924#else
925 requires std::is_member_object_pointer_v<decltype(Field)>
926#endif
927auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::First() -> std::optional<ReferencedFieldTypeOf<Field>>
928{
929 auto constexpr count = 1;
930 auto stmt = SqlStatement { _dm.Connection() };
931 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
932 FullyQualifiedNamesOf<Field>.string_view(),
933 RecordTableName<Record>,
934 this->_query.searchCondition.tableAlias,
935 this->_query.searchCondition.tableJoins,
936 this->_query.searchCondition.condition,
937 this->_query.orderBy,
938 count));
939 if (auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
940 return reader.template GetColumn<ReferencedFieldTypeOf<Field>>(1);
941 return std::nullopt;
942}
943
944template <typename Record, typename Derived, DataMapperOptions QueryOptions>
945template <auto... ReferencedFields>
946 requires(sizeof...(ReferencedFields) >= 2)
947auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::First() -> std::optional<Record>
948{
949 auto optionalRecord = std::optional<Record> {};
950
951 auto stmt = SqlStatement { _dm.Connection() };
952 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
953 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
954 RecordTableName<Record>,
955 this->_query.searchCondition.tableAlias,
956 this->_query.searchCondition.tableJoins,
957 this->_query.searchCondition.condition,
958 this->_query.orderBy,
959 1));
960
961 auto& record = optionalRecord.emplace();
962 auto reader = stmt.ExecuteWithVariants(_boundInputs);
963 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
964 if (outputColumnsBound)
965#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
966 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
967#else
968 reader.BindOutputColumns(&(record.*ReferencedFields)...);
969#endif
970 if (!reader.FetchRow())
971 return std::nullopt;
972 if (!outputColumnsBound)
973 {
974 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
975 detail::GetAllColumns<ElementMask>(reader, record);
976 }
977
978 if constexpr (QueryOptions.loadRelations)
979 _dm.ConfigureRelationAutoLoading(record);
980
981 return optionalRecord;
982}
983
984template <typename Record, typename Derived, DataMapperOptions QueryOptions>
986{
987 auto records = std::vector<Record> {};
988 auto stmt = SqlStatement { _dm.Connection() };
989 records.reserve(n);
990 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
991 _fields,
992 RecordTableName<Record>,
993 this->_query.searchCondition.tableAlias,
994 this->_query.searchCondition.tableJoins,
995 this->_query.searchCondition.condition,
996 this->_query.orderBy,
997 n));
998 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
999
1000 if constexpr (QueryOptions.loadRelations)
1001 {
1002 for (auto& record: records)
1003 _dm.ConfigureRelationAutoLoading(record);
1004 }
1005 return records;
1006}
1007
1008template <typename Record, typename Derived, DataMapperOptions QueryOptions>
1009std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::Range(size_t offset, size_t limit)
1010{
1011 auto records = std::vector<Record> {};
1012 auto stmt = SqlStatement { _dm.Connection() };
1013 records.reserve(limit);
1014 stmt.Prepare(
1015 _formatter.SelectRange(this->_query.distinct,
1016 _fields,
1017 RecordTableName<Record>,
1018 this->_query.searchCondition.tableAlias,
1019 this->_query.searchCondition.tableJoins,
1020 this->_query.searchCondition.condition,
1021 !this->_query.orderBy.empty()
1022 ? this->_query.orderBy
1023 : std::format(" ORDER BY \"{}\" ASC", FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1024 this->_query.groupBy,
1025 offset,
1026 limit));
1027 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1028 if constexpr (QueryOptions.loadRelations)
1029 {
1030 for (auto& record: records)
1031 _dm.ConfigureRelationAutoLoading(record);
1032 }
1033 return records;
1034}
1035
1036template <typename Record, typename Derived, DataMapperOptions QueryOptions>
1037template <auto... ReferencedFields>
1038std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::Range(size_t offset, size_t limit)
1039{
1040 auto records = std::vector<Record> {};
1041 auto stmt = SqlStatement { _dm.Connection() };
1042 records.reserve(limit);
1043 stmt.Prepare(
1044 _formatter.SelectRange(this->_query.distinct,
1045 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
1046 RecordTableName<Record>,
1047 this->_query.searchCondition.tableAlias,
1048 this->_query.searchCondition.tableJoins,
1049 this->_query.searchCondition.condition,
1050 !this->_query.orderBy.empty()
1051 ? this->_query.orderBy
1052 : std::format(" ORDER BY \"{}\" ASC", FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1053 this->_query.groupBy,
1054 offset,
1055 limit));
1056
1057 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1058 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1059 while (true)
1060 {
1061 auto& record = records.emplace_back();
1062 if (outputColumnsBound)
1063#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1064 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1065#else
1066 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1067#endif
1068 if (!reader.FetchRow())
1069 {
1070 records.pop_back();
1071 break;
1072 }
1073 if (!outputColumnsBound)
1074 {
1075 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1076 detail::GetAllColumns<ElementMask>(reader, record);
1077 }
1078 }
1079
1080 if constexpr (QueryOptions.loadRelations)
1081 {
1082 for (auto& record: records)
1083 _dm.ConfigureRelationAutoLoading(record);
1084 }
1085
1086 return records;
1087}
1088
1089template <typename Record, typename Derived, DataMapperOptions QueryOptions>
1090template <auto... ReferencedFields>
1092{
1093 auto records = std::vector<Record> {};
1094 auto stmt = SqlStatement { _dm.Connection() };
1095 records.reserve(n);
1096 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1097 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
1098 RecordTableName<Record>,
1099 this->_query.searchCondition.tableAlias,
1100 this->_query.searchCondition.tableJoins,
1101 this->_query.searchCondition.condition,
1102 this->_query.orderBy,
1103 n));
1104
1105 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1106 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1107 while (true)
1108 {
1109 auto& record = records.emplace_back();
1110 if (outputColumnsBound)
1111#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1112 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1113#else
1114 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1115#endif
1116 if (!reader.FetchRow())
1117 {
1118 records.pop_back();
1119 break;
1120 }
1121 if (!outputColumnsBound)
1122 {
1123 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1124 detail::GetAllColumns<ElementMask>(reader, record);
1125 }
1126 }
1127
1128 if constexpr (QueryOptions.loadRelations)
1129 {
1130 for (auto& record: records)
1131 _dm.ConfigureRelationAutoLoading(record);
1132 }
1133
1134 return records;
1135}
1136
1137template <typename Record, DataMapperOptions QueryOptions>
1139 SqlResultCursor reader,
1140 std::vector<Record>* records)
1141{
1142 while (true)
1143 {
1144 Record& record = records->emplace_back();
1145 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1146 {
1147 records->pop_back();
1148 break;
1149 }
1150 }
1151}
1152
1153template <typename Record, DataMapperOptions QueryOptions>
1154void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1155 SqlResultCursor reader,
1156 std::optional<Record>* optionalRecord)
1157{
1158 Record& record = optionalRecord->emplace();
1159 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1160 optionalRecord->reset();
1161}
1162
1163template <typename FirstRecord, typename SecondRecord, DataMapperOptions QueryOptions>
1164void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1165 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1166{
1167 while (true)
1168 {
1169 auto& record = records->emplace_back();
1170 auto& [firstRecord, secondRecord] = record;
1171
1172 using FirstRecordType = std::remove_cvref_t<decltype(firstRecord)>;
1173 using SecondRecordType = std::remove_cvref_t<decltype(secondRecord)>;
1174
1175 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1176 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1177 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1178
1179 if (canSafelyBindAll)
1180 {
1181 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1182 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1183 }
1184
1185 if (!reader.FetchRow())
1186 {
1187 records->pop_back();
1188 break;
1189 }
1190
1191 if (!canSafelyBindAll)
1192 detail::GetAllColumns(reader, record);
1193 }
1194}
1195
1196template <typename Record>
1197std::string DataMapper::Inspect(Record const& record)
1198{
1199 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1200
1201 std::string str;
1202 Reflection::CallOnMembers(record, [&str]<typename Name, typename Value>(Name const& name, Value const& value) {
1203 if (!str.empty())
1204 str += '\n';
1205
1206 if constexpr (FieldWithStorage<Value>)
1207 {
1208 if constexpr (Value::IsOptional)
1209 {
1210 if (!value.Value().has_value())
1211 {
1212 str += std::format("{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1213 }
1214 else
1215 {
1216 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1217 }
1218 }
1219 else if constexpr (IsBelongsTo<Value>)
1220 {
1221 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1222 }
1223 else if constexpr (std::same_as<typename Value::ValueType, char>)
1224 {
1225 }
1226 else
1227 {
1228 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1229 }
1230 }
1231 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1232 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1233 });
1234 return "{\n" + std::move(str) + "\n}";
1235}
1236
1237template <typename Record>
1238std::vector<std::string> DataMapper::CreateTableString(SqlServerType serverType)
1239{
1240 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1241
1242 auto migration = SqlQueryBuilder(*SqlQueryFormatter::Get(serverType)).Migration();
1243 auto createTable = migration.CreateTable(RecordTableName<Record>);
1244 detail::PopulateCreateTableBuilder<Record>(createTable);
1245 return migration.GetPlan().ToSql();
1246}
1247
1248template <typename FirstRecord, typename... MoreRecords>
1249std::vector<std::string> DataMapper::CreateTablesString(SqlServerType serverType)
1250{
1251 std::vector<std::string> output;
1252 auto const append = [&output](auto const& sql) {
1253 output.insert(output.end(), sql.begin(), sql.end());
1254 };
1255 append(CreateTableString<FirstRecord>(serverType));
1256 (append(CreateTableString<MoreRecords>(serverType)), ...);
1257 return output;
1258}
1259
1260template <typename Record>
1262{
1263 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1264
1265 auto const sqlQueryStrings = CreateTableString<Record>(_connection.ServerType());
1266 for (auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
1267 auto cursor = _stmt.ExecuteDirect(sqlQueryString);
1268}
1269
1270template <typename FirstRecord, typename... MoreRecords>
1272{
1273 CreateTable<FirstRecord>();
1274 (CreateTable<MoreRecords>(), ...);
1275}
1276
1277template <typename Record>
1278std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record const& record)
1279{
1280 std::optional<RecordPrimaryKeyType<Record>> result;
1281 Reflection::EnumerateMembers(
1282 record, [this, &result]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1283 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1284 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1285 {
1286 using ValueType = typename PrimaryKeyType::ValueType;
1287 if constexpr (std::same_as<ValueType, SqlGuid>)
1288 {
1289 if (!primaryKeyField.Value())
1290 [&](auto& res) {
1291 res.emplace(SqlGuid::Create());
1292 }(result);
1293 }
1294 else if constexpr (requires { ValueType {} + 1; })
1295 {
1296 if (primaryKeyField.Value() == ValueType {})
1297 {
1298 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1299 std::format(R"sql(SELECT MAX("{}") FROM "{}")sql",
1300 FieldNameAt<PrimaryKeyIndex, Record>,
1301 RecordTableName<Record>));
1302 result = maxId.value_or(ValueType {}) + 1;
1303 }
1304 }
1305 }
1306 });
1307 return result;
1308}
1309
1310template <DataMapper::PrimaryKeySource UsePkOverride, typename Record>
1311RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
1312 Record const& record,
1313 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>, int, RecordPrimaryKeyType<Record>>>
1314 pkOverride)
1315{
1316 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1317
1318 auto query = _connection.Query(RecordTableName<Record>).Insert(nullptr);
1319
1320#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1321 constexpr auto ctx = std::meta::access_context::current();
1322 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1323 {
1324 using FieldType = typename[:std::meta::type_of(el):];
1325 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1326 query.Set(FieldNameOf<el>, SqlWildcard);
1327 }
1328#else
1329 Reflection::EnumerateMembers(record, [&query]<auto I, typename FieldType>(FieldType const& /*field*/) {
1330 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1331 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1332 });
1333#endif
1334
1335 _stmt.Prepare(query);
1336
1337#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1338 int i = 1;
1339 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1340 {
1341 using FieldType = typename[:std::meta::type_of(el):];
1342 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1343 {
1344 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1345 _stmt.BindInputParameter(i++, *pkOverride, std::meta::identifier_of(el));
1346 else
1347 _stmt.BindInputParameter(i++, record.[:el:], std::meta::identifier_of(el));
1348 }
1349 }
1350#else
1351 Reflection::CallOnMembers(record,
1352 [this, &pkOverride, i = SQLSMALLINT { 1 }]<typename Name, typename FieldType>(
1353 Name const& name, FieldType const& field) mutable {
1354 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1355 {
1356 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1357 _stmt.BindInputParameter(i++, *pkOverride, name);
1358 else
1359 _stmt.BindInputParameter(i++, field, name);
1360 }
1361 });
1362#endif
1363 [[maybe_unused]] auto cursor = _stmt.Execute();
1364
1365 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1366 return { _stmt.LastInsertId(RecordTableName<Record>) };
1367 else if constexpr (HasPrimaryKey<Record>)
1368 {
1369 if constexpr (UsePkOverride == PrimaryKeySource::Override)
1370 return *pkOverride; // NOLINT(bugprone-unchecked-optional-access)
1371 else
1372 return RecordPrimaryKeyOf(record).Value();
1373 }
1374
1375 return {};
1376}
1377
1378template <typename Record>
1379RecordPrimaryKeyType<Record> DataMapper::CreateExplicit(Record const& record)
1380{
1381 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1382 return CreateInternal<PrimaryKeySource::Record>(record);
1383}
1384
1385template <DataMapperOptions QueryOptions, typename Record>
1386RecordPrimaryKeyType<Record> DataMapper::CreateCopyOf(Record const& originalRecord)
1387{
1388 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1389 static_assert(HasPrimaryKey<Record>, "CreateCopyOf requires a record type with a primary key");
1390
1391 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
1392 if (generatedKey)
1393 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
1394
1395 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1396 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
1397
1398 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
1399}
1400
1401template <DataMapperOptions QueryOptions, typename Record>
1402RecordPrimaryKeyType<Record> DataMapper::Create(Record& record)
1403{
1404 static_assert(!std::is_const_v<Record>);
1405 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1406
1407 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
1408 if (generatedKey)
1409 SetId(record, *generatedKey);
1410
1411 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
1412
1413 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1414 SetId(record, pk);
1415
1416 SetModifiedState<ModifiedState::NotModified>(record);
1417
1418 if constexpr (QueryOptions.loadRelations)
1420
1421 if constexpr (HasPrimaryKey<Record>)
1422 return GetPrimaryKeyField(record);
1423}
1424
1425template <typename Record>
1426bool DataMapper::IsModified(Record const& record) const noexcept
1427{
1428 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1429
1430 bool modified = false;
1431
1432#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1433 auto constexpr ctx = std::meta::access_context::current();
1434 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1435 {
1436 if constexpr (requires { record.[:el:].IsModified(); })
1437 {
1438 modified = modified || record.[:el:].IsModified();
1439 }
1440 }
1441#else
1442 Reflection::CallOnMembers(record, [&modified](auto const& /*name*/, auto const& field) {
1443 if constexpr (requires { field.IsModified(); })
1444 {
1445 modified = modified || field.IsModified();
1446 }
1447 });
1448#endif
1449
1450 return modified;
1451}
1452
1453template <typename Record>
1454void DataMapper::Update(Record& record)
1455{
1456 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1457
1458 auto query = _connection.Query(RecordTableName<Record>).Update();
1459
1460#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1461 auto constexpr ctx = std::meta::access_context::current();
1462 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1463 {
1464 using FieldType = typename[:std::meta::type_of(el):];
1465 if constexpr (FieldWithStorage<FieldType>)
1466 {
1467 if (record.[:el:].IsModified())
1468 query.Set(FieldNameOf<el>, SqlWildcard);
1469 if constexpr (IsPrimaryKey<FieldType>)
1470 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1471 }
1472 }
1473#else
1474 Reflection::CallOnMembersWithoutName(record, [&query]<size_t I, typename FieldType>(FieldType const& field) {
1475 if (field.IsModified())
1476 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1477 // for some reason compiler do not want to properly deduce FieldType, so here we
1478 // directly infer the type from the Record type and index
1479 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1480 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1481 });
1482#endif
1483 _stmt.Prepare(query);
1484
1485 SQLSMALLINT i = 1;
1486
1487#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1488 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1489 {
1490 if (record.[:el:].IsModified())
1491 {
1492 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1493 }
1494 }
1495
1496 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1497 {
1498 using FieldType = typename[:std::meta::type_of(el):];
1499 if constexpr (FieldType::IsPrimaryKey)
1500 {
1501 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1502 }
1503 }
1504#else
1505 // Bind the SET clause
1506 Reflection::CallOnMembersWithoutName(record, [this, &i]<size_t I, typename FieldType>(FieldType const& field) {
1507 if (field.IsModified())
1508 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1509 });
1510
1511 // Bind the WHERE clause
1512 Reflection::CallOnMembersWithoutName(record, [this, &i]<size_t I, typename FieldType>(FieldType const& field) {
1513 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1514 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1515 });
1516#endif
1517
1518 [[maybe_unused]] auto cursor = _stmt.Execute();
1519
1520 SetModifiedState<ModifiedState::NotModified>(record);
1521}
1522
1523template <typename Record>
1524std::size_t DataMapper::Delete(Record const& record)
1525{
1526 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1527
1528 auto query = _connection.Query(RecordTableName<Record>).Delete();
1529
1530#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1531 auto constexpr ctx = std::meta::access_context::current();
1532 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1533 {
1534 using FieldType = typename[:std::meta::type_of(el):];
1535 if constexpr (FieldType::IsPrimaryKey)
1536 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1537 }
1538#else
1539 Reflection::CallOnMembersWithoutName(record, [&query]<size_t I, typename FieldType>(FieldType const& /*field*/) {
1540 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1541 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1542 });
1543#endif
1544
1545 _stmt.Prepare(query);
1546
1547#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1548 SQLSMALLINT i = 1;
1549 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1550 {
1551 using FieldType = typename[:std::meta::type_of(el):];
1552 if constexpr (FieldType::IsPrimaryKey)
1553 {
1554 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1555 }
1556 }
1557#else
1558 // Bind the WHERE clause
1559 Reflection::CallOnMembersWithoutName(
1560 record, [this, i = SQLSMALLINT { 1 }]<size_t I, typename FieldType>(FieldType const& field) mutable {
1561 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1562 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1563 });
1564#endif
1565
1566 auto cursor = _stmt.Execute();
1567
1568 return cursor.NumRowsAffected();
1569}
1570
1571template <typename Record, DataMapperOptions QueryOptions, typename... PrimaryKeyTypes>
1572std::optional<Record> DataMapper::QuerySingle(PrimaryKeyTypes&&... primaryKeys)
1573{
1574 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1575
1576 auto queryBuilder = _connection.Query(RecordTableName<Record>).Select();
1577
1578 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1579 if constexpr (FieldWithStorage<FieldType>)
1580 {
1581 queryBuilder.Field(FieldNameAt<I, Record>);
1582
1583 if constexpr (FieldType::IsPrimaryKey)
1584 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1585 }
1586 });
1587
1588 _stmt.Prepare(queryBuilder.First());
1589 auto reader = _stmt.Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1590
1591 auto resultRecord = std::optional<Record> { Record {} };
1592 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
1593 return std::nullopt;
1594
1595 if (resultRecord)
1596 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1597
1598 if constexpr (QueryOptions.loadRelations)
1599 {
1600 if (resultRecord)
1601 ConfigureRelationAutoLoading(*resultRecord);
1602 }
1603
1604 return resultRecord;
1605}
1606
1607template <typename Record, typename... Args>
1608std::optional<Record> DataMapper::QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args)
1609{
1610 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1611
1612 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1613 if constexpr (FieldWithStorage<FieldType>)
1614 selectQuery.Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
1615 });
1616 _stmt.Prepare(selectQuery.First().ToSql());
1617 auto reader = _stmt.Execute(std::forward<Args>(args)...);
1618
1619 auto resultRecord = std::optional<Record> { Record {} };
1620 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
1621 return std::nullopt;
1622
1623 if (resultRecord)
1624 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
1625
1626 return resultRecord;
1627}
1628
1629// TODO: Provide Query(QueryBuilder, ...) method variant
1630
1631/// Queries multiple records from the database using a composed query and optional input parameters.
1632template <typename Record, DataMapperOptions QueryOptions, typename... InputParameters>
1633inline LIGHTWEIGHT_FORCE_INLINE std::vector<Record> DataMapper::Query(
1634 SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters)
1635{
1636 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>, "Record must satisfy DataMapperRecord");
1637
1638 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1639}
1640
1641template <typename Record, DataMapperOptions QueryOptions, typename... InputParameters>
1642std::vector<Record> DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1643{
1644 auto result = std::vector<Record> {};
1645 if constexpr (std::same_as<Record, SqlVariantRow>)
1646 {
1647 _stmt.Prepare(sqlQueryString);
1648 SqlResultCursor cursor = _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1649 size_t const numResultColumns = cursor.NumColumnsAffected();
1650 while (cursor.FetchRow())
1651 {
1652 auto& record = result.emplace_back();
1653 record.reserve(numResultColumns);
1654 for (auto const i: std::views::iota(1U, numResultColumns + 1))
1655 record.emplace_back(cursor.GetColumn<SqlVariant>(static_cast<SQLUSMALLINT>(i)));
1656 }
1657 }
1658 else
1659 {
1660 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1661
1662 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
1663
1664 _stmt.Prepare(sqlQueryString);
1665 auto reader = _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1666
1667 for (;;)
1668 {
1669 auto& record = result.emplace_back();
1670
1671 if (canSafelyBindOutputColumns)
1672 BindOutputColumns(record, reader);
1673
1674 if (!reader.FetchRow())
1675 break;
1676
1677 if (!canSafelyBindOutputColumns)
1678 detail::GetAllColumns(reader, record);
1679 }
1680
1681 // Drop the last record, which we failed to fetch (End of result set).
1682 result.pop_back();
1683
1684 for (auto& record: result)
1685 {
1686 SetModifiedState<ModifiedState::NotModified>(record);
1687 if constexpr (QueryOptions.loadRelations)
1689 }
1690 }
1691
1692 return result;
1693}
1694
1695template <typename First, typename Second, typename... Rest, DataMapperOptions QueryOptions>
1696 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
1697std::vector<std::tuple<First, Second, Rest...>> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery)
1698{
1699 using value_type = std::tuple<First, Second, Rest...>;
1700 auto result = std::vector<value_type> {};
1701
1702 _stmt.Prepare(selectQuery.ToSql());
1703 auto reader = _stmt.Execute();
1704
1705 constexpr auto calculateOffset = []<size_t I, typename Tuple>() {
1706 size_t offset = 1;
1707
1708 if constexpr (I > 0)
1709 {
1710 [&]<size_t... Indices>(std::index_sequence<Indices...>) {
1711 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1712 }(std::make_index_sequence<I> {});
1713 }
1714 return offset;
1715 };
1716
1717 auto const BindElements = [&](auto& record) {
1718 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
1719 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1720 auto& element = std::get<I>(record);
1721 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1722 this->BindOutputColumns<TupleElement, offset>(element, reader);
1723 });
1724 };
1725
1726 auto const GetElements = [&](auto& record) {
1727 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
1728 auto& element = std::get<I>(record);
1729 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1730 detail::GetAllColumns(reader, element, offset - 1);
1731 });
1732 };
1733
1734 bool const canSafelyBindOutputColumns = [&]() {
1735 bool result = true;
1736 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
1737 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1738 result &= detail::CanSafelyBindOutputColumns<TupleElement>(_stmt.Connection().ServerType());
1739 });
1740 return result;
1741 }();
1742
1743 for (;;)
1744 {
1745 auto& record = result.emplace_back();
1746
1747 if (canSafelyBindOutputColumns)
1748 BindElements(record);
1749
1750 if (!reader.FetchRow())
1751 break;
1752
1753 if (!canSafelyBindOutputColumns)
1754 GetElements(record);
1755 }
1756
1757 // Drop the last record, which we failed to fetch (End of result set).
1758 result.pop_back();
1759
1760 for (auto& record: result)
1761 {
1762 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
1763 auto& element = std::get<I>(record);
1764 SetModifiedState<ModifiedState::NotModified>(element);
1765 if constexpr (QueryOptions.loadRelations)
1766 {
1768 }
1769 });
1770 }
1771
1772 return result;
1773}
1774
1775template <typename ElementMask, typename Record, DataMapperOptions QueryOptions, typename... InputParameters>
1776std::vector<Record> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery,
1777 InputParameters&&... inputParameters)
1778{
1779 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1780
1781 _stmt.Prepare(selectQuery.ToSql());
1782
1783 auto records = std::vector<Record> {};
1784
1785 // TODO: We could optimize this further by only considering ElementMask fields in Record.
1786 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
1787
1788 auto reader = _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1789
1790 for (;;)
1791 {
1792 auto& record = records.emplace_back();
1793
1794 if (canSafelyBindOutputColumns)
1795 BindOutputColumns<ElementMask>(record, reader);
1796
1797 if (!reader.FetchRow())
1798 break;
1799
1800 if (!canSafelyBindOutputColumns)
1801 detail::GetAllColumns<ElementMask>(reader, record);
1802 }
1803
1804 // Drop the last record, which we failed to fetch (End of result set).
1805 records.pop_back();
1806
1807 for (auto& record: records)
1808 {
1809 SetModifiedState<ModifiedState::NotModified>(record);
1810 if constexpr (QueryOptions.loadRelations)
1812 }
1813
1814 return records;
1815}
1816
1817template <DataMapper::ModifiedState state, typename Record>
1818void DataMapper::SetModifiedState(Record& record) noexcept
1819{
1820 static_assert(!std::is_const_v<Record>);
1821 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1822
1823 Reflection::EnumerateMembers(record, []<size_t I, typename FieldType>(FieldType& field) {
1824 if constexpr (requires { field.SetModified(false); })
1825 {
1826 if constexpr (state == ModifiedState::Modified)
1827 field.SetModified(true);
1828 else
1829 field.SetModified(false);
1830 }
1831 });
1832}
1833
1834template <typename Record, typename Callable>
1835inline LIGHTWEIGHT_FORCE_INLINE void CallOnPrimaryKey(Record& record, Callable const& callable)
1836{
1837 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1838
1839 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
1840 if constexpr (IsField<FieldType>)
1841 {
1842 if constexpr (FieldType::IsPrimaryKey)
1843 {
1844 return callable.template operator()<I, FieldType>(field);
1845 }
1846 }
1847 });
1848}
1849
1850template <typename Record, typename Callable>
1851inline LIGHTWEIGHT_FORCE_INLINE void CallOnPrimaryKey(Callable const& callable)
1852{
1853 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1854
1855 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1856 if constexpr (IsField<FieldType>)
1857 {
1858 if constexpr (FieldType::IsPrimaryKey)
1859 {
1860 return callable.template operator()<I, FieldType>();
1861 }
1862 }
1863 });
1864}
1865
1866template <typename Record, typename Callable>
1867inline LIGHTWEIGHT_FORCE_INLINE void CallOnBelongsTo(Callable const& callable)
1868{
1869 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1870
1871 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1872 if constexpr (IsBelongsTo<FieldType>)
1873 {
1874 return callable.template operator()<I, FieldType>();
1875 }
1876 });
1877}
1878
1879template <typename FieldType>
1880std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1881{
1882 using ReferencedRecord = FieldType::ReferencedRecord;
1883
1884 std::optional<ReferencedRecord> record { std::nullopt };
1885
1886#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1887 auto constexpr ctx = std::meta::access_context::current();
1888 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1889 {
1890 using BelongsToFieldType = typename[:std::meta::type_of(el):];
1891 if constexpr (IsField<BelongsToFieldType>)
1892 if constexpr (BelongsToFieldType::IsPrimaryKey)
1893 {
1894 if (auto result = QuerySingle<ReferencedRecord>(value); result)
1895 record = std::move(result);
1896 else
1898 std::format("Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1899 }
1900 }
1901#else
1902 CallOnPrimaryKey<ReferencedRecord>([&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>() {
1903 if (auto result = QuerySingle<ReferencedRecord>(value); result)
1904 record = std::move(result);
1905 else
1907 std::format("Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1908 });
1909#endif
1910 return record;
1911}
1912
1913template <size_t FieldIndex, typename Record, typename OtherRecord, typename Callable>
1914void DataMapper::CallOnHasMany(Record& record, Callable const& callback)
1915{
1916 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1917 static_assert(DataMapperRecord<OtherRecord>, "OtherRecord must satisfy DataMapperRecord");
1918
1919 using FieldType = HasMany<OtherRecord>;
1920 using ReferencedRecord = FieldType::ReferencedRecord;
1921
1922 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1923 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
1924 .Select()
1925 .Build([&](auto& query) {
1926 Reflection::EnumerateMembers<ReferencedRecord>(
1927 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1928 if constexpr (FieldWithStorage<ReferencedFieldType>)
1929 {
1930 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1931 }
1932 });
1933 })
1934 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard)
1935 .OrderBy(FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
1936 callback(query, primaryKeyField);
1937 });
1938}
1939
1940template <size_t FieldIndex, typename OtherRecord>
1941SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
1942{
1943 return _connection.Query(RecordTableName<OtherRecord>)
1944 .Select()
1945 .Build([](auto& q) {
1946 Reflection::EnumerateMembers<OtherRecord>([&]<size_t I, typename F>() {
1947 if constexpr (FieldWithStorage<F>)
1948 q.Field(FieldNameAt<I, OtherRecord>);
1949 });
1950 })
1951 .Where(FieldNameAt<FieldIndex, OtherRecord>, SqlWildcard)
1952 .OrderBy(FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
1953}
1954
1955template <size_t FieldIndex, typename Record, typename OtherRecord>
1956void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1957{
1958 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1959 static_assert(DataMapperRecord<OtherRecord>, "OtherRecord must satisfy DataMapperRecord");
1960
1961 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery, auto& primaryKeyField) {
1962 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1963 });
1964}
1965
1966template <typename ReferencedRecord, typename ThroughRecord, typename Record>
1967void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1968{
1969 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1970 static_assert(DataMapperRecord<ThroughRecord>, "ThroughRecord must satisfy DataMapperRecord");
1971
1972 // Find the PK of Record
1973 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1974 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
1975 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToIndex, typename ThroughBelongsToType>() {
1976 // Find the PK of ThroughRecord
1977 CallOnPrimaryKey<ThroughRecord>([&]<size_t ThroughPrimaryKeyIndex, typename ThroughPrimaryKeyType>() {
1978 // Find the BelongsTo of ReferencedRecord pointing to the PK of ThroughRecord
1979 CallOnBelongsTo<ReferencedRecord>([&]<size_t ReferencedKeyIndex, typename ReferencedKeyType>() {
1980 // Query the ReferencedRecord where:
1981 // - the BelongsTo of ReferencedRecord points to the PK of ThroughRecord,
1982 // - and the BelongsTo of ThroughRecord points to the PK of Record
1983 auto query =
1984 _connection.Query(RecordTableName<ReferencedRecord>)
1985 .Select()
1986 .Build([&](auto& query) {
1987 Reflection::EnumerateMembers<ReferencedRecord>(
1988 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1989 if constexpr (FieldWithStorage<ReferencedFieldType>)
1990 {
1991 query.Field(SqlQualifiedTableColumnName {
1992 RecordTableName<ReferencedRecord>,
1993 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1994 }
1995 });
1996 })
1997 .InnerJoin(RecordTableName<ThroughRecord>,
1998 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1999 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2000 .InnerJoin(RecordTableName<Record>,
2001 FieldNameAt<PrimaryKeyIndex, Record>,
2002 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2003 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2004 .Where(
2005 SqlQualifiedTableColumnName {
2006 RecordTableName<Record>,
2007 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2008 },
2009 SqlWildcard);
2010 if (auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
2011 {
2012 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
2013 }
2014 });
2015 });
2016 });
2017 });
2018}
2019
2020template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename PKValue>
2021std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue const& pkValue)
2022{
2023 static_assert(DataMapperRecord<ThroughRecord>, "ThroughRecord must satisfy DataMapperRecord");
2024
2025 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2026 std::shared_ptr<ReferencedRecord> result;
2027
2028 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
2029 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToIndex, typename ThroughBelongsToType>() {
2030 // Find the PK of ThroughRecord
2031 CallOnPrimaryKey<ThroughRecord>([&]<size_t ThroughPrimaryKeyIndex, typename ThroughPrimaryKeyType>() {
2032 // Find the BelongsTo of ReferencedRecord pointing to the PK of ThroughRecord
2033 CallOnBelongsTo<ReferencedRecord>([&]<size_t ReferencedKeyIndex, typename ReferencedKeyType>() {
2034 auto query =
2035 _connection.Query(RecordTableName<ReferencedRecord>)
2036 .Select()
2037 .Build([&](auto& query) {
2038 Reflection::EnumerateMembers<ReferencedRecord>(
2039 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
2040 if constexpr (FieldWithStorage<ReferencedFieldType>)
2041 {
2042 query.Field(SqlQualifiedTableColumnName {
2043 RecordTableName<ReferencedRecord>,
2044 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2045 }
2046 });
2047 })
2048 .InnerJoin(RecordTableName<ThroughRecord>,
2049 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
2050 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2051 .InnerJoin(RecordTableName<Record>,
2052 FieldNameAt<PrimaryKeyIndex, Record>,
2053 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2054 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2055 .Where(
2056 SqlQualifiedTableColumnName {
2057 RecordTableName<Record>,
2058 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2059 },
2060 SqlWildcard);
2061 if (auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
2062 result = std::make_shared<ReferencedRecord>(std::move(*link));
2063 });
2064 });
2065 });
2066
2067 return result;
2068}
2069
2070template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename Callable>
2071void DataMapper::CallOnHasManyThrough(Record& record, Callable const& callback)
2072{
2073 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2074
2075 // Find the PK of Record
2076 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
2077 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
2078 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToRecordIndex, typename ThroughBelongsToRecordType>() {
2079 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2080 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2081 {
2082 // Find the BelongsTo of ThroughRecord pointing to the PK of ReferencedRecord
2083 CallOnBelongsTo<ThroughRecord>(
2084 [&]<size_t ThroughBelongsToReferenceRecordIndex, typename ThroughBelongsToReferenceRecordType>() {
2085 using ThroughBelongsToReferenceRecordFieldType =
2086 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2087 if constexpr (std::is_same_v<typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2088 ReferencedRecord>)
2089 {
2090 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
2091 .Select()
2092 .Build([&](auto& query) {
2093 Reflection::EnumerateMembers<ReferencedRecord>(
2094 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
2095 if constexpr (FieldWithStorage<ReferencedFieldType>)
2096 {
2097 query.Field(SqlQualifiedTableColumnName {
2098 RecordTableName<ReferencedRecord>,
2099 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2100 }
2101 });
2102 })
2103 .InnerJoin(RecordTableName<ThroughRecord>,
2104 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2105 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2106 FieldNameAt<PrimaryKeyIndex, Record> })
2107 .Where(
2108 SqlQualifiedTableColumnName {
2109 RecordTableName<ThroughRecord>,
2110 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2111 },
2112 SqlWildcard);
2113 callback(query, primaryKeyField);
2114 }
2115 });
2116 }
2117 });
2118 });
2119}
2120
2121template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename PKValue, typename Callable>
2122void DataMapper::CallOnHasManyThroughByPK(PKValue const& pkValue, Callable const& callback)
2123{
2124 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2125
2126 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2127
2128 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
2129 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToRecordIndex, typename ThroughBelongsToRecordType>() {
2130 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2131 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2132 {
2133 // Find the BelongsTo of ThroughRecord pointing to the PK of ReferencedRecord
2134 CallOnBelongsTo<ThroughRecord>(
2135 [&]<size_t ThroughBelongsToReferenceRecordIndex, typename ThroughBelongsToReferenceRecordType>() {
2136 using ThroughBelongsToReferenceRecordFieldType =
2137 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2138 if constexpr (std::is_same_v<typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2139 ReferencedRecord>)
2140 {
2141 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
2142 .Select()
2143 .Build([&](auto& query) {
2144 Reflection::EnumerateMembers<ReferencedRecord>(
2145 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
2146 if constexpr (FieldWithStorage<ReferencedFieldType>)
2147 {
2148 query.Field(SqlQualifiedTableColumnName {
2149 RecordTableName<ReferencedRecord>,
2150 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2151 }
2152 });
2153 })
2154 .InnerJoin(RecordTableName<ThroughRecord>,
2155 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2156 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2157 FieldNameAt<PrimaryKeyIndex, Record> })
2158 .Where(
2159 SqlQualifiedTableColumnName {
2160 RecordTableName<ThroughRecord>,
2161 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2162 },
2163 SqlWildcard);
2164 callback(query, pkValue);
2165 }
2166 });
2167 }
2168 });
2169}
2170
2171template <typename ReferencedRecord, typename ThroughRecord, typename Record>
2172void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2173{
2174 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2175
2176 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2177 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
2178 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2179 });
2180}
2181
2182template <typename Record>
2183void DataMapper::LoadRelations(Record& record)
2184{
2185 static_assert(!std::is_const_v<Record>);
2186 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2187
2188#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2189 constexpr auto ctx = std::meta::access_context::current();
2190 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2191 {
2192 using FieldType = typename[:std::meta::type_of(el):];
2193 if constexpr (IsBelongsTo<FieldType>)
2194 {
2195 auto& field = record.[:el:];
2196 field = LoadBelongsTo<FieldType>(field.Value());
2197 }
2198 else if constexpr (IsHasMany<FieldType>)
2199 {
2200 LoadHasMany<el>(record, record.[:el:]);
2201 }
2202 else if constexpr (IsHasOneThrough<FieldType>)
2203 {
2204 LoadHasOneThrough(record, record.[:el:]);
2205 }
2206 else if constexpr (IsHasManyThrough<FieldType>)
2207 {
2208 LoadHasManyThrough(record, record.[:el:]);
2209 }
2210 }
2211#else
2212 Reflection::EnumerateMembers(record, [&]<size_t FieldIndex, typename FieldType>(FieldType& field) {
2213 if constexpr (IsBelongsTo<FieldType>)
2214 {
2215 field = LoadBelongsTo<FieldType>(field.Value());
2216 }
2217 else if constexpr (IsHasMany<FieldType>)
2218 {
2219 LoadHasMany<FieldIndex>(record, field);
2220 }
2221 else if constexpr (IsHasOneThrough<FieldType>)
2222 {
2223 LoadHasOneThrough(record, field);
2224 }
2225 else if constexpr (IsHasManyThrough<FieldType>)
2226 {
2227 LoadHasManyThrough(record, field);
2228 }
2229 });
2230#endif
2231}
2232
2233/// Sets the primary key field(s) of the given record to the specified id value.
2234template <typename Record, typename ValueType>
2235inline LIGHTWEIGHT_FORCE_INLINE void DataMapper::SetId(Record& record, ValueType&& id)
2236{
2237 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2238 // static_assert(HasPrimaryKey<Record>);
2239
2240#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2241
2242 auto constexpr ctx = std::meta::access_context::current();
2243 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2244 {
2245 using FieldType = typename[:std::meta::type_of(el):];
2246 if constexpr (IsField<FieldType>)
2247 {
2248 if constexpr (FieldType::IsPrimaryKey)
2249 {
2250 record.[:el:] = std::forward<ValueType>(id);
2251 }
2252 }
2253 }
2254#else
2255 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
2256 if constexpr (IsField<FieldType>)
2257 {
2258 if constexpr (FieldType::IsPrimaryKey)
2259 {
2260 field = std::forward<FieldType>(id);
2261 }
2262 }
2263 });
2264#endif
2265}
2266
2267/// Binds all output columns of the record via the given cursor.
2268template <typename Record, size_t InitialOffset>
2269inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record, SqlResultCursor& cursor)
2270{
2271 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2272 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2273 record, cursor);
2274}
2275
2276template <typename ElementMask, typename Record, size_t InitialOffset>
2277Record& DataMapper::BindOutputColumns(Record& record, SqlResultCursor& cursor)
2278{
2279 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2280 static_assert(!std::is_const_v<Record>);
2281
2282#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2283 auto constexpr ctx = std::meta::access_context::current();
2284 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2285 template for (constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2286 {
2287 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2288 using FieldType = typename[:std::meta::type_of(el):];
2289 if constexpr (IsField<FieldType>)
2290 {
2291 cursor.BindOutputColumn(i++, &record.[:el:].MutableValue());
2292 }
2293 else if constexpr (SqlOutputColumnBinder<FieldType>)
2294 {
2295 cursor.BindOutputColumn(i++, &record.[:el:]);
2296 }
2297 }
2298#else
2299 Reflection::EnumerateMembers<ElementMask>(
2300 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<size_t I, typename Field>(Field& field) mutable {
2301 if constexpr (IsField<Field>)
2302 {
2303 cursor.BindOutputColumn(i++, &field.MutableValue());
2304 }
2305 else if constexpr (SqlOutputColumnBinder<Field>)
2306 {
2307 cursor.BindOutputColumn(i++, &field);
2308 }
2309 });
2310#endif
2311
2312 return record;
2313}
2314template <typename Record>
2315// NOLINTNEXTLINE(readability-function-cognitive-complexity)
2317{
2318 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2319
2320 auto const callback = [&]<size_t FieldIndex, typename FieldType>(FieldType& field) {
2321 if constexpr (IsBelongsTo<FieldType>)
2322 {
2323 field.SetAutoLoader(typename FieldType::Loader {
2324 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2326 return dm.LoadBelongsTo<FieldType>(value);
2327 },
2328 });
2329 }
2330 if constexpr (IsHasMany<FieldType>)
2331 {
2332 if constexpr (HasPrimaryKey<Record>)
2333 {
2334 using ReferencedRecord = FieldType::ReferencedRecord;
2335 HasMany<ReferencedRecord>& hasMany = field;
2336 // Capture the PK value by value to avoid dangling references if the record is moved.
2337 auto pkValue = GetPrimaryKeyField(record);
2338 hasMany.SetAutoLoader(typename FieldType::Loader {
2339 .count = [pkValue]() -> size_t {
2341 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2342 dm._stmt.Prepare(selectQuery.Count());
2343 SqlResultCursor cursor = dm._stmt.Execute(pkValue);
2344 size_t count = 0;
2345 if (cursor.FetchRow())
2346 count = cursor.GetColumn<size_t>(1);
2347 return count;
2348 },
2349 .all = [pkValue]() -> typename FieldType::ReferencedRecordList {
2351 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2352 return detail::ToSharedPtrList(dm.Query<ReferencedRecord>(selectQuery.All(), pkValue));
2353 },
2354 .each =
2355 [pkValue](auto const& each) {
2357 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2358 auto stmt = SqlStatement { dm._connection };
2359 stmt.Prepare(selectQuery.All());
2360 auto cursor = stmt.Execute(pkValue);
2361
2362 auto referencedRecord = ReferencedRecord {};
2363 dm.BindOutputColumns(referencedRecord, cursor);
2364 dm.ConfigureRelationAutoLoading(referencedRecord);
2365
2366 while (cursor.FetchRow())
2367 {
2368 each(referencedRecord);
2369 dm.BindOutputColumns(referencedRecord, cursor);
2370 }
2371 },
2372 });
2373 }
2374 }
2375 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
2376 {
2377 using ReferencedRecord = FieldType::ReferencedRecord;
2378 using ThroughRecord = FieldType::ThroughRecord;
2380 // Capture the PK value by value to avoid dangling references if the record is moved.
2381 auto pkValue = GetPrimaryKeyField(record);
2382 hasOneThrough.SetAutoLoader(typename FieldType::Loader {
2383 .loadReference = [pkValue]() -> std::shared_ptr<ReferencedRecord> {
2385 return dm.LoadHasOneThroughByPK<ReferencedRecord, ThroughRecord, Record>(pkValue);
2386 },
2387 });
2388 }
2389 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
2390 {
2391 using ReferencedRecord = FieldType::ReferencedRecord;
2392 using ThroughRecord = FieldType::ThroughRecord;
2394 // Capture the PK value by value to avoid dangling references if the record is moved.
2395 auto pkValue = GetPrimaryKeyField(record);
2396 hasManyThrough.SetAutoLoader(typename FieldType::Loader {
2397 .count = [pkValue]() -> size_t {
2398 // Load result for Count()
2399 size_t count = 0;
2401 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2402 pkValue, [&](SqlSelectQueryBuilder& selectQuery, auto const& pk) {
2403 dm._stmt.Prepare(selectQuery.Count());
2404 SqlResultCursor cursor = dm._stmt.Execute(pk);
2405 if (cursor.FetchRow())
2406 count = cursor.GetColumn<size_t>(1);
2407 });
2408 return count;
2409 },
2410 .all = [pkValue]() -> typename FieldType::ReferencedRecordList {
2411 // Load result for All()
2413 typename FieldType::ReferencedRecordList result;
2414 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2415 pkValue, [&](SqlSelectQueryBuilder& selectQuery, auto const& pk) {
2416 result = detail::ToSharedPtrList(dm.Query<ReferencedRecord>(selectQuery.All(), pk));
2417 });
2418 return result;
2419 },
2420 .each =
2421 [pkValue](auto const& each) {
2422 // Load result for Each()
2424 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2425 pkValue, [&](SqlSelectQueryBuilder& selectQuery, auto const& pk) {
2426 auto stmt = SqlStatement { dm._connection };
2427 stmt.Prepare(selectQuery.All());
2428 auto cursor = stmt.Execute(pk);
2429 auto referencedRecord = ReferencedRecord {};
2430 dm.BindOutputColumns(referencedRecord, cursor);
2431 dm.ConfigureRelationAutoLoading(referencedRecord);
2432
2433 while (cursor.FetchRow())
2434 {
2435 each(referencedRecord);
2436 dm.BindOutputColumns(referencedRecord, cursor);
2437 }
2438 });
2439 },
2440 });
2441 }
2442 };
2443
2444#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2445 constexpr auto ctx = std::meta::access_context::current();
2446
2447 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<auto I>() {
2448 constexpr auto localctx = std::meta::access_context::current();
2449 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2450 using FieldType = typename[:std::meta::type_of(members[I]):];
2451 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2452 });
2453#else
2454 Reflection::EnumerateMembers(record, callback);
2455#endif
2456}
2457
2458template <typename T>
2459std::optional<T> DataMapper::Execute(std::string_view sqlQueryString)
2460{
2461 return _stmt.ExecuteDirectScalar<T>(sqlQueryString);
2462}
2463
2464} // namespace Lightweight
Main API for mapping records to and from the database using high level C++ syntax.
DataMapper(DataMapper &&other) noexcept
Move constructor.
void Update(Record &record)
SqlConnection const & Connection() const noexcept
Returns the connection reference used by this data mapper.
bool IsModified(Record const &record) const noexcept
DataMapper()
Constructs a new data mapper, using the default connection.
std::vector< std::string > CreateTableString(SqlServerType serverType)
Constructs a string list of SQL queries to create the table for the given record type.
SqlQueryBuilder Query()
void LoadRelations(Record &record)
DataMapper & operator=(DataMapper &&other) noexcept
Move assignment operator.
static LIGHTWEIGHT_API DataMapper & AcquireThreadLocal()
Acquires a thread-local DataMapper instance that is safe for reuse within that thread.
void SetModifiedState(Record &record) noexcept
std::optional< T > Execute(std::string_view sqlQueryString)
DataMapper(std::optional< SqlConnectionString > connectionString)
Constructs a new data mapper, using the given connection string.
std::size_t Delete(Record const &record)
RecordPrimaryKeyType< Record > CreateCopyOf(Record const &originalRecord)
Creates a copy of an existing record in the database.
DataMapper(SqlConnection &&connection)
Constructs a new data mapper, using the given connection.
void CreateTable()
Creates the table for the given record type.
SqlAllFieldsQueryBuilder< Record, QueryOptions > Query()
std::optional< Record > QuerySingle(PrimaryKeyTypes &&... primaryKeys)
Queries a single record (based on primary key) from the database.
SqlConnection & Connection() noexcept
Returns the mutable connection reference used by this data mapper.
void CreateTables()
Creates the tables for the given record types.
static std::string Inspect(Record const &record)
Constructs a human readable string representation of the given record.
RecordPrimaryKeyType< Record > CreateExplicit(Record const &record)
Creates a new record in the database.
std::vector< std::string > CreateTablesString(SqlServerType serverType)
Constructs a string list of SQL queries to create the tables for the given record types.
void ConfigureRelationAutoLoading(Record &record)
SqlQueryBuilder FromTable(std::string_view tableName)
Constructs an SQL query builder for the given table name.
ModifiedState
Enum to set the modified state of a record.
RecordPrimaryKeyType< Record > Create(Record &record)
Creates a new record in the database.
std::vector< Record > Query(SqlSelectQueryBuilder::ComposedQuery const &selectQuery, InputParameters &&... inputParameters)
This API represents a many-to-many relationship between two records through a third record.
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
This HasMany<OtherRecord> represents a simple one-to-many relationship between two records.
Definition HasMany.hpp:34
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
Definition HasMany.hpp:144
Represents a one-to-one relationship through a join table.
void SetAutoLoader(Loader loader)
Used internally to configure on-demand loading of the record.
Represents a query builder that retrieves all fields of a record.
Represents a connection to a SQL database.
SqlServerType ServerType() const noexcept
Retrieves the type of the server.
LIGHTWEIGHT_API SqlQueryBuilder Query(std::string_view const &table={}) const
SqlQueryFormatter const & QueryFormatter() const noexcept
Retrieves a query formatter suitable for the SQL server being connected.
std::optional< Record > First()
Executes a SELECT query for the first record found and returns it.
std::vector< Record > All()
Executes a SELECT query and returns all records found.
LIGHTWEIGHT_FORCE_INLINE SqlCoreDataMapperQueryBuilder(DataMapper &dm, std::string fields) noexcept
Constructs a query builder with the given data mapper and field list.
std::vector< Record > Range(size_t offset, size_t limit)
Executes a SELECT query for a range of records and returns them.
void Delete()
Executes a DELETE query.
size_t Count()
Executes a SELECT COUNT query and returns the number of records found.
static LIGHTWEIGHT_API SqlLogger & GetLogger()
Retrieves the currently configured logger.
virtual void OnWarning(std::string_view const &message)=0
Invoked on a warning.
LIGHTWEIGHT_API SqlCreateTableQueryBuilder CreateTable(std::string_view tableName)
Creates a new table.
API Entry point for building SQL queries.
Definition SqlQuery.hpp:32
LIGHTWEIGHT_API SqlInsertQueryBuilder Insert(std::vector< SqlVariant > *boundInputs=nullptr) noexcept
LIGHTWEIGHT_API SqlSelectQueryBuilder Select() noexcept
Initiates SELECT query building.
LIGHTWEIGHT_API SqlDeleteQueryBuilder Delete() noexcept
Initiates DELETE query building.
LIGHTWEIGHT_API SqlMigrationQueryBuilder Migration()
Initiates query for building database migrations.
LIGHTWEIGHT_API SqlUpdateQueryBuilder Update(std::vector< SqlVariant > *boundInputs=nullptr) noexcept
static SqlQueryFormatter const * Get(SqlServerType serverType) noexcept
Retrieves the SQL query formatter for the given SqlServerType.
API for reading an SQL query result set.
LIGHTWEIGHT_FORCE_INLINE bool GetColumn(SQLUSMALLINT column, T *result) const
LIGHTWEIGHT_FORCE_INLINE size_t NumColumnsAffected() const
Retrieves the number of columns affected by the last query.
LIGHTWEIGHT_FORCE_INLINE size_t NumRowsAffected() const
Retrieves the number of rows affected by the last query.
LIGHTWEIGHT_FORCE_INLINE void BindOutputColumn(SQLUSMALLINT columnIndex, T *arg)
Binds a single output column at the given index to store fetched data.
LIGHTWEIGHT_FORCE_INLINE bool FetchRow()
Fetches the next row of the result set.
Query builder for building SELECT ... queries.
Definition Select.hpp:86
SqlSelectQueryBuilder & Build(Callable const &callable)
Builds the query using a callable.
LIGHTWEIGHT_API ComposedQuery First(size_t count=1)
Finalizes building the query as SELECT TOP n field names FROM ... query.
LIGHTWEIGHT_API SqlSelectQueryBuilder & Field(std::string_view const &fieldName)
Adds a single column to the SELECT clause.
High level API for (prepared) raw SQL statements.
LIGHTWEIGHT_API void Prepare(std::string_view query) &
LIGHTWEIGHT_API size_t LastInsertId(std::string_view tableName)
Retrieves the last insert ID of the given table.
LIGHTWEIGHT_API SqlConnection & Connection() noexcept
Retrieves the connection associated with this statement.
SqlResultCursor Execute(Args const &... args)
Binds the given arguments to the prepared statement and executes it.
void BindInputParameter(SQLSMALLINT columnIndex, Arg const &arg)
Binds an input parameter to the prepared statement at the given column index.
LIGHTWEIGHT_API SqlResultCursor ExecuteDirect(std::string_view const &query, std::source_location location=std::source_location::current())
Executes the given query directly.
std::optional< T > ExecuteDirectScalar(std::string_view const &query, std::source_location location=std::source_location::current())
Represents a record type that can be used with the DataMapper.
Definition Record.hpp:47
LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType< Record > GetPrimaryKeyField(Record const &record) noexcept
Definition Record.hpp:178
constexpr std::string_view FieldNameAt
Returns the SQL field name of the given field index in the record.
Definition Utils.hpp:170
std::remove_cvref_t< decltype(std::declval< MemberClassType< decltype(Field)> >().*Field)>::ValueType ReferencedFieldTypeOf
Retrieves the type of a member field in a record.
Definition Field.hpp:392
Represents a single column in a table.
Definition Field.hpp:84
T ValueType
The underlying value type of this field.
Definition Field.hpp:86
static constexpr auto IsOptional
Indicates if the field is optional, i.e., it can be NULL.
Definition Field.hpp:118
static SqlGuid Create() noexcept
Creates a new non-empty GUID.
SqlQualifiedTableColumnName represents a column name qualified with a table name.
Definition Core.hpp:60
Represents a value that can be any of the supported SQL data types.