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