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 /// 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(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 auto stmt = SqlStatement { _dm.Connection() };
635
636 auto const query = _formatter.SelectFirst(this->_query.distinct,
637 _fields,
638 RecordTableName<Record>,
639 this->_query.searchCondition.tableAlias,
640 this->_query.searchCondition.tableJoins,
641 this->_query.searchCondition.condition,
642 this->_query.orderBy,
643 1);
644
645 stmt.ExecuteDirect(query);
646 if (SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
647 return true;
648 return false;
649}
650
651template <typename Record, typename Derived, DataMapperOptions QueryOptions>
653{
654 auto stmt = SqlStatement { _dm.Connection() };
655
656 auto const query = _formatter.Delete(RecordTableName<Record>,
657 this->_query.searchCondition.tableAlias,
658 this->_query.searchCondition.tableJoins,
659 this->_query.searchCondition.condition);
660
661 stmt.Prepare(query);
662 stmt.Execute();
663 stmt.CloseCursor();
664}
665
666template <typename Record, typename Derived, DataMapperOptions QueryOptions>
668{
669
670 auto records = std::vector<Record> {};
671 auto stmt = SqlStatement { _dm.Connection() };
672 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
673 _fields,
674 RecordTableName<Record>,
675 this->_query.searchCondition.tableAlias,
676 this->_query.searchCondition.tableJoins,
677 this->_query.searchCondition.condition,
678 this->_query.orderBy,
679 this->_query.groupBy));
680 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
681 if constexpr (DataMapperRecord<Record>)
682 {
683 // This can be called when record type is not plain aggregate type
684 // but more complex tuple, like std::tuple<A, B>
685 // for now we do not unwrap this type and just skip auto-loading configuration
686 if constexpr (QueryOptions.loadRelations)
687 {
688 for (auto& record: records)
689 {
690 _dm.ConfigureRelationAutoLoading(record);
691 }
692 }
693 }
694 return records;
695}
696
697template <typename Record, typename Derived, DataMapperOptions QueryOptions>
698template <auto Field>
699#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
700 requires(is_aggregate_type(parent_of(Field)))
701#else
702 requires std::is_member_object_pointer_v<decltype(Field)>
703#endif
704auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::All() -> std::vector<ReferencedFieldTypeOf<Field>>
705{
706 using value_type = ReferencedFieldTypeOf<Field>;
707 auto result = std::vector<value_type> {};
708
709 auto stmt = SqlStatement { _dm.Connection() };
710 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
711 FullyQualifiedNamesOf<Field>.string_view(),
712 RecordTableName<Record>,
713 this->_query.searchCondition.tableAlias,
714 this->_query.searchCondition.tableJoins,
715 this->_query.searchCondition.condition,
716 this->_query.orderBy,
717 this->_query.groupBy));
718 SqlResultCursor reader = stmt.GetResultCursor();
719 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
720 while (true)
721 {
722 auto& value = result.emplace_back();
723 if (outputColumnsBound)
724 reader.BindOutputColumn(1, &value);
725
726 if (!reader.FetchRow())
727 {
728 result.pop_back();
729 break;
730 }
731
732 if (!outputColumnsBound)
733 value = reader.GetColumn<value_type>(1);
734 }
735
736 return result;
737}
738
739template <typename Record, typename Derived, DataMapperOptions QueryOptions>
740template <auto... ReferencedFields>
741 requires(sizeof...(ReferencedFields) >= 2)
742auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::All() -> std::vector<Record>
743{
744 auto records = std::vector<Record> {};
745 auto stmt = SqlStatement { _dm.Connection() };
746
747 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
748 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
749 RecordTableName<Record>,
750 this->_query.searchCondition.tableAlias,
751 this->_query.searchCondition.tableJoins,
752 this->_query.searchCondition.condition,
753 this->_query.orderBy,
754 this->_query.groupBy));
755
756 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
757 SqlResultCursor reader = stmt.GetResultCursor();
758 while (true)
759 {
760 auto& record = records.emplace_back();
761 if (outputColumnsBound)
762#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
763 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
764#else
765 reader.BindOutputColumns(&(record.*ReferencedFields)...);
766#endif
767 if (!reader.FetchRow())
768 {
769 records.pop_back();
770 break;
771 }
772 if (!outputColumnsBound)
773 {
774 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
775 detail::GetAllColumns<ElementMask>(reader, record);
776 }
777 }
778
779 return records;
780}
781
782template <typename Record, typename Derived, DataMapperOptions QueryOptions>
784{
785 std::optional<Record> record {};
786 auto stmt = SqlStatement { _dm.Connection() };
787 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
788 _fields,
789 RecordTableName<Record>,
790 this->_query.searchCondition.tableAlias,
791 this->_query.searchCondition.tableJoins,
792 this->_query.searchCondition.condition,
793 this->_query.orderBy,
794 1));
795 Derived::ReadResult(stmt.Connection().ServerType(), stmt.GetResultCursor(), &record);
796 if constexpr (QueryOptions.loadRelations)
797 {
798 if (record)
799 _dm.ConfigureRelationAutoLoading(record.value());
800 }
801 return record;
802}
803
804template <typename Record, typename Derived, DataMapperOptions QueryOptions>
805template <auto Field>
806#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
807 requires(is_aggregate_type(parent_of(Field)))
808#else
809 requires std::is_member_object_pointer_v<decltype(Field)>
810#endif
811auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::First() -> std::optional<ReferencedFieldTypeOf<Field>>
812{
813 auto constexpr count = 1;
814 auto stmt = SqlStatement { _dm.Connection() };
815 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
816 FullyQualifiedNamesOf<Field>.string_view(),
817 RecordTableName<Record>,
818 this->_query.searchCondition.tableAlias,
819 this->_query.searchCondition.tableJoins,
820 this->_query.searchCondition.condition,
821 this->_query.orderBy,
822 count));
823 if (SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
824 return reader.template GetColumn<ReferencedFieldTypeOf<Field>>(1);
825 return std::nullopt;
826}
827
828template <typename Record, typename Derived, DataMapperOptions QueryOptions>
829template <auto... ReferencedFields>
830 requires(sizeof...(ReferencedFields) >= 2)
831auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::First() -> std::optional<Record>
832{
833 auto optionalRecord = std::optional<Record> {};
834
835 auto stmt = SqlStatement { _dm.Connection() };
836 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
837 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
838 RecordTableName<Record>,
839 this->_query.searchCondition.tableAlias,
840 this->_query.searchCondition.tableJoins,
841 this->_query.searchCondition.condition,
842 this->_query.orderBy,
843 1));
844
845 auto& record = optionalRecord.emplace();
846 SqlResultCursor reader = stmt.GetResultCursor();
847 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
848 if (outputColumnsBound)
849#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
850 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
851#else
852 reader.BindOutputColumns(&(record.*ReferencedFields)...);
853#endif
854 if (!reader.FetchRow())
855 return std::nullopt;
856 if (!outputColumnsBound)
857 {
858 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
859 detail::GetAllColumns<ElementMask>(reader, record);
860 }
861
862 if constexpr (QueryOptions.loadRelations)
863 _dm.ConfigureRelationAutoLoading(record);
864
865 return optionalRecord;
866}
867
868template <typename Record, typename Derived, DataMapperOptions QueryOptions>
870{
871 auto records = std::vector<Record> {};
872 auto stmt = SqlStatement { _dm.Connection() };
873 records.reserve(n);
874 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
875 _fields,
876 RecordTableName<Record>,
877 this->_query.searchCondition.tableAlias,
878 this->_query.searchCondition.tableJoins,
879 this->_query.searchCondition.condition,
880 this->_query.orderBy,
881 n));
882 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
883
884 if constexpr (QueryOptions.loadRelations)
885 {
886 for (auto& record: records)
887 _dm.ConfigureRelationAutoLoading(record);
888 }
889 return records;
890}
891
892template <typename Record, typename Derived, DataMapperOptions QueryOptions>
893std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::Range(size_t offset, size_t limit)
894{
895 auto records = std::vector<Record> {};
896 auto stmt = SqlStatement { _dm.Connection() };
897 records.reserve(limit);
898 stmt.ExecuteDirect(
899 _formatter.SelectRange(this->_query.distinct,
900 _fields,
901 RecordTableName<Record>,
902 this->_query.searchCondition.tableAlias,
903 this->_query.searchCondition.tableJoins,
904 this->_query.searchCondition.condition,
905 !this->_query.orderBy.empty()
906 ? this->_query.orderBy
907 : std::format(" ORDER BY \"{}\" ASC", FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
908 this->_query.groupBy,
909 offset,
910 limit));
911 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
912 if constexpr (QueryOptions.loadRelations)
913 {
914 for (auto& record: records)
915 _dm.ConfigureRelationAutoLoading(record);
916 }
917 return records;
918}
919
920template <typename Record, typename Derived, DataMapperOptions QueryOptions>
921template <auto... ReferencedFields>
922std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::Range(size_t offset, size_t limit)
923{
924 auto records = std::vector<Record> {};
925 auto stmt = SqlStatement { _dm.Connection() };
926 records.reserve(limit);
927 stmt.ExecuteDirect(
928 _formatter.SelectRange(this->_query.distinct,
929 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
930 RecordTableName<Record>,
931 this->_query.searchCondition.tableAlias,
932 this->_query.searchCondition.tableJoins,
933 this->_query.searchCondition.condition,
934 !this->_query.orderBy.empty()
935 ? this->_query.orderBy
936 : std::format(" ORDER BY \"{}\" ASC", FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
937 this->_query.groupBy,
938 offset,
939 limit));
940
941 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
942 SqlResultCursor reader = stmt.GetResultCursor();
943 while (true)
944 {
945 auto& record = records.emplace_back();
946 if (outputColumnsBound)
947#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
948 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
949#else
950 reader.BindOutputColumns(&(record.*ReferencedFields)...);
951#endif
952 if (!reader.FetchRow())
953 {
954 records.pop_back();
955 break;
956 }
957 if (!outputColumnsBound)
958 {
959 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
960 detail::GetAllColumns<ElementMask>(reader, record);
961 }
962 }
963
964 if constexpr (QueryOptions.loadRelations)
965 {
966 for (auto& record: records)
967 _dm.ConfigureRelationAutoLoading(record);
968 }
969
970 return records;
971}
972
973template <typename Record, typename Derived, DataMapperOptions QueryOptions>
974template <auto... ReferencedFields>
975[[nodiscard]] std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::First(size_t n)
976{
977 auto records = std::vector<Record> {};
978 auto stmt = SqlStatement { _dm.Connection() };
979 records.reserve(n);
980 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
981 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
982 RecordTableName<Record>,
983 this->_query.searchCondition.tableAlias,
984 this->_query.searchCondition.tableJoins,
985 this->_query.searchCondition.condition,
986 this->_query.orderBy,
987 n));
988
989 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
990 SqlResultCursor reader = stmt.GetResultCursor();
991 while (true)
992 {
993 auto& record = records.emplace_back();
994 if (outputColumnsBound)
995#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
996 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
997#else
998 reader.BindOutputColumns(&(record.*ReferencedFields)...);
999#endif
1000 if (!reader.FetchRow())
1001 {
1002 records.pop_back();
1003 break;
1004 }
1005 if (!outputColumnsBound)
1006 {
1007 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1008 detail::GetAllColumns<ElementMask>(reader, record);
1009 }
1010 }
1011
1012 if constexpr (QueryOptions.loadRelations)
1013 {
1014 for (auto& record: records)
1015 _dm.ConfigureRelationAutoLoading(record);
1016 }
1017
1018 return records;
1019}
1020
1021template <typename Record, DataMapperOptions QueryOptions>
1022void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResults(SqlServerType sqlServerType,
1023 SqlResultCursor reader,
1024 std::vector<Record>* records)
1025{
1026 while (true)
1027 {
1028 Record& record = records->emplace_back();
1029 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1030 {
1031 records->pop_back();
1032 break;
1033 }
1034 }
1035}
1036
1037template <typename Record, DataMapperOptions QueryOptions>
1038void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1039 SqlResultCursor reader,
1040 std::optional<Record>* optionalRecord)
1041{
1042 Record& record = optionalRecord->emplace();
1043 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1044 optionalRecord->reset();
1045}
1046
1047template <typename FirstRecord, typename SecondRecord, DataMapperOptions QueryOptions>
1048void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1049 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1050{
1051 while (true)
1052 {
1053 auto& record = records->emplace_back();
1054 auto& [firstRecord, secondRecord] = record;
1055
1056 using FirstRecordType = std::remove_cvref_t<decltype(firstRecord)>;
1057 using SecondRecordType = std::remove_cvref_t<decltype(secondRecord)>;
1058
1059 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1060 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1061 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1062
1063 if (canSafelyBindAll)
1064 {
1065 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1066 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1067 }
1068
1069 if (!reader.FetchRow())
1070 {
1071 records->pop_back();
1072 break;
1073 }
1074
1075 if (!canSafelyBindAll)
1076 detail::GetAllColumns(reader, record);
1077 }
1078}
1079
1080template <typename Record>
1081std::string DataMapper::Inspect(Record const& record)
1082{
1083 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1084
1085 std::string str;
1086 Reflection::CallOnMembers(record, [&str]<typename Name, typename Value>(Name const& name, Value const& value) {
1087 if (!str.empty())
1088 str += '\n';
1089
1090 if constexpr (FieldWithStorage<Value>)
1091 {
1092 if constexpr (Value::IsOptional)
1093 {
1094 if (!value.Value().has_value())
1095 {
1096 str += std::format("{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1097 }
1098 else
1099 {
1100 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1101 }
1102 }
1103 else if constexpr (IsBelongsTo<Value>)
1104 {
1105 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1106 }
1107 else if constexpr (std::same_as<typename Value::ValueType, char>)
1108 {
1109 }
1110 else
1111 {
1112 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1113 }
1114 }
1115 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1116 str += std::format("{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1117 });
1118 return "{\n" + std::move(str) + "\n}";
1119}
1120
1121template <typename Record>
1122std::vector<std::string> DataMapper::CreateTableString(SqlServerType serverType)
1123{
1124 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1125
1126 auto migration = SqlQueryBuilder(*SqlQueryFormatter::Get(serverType)).Migration();
1127 auto createTable = migration.CreateTable(RecordTableName<Record>);
1128#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1129 constexpr auto ctx = std::meta::access_context::current();
1130 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1131 {
1132 using FieldType = typename[:std::meta::type_of(el):];
1133 if constexpr (FieldWithStorage<FieldType>)
1134 {
1135 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1136 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameOf<el>),
1137 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1138 else if constexpr (FieldType::IsPrimaryKey)
1139 createTable.PrimaryKey(std::string(FieldNameOf<el>),
1140 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1141 else if constexpr (IsBelongsTo<FieldType>)
1142 {
1143 constexpr size_t referencedFieldIndex = []() constexpr -> size_t {
1144 auto index = size_t(-1);
1145 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1146 [&index]<size_t J, typename ReferencedFieldType>() constexpr -> void {
1147 if constexpr (IsField<ReferencedFieldType>)
1148 if constexpr (ReferencedFieldType::IsPrimaryKey)
1149 index = J;
1150 });
1151 return index;
1152 }();
1153 createTable.ForeignKey(
1154 std::string(FieldNameOf<el>),
1155 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1157 .tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1158 .columnName = std::string { FieldNameOf<FieldType::ReferencedField> } });
1159 }
1160 else if constexpr (FieldType::IsMandatory)
1161 createTable.RequiredColumn(std::string(FieldNameOf<el>),
1162 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1163 else
1164 createTable.Column(std::string(FieldNameOf<el>), SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1165 }
1166 };
1167
1168#else
1169 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1170 if constexpr (FieldWithStorage<FieldType>)
1171 {
1172 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1173 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
1174 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1175 else if constexpr (FieldType::IsPrimaryKey)
1176 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
1177 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1178 else if constexpr (IsBelongsTo<FieldType>)
1179 {
1180 constexpr size_t referencedFieldIndex = []() constexpr -> size_t {
1181 auto index = size_t(-1);
1182 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1183 [&index]<size_t J, typename ReferencedFieldType>() constexpr -> void {
1184 if constexpr (IsField<ReferencedFieldType>)
1185 if constexpr (ReferencedFieldType::IsPrimaryKey)
1186 index = J;
1187 });
1188 return index;
1189 }();
1190 createTable.ForeignKey(
1191 std::string(FieldNameAt<I, Record>),
1192 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1194 .tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1195 .columnName =
1196 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
1197 }
1198 else if constexpr (FieldType::IsMandatory)
1199 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
1200 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1201 else
1202 createTable.Column(std::string(FieldNameAt<I, Record>),
1203 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1204 }
1205 });
1206#endif
1207 return migration.GetPlan().ToSql();
1208}
1209
1210template <typename FirstRecord, typename... MoreRecords>
1211std::vector<std::string> DataMapper::CreateTablesString(SqlServerType serverType)
1212{
1213 std::vector<std::string> output;
1214 auto const append = [&output](auto const& sql) {
1215 output.insert(output.end(), sql.begin(), sql.end());
1216 };
1217 append(CreateTableString<FirstRecord>(serverType));
1218 (append(CreateTableString<MoreRecords>(serverType)), ...);
1219 return output;
1220}
1221
1222template <typename Record>
1224{
1225 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1226
1227 auto const sqlQueryStrings = CreateTableString<Record>(_connection.ServerType());
1228 for (auto const& sqlQueryString: sqlQueryStrings)
1229 _stmt.ExecuteDirect(sqlQueryString);
1230}
1231
1232template <typename FirstRecord, typename... MoreRecords>
1234{
1235 CreateTable<FirstRecord>();
1236 (CreateTable<MoreRecords>(), ...);
1237}
1238
1239template <typename Record>
1240RecordPrimaryKeyType<Record> DataMapper::CreateExplicit(Record const& record)
1241{
1242 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1243
1244 auto query = _connection.Query(RecordTableName<Record>).Insert(nullptr);
1245
1246#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1247 constexpr auto ctx = std::meta::access_context::current();
1248 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1249 {
1250 using FieldType = typename[:std::meta::type_of(el):];
1251 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1252 query.Set(FieldNameOf<el>, SqlWildcard);
1253 }
1254#else
1255 Reflection::EnumerateMembers(record, [&query]<auto I, typename FieldType>(FieldType const& /*field*/) {
1256 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1257 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1258 });
1259#endif
1260
1261 _stmt.Prepare(query);
1262
1263#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1264 int i = 1;
1265 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1266 {
1267 using FieldType = typename[:std::meta::type_of(el):];
1268 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1269 _stmt.BindInputParameter(i++, record.[:el:], std::meta::identifier_of(el));
1270 }
1271#else
1272 Reflection::CallOnMembers(
1273 record,
1274 [this, i = SQLSMALLINT { 1 }]<typename Name, typename FieldType>(Name const& name, FieldType const& field) mutable {
1275 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1276 _stmt.BindInputParameter(i++, field, name);
1277 });
1278#endif
1279 _stmt.Execute();
1280
1281 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1282 return { _stmt.LastInsertId(RecordTableName<Record>) };
1283 else if constexpr (HasPrimaryKey<Record>)
1284 {
1285 RecordPrimaryKeyType<Record> const* primaryKey = nullptr;
1286#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1287 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1288 {
1289 using FieldType = typename[:std::meta::type_of(el):];
1290 if constexpr (IsField<FieldType>)
1291 {
1292 if constexpr (FieldType::IsPrimaryKey)
1293 {
1294 primaryKey = &record.[:el:].Value();
1295 }
1296 }
1297 }
1298#else
1299 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
1300 if constexpr (IsField<FieldType>)
1301 {
1302 if constexpr (FieldType::IsPrimaryKey)
1303 {
1304 primaryKey = &field.Value();
1305 }
1306 }
1307 });
1308#endif
1309 return *primaryKey;
1310 }
1311}
1312
1313template <typename Record>
1314RecordPrimaryKeyType<Record> DataMapper::Create(Record& record)
1315{
1316 static_assert(!std::is_const_v<Record>);
1317 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1318
1319// If the primary key is not an auto-increment field and the primary key is not set, we need to set it.
1320//
1321#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1322 constexpr auto ctx = std::meta::access_context::current();
1323 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1324 {
1325 using FieldType = typename[:std::meta::type_of(el):];
1326 if constexpr (IsField<FieldType>)
1327 if constexpr (FieldType::IsPrimaryKey)
1328 if constexpr (FieldType::IsAutoAssignPrimaryKey)
1329 {
1330 if (!record.[:el:].IsModified())
1331 {
1332 using ValueType = typename FieldType::ValueType;
1333 if constexpr (std::same_as<ValueType, SqlGuid>)
1334 {
1335 if (!record.[:el:].Value())
1336 record.[:el:] = SqlGuid::Create();
1337 }
1338 else if constexpr (requires { ValueType {} + 1; })
1339 {
1340 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(std::format(
1341 R"sql(SELECT MAX("{}") FROM "{}")sql", FieldNameOf<el>, RecordTableName<Record>));
1342 record.[:el:] = maxId.value_or(ValueType {}) + 1;
1343 }
1344 }
1345 }
1346 }
1347#else
1348 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
1349 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
1350 {
1351 if (!primaryKeyField.IsModified())
1352 {
1353 using ValueType = PrimaryKeyType::ValueType;
1354 if constexpr (std::same_as<ValueType, SqlGuid>)
1355 {
1356 if (!primaryKeyField.Value())
1357 primaryKeyField = SqlGuid::Create();
1358 }
1359 else if constexpr (requires { ValueType {} + 1; })
1360 {
1361 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1362 std::format(R"sql(SELECT MAX("{}") FROM "{}")sql",
1363 FieldNameAt<PrimaryKeyIndex, Record>,
1364 RecordTableName<Record>));
1365 primaryKeyField = maxId.value_or(ValueType {}) + 1;
1366 }
1367 }
1368 }
1369 });
1370#endif
1371
1372 CreateExplicit(record);
1373
1374 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1375 SetId(record, _stmt.LastInsertId(RecordTableName<Record>));
1376
1377 ClearModifiedState(record);
1379
1380 if constexpr (HasPrimaryKey<Record>)
1381 return GetPrimaryKeyField(record);
1382}
1383
1384template <typename Record>
1385bool DataMapper::IsModified(Record const& record) const noexcept
1386{
1387 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1388
1389 bool modified = false;
1390
1391#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1392 auto constexpr ctx = std::meta::access_context::current();
1393 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1394 {
1395 if constexpr (requires { record.[:el:].IsModified(); })
1396 {
1397 modified = modified || record.[:el:].IsModified();
1398 }
1399 }
1400#else
1401 Reflection::CallOnMembers(record, [&modified](auto const& /*name*/, auto const& field) {
1402 if constexpr (requires { field.IsModified(); })
1403 {
1404 modified = modified || field.IsModified();
1405 }
1406 });
1407#endif
1408
1409 return modified;
1410}
1411
1412template <typename Record>
1413void DataMapper::Update(Record& record)
1414{
1415 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1416
1417 auto query = _connection.Query(RecordTableName<Record>).Update();
1418
1419#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1420 auto constexpr ctx = std::meta::access_context::current();
1421 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1422 {
1423 using FieldType = typename[:std::meta::type_of(el):];
1424 if constexpr (FieldWithStorage<FieldType>)
1425 {
1426 if (record.[:el:].IsModified())
1427 query.Set(FieldNameOf<el>, SqlWildcard);
1428 if constexpr (IsPrimaryKey<FieldType>)
1429 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1430 }
1431 }
1432#else
1433 Reflection::CallOnMembersWithoutName(record, [&query]<size_t I, typename FieldType>(FieldType const& field) {
1434 if (field.IsModified())
1435 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1436 // for some reason compiler do not want to properly deduce FieldType, so here we
1437 // directly infer the type from the Record type and index
1438 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1439 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1440 });
1441#endif
1442 _stmt.Prepare(query);
1443
1444 SQLSMALLINT i = 1;
1445
1446#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1447 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1448 {
1449 if (record.[:el:].IsModified())
1450 {
1451 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1452 }
1453 }
1454
1455 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1456 {
1457 using FieldType = typename[:std::meta::type_of(el):];
1458 if constexpr (FieldType::IsPrimaryKey)
1459 {
1460 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1461 }
1462 }
1463#else
1464 // Bind the SET clause
1465 Reflection::CallOnMembersWithoutName(record, [this, &i]<size_t I, typename FieldType>(FieldType const& field) {
1466 if (field.IsModified())
1467 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1468 });
1469
1470 // Bind the WHERE clause
1471 Reflection::CallOnMembersWithoutName(record, [this, &i]<size_t I, typename FieldType>(FieldType const& field) {
1472 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1473 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1474 });
1475#endif
1476
1477 _stmt.Execute();
1478
1479 ClearModifiedState(record);
1480}
1481
1482template <typename Record>
1483std::size_t DataMapper::Delete(Record const& record)
1484{
1485 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1486
1487 auto query = _connection.Query(RecordTableName<Record>).Delete();
1488
1489#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1490 auto constexpr ctx = std::meta::access_context::current();
1491 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1492 {
1493 using FieldType = typename[:std::meta::type_of(el):];
1494 if constexpr (FieldType::IsPrimaryKey)
1495 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1496 }
1497#else
1498 Reflection::CallOnMembersWithoutName(record, [&query]<size_t I, typename FieldType>(FieldType const& /*field*/) {
1499 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1500 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1501 });
1502#endif
1503
1504 _stmt.Prepare(query);
1505
1506#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1507 SQLSMALLINT i = 1;
1508 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1509 {
1510 using FieldType = typename[:std::meta::type_of(el):];
1511 if constexpr (FieldType::IsPrimaryKey)
1512 {
1513 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1514 }
1515 }
1516#else
1517 // Bind the WHERE clause
1518 Reflection::CallOnMembersWithoutName(
1519 record, [this, i = SQLSMALLINT { 1 }]<size_t I, typename FieldType>(FieldType const& field) mutable {
1520 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1521 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1522 });
1523#endif
1524
1525 _stmt.Execute();
1526
1527 return _stmt.NumRowsAffected();
1528}
1529
1530template <typename Record, typename... PrimaryKeyTypes>
1531std::optional<Record> DataMapper::QuerySingleWithoutRelationAutoLoading(PrimaryKeyTypes&&... primaryKeys)
1532{
1533 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1534
1535 auto queryBuilder = _connection.Query(RecordTableName<Record>).Select();
1536
1537 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1538 if constexpr (FieldWithStorage<FieldType>)
1539 {
1540 queryBuilder.Field(FieldNameAt<I, Record>);
1541
1542 if constexpr (FieldType::IsPrimaryKey)
1543 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1544 }
1545 });
1546
1547 _stmt.Prepare(queryBuilder.First());
1548 _stmt.Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1549
1550 auto resultRecord = std::optional<Record> { Record {} };
1551 auto reader = _stmt.GetResultCursor();
1552 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
1553 return std::nullopt;
1554
1555 return resultRecord;
1556}
1557
1558template <typename Record, typename... PrimaryKeyTypes>
1559std::optional<Record> DataMapper::QuerySingle(PrimaryKeyTypes&&... primaryKeys)
1560{
1561 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1562 if (record)
1564 return record;
1565}
1566
1567template <typename Record, typename... Args>
1568std::optional<Record> DataMapper::QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args)
1569{
1570 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1571
1572 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1573 if constexpr (FieldWithStorage<FieldType>)
1574 selectQuery.Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
1575 });
1576 _stmt.Prepare(selectQuery.First().ToSql());
1577 _stmt.Execute(std::forward<Args>(args)...);
1578
1579 auto resultRecord = std::optional<Record> { Record {} };
1580 auto reader = _stmt.GetResultCursor();
1581 if (!detail::ReadSingleResult(_stmt.Connection().ServerType(), reader, *resultRecord))
1582 return std::nullopt;
1583 return resultRecord;
1584}
1585
1586// TODO: Provide Query(QueryBuilder, ...) method variant
1587
1588template <typename Record, typename... InputParameters>
1589inline LIGHTWEIGHT_FORCE_INLINE std::vector<Record> DataMapper::Query(
1590 SqlSelectQueryBuilder::ComposedQuery const& selectQuery, InputParameters&&... inputParameters)
1591{
1592 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>, "Record must satisfy DataMapperRecord");
1593
1594 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1595}
1596
1597template <typename Record, typename... InputParameters>
1598std::vector<Record> DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1599{
1600 auto result = std::vector<Record> {};
1601 if constexpr (std::same_as<Record, SqlVariantRow>)
1602 {
1603 _stmt.Prepare(sqlQueryString);
1604 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1605 size_t const numResultColumns = _stmt.NumColumnsAffected();
1606 while (_stmt.FetchRow())
1607 {
1608 auto& record = result.emplace_back();
1609 record.reserve(numResultColumns);
1610 for (auto const i: std::views::iota(1U, numResultColumns + 1))
1611 record.emplace_back(_stmt.GetColumn<SqlVariant>(static_cast<SQLUSMALLINT>(i)));
1612 }
1613 }
1614 else
1615 {
1616 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1617
1618 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
1619
1620 _stmt.Prepare(sqlQueryString);
1621 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1622
1623 auto reader = _stmt.GetResultCursor();
1624
1625 for (;;)
1626 {
1627 auto& record = result.emplace_back();
1628
1629 if (canSafelyBindOutputColumns)
1630 BindOutputColumns(record);
1631
1632 if (!reader.FetchRow())
1633 break;
1634
1635 if (!canSafelyBindOutputColumns)
1636 detail::GetAllColumns(reader, record);
1637 }
1638
1639 // Drop the last record, which we failed to fetch (End of result set).
1640 result.pop_back();
1641
1642 for (auto& record: result)
1644 }
1645
1646 return result;
1647}
1648
1649template <typename First, typename Second, typename... Rest, DataMapperOptions QueryOptions>
1650 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
1651std::vector<std::tuple<First, Second, Rest...>> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery)
1652{
1653 using value_type = std::tuple<First, Second, Rest...>;
1654 auto result = std::vector<value_type> {};
1655
1656 _stmt.Prepare(selectQuery.ToSql());
1657 _stmt.Execute();
1658 auto reader = _stmt.GetResultCursor();
1659
1660 constexpr auto calculateOffset = []<size_t I, typename Tuple>() {
1661 size_t offset = 1;
1662
1663 if constexpr (I > 0)
1664 {
1665 [&]<size_t... Indices>(std::index_sequence<Indices...>) {
1666 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1667 }(std::make_index_sequence<I> {});
1668 }
1669 return offset;
1670 };
1671
1672 auto const BindElements = [&](auto& record) {
1673 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
1674 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1675 auto& element = std::get<I>(record);
1676 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1677 this->BindOutputColumns<TupleElement, offset>(element);
1678 });
1679 };
1680
1681 auto const GetElements = [&](auto& record) {
1682 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
1683 auto& element = std::get<I>(record);
1684 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1685 detail::GetAllColumns(reader, element, offset - 1);
1686 });
1687 };
1688
1689 bool const canSafelyBindOutputColumns = [&]() {
1690 bool result = true;
1691 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
1692 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1693 result &= detail::CanSafelyBindOutputColumns<TupleElement>(_stmt.Connection().ServerType());
1694 });
1695 return result;
1696 }();
1697
1698 for (;;)
1699 {
1700 auto& record = result.emplace_back();
1701
1702 if (canSafelyBindOutputColumns)
1703 BindElements(record);
1704
1705 if (!reader.FetchRow())
1706 break;
1707
1708 if (!canSafelyBindOutputColumns)
1709 GetElements(record);
1710 }
1711
1712 // Drop the last record, which we failed to fetch (End of result set).
1713 result.pop_back();
1714
1715 if constexpr (QueryOptions.loadRelations)
1716 {
1717
1718 for (auto& record: result)
1719 {
1720 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<auto I>() {
1721 auto& element = std::get<I>(record);
1723 });
1724 }
1725 }
1726
1727 return result;
1728}
1729
1730template <typename ElementMask, typename Record, typename... InputParameters>
1731std::vector<Record> DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery const& selectQuery,
1732 InputParameters&&... inputParameters)
1733{
1734 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1735
1736 _stmt.Prepare(selectQuery.ToSql());
1737 _stmt.Execute(std::forward<InputParameters>(inputParameters)...);
1738
1739 auto records = std::vector<Record> {};
1740
1741 // TODO: We could optimize this further by only considering ElementMask fields in Record.
1742 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
1743
1744 auto reader = _stmt.GetResultCursor();
1745
1746 for (;;)
1747 {
1748 auto& record = records.emplace_back();
1749
1750 if (canSafelyBindOutputColumns)
1751 BindOutputColumns<ElementMask>(record);
1752
1753 if (!reader.FetchRow())
1754 break;
1755
1756 if (!canSafelyBindOutputColumns)
1757 detail::GetAllColumns<ElementMask>(reader, record);
1758 }
1759
1760 // Drop the last record, which we failed to fetch (End of result set).
1761 records.pop_back();
1762
1763 for (auto& record: records)
1765
1766 return records;
1767}
1768
1769template <typename Record>
1770void DataMapper::ClearModifiedState(Record& record) noexcept
1771{
1772 static_assert(!std::is_const_v<Record>);
1773 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1774
1775 Reflection::EnumerateMembers(record, []<size_t I, typename FieldType>(FieldType& field) {
1776 if constexpr (requires { field.SetModified(false); })
1777 {
1778 field.SetModified(false);
1779 }
1780 });
1781}
1782
1783template <typename Record, typename Callable>
1784inline LIGHTWEIGHT_FORCE_INLINE void CallOnPrimaryKey(Record& record, Callable const& callable)
1785{
1786 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1787
1788 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
1789 if constexpr (IsField<FieldType>)
1790 {
1791 if constexpr (FieldType::IsPrimaryKey)
1792 {
1793 return callable.template operator()<I, FieldType>(field);
1794 }
1795 }
1796 });
1797}
1798
1799template <typename Record, typename Callable>
1800inline LIGHTWEIGHT_FORCE_INLINE void CallOnPrimaryKey(Callable const& callable)
1801{
1802 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1803
1804 Reflection::EnumerateMembers<Record>([&]<size_t I, typename FieldType>() {
1805 if constexpr (IsField<FieldType>)
1806 {
1807 if constexpr (FieldType::IsPrimaryKey)
1808 {
1809 return callable.template operator()<I, FieldType>();
1810 }
1811 }
1812 });
1813}
1814
1815template <typename Record, typename Callable>
1816inline LIGHTWEIGHT_FORCE_INLINE void CallOnBelongsTo(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 (IsBelongsTo<FieldType>)
1822 {
1823 return callable.template operator()<I, FieldType>();
1824 }
1825 });
1826}
1827
1828template <typename FieldType>
1829std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1830{
1831 using ReferencedRecord = FieldType::ReferencedRecord;
1832
1833 std::optional<ReferencedRecord> record { std::nullopt };
1834
1835#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1836 auto constexpr ctx = std::meta::access_context::current();
1837 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1838 {
1839 using BelongsToFieldType = typename[:std::meta::type_of(el):];
1840 if constexpr (IsField<BelongsToFieldType>)
1841 if constexpr (BelongsToFieldType::IsPrimaryKey)
1842 {
1843 if (auto result = QuerySingle<ReferencedRecord>(value); result)
1844 record = std::move(result);
1845 else
1847 std::format("Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1848 }
1849 }
1850#else
1851 CallOnPrimaryKey<ReferencedRecord>([&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>() {
1852 if (auto result = QuerySingle<ReferencedRecord>(value); result)
1853 record = std::move(result);
1854 else
1856 std::format("Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1857 });
1858#endif
1859 return record;
1860}
1861
1862template <size_t FieldIndex, typename Record, typename OtherRecord, typename Callable>
1863void DataMapper::CallOnHasMany(Record& record, Callable const& callback)
1864{
1865 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1866 static_assert(DataMapperRecord<OtherRecord>, "OtherRecord must satisfy DataMapperRecord");
1867
1868 using FieldType = HasMany<OtherRecord>;
1869 using ReferencedRecord = FieldType::ReferencedRecord;
1870
1871 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1872 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
1873 .Select()
1874 .Build([&](auto& query) {
1875 Reflection::EnumerateMembers<ReferencedRecord>(
1876 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1877 if constexpr (FieldWithStorage<ReferencedFieldType>)
1878 {
1879 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1880 }
1881 });
1882 })
1883 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1884 callback(query, primaryKeyField);
1885 });
1886}
1887
1888template <size_t FieldIndex, typename Record, typename OtherRecord>
1889void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1890{
1891 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1892 static_assert(DataMapperRecord<OtherRecord>, "OtherRecord must satisfy DataMapperRecord");
1893
1894 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery, auto& primaryKeyField) {
1895 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1896 });
1897}
1898
1899template <typename ReferencedRecord, typename ThroughRecord, typename Record>
1900void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1901{
1902 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1903 static_assert(DataMapperRecord<ThroughRecord>, "ThroughRecord must satisfy DataMapperRecord");
1904
1905 // Find the PK of Record
1906 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1907 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
1908 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToIndex, typename ThroughBelongsToType>() {
1909 // Find the PK of ThroughRecord
1910 CallOnPrimaryKey<ThroughRecord>([&]<size_t ThroughPrimaryKeyIndex, typename ThroughPrimaryKeyType>() {
1911 // Find the BelongsTo of ReferencedRecord pointing to the PK of ThroughRecord
1912 CallOnBelongsTo<ReferencedRecord>([&]<size_t ReferencedKeyIndex, typename ReferencedKeyType>() {
1913 // Query the ReferencedRecord where:
1914 // - the BelongsTo of ReferencedRecord points to the PK of ThroughRecord,
1915 // - and the BelongsTo of ThroughRecord points to the PK of Record
1916 auto query =
1917 _connection.Query(RecordTableName<ReferencedRecord>)
1918 .Select()
1919 .Build([&](auto& query) {
1920 Reflection::EnumerateMembers<ReferencedRecord>(
1921 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1922 if constexpr (FieldWithStorage<ReferencedFieldType>)
1923 {
1924 query.Field(SqlQualifiedTableColumnName {
1925 RecordTableName<ReferencedRecord>,
1926 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1927 }
1928 });
1929 })
1930 .InnerJoin(RecordTableName<ThroughRecord>,
1931 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1932 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1933 .InnerJoin(RecordTableName<Record>,
1934 FieldNameAt<PrimaryKeyIndex, Record>,
1935 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1936 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1937 .Where(
1938 SqlQualifiedTableColumnName {
1939 RecordTableName<Record>,
1940 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1941 },
1942 SqlWildcard);
1943 if (auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1944 {
1945 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1946 }
1947 });
1948 });
1949 });
1950 });
1951}
1952
1953template <typename ReferencedRecord, typename ThroughRecord, typename Record, typename Callable>
1954void DataMapper::CallOnHasManyThrough(Record& record, Callable const& callback)
1955{
1956 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
1957
1958 // Find the PK of Record
1959 CallOnPrimaryKey(record, [&]<size_t PrimaryKeyIndex, typename PrimaryKeyType>(PrimaryKeyType const& primaryKeyField) {
1960 // Find the BelongsTo of ThroughRecord pointing to the PK of Record
1961 CallOnBelongsTo<ThroughRecord>([&]<size_t ThroughBelongsToRecordIndex, typename ThroughBelongsToRecordType>() {
1962 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1963 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1964 {
1965 // Find the BelongsTo of ThroughRecord pointing to the PK of ReferencedRecord
1966 CallOnBelongsTo<ThroughRecord>(
1967 [&]<size_t ThroughBelongsToReferenceRecordIndex, typename ThroughBelongsToReferenceRecordType>() {
1968 using ThroughBelongsToReferenceRecordFieldType =
1969 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1970 if constexpr (std::is_same_v<typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1971 ReferencedRecord>)
1972 {
1973 auto query = _connection.Query(RecordTableName<ReferencedRecord>)
1974 .Select()
1975 .Build([&](auto& query) {
1976 Reflection::EnumerateMembers<ReferencedRecord>(
1977 [&]<size_t ReferencedFieldIndex, typename ReferencedFieldType>() {
1978 if constexpr (FieldWithStorage<ReferencedFieldType>)
1979 {
1980 query.Field(SqlQualifiedTableColumnName {
1981 RecordTableName<ReferencedRecord>,
1982 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1983 }
1984 });
1985 })
1986 .InnerJoin(RecordTableName<ThroughRecord>,
1987 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1988 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1989 FieldNameAt<PrimaryKeyIndex, Record> })
1990 .Where(
1991 SqlQualifiedTableColumnName {
1992 RecordTableName<ThroughRecord>,
1993 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1994 },
1995 SqlWildcard);
1996 callback(query, primaryKeyField);
1997 }
1998 });
1999 }
2000 });
2001 });
2002}
2003
2004template <typename ReferencedRecord, typename ThroughRecord, typename Record>
2005void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2006{
2007 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2008
2009 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2010 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
2011 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2012 });
2013}
2014
2015template <typename Record>
2016void DataMapper::LoadRelations(Record& record)
2017{
2018 static_assert(!std::is_const_v<Record>);
2019 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2020
2021#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2022 constexpr auto ctx = std::meta::access_context::current();
2023 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2024 {
2025 using FieldType = typename[:std::meta::type_of(el):];
2026 if constexpr (IsBelongsTo<FieldType>)
2027 {
2028 auto& field = record.[:el:];
2029 field = LoadBelongsTo<FieldType>(field.Value());
2030 }
2031 else if constexpr (IsHasMany<FieldType>)
2032 {
2033 LoadHasMany<el>(record, record.[:el:]);
2034 }
2035 else if constexpr (IsHasOneThrough<FieldType>)
2036 {
2037 LoadHasOneThrough(record, record.[:el:]);
2038 }
2039 else if constexpr (IsHasManyThrough<FieldType>)
2040 {
2041 LoadHasManyThrough(record, record.[:el:]);
2042 }
2043 }
2044#else
2045 Reflection::EnumerateMembers(record, [&]<size_t FieldIndex, typename FieldType>(FieldType& field) {
2046 if constexpr (IsBelongsTo<FieldType>)
2047 {
2048 field = LoadBelongsTo<FieldType>(field.Value());
2049 }
2050 else if constexpr (IsHasMany<FieldType>)
2051 {
2052 LoadHasMany<FieldIndex>(record, field);
2053 }
2054 else if constexpr (IsHasOneThrough<FieldType>)
2055 {
2056 LoadHasOneThrough(record, field);
2057 }
2058 else if constexpr (IsHasManyThrough<FieldType>)
2059 {
2060 LoadHasManyThrough(record, field);
2061 }
2062 });
2063#endif
2064}
2065
2066template <typename Record, typename ValueType>
2067inline LIGHTWEIGHT_FORCE_INLINE void DataMapper::SetId(Record& record, ValueType&& id)
2068{
2069 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2070 // static_assert(HasPrimaryKey<Record>);
2071
2072#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2073
2074 auto constexpr ctx = std::meta::access_context::current();
2075 template for (constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2076 {
2077 using FieldType = typename[:std::meta::type_of(el):];
2078 if constexpr (IsField<FieldType>)
2079 {
2080 if constexpr (FieldType::IsPrimaryKey)
2081 {
2082 record.[:el:] = std::forward<ValueType>(id);
2083 }
2084 }
2085 }
2086#else
2087 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType& field) {
2088 if constexpr (IsField<FieldType>)
2089 {
2090 if constexpr (FieldType::IsPrimaryKey)
2091 {
2092 field = std::forward<FieldType>(id);
2093 }
2094 }
2095 });
2096#endif
2097}
2098
2099template <typename Record, size_t InitialOffset>
2100inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2101{
2102 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2103 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
2104 return record;
2105}
2106
2107template <typename Record, size_t InitialOffset>
2108Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2109{
2110 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2111 record, stmt);
2112}
2113
2114template <typename ElementMask, typename Record, size_t InitialOffset>
2115inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2116{
2117 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2118 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
2119}
2120
2121template <typename ElementMask, typename Record, size_t InitialOffset>
2122Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2123{
2124 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2125 static_assert(!std::is_const_v<Record>);
2126 assert(stmt != nullptr);
2127
2128#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2129 auto constexpr ctx = std::meta::access_context::current();
2130 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2131 template for (constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2132 {
2133 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2134 using FieldType = typename[:std::meta::type_of(el):];
2135 if constexpr (IsField<FieldType>)
2136 {
2137 stmt->BindOutputColumn(i++, &record.[:el:].MutableValue());
2138 }
2139 else if constexpr (SqlOutputColumnBinder<FieldType>)
2140 {
2141 stmt->BindOutputColumn(i++, &record.[:el:]);
2142 }
2143 }
2144#else
2145 Reflection::EnumerateMembers<ElementMask>(
2146 record, [stmt, i = SQLSMALLINT { InitialOffset }]<size_t I, typename Field>(Field& field) mutable {
2147 if constexpr (IsField<Field>)
2148 {
2149 stmt->BindOutputColumn(i++, &field.MutableValue());
2150 }
2151 else if constexpr (SqlOutputColumnBinder<Field>)
2152 {
2153 stmt->BindOutputColumn(i++, &field);
2154 }
2155 });
2156#endif
2157
2158 return record;
2159}
2160
2161template <typename Record>
2163{
2164 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
2165
2166 auto self = shared_from_this();
2167 auto const callback = [&]<size_t FieldIndex, typename FieldType>(FieldType& field) {
2168 if constexpr (IsBelongsTo<FieldType>)
2169 {
2170 field.SetAutoLoader(typename FieldType::Loader {
2171 .loadReference = [self, value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2172 return self->LoadBelongsTo<FieldType>(value);
2173 },
2174 });
2175 }
2176 else if constexpr (IsHasMany<FieldType>)
2177 {
2178 using ReferencedRecord = FieldType::ReferencedRecord;
2179 HasMany<ReferencedRecord>& hasMany = field;
2180 hasMany.SetAutoLoader(typename FieldType::Loader {
2181 .count = [self, &record]() -> size_t {
2182 size_t count = 0;
2183 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2184 record, [&](SqlSelectQueryBuilder selectQuery, auto const& primaryKeyField) {
2185 self->_stmt.Prepare(selectQuery.Count());
2186 self->_stmt.Execute(primaryKeyField.Value());
2187 if (self->_stmt.FetchRow())
2188 count = self->_stmt.GetColumn<size_t>(1);
2189 self->_stmt.CloseCursor();
2190 });
2191 return count;
2192 },
2193 .all = [self, &record, &hasMany]() { self->LoadHasMany<FieldIndex>(record, hasMany); },
2194 .each =
2195 [self, &record](auto const& each) {
2196 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2197 record, [&](SqlSelectQueryBuilder selectQuery, auto const& primaryKeyField) {
2198 auto stmt = SqlStatement { self->_connection };
2199 stmt.Prepare(selectQuery.All());
2200 stmt.Execute(primaryKeyField.Value());
2201
2202 auto referencedRecord = ReferencedRecord {};
2203 self->BindOutputColumns(referencedRecord, &stmt);
2204 self->ConfigureRelationAutoLoading(referencedRecord);
2205
2206 while (stmt.FetchRow())
2207 {
2208 each(referencedRecord);
2209 self->BindOutputColumns(referencedRecord, &stmt);
2210 }
2211 });
2212 },
2213 });
2214 }
2215 else if constexpr (IsHasOneThrough<FieldType>)
2216 {
2217 using ReferencedRecord = FieldType::ReferencedRecord;
2218 using ThroughRecord = FieldType::ThroughRecord;
2220 hasOneThrough.SetAutoLoader(typename FieldType::Loader {
2221 .loadReference =
2222 [self, &record, &hasOneThrough]() {
2223 self->LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
2224 },
2225 });
2226 }
2227 else if constexpr (IsHasManyThrough<FieldType>)
2228 {
2229 using ReferencedRecord = FieldType::ReferencedRecord;
2230 using ThroughRecord = FieldType::ThroughRecord;
2232 hasManyThrough.SetAutoLoader(typename FieldType::Loader {
2233 .count = [self, &record]() -> size_t {
2234 // Load result for Count()
2235 size_t count = 0;
2236 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2237 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
2238 self->_stmt.Prepare(selectQuery.Count());
2239 self->_stmt.Execute(primaryKeyField.Value());
2240 if (self->_stmt.FetchRow())
2241 count = self->_stmt.GetColumn<size_t>(1);
2242 self->_stmt.CloseCursor();
2243 });
2244 return count;
2245 },
2246 .all =
2247 [self, &record, &hasManyThrough]() {
2248 // Load result for All()
2249 self->LoadHasManyThrough(record, hasManyThrough);
2250 },
2251 .each =
2252 [self, &record](auto const& each) {
2253 // Load result for Each()
2254 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2255 record, [&](SqlSelectQueryBuilder& selectQuery, auto& primaryKeyField) {
2256 auto stmt = SqlStatement { self->_connection };
2257 stmt.Prepare(selectQuery.All());
2258 stmt.Execute(primaryKeyField.Value());
2259
2260 auto referencedRecord = ReferencedRecord {};
2261 self->BindOutputColumns(referencedRecord, &stmt);
2262 self->ConfigureRelationAutoLoading(referencedRecord);
2263
2264 while (stmt.FetchRow())
2265 {
2266 each(referencedRecord);
2267 self->BindOutputColumns(referencedRecord, &stmt);
2268 }
2269 });
2270 },
2271 });
2272 }
2273 };
2274
2275#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2276 constexpr auto ctx = std::meta::access_context::current();
2277
2278 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<auto I>() {
2279 constexpr auto localctx = std::meta::access_context::current();
2280 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2281 using FieldType = typename[:std::meta::type_of(members[I]):];
2282 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2283 });
2284#else
2285 Reflection::EnumerateMembers(record, callback);
2286#endif
2287}
2288
2289} // 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.
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
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.