Lightweight 0.1.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 "Record.hpp"
17
18#include <reflection-cpp/reflection.hpp>
19
20#include <cassert>
21#include <concepts>
22#include <tuple>
23#include <type_traits>
24#include <utility>
25
26/// @defgroup DataMapper Data Mapper
27///
28/// @brief The data mapper is a high level API for mapping records to and from the database using high level C++ syntax.
29
30/// Requires that T satisfies to be a field with storage.
31///
32/// @ingroup DataMapper
33template <typename T>
34concept FieldWithStorage = requires(T const& field, T& mutableField) {
35 // clang-format off
36 { field.Value() } -> std::convertible_to<typename T::ValueType const&>;
37 { mutableField.MutableValue() } -> std::convertible_to<typename T::ValueType&>;
38 { field.IsModified() } -> std::convertible_to<bool>;
39 { mutableField.SetModified(bool {}) } -> std::convertible_to<void>;
40 // clang-format on
41};
42
43/// Represents the number of fields with storage in a record.
44///
45/// @ingroup DataMapper
46template <typename Record>
47constexpr size_t RecordStorageFieldCount =
48 Reflection::FoldMembers<Record>(size_t { 0 }, []<size_t I, typename Field>(size_t const accum) constexpr {
49 if constexpr (FieldWithStorage<Field>)
50 return accum + 1;
51 else
52 return accum;
53 });
54
55template <typename Record>
56concept RecordWithStorageFields = (RecordStorageFieldCount<Record> > 0);
57
58namespace detail
59{
60
61template <auto Test, typename T>
62constexpr bool CheckFieldProperty = Reflection::FoldMembers<T>(false, []<size_t I, typename Field>(bool const accum) {
63 if constexpr (Test.template operator()<Field>())
64 return true;
65 else
66 return accum;
67});
68
69} // namespace detail
70
71/// @brief Tests if the given record type does contain a primary key.
72///
73/// @ingroup DataMapper
74template <typename T>
75constexpr bool HasPrimaryKey = detail::CheckFieldProperty<[]<typename Field>() { return IsPrimaryKey<Field>; }, T>;
76
77/// @brief Tests if the given record type does contain an auto increment primary key.
78///
79/// @ingroup DataMapper
80template <typename T>
82 detail::CheckFieldProperty<[]<typename Field>() { return IsAutoIncrementPrimaryKey<Field>; }, T>;
83
84namespace detail
85{
86
87template <template <typename> class Allocator, template <typename, typename> class Container, typename Object>
88auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
89{
90 using SharedPtrRecord = std::shared_ptr<Object>;
91 auto sharedPtrContainer = Container<SharedPtrRecord, Allocator<SharedPtrRecord>> {};
92 for (auto& object: container)
93 sharedPtrContainer.emplace_back(std::make_shared<Object>(std::move(object)));
94 return sharedPtrContainer;
95}
96
97template <typename Record>
98constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType) noexcept
99{
100 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
101 return true;
102
103 // Test if we have some columns that might not be sufficient to store the result (e.g. string truncation),
104 // then don't call BindOutputColumn but SQLFetch to get the result, because
105 // regrowing previously bound columns is not supported in MS-SQL's ODBC driver, so it seems.
106 bool result = true;
107 Reflection::EnumerateMembers<Record>([&result]<size_t I, typename Field>() {
108 if constexpr (IsField<Field>)
109 {
110 if constexpr (detail::OneOf<typename Field::ValueType,
111 std::string,
112 std::wstring,
113 std::u16string,
114 std::u32string,
115 SqlBinary>
116 || IsSqlDynamicString<typename Field::ValueType> || IsSqlDynamicBinary<typename Field::ValueType>)
117 {
118 // Known types that MAY require growing due to truncation.
119 result = false;
120 }
121 }
122 });
123 return result;
124}
125
126template <typename Record>
127void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLSMALLINT startOffset)
128{
129 Reflection::EnumerateMembers(record,
130 [reader = &reader, i = startOffset]<size_t I, typename Field>(Field& field) mutable {
131 if constexpr (IsField<Field>)
132 {
133 reader->BindOutputColumn(i++, &field.MutableValue());
134 }
135 else if constexpr (IsBelongsTo<Field>)
136 {
137 reader->BindOutputColumn(i++, &field.MutableValue());
138 }
139 else if constexpr (SqlOutputColumnBinder<Field>)
140 {
141 reader->BindOutputColumn(i++, &field);
142 }
143 });
144}
145
146template <typename Record>
147void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
148{
149 BindAllOutputColumnsWithOffset(reader, record, 1);
150}
151
152// when we iterate over all columns using element mask
153// indexes of the mask corresponds to the indexe of the field
154// inside the structure, not inside the SQL result set
155template <typename ElementMask, typename Record>
156void GetAllColumns(SqlResultCursor& reader, Record& record)
157{
158 SQLUSMALLINT indexFromQuery = 0;
159 Reflection::EnumerateMembers<ElementMask>(
160 record, [reader = &reader, &indexFromQuery]<size_t I, typename Field>(Field& field) mutable {
161 ++indexFromQuery;
162 if constexpr (IsField<Field>)
163 {
164 field.MutableValue() = reader->GetColumn<typename Field::ValueType>(indexFromQuery);
165 }
166 else if constexpr (SqlGetColumnNativeType<Field>)
167 {
168 field = reader->GetColumn<Field>(indexFromQuery);
169 }
170 });
171}
172
173template <typename Record>
174void GetAllColumns(SqlResultCursor& reader, Record& record)
175{
176 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(reader, record);
177}
178
179template <typename FirstRecord, typename SecondRecord>
180void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
181{
182 auto& [firstRecord, secondRecord] = record;
183
184 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<size_t I, typename Field>(Field& field) mutable {
185 if constexpr (IsField<Field>)
186 {
187 field.MutableValue() = reader->GetColumn<typename Field::ValueType>(I + 1);
188 }
189 else if constexpr (SqlGetColumnNativeType<Field>)
190 {
191 field = reader->GetColumn<Field>(I + 1);
192 }
193 });
194
195 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<size_t I, typename Field>(Field& field) mutable {
196 if constexpr (IsField<Field>)
197 {
198 field.MutableValue() =
199 reader->GetColumn<typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
200 }
201 else if constexpr (SqlGetColumnNativeType<Field>)
202 {
203 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
204 }
205 });
206}
207
208template <typename Record>
209bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
210{
211 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
212
213 if (outputColumnsBound)
214 BindAllOutputColumns(reader, record);
215
216 if (!reader.FetchRow())
217 return false;
218
219 if (!outputColumnsBound)
220 GetAllColumns(reader, record);
221
222 return true;
223}
224
225} // namespace detail
226
227/// Main API for mapping records to C++ from the database using high level C++ syntax.
228///
229/// @ingroup DataMapper
230template <typename Record, typename Derived>
231class [[nodiscard]] SqlCoreDataMapperQueryBuilder: public SqlBasicSelectQueryBuilder<Derived>
232{
233 private:
234 SqlStatement& _stmt;
235 SqlQueryFormatter const& _formatter;
236
237 std::string _fields;
238
239 friend class SqlWhereClauseBuilder<Derived>;
240
241 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition() noexcept
242 {
243 return this->_query.searchCondition;
244 }
245
246 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE SqlQueryFormatter const& Formatter() const noexcept
247 {
248 return _formatter;
249 }
250
251 protected:
252 LIGHTWEIGHT_FORCE_INLINE explicit SqlCoreDataMapperQueryBuilder(SqlStatement& stmt, std::string fields) noexcept:
253 _stmt { stmt },
254 _formatter { stmt.Connection().QueryFormatter() },
255 _fields { std::move(fields) }
256 {
257 }
258
259 public:
260 [[nodiscard]] std::vector<Record> All()
261 {
262 auto records = std::vector<Record> {};
263 _stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
264 _fields,
265 RecordTableName<Record>,
266 this->_query.searchCondition.tableAlias,
267 this->_query.searchCondition.tableJoins,
268 this->_query.searchCondition.condition,
269 this->_query.orderBy,
270 this->_query.groupBy));
271 Derived::ReadResults(_stmt.Connection().ServerType(), _stmt.GetResultCursor(), &records);
272 return records;
273 }
274
275 [[nodiscard]] std::optional<Record> First()
276 {
277 std::optional<Record> record {};
278 _stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
279 _fields,
280 RecordTableName<Record>,
281 this->_query.searchCondition.tableAlias,
282 this->_query.searchCondition.tableJoins,
283 this->_query.searchCondition.condition,
284 this->_query.orderBy,
285 1));
286 Derived::ReadResult(_stmt.Connection().ServerType(), _stmt.GetResultCursor(), &record);
287 return record;
288 }
289
290 [[nodiscard]] std::vector<Record> First(size_t n)
291 {
292 auto records = std::vector<Record> {};
293 records.reserve(n);
294 _stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
295 _fields,
296 RecordTableName<Record>,
297 this->_query.searchCondition.tableAlias,
298 this->_query.searchCondition.tableJoins,
299 this->_query.searchCondition.condition,
300 this->_query.orderBy,
301 n));
302 Derived::ReadResults(_stmt.Connection().ServerType(), _stmt.GetResultCursor(), &records);
303 return records;
304 }
305
306 [[nodiscard]] std::vector<Record> Range(size_t offset, size_t limit)
307 {
308 auto records = std::vector<Record> {};
309 records.reserve(limit);
310 _stmt.ExecuteDirect(_formatter.SelectRange(
311 this->_query.distinct,
312 _fields,
313 RecordTableName<Record>,
314 this->_query.searchCondition.tableAlias,
315 this->_query.searchCondition.tableJoins,
316 this->_query.searchCondition.condition,
317 !this->_query.orderBy.empty()
318 ? this->_query.orderBy
319 : std::format(" ORDER BY \"{}\" ASC", FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
320 this->_query.groupBy,
321 offset,
322 limit));
323 Derived::ReadResults(_stmt.Connection().ServerType(), _stmt.GetResultCursor(), &records);
324 return records;
325 }
326};
327
328/// @brief Represents a query builder that retrieves only the fields specified.
329///
330/// @ingroup DataMapper
331template <typename Record, auto... ReferencedFields>
332class [[nodiscard]] SqlSparseFieldQueryBuilder final:
333 public SqlCoreDataMapperQueryBuilder<Record, SqlSparseFieldQueryBuilder<Record, ReferencedFields...>>
334{
335 using ElementMask = std::integer_sequence<size_t, Reflection::MemberIndexOf<ReferencedFields>...>;
336
337 private:
338 friend class DataMapper;
339 friend class SqlCoreDataMapperQueryBuilder<Record, SqlSparseFieldQueryBuilder<Record, ReferencedFields...>>;
340
341 LIGHTWEIGHT_FORCE_INLINE explicit SqlSparseFieldQueryBuilder(SqlStatement& stmt, std::string fields) noexcept:
342 SqlCoreDataMapperQueryBuilder<Record, SqlSparseFieldQueryBuilder<Record, ReferencedFields...>> { stmt,
343 std::move(fields) }
344 {
345 }
346
347 // NB: Required by SqlCoreDataMapperQueryBuilder:
348
349 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<Record>* records)
350 {
351 while (true)
352 {
353 auto& record = records->emplace_back();
354 if (!ReadResultImpl(sqlServerType, reader, record))
355 {
356 records->pop_back();
357 break;
358 }
359 }
360 }
361
362 static void ReadResult(SqlServerType sqlServerType, SqlResultCursor reader, std::optional<Record>* optionalRecord)
363 {
364 auto& record = optionalRecord->emplace();
365 if (!ReadResultImpl(sqlServerType, reader, record))
366 optionalRecord->reset();
367 }
368
369 static bool ReadResultImpl(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
370 {
371 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(sqlServerType);
372 if (outputColumnsBound)
373 reader.BindOutputColumns(&(record.*ReferencedFields)...);
374
375 if (!reader.FetchRow())
376 return false;
377
378 if (!outputColumnsBound)
379 detail::GetAllColumns<ElementMask>(reader, record);
380
381 return true;
382 }
383};
384
385/// @brief Represents a query builder that retrieves all fields of a record.
386///
387/// @ingroup DataMapper
388template <typename Record>
389class [[nodiscard]] SqlAllFieldsQueryBuilder final:
390 public SqlCoreDataMapperQueryBuilder<Record, SqlAllFieldsQueryBuilder<Record>>
391{
392 private:
393 friend class DataMapper;
394 friend class SqlCoreDataMapperQueryBuilder<Record, SqlAllFieldsQueryBuilder<Record>>;
395
396 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(SqlStatement& stmt, std::string fields) noexcept:
398 {
399 }
400
401 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<Record>* records)
402 {
403 while (true)
404 {
405 Record& record = records->emplace_back();
406 if (!detail::ReadSingleResult(sqlServerType, reader, record))
407 {
408 records->pop_back();
409 break;
410 }
411 }
412 }
413
414 static void ReadResult(SqlServerType sqlServerType, SqlResultCursor reader, std::optional<Record>* optionalRecord)
415 {
416 Record& record = optionalRecord->emplace();
417 if (!detail::ReadSingleResult(sqlServerType, reader, record))
418 optionalRecord->reset();
419 }
420};
421
422/// @brief Specialization of SqlAllFieldsQueryBuilder for the case when we return std::tuple
423/// of two records
424///
425/// @ingroup DataMapper
426template <typename FirstRecord, typename SecondRecord>
427class [[nodiscard]] SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>> final:
428 public SqlCoreDataMapperQueryBuilder<std::tuple<FirstRecord, SecondRecord>,
429 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>>>
430{
431 private:
432 using RecordType = std::tuple<FirstRecord, SecondRecord>;
433 friend class DataMapper;
434 friend class SqlCoreDataMapperQueryBuilder<RecordType, SqlAllFieldsQueryBuilder<RecordType>>;
435
436 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(SqlStatement& stmt, std::string fields) noexcept:
438 {
439 }
440
441 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
442 {
443 while (true)
444 {
445 auto& record = records->emplace_back();
446 auto& [firstRecord, secondRecord] = record;
447
448 using FirstRecordType = std::remove_cvref_t<decltype(firstRecord)>;
449 using SecondRecordType = std::remove_cvref_t<decltype(secondRecord)>;
450
451 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
452 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
453 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
454
455 if (canSafelyBindAll)
456 {
457 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
458 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
459 }
460
461 if (!reader.FetchRow())
462 {
463 records->pop_back();
464 break;
465 }
466
467 if (!canSafelyBindAll)
468 detail::GetAllColumns(reader, record);
469 }
470 }
471};
472
473/// @brief Represents a query builder that retrieves only the first record found.
474///
475/// @see DataMapper::QuerySingle()
476///
477/// @ingroup DataMapper
478template <typename Record>
479class [[nodiscard]] SqlQuerySingleBuilder: public SqlWhereClauseBuilder<SqlQuerySingleBuilder<Record>>
480{
481 private:
482 SqlStatement& _stmt;
483 SqlQueryFormatter const& _formatter;
484
485 std::string _fields;
486 SqlSearchCondition _searchCondition {};
487
488 friend class DataMapper;
489 friend class SqlWhereClauseBuilder<SqlQuerySingleBuilder<Record>>;
490
491 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition() noexcept
492 {
493 return _searchCondition;
494 }
495
496 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE SqlQueryFormatter const& Formatter() const noexcept
497 {
498 return _formatter;
499 }
500
501 protected:
502 LIGHTWEIGHT_FORCE_INLINE explicit SqlQuerySingleBuilder(SqlStatement& stmt, std::string fields) noexcept:
503 _stmt { stmt },
504 _formatter { stmt.Connection().QueryFormatter() },
505 _fields { std::move(fields) }
506 {
507 }
508
509 public:
510 /// @brief Executes the query and returns the first record found.
511 [[nodiscard]] std::optional<Record> Get()
512 {
513 auto constexpr count = 1;
514 auto constexpr distinct = false;
515 auto constexpr orderBy = std::string_view {};
516 _stmt.ExecuteDirect(_formatter.SelectFirst(distinct,
517 _fields,
518 RecordTableName<Record>,
519 _searchCondition.tableAlias,
520 _searchCondition.tableJoins,
521 _searchCondition.condition,
522 orderBy,
523 count));
524 auto record = std::optional<Record> { Record {} };
525 auto reader = _stmt.GetResultCursor();
526 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *record))
527 return std::nullopt;
528 return record;
529 }
530};
531
532/// Returns the first primary key field of the record.
533///
534/// @ingroup DataMapper
535template <typename Record>
536inline LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType<Record> GetPrimaryKeyField(Record const& record) noexcept
537{
538 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
539 static_assert(HasPrimaryKey<Record>, "Record must have a primary key");
540
541 auto result = RecordPrimaryKeyType<Record> {};
542 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType const& field) {
543 if constexpr (IsPrimaryKey<FieldType> && std::same_as<FieldType, RecordPrimaryKeyType<Record>>)
544 {
545 result = field;
546 }
547 });
548 return result;
549}
550
551/// @brief Main API for mapping records to and from the database using high level C++ syntax.
552///
553/// A DataMapper instances operates on a single SQL connection and provides methods to
554/// create, read, update and delete records in the database.
555///
556/// @see Field, BelongsTo, HasMany, HasManyThrough, HasOneThrough
557/// @ingroup DataMapper
559{
560 public:
561 /// Constructs a new data mapper, using the default connection.
563 _connection {},
564 _stmt { _connection }
565 {
566 }
567
568 /// Constructs a new data mapper, using the given connection.
569 explicit DataMapper(SqlConnection&& connection):
570 _connection { std::move(connection) },
571 _stmt { _connection }
572 {
573 }
574
575 /// Constructs a new data mapper, using the given connection string.
576 explicit DataMapper(SqlConnectionString connectionString):
577 _connection { std::move(connectionString) },
578 _stmt { _connection }
579 {
580 }
581
582 DataMapper(DataMapper const&) = delete;
583 DataMapper(DataMapper&&) noexcept = default;
584 DataMapper& operator=(DataMapper const&) = delete;
585 DataMapper& operator=(DataMapper&&) noexcept = default;
586 ~DataMapper() = default;
587
588 /// Returns the connection reference used by this data mapper.
589 [[nodiscard]] SqlConnection const& Connection() const noexcept
590 {
591 return _connection;
592 }
593
594 /// Returns the mutable connection reference used by this data mapper.
595 [[nodiscard]] SqlConnection& Connection() noexcept
596 {
597 return _connection;
598 }
599
600 /// Constructs a human readable string representation of the given record.
601 template <typename Record>
602 static std::string Inspect(Record const& record);
603
604 /// Constructs a string list of SQL queries to create the table for the given record type.
605 template <typename Record>
606 std::vector<std::string> CreateTableString(SqlServerType serverType);
607
608 /// Constructs a string list of SQL queries to create the tables for the given record types.
609 template <typename FirstRecord, typename... MoreRecords>
610 std::vector<std::string> CreateTablesString(SqlServerType serverType);
611
612 /// Creates the table for the given record type.
613 template <typename Record>
614 void CreateTable();
615
616 /// Creates the tables for the given record types.
617 template <typename FirstRecord, typename... MoreRecords>
618 void CreateTables();
619
620 /// @brief Creates a new record in the database.
621 ///
622 /// The record is inserted into the database and the primary key is set on this record.
623 ///
624 /// @return The primary key of the newly created record.
625 template <typename Record>
626 RecordPrimaryKeyType<Record> Create(Record& record);
627
628 /// @brief Creates a new record in the database.
629 ///
630 /// @note This is a variation of the Create() method and does not update the record's primary key.
631 ///
632 /// @return The primary key of the newly created record.
633 template <typename Record>
634 RecordPrimaryKeyType<Record> CreateExplicit(Record const& record);
635
636 /// @brief Queries a single record from the database based on the given query.
637 ///
638 /// @param selectQuery The SQL select query to execute.
639 /// @param args The input parameters for the query.
640 ///
641 /// @return The record if found, otherwise std::nullopt.
642 template <typename Record, typename... Args>
643 std::optional<Record> QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args);
644
645 /// @brief Queries a single record (based on primary key) from the database.
646 ///
647 /// The primary key(s) are used to identify the record to load.
648 /// If the record is not found, std::nullopt is returned.
649 template <typename Record, typename... PrimaryKeyTypes>
650 std::optional<Record> QuerySingle(PrimaryKeyTypes&&... primaryKeys);
651
652 /// @brief Queries a single record (based on primary key) from the database without auto-loading relations.
653 ///
654 /// The primary key(s) are used to identify the record to load.
655 ///
656 /// Main goal of this function is to load record without relationships to
657 /// decrease compilation time and work around some limitations of template instantiation
658 /// depth on MSVC compiler.
659 template <typename Record, typename... PrimaryKeyTypes>
660 std::optional<Record> QuerySingleWithoutRelationAutoLoading(PrimaryKeyTypes&&... primaryKeys);
661
662 /// @brief Queries a single record from the database.
663 ///
664 /// @return A query builder for the given Record type that will also allow executing the query.
665 ///
666 /// @code
667 /// auto const record = dm.QuerySingle<Person>(personId)
668 /// .Where(FieldNameOf<&Person::id>, "=", 42)
669 /// .Get();
670 /// if (record.has_value())
671 /// std::println("Person: {}", DataMapper::Inspect(record.value()));
672 /// @endcode
673 template <typename Record>
675 {
676 std::string fields;
677 Reflection::EnumerateMembers<Record>([&fields]<size_t I, typename Field>() {
678 if (!fields.empty())
679 fields += ", ";
680 fields += '"';
681 fields += RecordTableName<Record>;
682 fields += "\".\"";
683 fields += FieldNameAt<I, Record>;
684 fields += '"';
685 });
686
687 return SqlQuerySingleBuilder<Record>(_stmt, std::move(fields));
688 }
689
690 /// @brief Queries a single record by the given column name and value.
691 ///
692 /// @param columnName The name of the column to search.
693 /// @param value The value to search for.
694 /// @return The record if found, otherwise std::nullopt.
695 template <typename Record, typename ColumnName, typename T>
696 std::optional<Record> QuerySingleBy(ColumnName const& columnName, T const& value);
697
698 /// Queries multiple records from the database, based on the given query.
699 template <typename Record, typename... InputParameters>
700 std::vector<Record> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters);
701
702 /// Queries multiple records from the database, based on the given query.
703 template <typename Record, typename... InputParameters>
704 std::vector<Record> Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
705
706 /// Queries records from the database, based on the given query and can be used to retrieve only part of the record
707 /// by specifying the ElementMask.
708 ///
709 /// example:
710 /// @code
711 ///
712 /// struct Person
713 /// {
714 /// int id;
715 /// std::string name;
716 /// std::string email;
717 /// std::string phone;
718 /// std::string address;
719 /// std::string city;
720 /// std::string country;
721 /// };
722 ///
723 /// auto infos = dm.Query<SqlElements<1,5>(RecordTableName<Person>.Fields({"name"sv, "city"sv}));
724 ///
725 /// for(auto const& info : infos)
726 /// {
727 /// // only info.name and info.city are loaded
728 /// }
729 /// @endcode
730 template <typename ElementMask, typename Record, typename... InputParameters>
731 std::vector<Record> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters);
732
733 /// Queries records of different types from the database, based on the given query.
734 /// User can constructed query that selects columns from the multiple tables
735 /// this function is uset to get result of the
736 ///
737 /// example:
738 /// @code
739 ///
740 /// struct JointA{};
741 /// struct JointB{};
742 /// struct JointC{};
743 ///
744 /// // the following query will construct statement to fetch all elements of JointA and JointC types
745 /// auto dm = DataMapper {};
746 /// auto const query = dm.FromTable(RecordTableName<JoinTestA>)
747 /// .Select()
748 /// .Fields<JointA, JointC>()
749 /// .InnerJoin<&JointB::a_id, &JointA::id>()
750 /// .InnerJoin<&JointC::id, &JointB::c_id>()
751 /// .All();
752 /// auto const records = dm.Query<JointA, JointC>(query);
753 /// for(const auto [elementA, elementC] : records)
754 /// {
755 /// // do something with elementA and elementC
756 /// }
757 template <typename FirstRecord, typename NextRecord, typename... InputParameters>
759 std::vector<std::tuple<FirstRecord, NextRecord>> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery,
760 InputParameters&&... inputParameters);
761
762 /// Similar to previous one but quiery is builded from the object return by the quiery
763 template <typename FirstRecord, typename NextRecord>
766 {
767
768 std::string fields;
769
770 auto const emplaceRecordsFrom = [&fields]<typename Record>() {
771 Reflection::EnumerateMembers<Record>([&fields]<size_t I, typename Field>() {
772 if (!fields.empty())
773 fields += ", ";
774 fields += std::format(R"("{}"."{}")", RecordTableName<Record>, FieldNameAt<I, Record>);
775 });
776 };
777
778 emplaceRecordsFrom.template operator()<FirstRecord>();
779 emplaceRecordsFrom.template operator()<NextRecord>();
780
781 return SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>>(_stmt, std::move(fields));
782 }
783
784 /// Queries records of given Record type.
785 ///
786 /// @returns A query builder for the given Record type. The query builder can be used to further refine the
787 /// query.
788 /// The query builder will execute the query when a method like All(), First(n), etc. is called.
789 ///
790 /// @code
791 /// auto const records = dm.Query<Person>()
792 /// .Where(FieldNameOf<&Person::is_active>, "=", true)
793 /// .All();
794 /// @endcode
795 template <typename Record>
797 {
798 std::string fields;
799 Reflection::EnumerateMembers<Record>([&fields]<size_t I, typename Field>() {
800 if (!fields.empty())
801 fields += ", ";
802 fields += '"';
803 fields += RecordTableName<Record>;
804 fields += "\".\"";
805 fields += FieldNameAt<I, Record>;
806 fields += '"';
807 });
808
809 return SqlAllFieldsQueryBuilder<Record>(_stmt, std::move(fields));
810 }
811
812 /// Queries select fields from the given Record type.
813 ///
814 /// The fields are given in form of &Record::field1, &Record::field2, ...
815 ///
816 /// @returns A query builder for the given Record type. The query builder can be used to further refine the query.
817 /// The query builder will execute the query when a method like All(), First(n), etc. is called.
818 ///
819 /// @code
820 /// auto const records = dm.QuerySparse<Person, &Person::id, &Person::name, &Person::age>()
821 /// .Where(FieldNameOf<&Person::is_active>, "=", true)
822 /// .All();
823 /// @endcode
824 template <typename Record, auto... ReferencedFields>
825 SqlSparseFieldQueryBuilder<Record, ReferencedFields...> QuerySparse()
826 {
827 auto const appendFieldTo = []<auto ReferencedField>(std::string& fields) {
828 using ReferencedRecord = Reflection::MemberClassType<ReferencedField>;
829 if (!fields.empty())
830 fields += ", ";
831 fields += '"';
832 fields += RecordTableName<ReferencedRecord>;
833 fields += "\".\"";
834 fields += FieldNameOf<ReferencedField>;
835 fields += '"';
836 };
837 std::string fields;
838 (appendFieldTo.template operator()<ReferencedFields>(fields), ...);
839
840 return SqlSparseFieldQueryBuilder<Record, ReferencedFields...>(_stmt, std::move(fields));
841 }
842
843 /// Checks if the record has any modified fields.
844 template <typename Record>
845 bool IsModified(Record const& record) const noexcept;
846
847 /// Updates the record in the database.
848 template <typename Record>
849 void Update(Record& record);
850
851 /// Deletes the record from the database.
852 template <typename Record>
853 std::size_t Delete(Record const& record);
854
855 /// Counts the total number of records in the database for the given record type.
856 template <typename Record>
857 std::size_t Count();
858
859 /// Loads all records from the database for the given record type.
860 template <typename Record>
861 std::vector<Record> All();
862
863 /// Constructs an SQL query builder for the given record type.
864 template <typename Record>
866 {
867 return _connection.Query(RecordTableName<Record>);
868 }
869
870 /// Constructs an SQL query builder for the given table name.
871 SqlQueryBuilder FromTable(std::string_view tableName)
872 {
873 return _connection.Query(tableName);
874 }
875
876 /// Clears the modified state of the record.
877 template <typename Record>
878 void ClearModifiedState(Record& record) noexcept;
879
880 /// Loads all direct relations to this record.
881 template <typename Record>
882 void LoadRelations(Record& record);
883
884 /// Configures the auto loading of relations for the given record.
885 ///
886 /// This means, that no explicit loading of relations is required.
887 /// The relations are automatically loaded when accessed.
888 template <typename Record>
889 void ConfigureRelationAutoLoading(Record& record);
890
891 private:
892 template <typename Record, typename ValueType>
893 void SetId(Record& record, ValueType&& id);
894
895 template <typename Record, size_t InitialOffset = 1>
896 Record& BindOutputColumns(Record& record);
897
898 template <typename Record, size_t InitialOffset = 1>
899 Record& BindOutputColumns(Record& record, SqlStatement* stmt);
900
901 template <typename ElementMask, typename Record, size_t InitialOffset = 1>
902 Record& BindOutputColumns(Record& record);
903
904 template <typename ElementMask, typename Record, size_t InitialOffset = 1>
905 Record& BindOutputColumns(Record& record, SqlStatement* stmt);
906
907 template <auto ReferencedRecordField, auto BelongsToAlias>
908 void LoadBelongsTo(BelongsTo<ReferencedRecordField, BelongsToAlias>& field);
909
910 template <size_t FieldIndex, typename Record, typename OtherRecord>
911 void LoadHasMany(Record& record, HasMany<OtherRecord>& field);
912
913 template <typename ReferencedRecord, typename ThroughRecord, typename Record>
914 void LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field);
915
916 template <typename ReferencedRecord, typename ThroughRecord, typename Record>
917 void LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field);
918
919 template <size_t FieldIndex, typename Record, typename OtherRecord, typename Callable>
920 void CallOnHasMany(Record& record, Callable const& callback);
921
922 template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename Callable>
923 void CallOnHasManyThrough(Record& record, Callable const& callback);
924
925 SqlConnection _connection;
926 SqlStatement _stmt;
927};
928
929// ------------------------------------------------------------------------------------------------
930
931template <typename Record>
932std::string DataMapper::Inspect(Record const& record)
933{
934 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
935
936 std::string str;
937 Reflection::CallOnMembers(record, [&str]<typename Name, typename Value>(Name const& name, Value const& value) {
938 if (!str.empty())
939 str += '\n';
940
941 if constexpr (FieldWithStorage<Value>)
942 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
943 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
944 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
945 });
946 return "{" + std::move(str) + "}";
947}
948
949template <typename Record>
950std::vector<std::string> DataMapper::CreateTableString(SqlServerType serverType)
951{
952 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
953
954 auto migration = SqlQueryBuilder(*SqlQueryFormatter::Get(serverType)).Migration();
955 auto createTable = migration.CreateTable(RecordTableName<Record>);
956
957 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
958 if constexpr (FieldWithStorage<FieldType>)
959 {
960 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
961 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
962 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
963 else if constexpr (FieldType::IsPrimaryKey)
964 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
965 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
966 else if constexpr (IsBelongsTo<FieldType>)
967 {
968 constexpr size_t referencedFieldIndex = []() constexpr -> size_t {
969 auto index = size_t(-1);
970 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
971 [&index]<size_t J, typename ReferencedFieldType>() constexpr -> void {
972 if constexpr (IsField<ReferencedFieldType>)
973 if constexpr (ReferencedFieldType::IsPrimaryKey)
974 index = J;
975 });
976 return index;
977 }();
978 createTable.ForeignKey(
979 std::string(FieldNameAt<I, Record>),
980 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
982 .tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
983 .columnName =
984 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
985 }
986 else if constexpr (FieldType::IsMandatory)
987 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
988 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
989 else
990 createTable.Column(std::string(FieldNameAt<I, Record>),
991 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
992 }
993 });
994
995 return migration.GetPlan().ToSql();
996}
997
998template <typename FirstRecord, typename... MoreRecords>
999std::vector<std::string> DataMapper::CreateTablesString(SqlServerType serverType)
1000{
1001 std::vector<std::string> output;
1002 auto const append = [&output](auto const& sql) {
1003 output.insert(output.end(), sql.begin(), sql.end());
1004 };
1005 append(CreateTableString<FirstRecord>(serverType));
1006 (append(CreateTableString<MoreRecords>(serverType)), ...);
1007 return output;
1008}
1009
1010template <typename Record>
1012{
1013 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1014
1015 auto const sqlQueryStrings = CreateTableString<Record>(_connection.ServerType());
1016 for (auto const& sqlQueryString: sqlQueryStrings)
1017 _stmt.ExecuteDirect(sqlQueryString);
1018}
1019
1020template <typename FirstRecord, typename... MoreRecords>
1022{
1023 CreateTable<FirstRecord>();
1024 (CreateTable<MoreRecords>(), ...);
1025}
1026
1027template <typename Record>
1028RecordPrimaryKeyType<Record> DataMapper::CreateExplicit(Record const& record)
1029{
1030 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1031
1032 auto query = _connection.Query(RecordTableName<Record>).Insert(nullptr);
1033
1034 Reflection::EnumerateMembers(record, [&query]<auto I, typename FieldType>(FieldType const& /*field*/) {
1035 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1036 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1037 });
1038
1039 _stmt.Prepare(query);
1040
1041 Reflection::CallOnMembers(
1042 record,
1043 [this, i = SQLSMALLINT { 1 }]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
1044 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1045 _stmt.BindInputParameter(i++, field, name);
1046 });
1047
1048 _stmt.Execute();
1049
1050 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1051 return { _stmt.LastInsertId(RecordTableName<Record>) };
1052 else if constexpr (HasPrimaryKey<Record>)
1053 {
1054 RecordPrimaryKeyType<Record> const* primaryKey = nullptr;
1055 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
1056 if constexpr (IsField<FieldType>)
1057 {
1058 if constexpr (FieldType::IsPrimaryKey)
1059 {
1060 primaryKey = &field.Value();
1061 }
1062 }
1063 });
1064 return *primaryKey;
1065 }
1066}
1067
1068template <typename Record>
1069RecordPrimaryKeyType<Record> DataMapper::Create(Record& record)
1070{
1071 static_assert(!std::is_const_v<Record>);
1072 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1073
1074 // If the primary key is not an auto-increment field and the primary key is not set, we need to set it.
1075 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
1076 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
1077 {
1078 if (!primaryKeyField.IsModified())
1079 {
1080 using ValueType = typename PrimaryKeyType::ValueType;
1081 if constexpr (std::same_as<ValueType, SqlGuid>)
1082 {
1083 primaryKeyField = SqlGuid::Create();
1084 }
1085 else if constexpr (requires { ValueType {} + 1; })
1086 {
1087 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1088 std::format(R"sql(SELECT MAX("{}") FROM "{}")sql",
1089 FieldNameAt<PrimaryKeyIndex, Record>,
1090 RecordTableName<Record>));
1091 primaryKeyField = maxId.value_or(ValueType {}) + 1;
1092 }
1093 }
1094 }
1095 });
1096
1097 CreateExplicit(record);
1098
1099 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1100 SetId(record, _stmt.LastInsertId(RecordTableName<Record>));
1101
1102 ClearModifiedState(record);
1104
1105 if constexpr (HasPrimaryKey<Record>)
1106 return GetPrimaryKeyField(record);
1107}
1108
1109template <typename Record>
1110bool DataMapper::IsModified(Record const& record) const noexcept
1111{
1112 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1113
1114 bool modified = false;
1115
1116 Reflection::CallOnMembers(record, [&modified](auto const& /*name*/, auto const& field) {
1117 if constexpr (requires { field.IsModified(); })
1118 {
1119 modified = modified || field.IsModified();
1120 }
1121 });
1122
1123 return modified;
1124}
1125
1126template <typename Record>
1127void DataMapper::Update(Record& record)
1128{
1129 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1130
1131 auto query = _connection.Query(RecordTableName<Record>).Update();
1132
1133 Reflection::CallOnMembersWithoutName(record, [&query]<size_t I, typename FieldType>(FieldType const& field) {
1134 if (field.IsModified())
1135 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1136 // for some reason compiler do not want to properly deduce FieldType, so here we
1137 // directly infer the type from the Record type and index
1138 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1139 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1140 });
1141
1142 _stmt.Prepare(query);
1143
1144 // Bind the SET clause
1145 SQLSMALLINT i = 1;
1146 Reflection::CallOnMembers(
1147 record, [this, &i]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
1148 if (field.IsModified())
1149 _stmt.BindInputParameter(i++, field.Value(), name);
1150 });
1151
1152 // Bind the WHERE clause
1153 Reflection::CallOnMembers(
1154 record, [this, &i]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
1155 if constexpr (FieldType::IsPrimaryKey)
1156 _stmt.BindInputParameter(i++, field.Value(), name);
1157 });
1158
1159 _stmt.Execute();
1160
1161 ClearModifiedState(record);
1162}
1163
1164template <typename Record>
1165std::size_t DataMapper::Delete(Record const& record)
1166{
1167 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1168
1169 auto query = _connection.Query(RecordTableName<Record>).Delete();
1170
1171 Reflection::CallOnMembers(record,
1172 [&query]<typename Name, typename FieldType>(Name const& name, FieldType const& /*field*/) {
1173 if constexpr (FieldType::IsPrimaryKey)
1174 std::ignore = query.Where(name, SqlWildcard);
1175 });
1176
1177 _stmt.Prepare(query);
1178
1179 // Bind the WHERE clause
1180 Reflection::CallOnMembers(
1181 record,
1182 [this, i = SQLSMALLINT { 1 }]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
1183 if constexpr (FieldType::IsPrimaryKey)
1184 _stmt.BindInputParameter(i++, field.Value(), name);
1185 });
1186
1187 _stmt.Execute();
1188
1189 return _stmt.NumRowsAffected();
1190}
1191
1192template <typename Record>
1193size_t DataMapper::Count()
1194{
1195 _stmt.Prepare(_connection.Query(RecordTableName<Record>).Select().Count().ToSql());
1196 _stmt.Execute();
1197
1198 auto result = size_t {};
1199 _stmt.BindOutputColumns(&result);
1200 std::ignore = _stmt.FetchRow();
1201 _stmt.CloseCursor();
1202 return result;
1203}
1204
1205template <typename Record>
1206std::vector<Record> DataMapper::All()
1207{
1208 return Query<Record>(_connection.Query(RecordTableName<Record>).Select().template Fields<Record>().All());
1209}
1210
1211template <typename Record, typename... PrimaryKeyTypes>
1212std::optional<Record> DataMapper::QuerySingleWithoutRelationAutoLoading(PrimaryKeyTypes&&... primaryKeys)
1213{
1214 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1215
1216 auto queryBuilder = _connection.Query(RecordTableName<Record>).Select();
1217
1218 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1219 if constexpr (FieldWithStorage<FieldType>)
1220 {
1221 queryBuilder.Field(FieldNameAt<I, Record>);
1222
1223 if constexpr (FieldType::IsPrimaryKey)
1224 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1225 }
1226 });
1227
1228 _stmt.Prepare(queryBuilder.First());
1229 _stmt.Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1230
1231 auto resultRecord = std::optional<Record> { Record {} };
1232 auto reader = _stmt.GetResultCursor();
1233 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
1234 return std::nullopt;
1235
1236 return resultRecord;
1237}
1238
1239template <typename Record, typename... PrimaryKeyTypes>
1240std::optional<Record> DataMapper::QuerySingle(PrimaryKeyTypes&&... primaryKeys)
1241{
1242 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1243 if (record)
1245 return record;
1246}
1247
1248template <typename Record, typename... Args>
1249std::optional<Record> DataMapper::QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args)
1250{
1251 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1252
1253 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1254 if constexpr (FieldWithStorage<FieldType>)
1255 selectQuery.Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
1256 });
1257 _stmt.Prepare(selectQuery.First().ToSql());
1258 _stmt.Execute(std::forward<Args>(args)...);
1259
1260 auto resultRecord = std::optional<Record> { Record {} };
1261 auto reader = _stmt.GetResultCursor();
1262 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
1263 return std::nullopt;
1264 return resultRecord;
1265}
1266
1267// TODO: Provide Query(QueryBuilder, ...) method variant
1268
1269template <typename Record, typename... InputParameters>
1270inline LIGHTWEIGHT_FORCE_INLINE std::vector<Record> DataMapper::Query(
1271 SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters)
1272{
1273 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>, "Record must satisfy DataMapperRecord");
1274
1275 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1276}
1277
1278template <typename Record, typename... InputParameters>
1279std::vector<Record> DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1280{
1281 auto result = std::vector<Record> {};
1282
1283 if constexpr (std::same_as<Record, SqlVariantRow>)
1284 {
1285 _stmt.Prepare(sqlQueryString);
1286 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1287 size_t const numResultColumns = _stmt.NumColumnsAffected();
1288 while (_stmt.FetchRow())
1289 {
1290 auto& record = result.emplace_back();
1291 record.reserve(numResultColumns);
1292 for (auto const i: std::views::iota(1U, numResultColumns + 1))
1293 record.emplace_back(_stmt.GetColumn<SqlVariant>(static_cast<SQLUSMALLINT>(i)));
1294 }
1295 }
1296 else
1297 {
1298 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1299
1300 _stmt.Prepare(sqlQueryString);
1301 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1302
1303 auto record = Record {};
1304 BindOutputColumns(record);
1306 while (_stmt.FetchRow())
1307 {
1308 result.emplace_back(std::move(record));
1309 record = Record {};
1310 BindOutputColumns(record);
1312 }
1313 }
1314
1315 return result;
1316}
1317
1318template <typename FirstRecord, typename SecondRecord, typename... InputParameters>
1320std::vector<std::tuple<FirstRecord, SecondRecord>> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery,
1321 InputParameters&&... inputParameters)
1322{
1323 auto result = std::vector<std::tuple<FirstRecord, SecondRecord>> {};
1324
1325 _stmt.Prepare(selectQuery.ToSql());
1326 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1327
1328 auto const ConfigureFetchAndBind = [this](auto& record) {
1329 auto& [recordFirst, recordSecond] = record;
1330 // clang-cl gives false possitive error that *this*
1331 // is not used in the lambda, to avoid the warning,
1332 // use it here explicitly
1333 this->BindOutputColumns<FirstRecord, 1>(recordFirst);
1334 this->BindOutputColumns<SecondRecord, Reflection::CountMembers<FirstRecord> + 1>(recordSecond);
1335 this->ConfigureRelationAutoLoading(recordFirst);
1336 this->ConfigureRelationAutoLoading(recordSecond);
1337 };
1338
1339 ConfigureFetchAndBind(result.emplace_back());
1340 while (_stmt.FetchRow())
1341 ConfigureFetchAndBind(result.emplace_back());
1342
1343 // remove the last empty record
1344 if (!result.empty())
1345 result.pop_back();
1346
1347 return result;
1348}
1349
1350template <typename ElementMask, typename Record, typename... InputParameters>
1351std::vector<Record> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery,
1352 InputParameters&&... inputParameters)
1353{
1354 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1355
1356 _stmt.Prepare(selectQuery.ToSql());
1357 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1358
1359 auto records = std::vector<Record> {};
1360
1361 // TODO: We could optimize this further by only considering ElementMask fields in Record.
1362 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
1363
1364 auto reader = _stmt.GetResultCursor();
1365
1366 for (;;)
1367 {
1368 auto& record = records.emplace_back();
1369
1370 if (canSafelyBindOutputColumns)
1371 BindOutputColumns<ElementMask>(record);
1372
1373 if (!reader.FetchRow())
1374 break;
1375
1376 if (!canSafelyBindOutputColumns)
1377 detail::GetAllColumns<ElementMask>(reader, record);
1378 }
1379
1380 // Drop the last record, which we failed to fetch (End of result set).
1381 records.pop_back();
1382
1383 for (auto& record: records)
1385
1386 return records;
1387}
1388
1389template <typename Record>
1390void DataMapper::ClearModifiedState(Record& record) noexcept
1391{
1392 static_assert(!std::is_const_v<Record>);
1393 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1394
1395 Reflection::EnumerateMembers(record, []<size_t I, typename FieldType>(FieldType& field) {
1396 if constexpr (requires { field.SetModified(false); })
1397 {
1398 field.SetModified(false);
1399 }
1400 });
1401}
1402
1403template <typename Record, typename Callable>
1404inline LIGHTWEIGHT_FORCE_INLINE void CallOnPrimaryKey(Record& record, Callable const& callable)
1405{
1406 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1407
1408 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
1409 if constexpr (IsField<FieldType>)
1410 {
1411 if constexpr (FieldType::IsPrimaryKey)
1412 {
1413 return callable.template operator()<I, FieldType>(field);
1414 }
1415 }
1416 });
1417}
1418
1419template <typename Record, typename Callable>
1420inline LIGHTWEIGHT_FORCE_INLINE void CallOnPrimaryKey(Callable const& callable)
1421{
1422 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1423
1424 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1425 if constexpr (IsField<FieldType>)
1426 {
1427 if constexpr (FieldType::IsPrimaryKey)
1428 {
1429 return callable.template operator()<I, FieldType>();
1430 }
1431 }
1432 });
1433}
1434
1435template <typename Record, typename Callable>
1436inline LIGHTWEIGHT_FORCE_INLINE void CallOnBelongsTo(Callable const& callable)
1437{
1438 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1439
1440 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1441 if constexpr (IsBelongsTo<FieldType>)
1442 {
1443 return callable.template operator()<I, FieldType>();
1444 }
1445 });
1446}
1447
1448template <auto ReferencedRecordField, auto BelongsToAlias>
1449void DataMapper::LoadBelongsTo(BelongsTo<ReferencedRecordField, BelongsToAlias>& field)
1450{
1451 using FieldType = BelongsTo<ReferencedRecordField>;
1452 using ReferencedRecord = typename FieldType::ReferencedRecord;
1453
1454 CallOnPrimaryKey<ReferencedRecord>([&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>() {
1455 if (auto result = QuerySingle<ReferencedRecord>(field.Value()); result)
1456 field.EmplaceRecord() = std::move(*result);
1457 else
1459 std::format("Loading BelongsTo failed for {} ({})", RecordTableName<ReferencedRecord>, field.Value()));
1460 });
1461}
1462
1463template <size_t FieldIndex, typename Record, typename OtherRecord, typename Callable>
1464void DataMapper::CallOnHasMany(Record& record, Callable const& callback)
1465{
1466 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1467 static_assert(DataMapperRecord<OtherRecord>, "OtherRecord must satisfy DataMapperRecord");
1468
1469 using FieldType = HasMany<OtherRecord>;
1470 using ReferencedRecord = typename FieldType::ReferencedRecord;
1471
1472 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1473 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
1474 .Select()
1475 .Build([&](auto& query) {
1476 Reflection::EnumerateMembers<ReferencedRecord>(
1477 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1479 {
1480 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1481 }
1482 });
1483 })
1484 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1485 callback(query, primaryKeyField);
1486 });
1487}
1488
1489template <size_t FieldIndex, typename Record, typename OtherRecord>
1490void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1491{
1492 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1493 static_assert(DataMapperRecord<OtherRecord>, "OtherRecord must satisfy DataMapperRecord");
1494
1495 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery, auto& primaryKeyField) {
1496 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1497 });
1498}
1499
1500template <typename ReferencedRecord, typename ThroughRecord, typename Record>
1501void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1502{
1503 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1504 static_assert(DataMapperRecord<ThroughRecord>, "ThroughRecord must satisfy DataMapperRecord");
1505
1506 // Find the PK of Record
1507 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1508 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
1509 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToIndex, typename ThroughBelongsToType>() {
1510 // Find the PK of ThroughRecord
1511 CallOnPrimaryKey<ThroughRecord>([&]<size_t ThroughPrimaryKeyIndex, typename ThroughPrimaryKeyType>() {
1512 // Find the BelongsTo of ReferencedRecord pointing to the PK of ThroughRecord
1513 CallOnBelongsTo<ReferencedRecord>([&]<size_t ReferencedKeyIndex, typename ReferencedKeyType>() {
1514 // Query the ReferencedRecord where:
1515 // - the BelongsTo of ReferencedRecord points to the PK of ThroughRecord,
1516 // - and the BelongsTo of ThroughRecord points to the PK of Record
1517 auto query =
1518 _connection.Query(RecordTableName<ReferencedRecord>)
1519 .Select()
1520 .Build([&](auto& query) {
1521 Reflection::EnumerateMembers<ReferencedRecord>(
1522 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1524 {
1525 query.Field(SqlQualifiedTableColumnName {
1526 RecordTableName<ReferencedRecord>,
1527 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1528 }
1529 });
1530 })
1531 .InnerJoin(RecordTableName<ThroughRecord>,
1532 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1533 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1534 .InnerJoin(RecordTableName<Record>,
1535 FieldNameAt<PrimaryKeyIndex, Record>,
1536 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1537 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1538 .Where(
1540 RecordTableName<Record>,
1541 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1542 },
1543 SqlWildcard);
1544 if (auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1545 {
1546 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1547 }
1548 });
1549 });
1550 });
1551 });
1552}
1553
1554template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename Callable>
1555void DataMapper::CallOnHasManyThrough(Record& record, Callable const& callback)
1556{
1557 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1558
1559 // Find the PK of Record
1560 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1561 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
1562 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToRecordIndex, typename ThroughBelongsToRecordType>() {
1563 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1564 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1565 {
1566 // Find the BelongsTo of ThroughRecord pointing to the PK of ReferencedRecord
1567 CallOnBelongsTo<ThroughRecord>(
1568 [&]<size_t ThroughBelongsToReferenceRecordIndex, typename ThroughBelongsToReferenceRecordType>() {
1569 using ThroughBelongsToReferenceRecordFieldType =
1570 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1571 if constexpr (std::is_same_v<typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1572 ReferencedRecord>)
1573 {
1574 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
1575 .Select()
1576 .Build([&](auto& query) {
1577 Reflection::EnumerateMembers<ReferencedRecord>(
1578 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1580 {
1581 query.Field(SqlQualifiedTableColumnName {
1582 RecordTableName<ReferencedRecord>,
1583 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1584 }
1585 });
1586 })
1587 .InnerJoin(RecordTableName<ThroughRecord>,
1588 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1589 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1590 FieldNameAt<PrimaryKeyIndex, Record> })
1591 .Where(
1593 RecordTableName<ThroughRecord>,
1594 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1595 },
1596 SqlWildcard);
1597 callback(query, primaryKeyField);
1598 }
1599 });
1600 }
1601 });
1602 });
1603}
1604
1605template <typename ReferencedRecord, typename ThroughRecord, typename Record>
1606void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
1607{
1608 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1609
1610 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1611 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
1612 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
1613 });
1614}
1615
1616template <typename Record>
1617void DataMapper::LoadRelations(Record& record)
1618{
1619 static_assert(!std::is_const_v<Record>);
1620 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1621
1622 Reflection::EnumerateMembers(record, [&]<size_t FieldIndex, typename FieldType>(FieldType& field) {
1623 if constexpr (IsBelongsTo<FieldType>)
1624 {
1625 LoadBelongsTo(field);
1626 }
1627 else if constexpr (IsHasMany<FieldType>)
1628 {
1629 LoadHasMany<FieldIndex>(record, field);
1630 }
1631 else if constexpr (IsHasOneThrough<FieldType>)
1632 {
1633 LoadHasOneThrough(record, field);
1634 }
1635 else if constexpr (IsHasManyThrough<FieldType>)
1636 {
1637 LoadHasManyThrough(record, field);
1638 }
1639 });
1640}
1641
1642template <typename Record, typename ValueType>
1643inline LIGHTWEIGHT_FORCE_INLINE void DataMapper::SetId(Record& record, ValueType&& id)
1644{
1645 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1646 // static_assert(HasPrimaryKey<Record>);
1647
1648 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
1649 if constexpr (IsField<FieldType>)
1650 {
1651 if constexpr (FieldType::IsPrimaryKey)
1652 {
1653 field = std::forward<FieldType>(id);
1654 }
1655 }
1656 });
1657}
1658
1659template <typename Record, size_t InitialOffset>
1660inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1661{
1662 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1663 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
1664 return record;
1665}
1666
1667template <typename Record, size_t InitialOffset>
1668Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
1669{
1670 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
1671 record, stmt);
1672}
1673
1674template <typename ElementMask, typename Record, size_t InitialOffset>
1675inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1676{
1677 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1678 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
1679}
1680
1681template <typename ElementMask, typename Record, size_t InitialOffset>
1682Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
1683{
1684 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1685 static_assert(!std::is_const_v<Record>);
1686 assert(stmt != nullptr);
1687
1688 Reflection::EnumerateMembers<ElementMask>(
1689 record, [stmt, i = SQLSMALLINT { InitialOffset }]<size_t I, typename Field>(Field& field) mutable {
1690 if constexpr (IsField<Field>)
1691 {
1692 stmt->BindOutputColumn(i++, &field.MutableValue());
1693 }
1694 else if constexpr (SqlOutputColumnBinder<Field>)
1695 {
1696 stmt->BindOutputColumn(i++, &field);
1697 }
1698 });
1699
1700 return record;
1701}
1702
1703template <typename Record>
1705{
1706 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1707
1708 Reflection::EnumerateMembers(record, [&]<size_t FieldIndex, typename FieldType>(FieldType& field) {
1709 if constexpr (IsBelongsTo<FieldType>)
1710 {
1711 field.SetAutoLoader(typename FieldType::Loader {
1712 .loadReference = [this, &field]() { LoadBelongsTo(field); },
1713 });
1714 }
1715 else if constexpr (IsHasMany<FieldType>)
1716 {
1717 using ReferencedRecord = typename FieldType::ReferencedRecord;
1718 HasMany<ReferencedRecord>& hasMany = field;
1719 hasMany.SetAutoLoader(typename FieldType::Loader {
1720 .count = [this, &record]() -> size_t {
1721 size_t count = 0;
1722 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1723 record, [&](SqlSelectQueryBuilder selectQuery, auto const& primaryKeyField) {
1724 _stmt.Prepare(selectQuery.Count());
1725 _stmt.Execute(primaryKeyField.Value());
1726 if (_stmt.FetchRow())
1727 count = _stmt.GetColumn<size_t>(1);
1728 _stmt.CloseCursor();
1729 });
1730 return count;
1731 },
1732 .all = [this, &record, &hasMany]() { LoadHasMany<FieldIndex>(record, hasMany); },
1733 .each =
1734 [this, &record](auto const& each) {
1735 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1736 record, [&](SqlSelectQueryBuilder selectQuery, auto const& primaryKeyField) {
1737 auto stmt = SqlStatement { _connection };
1738 stmt.Prepare(selectQuery.All());
1739 stmt.Execute(primaryKeyField.Value());
1740
1741 auto referencedRecord = ReferencedRecord {};
1742 BindOutputColumns(referencedRecord, &stmt);
1743 ConfigureRelationAutoLoading(referencedRecord);
1744
1745 while (stmt.FetchRow())
1746 {
1747 each(referencedRecord);
1748 BindOutputColumns(referencedRecord, &stmt);
1749 }
1750 });
1751 },
1752 });
1753 }
1754 else if constexpr (IsHasOneThrough<FieldType>)
1755 {
1756 using ReferencedRecord = typename FieldType::ReferencedRecord;
1757 using ThroughRecord = typename FieldType::ThroughRecord;
1759 hasOneThrough.SetAutoLoader(typename FieldType::Loader {
1760 .loadReference =
1761 [this, &record, &hasOneThrough]() {
1762 LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
1763 },
1764 });
1765 }
1766 else if constexpr (IsHasManyThrough<FieldType>)
1767 {
1768 using ReferencedRecord = typename FieldType::ReferencedRecord;
1769 using ThroughRecord = typename FieldType::ThroughRecord;
1771 hasManyThrough.SetAutoLoader(typename FieldType::Loader {
1772 .count = [this, &record]() -> size_t {
1773 // Load result for Count()
1774 size_t count = 0;
1775 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1776 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
1777 _stmt.Prepare(selectQuery.Count());
1778 _stmt.Execute(primaryKeyField.Value());
1779 if (_stmt.FetchRow())
1780 count = _stmt.GetColumn<size_t>(1);
1781 _stmt.CloseCursor();
1782 });
1783 return count;
1784 },
1785 .all =
1786 [this, &record, &hasManyThrough]() {
1787 // Load result for All()
1788 LoadHasManyThrough(record, hasManyThrough);
1789 },
1790 .each =
1791 [this, &record](auto const& each) {
1792 // Load result for Each()
1793 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1794 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
1795 auto stmt = SqlStatement { _connection };
1796 stmt.Prepare(selectQuery.All());
1797 stmt.Execute(primaryKeyField.Value());
1798
1799 auto referencedRecord = ReferencedRecord {};
1800 BindOutputColumns(referencedRecord, &stmt);
1801 ConfigureRelationAutoLoading(referencedRecord);
1802
1803 while (stmt.FetchRow())
1804 {
1805 each(referencedRecord);
1806 BindOutputColumns(referencedRecord, &stmt);
1807 }
1808 });
1809 },
1810 });
1811 }
1812 });
1813}
Represents a one-to-one relationship.
Definition BelongsTo.hpp:42
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & EmplaceRecord()
Emplaces a record into the relationship. This will mark the relationship as loaded.
LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const & Value() const noexcept
Retrieves the reference to the value of the field.
Main API for mapping records to and from the database using high level C++ syntax.
RecordPrimaryKeyType< Record > CreateExplicit(Record const &record)
Creates a new record in the database.
SqlSparseFieldQueryBuilder< Record, ReferencedFields... > QuerySparse()
std::vector< Record > Query(SqlSelectQueryBuilder::ComposedQuery const &selectQuery, InputParameters &&... inputParameters)
Queries multiple records from the database, based on the given query.
bool IsModified(Record const &record) const noexcept
Checks if the record has any modified fields.
DataMapper(SqlConnection &&connection)
Constructs a new data mapper, using the given connection.
void LoadRelations(Record &record)
Loads all direct relations to this record.
std::size_t Count()
Counts the total number of records in the database for the given record type.
std::vector< Record > All()
Loads all records from the database for the given record type.
std::optional< Record > QuerySingleBy(ColumnName const &columnName, T const &value)
Queries a single record by the given column name and value.
SqlConnection const & Connection() const noexcept
Returns the connection reference used by this data mapper.
std::vector< std::string > CreateTablesString(SqlServerType serverType)
Constructs a string list of SQL queries to create the tables for the given record types.
static std::string Inspect(Record const &record)
Constructs a human readable string representation of the given record.
DataMapper(SqlConnectionString connectionString)
Constructs a new data mapper, using the given connection string.
SqlQuerySingleBuilder< Record > QuerySingle()
Queries a single record from the database.
void CreateTables()
Creates the tables for the given record types.
void CreateTable()
Creates the table for the given record type.
std::vector< std::string > CreateTableString(SqlServerType serverType)
Constructs a string list of SQL queries to create the table for the given record type.
SqlQueryBuilder FromTable(std::string_view tableName)
Constructs an SQL query builder for the given table name.
RecordPrimaryKeyType< Record > Create(Record &record)
Creates a new 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.
void ConfigureRelationAutoLoading(Record &record)
SqlAllFieldsQueryBuilder< Record > Query()
auto BuildQuery() -> SqlQueryBuilder
Constructs an SQL query builder for the given record type.
void Update(Record &record)
Updates the record in the database.
std::size_t Delete(Record const &record)
Deletes the record from the database.
SqlConnection & Connection() noexcept
Returns the mutable connection reference used by this data mapper.
DataMapper()
Constructs a new data mapper, using the default connection.
void ClearModifiedState(Record &record) noexcept
Clears the modified state of the record.
This API represents a many-to-many relationship between two records through a third record.
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of records into this relationship.
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:31
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
Definition HasMany.hpp:129
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of records.
Definition HasMany.hpp:142
Represents a one-to-one relationship through a join table.
void SetAutoLoader(Loader loader)
Used internally to configure on-demand loading of the record.
LIGHTWEIGHT_FORCE_INLINE constexpr void EmplaceRecord(std::shared_ptr< ReferencedRecord > record)
Emplaces the given record into this relationship.
Represents a query builder that retrieves all fields of a record.
Represents a binary data type.
Definition SqlBinary.hpp:17
Represents a connection to a SQL database.
LIGHTWEIGHT_API SqlQueryBuilder Query(std::string_view const &table={}) const
SqlServerType ServerType() const noexcept
Retrieves the type of the server.
SqlQueryFormatter const & QueryFormatter() const noexcept
Retrieves a query formatter suitable for the SQL server being connected.
static 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:26
LIGHTWEIGHT_API SqlInsertQueryBuilder Insert(std::vector< SqlVariant > *boundInputs=nullptr) noexcept
LIGHTWEIGHT_API SqlDeleteQueryBuilder Delete() noexcept
Initiates DELETE query building.
LIGHTWEIGHT_API SqlSelectQueryBuilder Select() noexcept
Initiates SELECT query building.
LIGHTWEIGHT_API SqlUpdateQueryBuilder Update(std::vector< SqlVariant > *boundInputs=nullptr) noexcept
LIGHTWEIGHT_API SqlMigrationQueryBuilder Migration()
Initiates query for building database migrations.
API to format SQL queries for different SQL dialects.
virtual std::string SelectFirst(bool distinct, std::string_view fields, std::string_view fromTable, std::string_view fromTableAlias, std::string_view tableJoins, std::string_view whereCondition, std::string_view orderBy, size_t count) const =0
Constructs an SQL SELECT query for the first row.
static SqlQueryFormatter const * Get(SqlServerType serverType) noexcept
Retrieves the SQL query formatter for the given SqlServerType.
virtual std::string SelectRange(bool distinct, std::string_view fields, std::string_view fromTable, std::string_view fromTableAlias, std::string_view tableJoins, std::string_view whereCondition, std::string_view orderBy, std::string_view groupBy, std::size_t offset, std::size_t limit) const =0
Constructs an SQL SELECT query for a range of rows.
virtual std::string SelectAll(bool distinct, std::string_view fields, std::string_view fromTable, std::string_view fromTableAlias, std::string_view tableJoins, std::string_view whereCondition, std::string_view orderBy, std::string_view groupBy) const =0
Constructs an SQL SELECT query for all rows.
Represents a query builder that retrieves only the first record found.
std::optional< Record > Get()
Executes the query and returns the first record found.
API for reading an SQL query result set.
LIGHTWEIGHT_FORCE_INLINE void BindOutputColumns(Args *... args)
LIGHTWEIGHT_FORCE_INLINE bool FetchRow()
Fetches the next row of the result set.
LIGHTWEIGHT_FORCE_INLINE bool GetColumn(SQLUSMALLINT column, T *result) const
Query builder for building SELECT ... queries.
Definition Select.hpp:81
LIGHTWEIGHT_API ComposedQuery Count()
Finalizes building the query as SELECT COUNT(*) ... query.
LIGHTWEIGHT_API ComposedQuery All()
Finalizes building the query as SELECT field names FROM ... query.
LIGHTWEIGHT_API SqlSelectQueryBuilder & Field(std::string_view const &fieldName)
Adds a single column to the SELECT clause.
LIGHTWEIGHT_API ComposedQuery First(size_t count=1)
Finalizes building the query as SELECT TOP n field names FROM ... query.
Represents a query builder that retrieves only the fields specified.
High level API for (prepared) raw SQL statements.
LIGHTWEIGHT_API SqlConnection & Connection() noexcept
Retrieves the connection associated with this statement.
SqlResultCursor GetResultCursor() noexcept
Retrieves the result cursor for reading an SQL query result.
LIGHTWEIGHT_API size_t NumRowsAffected() const
Retrieves the number of rows affected by the last query.
void Execute(Args const &... args)
Binds the given arguments to the prepared statement and executes it.
void CloseCursor() noexcept
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 NumColumnsAffected() const
Retrieves the number of columns affected by the last query.
LIGHTWEIGHT_API size_t LastInsertId(std::string_view tableName)
Retrieves the last insert ID of the given table.
LIGHTWEIGHT_API bool FetchRow()
void BindOutputColumns(Args *... args)
LIGHTWEIGHT_API void Prepare(std::string_view query) &
bool GetColumn(SQLUSMALLINT column, T *result) const
Represents a record type that can be used with the DataMapper.
Definition Record.hpp:44
LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType< Record > GetPrimaryKeyField(Record const &record) noexcept
constexpr size_t RecordStorageFieldCount
constexpr std::string_view FieldNameAt
Returns the SQL field name of the given field index in the record.
Definition Utils.hpp:163
constexpr bool HasPrimaryKey
Tests if the given record type does contain a primary key.
constexpr bool HasAutoIncrementPrimaryKey
Tests if the given record type does contain an auto increment primary key.
Represents a single column in a table.
Definition Field.hpp:79
constexpr T & MutableValue() noexcept
Definition Field.hpp:296
constexpr T const & Value() const noexcept
Returns the value of the field.
Definition Field.hpp:290
constexpr bool IsModified() const noexcept
Checks if the field has been modified.
Definition Field.hpp:284
Represents an ODBC connection string.
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.
SqlQualifiedTableColumnName represents a column name qualified with a table name.
Definition Core.hpp:40
Represents a value that can be any of the supported SQL data types.