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