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