Lightweight 0.20250904.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 <tuple>
24#include <type_traits>
25#include <utility>
26
27namespace Lightweight
28{
29
30/// @defgroup DataMapper Data Mapper
31///
32/// @brief The data mapper is a high level API for mapping records to and from the database using high level C++ syntax.
33
34namespace detail
35{
36 // Converts a container of T to a container of std::shared_ptr<T>.
37 template <template <typename> class Allocator, template <typename, typename> class Container, typename Object>
38 auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
39 {
40 using SharedPtrRecord = std::shared_ptr<Object>;
41 auto sharedPtrContainer = Container<SharedPtrRecord, Allocator<SharedPtrRecord>> {};
42 for (auto& object: container)
43 sharedPtrContainer.emplace_back(std::make_shared<Object>(std::move(object)));
44 return sharedPtrContainer;
45 }
46} // namespace detail
47
48/// @brief Main API for mapping records to and from the database using high level C++ syntax.
49///
50/// A DataMapper instances operates on a single SQL connection and provides methods to
51/// create, read, update and delete records in the database.
52///
53/// @see Field, BelongsTo, HasMany, HasManyThrough, HasOneThrough
54/// @ingroup DataMapper
55///
56/// @code
57/// struct Person
58/// {
59/// Field<SqlGuid, PrimaryKey::AutoAssign> id;
60/// Field<SqlAnsiString<30>> name;
61/// Field<SqlAnsiString<40>> email;
62/// };
63///
64/// auto dm = DataMapper {};
65///
66/// // Create a new person record
67/// auto person = Person { .id = SqlGuid::Create(), .name = "John Doe", .email = "johnt@doe.com" };
68///
69/// // Create the record in the database and set the primary key on the record
70/// auto const personId = dm.Create(person);
71///
72/// // Query the person record from the database
73/// auto const queriedPerson = dm.Query<Person>(personId)
74/// .Where(FieldNameOf<&Person::id>, "=", personId)
75/// .First();
76///
77/// if (queriedPerson.has_value())
78/// std::println("Queried Person: {}", DataMapper::Inspect(queriedPerson.value()));
79///
80/// // Update the person record in the database
81/// person.email = "alt@doe.com";
82/// dm.Update(person);
83///
84/// // Delete the person record from the database
85/// dm.Delete(person);
86/// @endcode
88{
89 public:
90 /// Constructs a new data mapper, using the default connection.
92 _connection {},
93 _stmt { _connection }
94 {
95 }
96
97 /// Constructs a new data mapper, using the given connection.
98 explicit DataMapper(SqlConnection&& connection):
99 _connection { std::move(connection) },
100 _stmt { _connection }
101 {
102 }
103
104 /// Constructs a new data mapper, using the given connection string.
105 explicit DataMapper(SqlConnectionString connectionString):
106 _connection { std::move(connectionString) },
107 _stmt { _connection }
108 {
109 }
110
111 DataMapper(DataMapper const&) = delete;
112 DataMapper(DataMapper&&) noexcept = default;
113 DataMapper& operator=(DataMapper const&) = delete;
114 DataMapper& operator=(DataMapper&&) noexcept = default;
115 ~DataMapper() = default;
116
117 /// Returns the connection reference used by this data mapper.
118 [[nodiscard]] SqlConnection const& Connection() const noexcept
119 {
120 return _connection;
121 }
122
123 /// Returns the mutable connection reference used by this data mapper.
124 [[nodiscard]] SqlConnection& Connection() noexcept
125 {
126 return _connection;
127 }
128
129 /// Constructs a human readable string representation of the given record.
130 template <typename Record>
131 static std::string Inspect(Record const& record);
132
133 /// Constructs a string list of SQL queries to create the table for the given record type.
134 template <typename Record>
135 std::vector<std::string> CreateTableString(SqlServerType serverType);
136
137 /// Constructs a string list of SQL queries to create the tables for the given record types.
138 template <typename FirstRecord, typename... MoreRecords>
139 std::vector<std::string> CreateTablesString(SqlServerType serverType);
140
141 /// Creates the table for the given record type.
142 template <typename Record>
143 void CreateTable();
144
145 /// Creates the tables for the given record types.
146 template <typename FirstRecord, typename... MoreRecords>
147 void CreateTables();
148
149 /// @brief Creates a new record in the database.
150 ///
151 /// The record is inserted into the database and the primary key is set on this record.
152 ///
153 /// @return The primary key of the newly created record.
154 template <typename Record>
155 RecordPrimaryKeyType<Record> Create(Record& record);
156
157 /// @brief Creates a new record in the database.
158 ///
159 /// @note This is a variation of the Create() method and does not update the record's primary key.
160 ///
161 /// @return The primary key of the newly created record.
162 template <typename Record>
163 RecordPrimaryKeyType<Record> CreateExplicit(Record const& record);
164
165 /// @brief Queries a single record (based on primary key) from the database.
166 ///
167 /// The primary key(s) are used to identify the record to load.
168 /// If the record is not found, std::nullopt is returned.
169 template <typename Record, typename... PrimaryKeyTypes>
170 std::optional<Record> QuerySingle(PrimaryKeyTypes&&... primaryKeys);
171
172 /// @brief Queries a single record (based on primary key) from the database without auto-loading relations.
173 ///
174 /// The primary key(s) are used to identify the record to load.
175 ///
176 /// Main goal of this function is to load record without relationships to
177 /// decrease compilation time and work around some limitations of template instantiation
178 /// depth on MSVC compiler.
179 template <typename Record, typename... PrimaryKeyTypes>
180 std::optional<Record> QuerySingleWithoutRelationAutoLoading(PrimaryKeyTypes&&... primaryKeys);
181
182 /// Queries multiple records from the database, based on the given query.
183 template <typename Record, typename... InputParameters>
184 std::vector<Record> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters);
185
186 /// Queries multiple records and returns them as a vector of std::tuple of the given record types.
187 /// This can be used to query multiple record types in a single query.
188 ///
189 /// example:
190 /// @code
191 /// struct Person
192 /// {
193 /// int id;
194 /// std::string name;
195 /// std::string email;
196 /// std::string phone;
197 /// };
198 /// struct Address
199 /// {
200 /// int id;
201 /// std::string address;
202 /// std::string city;
203 /// std::string country;
204 /// };
205 /// void example(DataMapper& dm)
206 /// {
207 /// auto const sqlQueryString = R"(SELECT p.*, a.* FROM "Person" p INNER JOIN "Address" a ON p.id = a.id WHERE p.city =
208 /// Berlin AND a.country = Germany)";
209 /// auto const records = dm.QueryToTuple<Person, Address>(sqlQueryString);
210 /// for (auto const& [person, address] : records)
211 /// {
212 /// std::println("Person: {}", DataMapper::Inspect(person));
213 /// std::println("Address: {}", DataMapper::Inspect(address));
214 /// }
215 /// }
216 /// @endcode
217 template <typename... Records>
218 requires DataMapperRecords<Records...>
219 std::vector<std::tuple<Records...>> QueryToTuple(SqlSelectQueryBuilder::ComposedQuery const& selectQuery);
220
221 /// Queries multiple records from the database, based on the given query.
222 ///
223 /// @param sqlQueryString The SQL query string to execute.
224 /// @param inputParameters The input parameters for the query to be bound before executing.
225 /// @return A vector of records of the given type that were found via the query.
226 ///
227 /// example:
228 /// @code
229 /// struct Person
230 /// {
231 /// int id;
232 /// std::string name;
233 /// std::string email;
234 /// std::string phone;
235 /// std::string address;
236 /// std::string city;
237 /// std::string country;
238 /// };
239 ///
240 /// void example(DataMapper& dm)
241 /// {
242 /// auto const sqlQueryString = R"(SELECT * FROM "Person" WHERE "city" = ? AND "country" = ?)";
243 /// auto const records = dm.Query<Person>(sqlQueryString, "Berlin", "Germany");
244 /// for (auto const& record: records)
245 /// {
246 /// std::println("Person: {}", DataMapper::Inspect(record));
247 /// }
248 /// }
249 /// @endcode
250 template <typename Record, typename... InputParameters>
251 std::vector<Record> Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
252
253 /// Queries records from the database, based on the given query and can be used to retrieve only part of the record
254 /// by specifying the ElementMask.
255 ///
256 /// example:
257 /// @code
258 ///
259 /// struct Person
260 /// {
261 /// int id;
262 /// std::string name;
263 /// std::string email;
264 /// std::string phone;
265 /// std::string address;
266 /// std::string city;
267 /// std::string country;
268 /// };
269 ///
270 /// auto infos = dm.Query<SqlElements<1,5>(RecordTableName<Person>.Fields({"name"sv, "city"sv}));
271 ///
272 /// for(auto const& info : infos)
273 /// {
274 /// // only info.name and info.city are loaded
275 /// }
276 /// @endcode
277 template <typename ElementMask, typename Record, typename... InputParameters>
278 std::vector<Record> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters);
279
280 /// Queries records of different types from the database, based on the given query.
281 /// User can constructed query that selects columns from the multiple tables
282 /// this function is uset to get result of the
283 ///
284 /// example:
285 /// @code
286 ///
287 /// struct JointA{};
288 /// struct JointB{};
289 /// struct JointC{};
290 ///
291 /// // the following query will construct statement to fetch all elements of JointA and JointC types
292 /// auto dm = DataMapper {};
293 /// auto const query = dm.FromTable(RecordTableName<JoinTestA>)
294 /// .Select()
295 /// .Fields<JointA, JointC>()
296 /// .InnerJoin<&JointB::a_id, &JointA::id>()
297 /// .InnerJoin<&JointC::id, &JointB::c_id>()
298 /// .All();
299 /// auto const records = dm.Query<JointA, JointC>(query);
300 /// for(const auto [elementA, elementC] : records)
301 /// {
302 /// // do something with elementA and elementC
303 /// }
304 template <typename FirstRecord, typename NextRecord, typename... InputParameters>
306 // TODO : need more generic one and we also have queryToTuple
307 std::vector<std::tuple<FirstRecord, NextRecord>> Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery,
308 InputParameters&&... inputParameters);
309
310 /// Queries records of different types from the database, based on the given query.
311 template <typename FirstRecord, typename NextRecord>
313 // TODO : need more generic one and we also have queryToTuple
315 {
316 std::string fields;
317
318 auto const emplaceRecordsFrom = [&fields]<typename Record>() {
319 Reflection::EnumerateMembers<Record>([&fields]<size_t I, typename Field>() {
320 if (!fields.empty())
321 fields += ", ";
322 fields += std::format(R"("{}"."{}")", RecordTableName<Record>, FieldNameAt<I, Record>);
323 });
324 };
325
326 emplaceRecordsFrom.template operator()<FirstRecord>();
327 emplaceRecordsFrom.template operator()<NextRecord>();
328
329 return SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>>(_stmt, std::move(fields));
330 }
331
332 /// Queries records of given Record type.
333 ///
334 /// The query builder can be used to further refine the query.
335 /// The query builder will execute the query when a method like All(), First(n), etc. is called.
336 ///
337 /// @returns A query builder for the given Record type.
338 ///
339 /// @code
340 /// auto const records = dm.Query<Person>()
341 /// .Where(FieldNameOf<&Person::is_active>, "=", true)
342 /// .All();
343 /// @endcode
344 template <typename Record>
346 {
347 std::string fields;
348 Reflection::EnumerateMembers<Record>([&fields]<size_t I, typename Field>() {
349 if (!fields.empty())
350 fields += ", ";
351 fields += '"';
352 fields += RecordTableName<Record>;
353 fields += "\".\"";
354 fields += FieldNameAt<I, Record>;
355 fields += '"';
356 });
357 return SqlAllFieldsQueryBuilder<Record>(_stmt, std::move(fields));
358 }
359
360 /// Updates the record in the database.
361 template <typename Record>
362 void Update(Record& record);
363
364 /// Deletes the record from the database.
365 template <typename Record>
366 std::size_t Delete(Record const& record);
367
368 /// Constructs an SQL query builder for the given table name.
369 SqlQueryBuilder FromTable(std::string_view tableName)
370 {
371 return _connection.Query(tableName);
372 }
373
374 /// Checks if the record has any modified fields.
375 template <typename Record>
376 bool IsModified(Record const& record) const noexcept;
377
378 /// Clears the modified state of the record.
379 template <typename Record>
380 void ClearModifiedState(Record& record) noexcept;
381
382 /// Loads all direct relations to this record.
383 template <typename Record>
384 void LoadRelations(Record& record);
385
386 /// Configures the auto loading of relations for the given record.
387 ///
388 /// This means, that no explicit loading of relations is required.
389 /// The relations are automatically loaded when accessed.
390 template <typename Record>
391 void ConfigureRelationAutoLoading(Record& record);
392
393 private:
394 /// @brief Queries a single record from the database based on the given query.
395 ///
396 /// @param selectQuery The SQL select query to execute.
397 /// @param args The input parameters for the query.
398 ///
399 /// @return The record if found, otherwise std::nullopt.
400 template <typename Record, typename... Args>
401 std::optional<Record> QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args);
402
403 template <typename Record, typename ValueType>
404 void SetId(Record& record, ValueType&& id);
405
406 template <typename Record, size_t InitialOffset = 1>
407 Record& BindOutputColumns(Record& record);
408
409 template <typename Record, size_t InitialOffset = 1>
410 Record& BindOutputColumns(Record& record, SqlStatement* stmt);
411
412 template <typename ElementMask, typename Record, size_t InitialOffset = 1>
413 Record& BindOutputColumns(Record& record);
414
415 template <typename ElementMask, typename Record, size_t InitialOffset = 1>
416 Record& BindOutputColumns(Record& record, SqlStatement* stmt);
417
418 template <typename FieldType>
419 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(typename FieldType::ValueType value);
420
421 template <size_t FieldIndex, typename Record, typename OtherRecord>
422 void LoadHasMany(Record& record, HasMany<OtherRecord>& field);
423
424 template <typename ReferencedRecord, typename ThroughRecord, typename Record>
425 void LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field);
426
427 template <typename ReferencedRecord, typename ThroughRecord, typename Record>
428 void LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field);
429
430 template <size_t FieldIndex, typename Record, typename OtherRecord, typename Callable>
431 void CallOnHasMany(Record& record, Callable const& callback);
432
433 template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename Callable>
434 void CallOnHasManyThrough(Record& record, Callable const& callback);
435
436 SqlConnection _connection;
437 SqlStatement _stmt;
438};
439
440// ------------------------------------------------------------------------------------------------
441
442template <typename Record>
443std::string DataMapper::Inspect(Record const& record)
444{
445 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
446
447 std::string str;
448 Reflection::CallOnMembers(record, [&str]<typename Name, typename Value>(Name const& name, Value const& value) {
449 if (!str.empty())
450 str += '\n';
451
452 if constexpr (FieldWithStorage<Value>)
453 {
454 if constexpr (Value::IsOptional)
455 {
456 if (!value.Value().has_value())
457 {
458 str += std::format("{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
459 }
460 else
461 {
462 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
463 }
464 }
465 else if constexpr (IsBelongsTo<Value>)
466 {
467 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
468 }
469 else if constexpr (std::same_as<typename Value::ValueType, char>)
470 {
471 }
472 else
473 {
474 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
475 }
476 }
477 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
478 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
479 });
480 return "{\n" + std::move(str) + "\n}";
481}
482
483template <typename Record>
484std::vector<std::string> DataMapper::CreateTableString(SqlServerType serverType)
485{
486 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
487
488 auto migration = SqlQueryBuilder(*SqlQueryFormatter::Get(serverType)).Migration();
489 auto createTable = migration.CreateTable(RecordTableName<Record>);
490
491 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
492 if constexpr (FieldWithStorage<FieldType>)
493 {
494 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
495 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
496 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
497 else if constexpr (FieldType::IsPrimaryKey)
498 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
499 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
500 else if constexpr (IsBelongsTo<FieldType>)
501 {
502 constexpr size_t referencedFieldIndex = []() constexpr -> size_t {
503 auto index = size_t(-1);
504 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
505 [&index]<size_t J, typename ReferencedFieldType>() constexpr -> void {
506 if constexpr (IsField<ReferencedFieldType>)
507 if constexpr (ReferencedFieldType::IsPrimaryKey)
508 index = J;
509 });
510 return index;
511 }();
512 createTable.ForeignKey(
513 std::string(FieldNameAt<I, Record>),
514 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
516 .tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
517 .columnName =
518 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
519 }
520 else if constexpr (FieldType::IsMandatory)
521 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
522 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
523 else
524 createTable.Column(std::string(FieldNameAt<I, Record>),
525 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
526 }
527 });
528
529 return migration.GetPlan().ToSql();
530}
531
532template <typename FirstRecord, typename... MoreRecords>
533std::vector<std::string> DataMapper::CreateTablesString(SqlServerType serverType)
534{
535 std::vector<std::string> output;
536 auto const append = [&output](auto const& sql) {
537 output.insert(output.end(), sql.begin(), sql.end());
538 };
539 append(CreateTableString<FirstRecord>(serverType));
540 (append(CreateTableString<MoreRecords>(serverType)), ...);
541 return output;
542}
543
544template <typename Record>
546{
547 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
548
549 auto const sqlQueryStrings = CreateTableString<Record>(_connection.ServerType());
550 for (auto const& sqlQueryString: sqlQueryStrings)
551 _stmt.ExecuteDirect(sqlQueryString);
552}
553
554template <typename FirstRecord, typename... MoreRecords>
556{
557 CreateTable<FirstRecord>();
558 (CreateTable<MoreRecords>(), ...);
559}
560
561template <typename Record>
562RecordPrimaryKeyType<Record> DataMapper::CreateExplicit(Record const& record)
563{
564 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
565
566 auto query = _connection.Query(RecordTableName<Record>).Insert(nullptr);
567
568 Reflection::EnumerateMembers(record, [&query]<auto I, typename FieldType>(FieldType const& /*field*/) {
569 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
570 query.Set(FieldNameAt<I, Record>, SqlWildcard);
571 });
572
573 _stmt.Prepare(query);
574
575 Reflection::CallOnMembers(
576 record,
577 [this, i = SQLSMALLINT { 1 }]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
578 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
579 _stmt.BindInputParameter(i++, field, name);
580 });
581
582 _stmt.Execute();
583
584 if constexpr (HasAutoIncrementPrimaryKey<Record>)
585 return { _stmt.LastInsertId(RecordTableName<Record>) };
586 else if constexpr (HasPrimaryKey<Record>)
587 {
588 RecordPrimaryKeyType<Record> const* primaryKey = nullptr;
589 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
590 if constexpr (IsField<FieldType>)
591 {
592 if constexpr (FieldType::IsPrimaryKey)
593 {
594 primaryKey = &field.Value();
595 }
596 }
597 });
598 return *primaryKey;
599 }
600}
601
602template <typename Record>
603RecordPrimaryKeyType<Record> DataMapper::Create(Record& record)
604{
605 static_assert(!std::is_const_v<Record>);
606 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
607
608 // If the primary key is not an auto-increment field and the primary key is not set, we need to set it.
609 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
610 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
611 {
612 if (!primaryKeyField.IsModified())
613 {
614 using ValueType = typename PrimaryKeyType::ValueType;
615 if constexpr (std::same_as<ValueType, SqlGuid>)
616 {
617 primaryKeyField = SqlGuid::Create();
618 }
619 else if constexpr (requires { ValueType {} + 1; })
620 {
621 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
622 std::format(R"sql(SELECT MAX("{}") FROM "{}")sql",
623 FieldNameAt<PrimaryKeyIndex, Record>,
624 RecordTableName<Record>));
625 primaryKeyField = maxId.value_or(ValueType {}) + 1;
626 }
627 }
628 }
629 });
630
631 CreateExplicit(record);
632
633 if constexpr (HasAutoIncrementPrimaryKey<Record>)
634 SetId(record, _stmt.LastInsertId(RecordTableName<Record>));
635
636 ClearModifiedState(record);
638
639 if constexpr (HasPrimaryKey<Record>)
640 return GetPrimaryKeyField(record);
641}
642
643template <typename Record>
644bool DataMapper::IsModified(Record const& record) const noexcept
645{
646 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
647
648 bool modified = false;
649
650 Reflection::CallOnMembers(record, [&modified](auto const& /*name*/, auto const& field) {
651 if constexpr (requires { field.IsModified(); })
652 {
653 modified = modified || field.IsModified();
654 }
655 });
656
657 return modified;
658}
659
660template <typename Record>
661void DataMapper::Update(Record& record)
662{
663 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
664
665 auto query = _connection.Query(RecordTableName<Record>).Update();
666
667 Reflection::CallOnMembersWithoutName(record, [&query]<size_t I, typename FieldType>(FieldType const& field) {
668 if (field.IsModified())
669 query.Set(FieldNameAt<I, Record>, SqlWildcard);
670 // for some reason compiler do not want to properly deduce FieldType, so here we
671 // directly infer the type from the Record type and index
672 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
673 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
674 });
675
676 _stmt.Prepare(query);
677
678 // Bind the SET clause
679 SQLSMALLINT i = 1;
680 Reflection::CallOnMembers(
681 record, [this, &i]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
682 if (field.IsModified())
683 _stmt.BindInputParameter(i++, field.Value(), name);
684 });
685
686 // Bind the WHERE clause
687 Reflection::CallOnMembers(
688 record, [this, &i]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
689 if constexpr (FieldType::IsPrimaryKey)
690 _stmt.BindInputParameter(i++, field.Value(), name);
691 });
692
693 _stmt.Execute();
694
695 ClearModifiedState(record);
696}
697
698template <typename Record>
699std::size_t DataMapper::Delete(Record const& record)
700{
701 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
702
703 auto query = _connection.Query(RecordTableName<Record>).Delete();
704
705 Reflection::CallOnMembers(record,
706 [&query]<typename Name, typename FieldType>(Name const& name, FieldType const& /*field*/) {
707 if constexpr (FieldType::IsPrimaryKey)
708 std::ignore = query.Where(name, SqlWildcard);
709 });
710
711 _stmt.Prepare(query);
712
713 // Bind the WHERE clause
714 Reflection::CallOnMembers(
715 record,
716 [this, i = SQLSMALLINT { 1 }]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
717 if constexpr (FieldType::IsPrimaryKey)
718 _stmt.BindInputParameter(i++, field.Value(), name);
719 });
720
721 _stmt.Execute();
722
723 return _stmt.NumRowsAffected();
724}
725
726template <typename Record, typename... PrimaryKeyTypes>
727std::optional<Record> DataMapper::QuerySingleWithoutRelationAutoLoading(PrimaryKeyTypes&&... primaryKeys)
728{
729 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
730
731 auto queryBuilder = _connection.Query(RecordTableName<Record>).Select();
732
733 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
734 if constexpr (FieldWithStorage<FieldType>)
735 {
736 queryBuilder.Field(FieldNameAt<I, Record>);
737
738 if constexpr (FieldType::IsPrimaryKey)
739 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
740 }
741 });
742
743 _stmt.Prepare(queryBuilder.First());
744 _stmt.Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
745
746 auto resultRecord = std::optional<Record> { Record {} };
747 auto reader = _stmt.GetResultCursor();
748 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
749 return std::nullopt;
750
751 return resultRecord;
752}
753
754template <typename Record, typename... PrimaryKeyTypes>
755std::optional<Record> DataMapper::QuerySingle(PrimaryKeyTypes&&... primaryKeys)
756{
757 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
758 if (record)
760 return record;
761}
762
763template <typename Record, typename... Args>
764std::optional<Record> DataMapper::QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args)
765{
766 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
767
768 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
769 if constexpr (FieldWithStorage<FieldType>)
770 selectQuery.Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
771 });
772 _stmt.Prepare(selectQuery.First().ToSql());
773 _stmt.Execute(std::forward<Args>(args)...);
774
775 auto resultRecord = std::optional<Record> { Record {} };
776 auto reader = _stmt.GetResultCursor();
777 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
778 return std::nullopt;
779 return resultRecord;
780}
781
782// TODO: Provide Query(QueryBuilder, ...) method variant
783
784template <typename Record, typename... InputParameters>
785inline LIGHTWEIGHT_FORCE_INLINE std::vector<Record> DataMapper::Query(
786 SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters)
787{
788 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>, "Record must satisfy DataMapperRecord");
789
790 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
791}
792
793template <typename Record, typename... InputParameters>
794std::vector<Record> DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
795{
796 auto result = std::vector<Record> {};
797
798 if constexpr (std::same_as<Record, SqlVariantRow>)
799 {
800 _stmt.Prepare(sqlQueryString);
801 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
802 size_t const numResultColumns = _stmt.NumColumnsAffected();
803 while (_stmt.FetchRow())
804 {
805 auto& record = result.emplace_back();
806 record.reserve(numResultColumns);
807 for (auto const i: std::views::iota(1U, numResultColumns + 1))
808 record.emplace_back(_stmt.GetColumn<SqlVariant>(static_cast<SQLUSMALLINT>(i)));
809 }
810 }
811 else
812 {
813 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
814
815 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
816
817 _stmt.Prepare(sqlQueryString);
818 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
819
820 auto reader = _stmt.GetResultCursor();
821
822 for (;;)
823 {
824 auto& record = result.emplace_back();
825
826 if (canSafelyBindOutputColumns)
827 BindOutputColumns(record);
828
829 if (!reader.FetchRow())
830 break;
831
832 if (!canSafelyBindOutputColumns)
833 detail::GetAllColumns(reader, record);
834 }
835
836 // Drop the last record, which we failed to fetch (End of result set).
837 result.pop_back();
838
839 for (auto& record: result)
841 }
842
843 return result;
844}
845
846template <typename... Records>
847 requires DataMapperRecords<Records...>
848std::vector<std::tuple<Records...>> DataMapper::QueryToTuple(SqlSelectQueryBuilder::ComposedQuery const& selectQuery)
849{
850 using value_type = std::tuple<Records...>;
851 auto result = std::vector<value_type> {};
852
853 _stmt.Prepare(selectQuery.ToSql());
854 _stmt.Execute();
855 auto reader = _stmt.GetResultCursor();
856
857 constexpr auto calculateOffset = []<size_t I, typename Tuple>() {
858 size_t offset = 1;
859
860 if constexpr (I > 0)
861 {
862 [&]<size_t... Indices>(std::index_sequence<Indices...>) {
863 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
864 }(std::make_index_sequence<I> {});
865 }
866 return offset;
867 };
868
869 auto const BindElements = [&](auto& record) {
870 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
871 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
872 auto& element = std::get<I>(record);
873 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
874 this->BindOutputColumns<TupleElement, offset>(element);
875 });
876 };
877
878 auto const GetElements = [&](auto& record) {
879 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
880 auto& element = std::get<I>(record);
881 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
882 detail::GetAllColumns(reader, element, offset - 1);
883 });
884 };
885
886 bool const canSafelyBindOutputColumns = [&]() {
887 bool result = true;
888 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
889 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
890 result &= detail::CanSafelyBindOutputColumns<TupleElement>(_stmt.Connection().ServerType());
891 });
892 return result;
893 }();
894
895 for (;;)
896 {
897 auto& record = result.emplace_back();
898
899 if (canSafelyBindOutputColumns)
900 BindElements(record);
901
902 if (!reader.FetchRow())
903 break;
904
905 if (!canSafelyBindOutputColumns)
906 GetElements(record);
907 }
908
909 // Drop the last record, which we failed to fetch (End of result set).
910 result.pop_back();
911
912 for (auto& record: result)
913 {
914 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
915 auto& element = std::get<I>(record);
917 });
918 }
919
920 return result;
921}
922
923template <typename FirstRecord, typename SecondRecord, typename... InputParameters>
925std::vector<std::tuple<FirstRecord, SecondRecord>> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery,
926 InputParameters&&... inputParameters)
927{
928 auto result = std::vector<std::tuple<FirstRecord, SecondRecord>> {};
929
930 _stmt.Prepare(selectQuery.ToSql());
931 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
932
933 auto const ConfigureFetchAndBind = [this](auto& record) {
934 auto& [recordFirst, recordSecond] = record;
935 // clang-cl gives false possitive error that *this*
936 // is not used in the lambda, to avoid the warning,
937 // use it here explicitly
938 this->BindOutputColumns<FirstRecord, 1>(recordFirst);
939 this->BindOutputColumns<SecondRecord, Reflection::CountMembers<FirstRecord> + 1>(recordSecond);
940 this->ConfigureRelationAutoLoading(recordFirst);
941 this->ConfigureRelationAutoLoading(recordSecond);
942 };
943
944 ConfigureFetchAndBind(result.emplace_back());
945 while (_stmt.FetchRow())
946 ConfigureFetchAndBind(result.emplace_back());
947
948 // remove the last empty record
949 if (!result.empty())
950 result.pop_back();
951
952 return result;
953}
954
955template <typename ElementMask, typename Record, typename... InputParameters>
956std::vector<Record> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery,
957 InputParameters&&... inputParameters)
958{
959 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
960
961 _stmt.Prepare(selectQuery.ToSql());
962 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
963
964 auto records = std::vector<Record> {};
965
966 // TODO: We could optimize this further by only considering ElementMask fields in Record.
967 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
968
969 auto reader = _stmt.GetResultCursor();
970
971 for (;;)
972 {
973 auto& record = records.emplace_back();
974
975 if (canSafelyBindOutputColumns)
976 BindOutputColumns<ElementMask>(record);
977
978 if (!reader.FetchRow())
979 break;
980
981 if (!canSafelyBindOutputColumns)
982 detail::GetAllColumns<ElementMask>(reader, record);
983 }
984
985 // Drop the last record, which we failed to fetch (End of result set).
986 records.pop_back();
987
988 for (auto& record: records)
990
991 return records;
992}
993
994template <typename Record>
995void DataMapper::ClearModifiedState(Record& record) noexcept
996{
997 static_assert(!std::is_const_v<Record>);
998 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
999
1000 Reflection::EnumerateMembers(record, []<size_t I, typename FieldType>(FieldType& field) {
1001 if constexpr (requires { field.SetModified(false); })
1002 {
1003 field.SetModified(false);
1004 }
1005 });
1006}
1007
1008template <typename Record, typename Callable>
1009inline LIGHTWEIGHT_FORCE_INLINE void CallOnPrimaryKey(Record& record, Callable const& callable)
1010{
1011 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1012
1013 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
1014 if constexpr (IsField<FieldType>)
1015 {
1016 if constexpr (FieldType::IsPrimaryKey)
1017 {
1018 return callable.template operator()<I, FieldType>(field);
1019 }
1020 }
1021 });
1022}
1023
1024template <typename Record, typename Callable>
1025inline LIGHTWEIGHT_FORCE_INLINE void CallOnPrimaryKey(Callable const& callable)
1026{
1027 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1028
1029 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1030 if constexpr (IsField<FieldType>)
1031 {
1032 if constexpr (FieldType::IsPrimaryKey)
1033 {
1034 return callable.template operator()<I, FieldType>();
1035 }
1036 }
1037 });
1038}
1039
1040template <typename Record, typename Callable>
1041inline LIGHTWEIGHT_FORCE_INLINE void CallOnBelongsTo(Callable const& callable)
1042{
1043 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1044
1045 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1046 if constexpr (IsBelongsTo<FieldType>)
1047 {
1048 return callable.template operator()<I, FieldType>();
1049 }
1050 });
1051}
1052
1053template <typename FieldType>
1054std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(typename FieldType::ValueType value)
1055{
1056 using ReferencedRecord = typename FieldType::ReferencedRecord;
1057
1058 std::optional<ReferencedRecord> record { std::nullopt };
1059 CallOnPrimaryKey<ReferencedRecord>([&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>() {
1060 if (auto result = QuerySingle<ReferencedRecord>(value); result)
1061 record = std::move(result);
1062 else
1064 std::format("Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1065 });
1066 return record;
1067}
1068
1069template <size_t FieldIndex, typename Record, typename OtherRecord, typename Callable>
1070void DataMapper::CallOnHasMany(Record& record, Callable const& callback)
1071{
1072 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1073 static_assert(DataMapperRecord<OtherRecord>, "OtherRecord must satisfy DataMapperRecord");
1074
1075 using FieldType = HasMany<OtherRecord>;
1076 using ReferencedRecord = typename FieldType::ReferencedRecord;
1077
1078 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1079 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
1080 .Select()
1081 .Build([&](auto& query) {
1082 Reflection::EnumerateMembers<ReferencedRecord>(
1083 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1084 if constexpr (FieldWithStorage<ReferencedFieldType>)
1085 {
1086 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1087 }
1088 });
1089 })
1090 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1091 callback(query, primaryKeyField);
1092 });
1093}
1094
1095template <size_t FieldIndex, typename Record, typename OtherRecord>
1096void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1097{
1098 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1099 static_assert(DataMapperRecord<OtherRecord>, "OtherRecord must satisfy DataMapperRecord");
1100
1101 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery, auto& primaryKeyField) {
1102 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1103 });
1104}
1105
1106template <typename ReferencedRecord, typename ThroughRecord, typename Record>
1107void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1108{
1109 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1110 static_assert(DataMapperRecord<ThroughRecord>, "ThroughRecord must satisfy DataMapperRecord");
1111
1112 // Find the PK of Record
1113 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1114 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
1115 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToIndex, typename ThroughBelongsToType>() {
1116 // Find the PK of ThroughRecord
1117 CallOnPrimaryKey<ThroughRecord>([&]<size_t ThroughPrimaryKeyIndex, typename ThroughPrimaryKeyType>() {
1118 // Find the BelongsTo of ReferencedRecord pointing to the PK of ThroughRecord
1119 CallOnBelongsTo<ReferencedRecord>([&]<size_t ReferencedKeyIndex, typename ReferencedKeyType>() {
1120 // Query the ReferencedRecord where:
1121 // - the BelongsTo of ReferencedRecord points to the PK of ThroughRecord,
1122 // - and the BelongsTo of ThroughRecord points to the PK of Record
1123 auto query =
1124 _connection.Query(RecordTableName<ReferencedRecord>)
1125 .Select()
1126 .Build([&](auto& query) {
1127 Reflection::EnumerateMembers<ReferencedRecord>(
1128 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1129 if constexpr (FieldWithStorage<ReferencedFieldType>)
1130 {
1131 query.Field(SqlQualifiedTableColumnName {
1132 RecordTableName<ReferencedRecord>,
1133 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1134 }
1135 });
1136 })
1137 .InnerJoin(RecordTableName<ThroughRecord>,
1138 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1139 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1140 .InnerJoin(RecordTableName<Record>,
1141 FieldNameAt<PrimaryKeyIndex, Record>,
1142 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1143 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1144 .Where(
1145 SqlQualifiedTableColumnName {
1146 RecordTableName<Record>,
1147 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1148 },
1149 SqlWildcard);
1150 if (auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1151 {
1152 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1153 }
1154 });
1155 });
1156 });
1157 });
1158}
1159
1160template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename Callable>
1161void DataMapper::CallOnHasManyThrough(Record& record, Callable const& callback)
1162{
1163 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1164
1165 // Find the PK of Record
1166 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1167 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
1168 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToRecordIndex, typename ThroughBelongsToRecordType>() {
1169 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1170 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1171 {
1172 // Find the BelongsTo of ThroughRecord pointing to the PK of ReferencedRecord
1173 CallOnBelongsTo<ThroughRecord>(
1174 [&]<size_t ThroughBelongsToReferenceRecordIndex, typename ThroughBelongsToReferenceRecordType>() {
1175 using ThroughBelongsToReferenceRecordFieldType =
1176 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1177 if constexpr (std::is_same_v<typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1178 ReferencedRecord>)
1179 {
1180 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
1181 .Select()
1182 .Build([&](auto& query) {
1183 Reflection::EnumerateMembers<ReferencedRecord>(
1184 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1185 if constexpr (FieldWithStorage<ReferencedFieldType>)
1186 {
1187 query.Field(SqlQualifiedTableColumnName {
1188 RecordTableName<ReferencedRecord>,
1189 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1190 }
1191 });
1192 })
1193 .InnerJoin(RecordTableName<ThroughRecord>,
1194 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1195 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1196 FieldNameAt<PrimaryKeyIndex, Record> })
1197 .Where(
1198 SqlQualifiedTableColumnName {
1199 RecordTableName<ThroughRecord>,
1200 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1201 },
1202 SqlWildcard);
1203 callback(query, primaryKeyField);
1204 }
1205 });
1206 }
1207 });
1208 });
1209}
1210
1211template <typename ReferencedRecord, typename ThroughRecord, typename Record>
1212void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
1213{
1214 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1215
1216 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1217 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
1218 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
1219 });
1220}
1221
1222template <typename Record>
1223void DataMapper::LoadRelations(Record& record)
1224{
1225 static_assert(!std::is_const_v<Record>);
1226 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1227
1228 Reflection::EnumerateMembers(record, [&]<size_t FieldIndex, typename FieldType>(FieldType& field) {
1229 if constexpr (IsBelongsTo<FieldType>)
1230 {
1231 field = LoadBelongsTo<FieldType>(field.Value());
1232 }
1233 else if constexpr (IsHasMany<FieldType>)
1234 {
1235 LoadHasMany<FieldIndex>(record, field);
1236 }
1237 else if constexpr (IsHasOneThrough<FieldType>)
1238 {
1239 LoadHasOneThrough(record, field);
1240 }
1241 else if constexpr (IsHasManyThrough<FieldType>)
1242 {
1243 LoadHasManyThrough(record, field);
1244 }
1245 });
1246}
1247
1248template <typename Record, typename ValueType>
1249inline LIGHTWEIGHT_FORCE_INLINE void DataMapper::SetId(Record& record, ValueType&& id)
1250{
1251 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1252 // static_assert(HasPrimaryKey<Record>);
1253
1254 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
1255 if constexpr (IsField<FieldType>)
1256 {
1257 if constexpr (FieldType::IsPrimaryKey)
1258 {
1259 field = std::forward<FieldType>(id);
1260 }
1261 }
1262 });
1263}
1264
1265template <typename Record, size_t InitialOffset>
1266inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1267{
1268 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1269 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
1270 return record;
1271}
1272
1273template <typename Record, size_t InitialOffset>
1274Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
1275{
1276 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
1277 record, stmt);
1278}
1279
1280template <typename ElementMask, typename Record, size_t InitialOffset>
1281inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1282{
1283 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1284 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
1285}
1286
1287template <typename ElementMask, typename Record, size_t InitialOffset>
1288Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
1289{
1290 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1291 static_assert(!std::is_const_v<Record>);
1292 assert(stmt != nullptr);
1293
1294 Reflection::EnumerateMembers<ElementMask>(
1295 record, [stmt, i = SQLSMALLINT { InitialOffset }]<size_t I, typename Field>(Field& field) mutable {
1296 if constexpr (IsField<Field>)
1297 {
1298 stmt->BindOutputColumn(i++, &field.MutableValue());
1299 }
1300 else if constexpr (SqlOutputColumnBinder<Field>)
1301 {
1302 stmt->BindOutputColumn(i++, &field);
1303 }
1304 });
1305
1306 return record;
1307}
1308
1309template <typename Record>
1311{
1312 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1313
1314 Reflection::EnumerateMembers(record, [&]<size_t FieldIndex, typename FieldType>(FieldType& field) {
1315 if constexpr (IsBelongsTo<FieldType>)
1316 {
1317 field.SetAutoLoader(typename FieldType::Loader {
1318 .loadReference = [this, value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
1319 return LoadBelongsTo<FieldType>(value);
1320 },
1321 });
1322 }
1323 else if constexpr (IsHasMany<FieldType>)
1324 {
1325 using ReferencedRecord = typename FieldType::ReferencedRecord;
1326 HasMany<ReferencedRecord>& hasMany = field;
1327 hasMany.SetAutoLoader(typename FieldType::Loader {
1328 .count = [this, &record]() -> size_t {
1329 size_t count = 0;
1330 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1331 record, [&](SqlSelectQueryBuilder selectQuery, auto const& primaryKeyField) {
1332 _stmt.Prepare(selectQuery.Count());
1333 _stmt.Execute(primaryKeyField.Value());
1334 if (_stmt.FetchRow())
1335 count = _stmt.GetColumn<size_t>(1);
1336 _stmt.CloseCursor();
1337 });
1338 return count;
1339 },
1340 .all = [this, &record, &hasMany]() { LoadHasMany<FieldIndex>(record, hasMany); },
1341 .each =
1342 [this, &record](auto const& each) {
1343 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1344 record, [&](SqlSelectQueryBuilder selectQuery, auto const& primaryKeyField) {
1345 auto stmt = SqlStatement { _connection };
1346 stmt.Prepare(selectQuery.All());
1347 stmt.Execute(primaryKeyField.Value());
1348
1349 auto referencedRecord = ReferencedRecord {};
1350 BindOutputColumns(referencedRecord, &stmt);
1351 ConfigureRelationAutoLoading(referencedRecord);
1352
1353 while (stmt.FetchRow())
1354 {
1355 each(referencedRecord);
1356 BindOutputColumns(referencedRecord, &stmt);
1357 }
1358 });
1359 },
1360 });
1361 }
1362 else if constexpr (IsHasOneThrough<FieldType>)
1363 {
1364 using ReferencedRecord = typename FieldType::ReferencedRecord;
1365 using ThroughRecord = typename FieldType::ThroughRecord;
1367 hasOneThrough.SetAutoLoader(typename FieldType::Loader {
1368 .loadReference =
1369 [this, &record, &hasOneThrough]() {
1370 LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
1371 },
1372 });
1373 }
1374 else if constexpr (IsHasManyThrough<FieldType>)
1375 {
1376 using ReferencedRecord = typename FieldType::ReferencedRecord;
1377 using ThroughRecord = typename FieldType::ThroughRecord;
1379 hasManyThrough.SetAutoLoader(typename FieldType::Loader {
1380 .count = [this, &record]() -> size_t {
1381 // Load result for Count()
1382 size_t count = 0;
1383 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1384 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
1385 _stmt.Prepare(selectQuery.Count());
1386 _stmt.Execute(primaryKeyField.Value());
1387 if (_stmt.FetchRow())
1388 count = _stmt.GetColumn<size_t>(1);
1389 _stmt.CloseCursor();
1390 });
1391 return count;
1392 },
1393 .all =
1394 [this, &record, &hasManyThrough]() {
1395 // Load result for All()
1396 LoadHasManyThrough(record, hasManyThrough);
1397 },
1398 .each =
1399 [this, &record](auto const& each) {
1400 // Load result for Each()
1401 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1402 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
1403 auto stmt = SqlStatement { _connection };
1404 stmt.Prepare(selectQuery.All());
1405 stmt.Execute(primaryKeyField.Value());
1406
1407 auto referencedRecord = ReferencedRecord {};
1408 BindOutputColumns(referencedRecord, &stmt);
1409 ConfigureRelationAutoLoading(referencedRecord);
1410
1411 while (stmt.FetchRow())
1412 {
1413 each(referencedRecord);
1414 BindOutputColumns(referencedRecord, &stmt);
1415 }
1416 });
1417 },
1418 });
1419 }
1420 });
1421}
1422
1423} // 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.
void LoadRelations(Record &record)
Loads all direct relations to this record.
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.
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.
SqlAllFieldsQueryBuilder< Record > Query()
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.
DataMapper(SqlConnectionString connectionString)
Constructs a new data mapper, using the given connection string.
std::vector< std::tuple< Records... > > QueryToTuple(SqlSelectQueryBuilder::ComposedQuery const &selectQuery)
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
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.
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 ComposedQuery All()
Finalizes building the query as SELECT field names FROM ... query.
LIGHTWEIGHT_API ComposedQuery Count()
Finalizes building the query as SELECT COUNT(*) ... 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
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
Represents a single column in a table.
Definition Field.hpp:84
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.
Represents a value that can be any of the supported SQL data types.