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