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