4#include "../SqlConnection.hpp"
5#include "../SqlDataBinder.hpp"
6#include "../SqlLogger.hpp"
7#include "../SqlRealName.hpp"
8#include "../SqlStatement.hpp"
10#include "BelongsTo.hpp"
11#include "CollectDifferences.hpp"
14#include "HasManyThrough.hpp"
15#include "HasOneThrough.hpp"
16#include "QueryBuilders.hpp"
19#include <reflection-cpp/reflection.hpp>
38 template <
template <
typename>
class Allocator,
template <
typename,
typename>
class Container,
typename Object>
39 auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
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;
88class DataMapper:
public std::enable_shared_from_this<DataMapper>
92 explicit PrivateTag() =
default;
105 _connection { std::move(connection) },
106 _stmt { _connection }
112 _connection { std::move(connectionString) },
113 _stmt { _connection }
118 [[nodiscard]]
static std::shared_ptr<DataMapper>
Create()
120 return std::make_shared<DataMapper>(PrivateTag {});
126 return std::make_shared<DataMapper>(std::move(connection), PrivateTag {});
132 return std::make_shared<DataMapper>(std::move(connectionString), PrivateTag {});
154 template <
typename Record>
155 static std::string
Inspect(Record
const& record);
158 template <
typename Record>
162 template <
typename FirstRecord,
typename... MoreRecords>
166 template <
typename Record>
170 template <
typename FirstRecord,
typename... MoreRecords>
178 template <
typename Record>
179 RecordPrimaryKeyType<Record>
Create(Record& record);
186 template <
typename Record>
187 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
193 template <
typename Record,
typename... PrimaryKeyTypes>
194 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
203 template <
typename Record,
typename... PrimaryKeyTypes>
207 template <
typename Record,
typename... InputParameters>
208 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
241 template <
typename... Records>
242 requires DataMapperRecords<Records...>
243 std::vector<std::tuple<Records...>>
QueryToTuple(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
274 template <
typename Record,
typename... InputParameters>
275 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
301 template <
typename ElementMask,
typename Record,
typename... InputParameters>
302 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
328 template <
typename FirstRecord,
typename NextRecord,
typename... InputParameters>
331 std::vector<std::tuple<FirstRecord, NextRecord>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery,
332 InputParameters&&... inputParameters);
335 template <
typename FirstRecord,
typename NextRecord>
342 auto const emplaceRecordsFrom = [&fields]<
typename Record>() {
343 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
346 fields += std::format(R
"("{}"."{}")", RecordTableName<Record>, FieldNameAt<I, Record>);
350 emplaceRecordsFrom.template operator()<FirstRecord>();
351 emplaceRecordsFrom.template operator()<NextRecord>();
353 return SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>>(_stmt, std::move(fields));
368 template <
typename Record>
372 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
376 fields += RecordTableName<Record>;
378 fields += FieldNameAt<I, Record>;
385 template <
typename Record>
386 void Update(Record& record);
389 template <
typename Record>
390 std::size_t
Delete(Record
const& record);
395 return _connection.
Query(tableName);
399 template <
typename Record>
400 bool IsModified(Record
const& record)
const noexcept;
403 template <
typename Record>
407 template <
typename Record>
414 template <
typename Record>
424 template <
typename Record,
typename... Args>
427 template <
typename Record,
typename ValueType>
428 void SetId(Record& record, ValueType&&
id);
430 template <
typename Record,
size_t InitialOffset = 1>
431 Record& BindOutputColumns(Record& record);
433 template <
typename Record,
size_t InitialOffset = 1>
434 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
436 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
437 Record& BindOutputColumns(Record& record);
439 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
440 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
442 template <
typename FieldType>
443 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(
typename FieldType::ValueType value);
445 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
448 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
451 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
454 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
455 void CallOnHasMany(Record& record, Callable
const& callback);
457 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
458 void CallOnHasManyThrough(Record& record, Callable
const& callback);
466template <
typename Record>
472 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
478 if constexpr (Value::IsOptional)
480 if (!value.Value().has_value())
482 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
486 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
489 else if constexpr (IsBelongsTo<Value>)
491 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
493 else if constexpr (std::same_as<typename Value::ValueType, char>)
498 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
501 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
502 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
504 return "{\n" + std::move(str) +
"\n}";
507template <
typename Record>
513 auto createTable = migration.
CreateTable(RecordTableName<Record>);
514#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
515 constexpr auto ctx = std::meta::access_context::current();
516 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
518 using FieldType =
typename[:std::meta::type_of(el):];
521 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
522 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameOf<el>),
523 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
524 else if constexpr (FieldType::IsPrimaryKey)
525 createTable.PrimaryKey(std::string(FieldNameOf<el>),
526 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
527 else if constexpr (IsBelongsTo<FieldType>)
529 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
530 auto index = size_t(-1);
531 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
532 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
533 if constexpr (IsField<ReferencedFieldType>)
534 if constexpr (ReferencedFieldType::IsPrimaryKey)
539 createTable.ForeignKey(
540 std::string(FieldNameOf<el>),
541 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
543 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
544 .columnName = std::string { FieldNameOf<FieldType::ReferencedField> } });
546 else if constexpr (FieldType::IsMandatory)
547 createTable.RequiredColumn(std::string(FieldNameOf<el>),
548 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
550 createTable.Column(std::string(FieldNameOf<el>), SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
555 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
558 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
559 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
560 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
561 else if constexpr (FieldType::IsPrimaryKey)
562 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
563 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
564 else if constexpr (IsBelongsTo<FieldType>)
566 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
567 auto index = size_t(-1);
568 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
569 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
570 if constexpr (IsField<ReferencedFieldType>)
571 if constexpr (ReferencedFieldType::IsPrimaryKey)
576 createTable.ForeignKey(
577 std::string(FieldNameAt<I, Record>),
578 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
580 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
582 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
584 else if constexpr (FieldType::IsMandatory)
585 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
586 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
588 createTable.Column(std::string(FieldNameAt<I, Record>),
589 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
593 return migration.GetPlan().ToSql();
596template <
typename FirstRecord,
typename... MoreRecords>
599 std::vector<std::string> output;
600 auto const append = [&output](
auto const& sql) {
601 output.insert(output.end(), sql.begin(), sql.end());
603 append(CreateTableString<FirstRecord>(serverType));
604 (append(CreateTableString<MoreRecords>(serverType)), ...);
608template <
typename Record>
613 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
614 for (
auto const& sqlQueryString: sqlQueryStrings)
618template <
typename FirstRecord,
typename... MoreRecords>
621 CreateTable<FirstRecord>();
622 (CreateTable<MoreRecords>(), ...);
625template <
typename Record>
630 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
632#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
633 constexpr auto ctx = std::meta::access_context::current();
634 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
636 using FieldType =
typename[:std::meta::type_of(el):];
637 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
638 query.Set(FieldNameOf<el>, SqlWildcard);
641 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
642 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
643 query.Set(FieldNameAt<I, Record>, SqlWildcard);
649#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
651 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
653 using FieldType =
typename[:std::meta::type_of(el):];
654 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
655 _stmt.BindInputParameter(i++, record.[:el:], std::meta::identifier_of(el));
658 Reflection::CallOnMembers(
660 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
661 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
662 _stmt.BindInputParameter(i++, field, name);
667 if constexpr (HasAutoIncrementPrimaryKey<Record>)
669 else if constexpr (HasPrimaryKey<Record>)
671 RecordPrimaryKeyType<Record>
const* primaryKey =
nullptr;
672#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
673 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
675 using FieldType =
typename[:std::meta::type_of(el):];
676 if constexpr (IsField<FieldType>)
678 if constexpr (FieldType::IsPrimaryKey)
680 primaryKey = &record.[:el:].Value();
685 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
686 if constexpr (IsField<FieldType>)
688 if constexpr (FieldType::IsPrimaryKey)
690 primaryKey = &field.Value();
699template <
typename Record>
702 static_assert(!std::is_const_v<Record>);
707#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
708 constexpr auto ctx = std::meta::access_context::current();
709 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
711 using FieldType =
typename[:std::meta::type_of(el):];
712 if constexpr (IsField<FieldType>)
713 if constexpr (FieldType::IsPrimaryKey)
714 if constexpr (FieldType::IsAutoAssignPrimaryKey)
716 if (!record.[:el:].IsModified())
718 using ValueType =
typename FieldType::ValueType;
719 if constexpr (std::same_as<ValueType, SqlGuid>)
723 else if constexpr (
requires { ValueType {} + 1; })
725 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(std::format(
726 R
"sql(SELECT MAX("{}") FROM "{}")sql", FieldNameOf<el>, RecordTableName<Record>));
727 record.[:el:] = maxId.value_or(ValueType {}) + 1;
733 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
734 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
736 if (!primaryKeyField.IsModified())
738 using ValueType =
typename PrimaryKeyType::ValueType;
739 if constexpr (std::same_as<ValueType, SqlGuid>)
743 else if constexpr (
requires { ValueType {} + 1; })
745 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
746 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
747 FieldNameAt<PrimaryKeyIndex, Record>,
748 RecordTableName<Record>));
749 primaryKeyField = maxId.value_or(ValueType {}) + 1;
758 if constexpr (HasAutoIncrementPrimaryKey<Record>)
759 SetId(record, _stmt.
LastInsertId(RecordTableName<Record>));
764 if constexpr (HasPrimaryKey<Record>)
768template <
typename Record>
773 bool modified =
false;
775#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
776 auto constexpr ctx = std::meta::access_context::current();
777 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
779 if constexpr (
requires { record.[:el:].IsModified(); })
781 modified = modified || record.[:el:].IsModified();
785 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
786 if constexpr (
requires { field.IsModified(); })
788 modified = modified || field.IsModified();
796template <
typename Record>
801 auto query = _connection.
Query(RecordTableName<Record>).
Update();
803#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
804 auto constexpr ctx = std::meta::access_context::current();
805 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
807 using FieldType =
typename[:std::meta::type_of(el):];
810 if (record.[:el:].IsModified())
811 query.Set(FieldNameOf<el>, SqlWildcard);
812 if constexpr (IsPrimaryKey<FieldType>)
813 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
817 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
818 if (field.IsModified())
819 query.Set(FieldNameAt<I, Record>, SqlWildcard);
822 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
823 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
830#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
831 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
833 if (record.[:el:].IsModified())
835 _stmt.BindInputParameter(i++, record.[:el:].Value(), std::meta::identifier_of(el));
839 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
841 using FieldType =
typename[:std::meta::type_of(el):];
842 if constexpr (FieldType::IsPrimaryKey)
844 _stmt.BindInputParameter(i++, record.[:el:].Value(), std::meta::identifier_of(el));
849 Reflection::CallOnMembers(
850 record, [
this, &i]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
851 if (field.IsModified())
852 _stmt.BindInputParameter(i++, field.Value(), name);
856 Reflection::CallOnMembers(
857 record, [
this, &i]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
858 if constexpr (FieldType::IsPrimaryKey)
859 _stmt.BindInputParameter(i++, field.Value(), name);
868template <
typename Record>
873 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
875#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
876 auto constexpr ctx = std::meta::access_context::current();
877 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
879 using FieldType =
typename[:std::meta::type_of(el):];
880 if constexpr (FieldType::IsPrimaryKey)
881 std::ignore = query.Where(std::meta::identifier_of(el), SqlWildcard);
884 Reflection::CallOnMembers(record,
885 [&query]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& ) {
886 if constexpr (FieldType::IsPrimaryKey)
887 std::ignore = query.Where(name, SqlWildcard);
893#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
895 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
897 using FieldType =
typename[:std::meta::type_of(el):];
898 if constexpr (FieldType::IsPrimaryKey)
900 _stmt.BindInputParameter(i++, record.[:el:].Value(), std::meta::identifier_of(el));
905 Reflection::CallOnMembers(
907 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
908 if constexpr (FieldType::IsPrimaryKey)
909 _stmt.BindInputParameter(i++, field.Value(), name);
918template <
typename Record,
typename... PrimaryKeyTypes>
923 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
925 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
928 queryBuilder.Field(FieldNameAt<I, Record>);
930 if constexpr (FieldType::IsPrimaryKey)
931 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
935 _stmt.
Prepare(queryBuilder.First());
936 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
938 auto resultRecord = std::optional<Record> { Record {} };
946template <
typename Record,
typename... PrimaryKeyTypes>
949 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
955template <
typename Record,
typename... Args>
960 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
962 selectQuery.
Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
965 _stmt.
Execute(std::forward<Args>(args)...);
967 auto resultRecord = std::optional<Record> { Record {} };
976template <
typename Record,
typename... InputParameters>
978 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
980 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
982 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
985template <
typename Record,
typename... InputParameters>
986std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
988 auto result = std::vector<Record> {};
989 if constexpr (std::same_as<Record, SqlVariantRow>)
992 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
996 auto& record = result.emplace_back();
997 record.reserve(numResultColumns);
998 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1006 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1008 _stmt.
Prepare(sqlQueryString);
1009 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1015 auto& record = result.emplace_back();
1017 if (canSafelyBindOutputColumns)
1018 BindOutputColumns(record);
1020 if (!reader.FetchRow())
1023 if (!canSafelyBindOutputColumns)
1024 detail::GetAllColumns(reader, record);
1030 for (
auto& record: result)
1037template <
typename... Records>
1038 requires DataMapperRecords<Records...>
1041 using value_type = std::tuple<Records...>;
1042 auto result = std::vector<value_type> {};
1044 _stmt.
Prepare(selectQuery.ToSql());
1048 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1051 if constexpr (I > 0)
1053 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1054 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1055 }(std::make_index_sequence<I> {});
1060 auto const BindElements = [&](
auto& record) {
1061 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1062 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1063 auto& element = std::get<I>(record);
1064 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1065 this->BindOutputColumns<TupleElement, offset>(element);
1069 auto const GetElements = [&](
auto& record) {
1070 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1071 auto& element = std::get<I>(record);
1072 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1073 detail::GetAllColumns(reader, element, offset - 1);
1077 bool const canSafelyBindOutputColumns = [&]() {
1079 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1080 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1088 auto& record = result.emplace_back();
1090 if (canSafelyBindOutputColumns)
1091 BindElements(record);
1093 if (!reader.FetchRow())
1096 if (!canSafelyBindOutputColumns)
1097 GetElements(record);
1103 for (
auto& record: result)
1105 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1106 auto& element = std::get<I>(record);
1114template <
typename FirstRecord,
typename SecondRecord,
typename... InputParameters>
1116std::vector<std::tuple<FirstRecord, SecondRecord>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery,
1117 InputParameters&&... inputParameters)
1119 auto result = std::vector<std::tuple<FirstRecord, SecondRecord>> {};
1121 _stmt.
Prepare(selectQuery.ToSql());
1122 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1124 auto const ConfigureFetchAndBind = [
this](
auto& record) {
1125 auto& [recordFirst, recordSecond] = record;
1129 this->BindOutputColumns<FirstRecord, 1>(recordFirst);
1130 this->BindOutputColumns<SecondRecord, Reflection::CountMembers<FirstRecord> + 1>(recordSecond);
1135 ConfigureFetchAndBind(result.emplace_back());
1137 ConfigureFetchAndBind(result.emplace_back());
1140 if (!result.empty())
1146template <
typename ElementMask,
typename Record,
typename... InputParameters>
1148 InputParameters&&... inputParameters)
1152 _stmt.
Prepare(selectQuery.ToSql());
1153 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1155 auto records = std::vector<Record> {};
1158 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1164 auto& record = records.emplace_back();
1166 if (canSafelyBindOutputColumns)
1167 BindOutputColumns<ElementMask>(record);
1169 if (!reader.FetchRow())
1172 if (!canSafelyBindOutputColumns)
1173 detail::GetAllColumns<ElementMask>(reader, record);
1179 for (
auto& record: records)
1185template <
typename Record>
1188 static_assert(!std::is_const_v<Record>);
1191 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1192 if constexpr (
requires { field.SetModified(
false); })
1194 field.SetModified(
false);
1199template <
typename Record,
typename Callable>
1200inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1204 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1205 if constexpr (IsField<FieldType>)
1207 if constexpr (FieldType::IsPrimaryKey)
1209 return callable.template operator()<I, FieldType>(field);
1215template <
typename Record,
typename Callable>
1216inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1218 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1220 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1221 if constexpr (IsField<FieldType>)
1223 if constexpr (FieldType::IsPrimaryKey)
1225 return callable.template operator()<I, FieldType>();
1231template <
typename Record,
typename Callable>
1232inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1234 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1236 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1237 if constexpr (IsBelongsTo<FieldType>)
1239 return callable.template operator()<I, FieldType>();
1244template <
typename FieldType>
1245std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(
typename FieldType::ValueType value)
1247 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1249 std::optional<ReferencedRecord> record { std::nullopt };
1251#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1252 auto constexpr ctx = std::meta::access_context::current();
1253 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1255 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1256 if constexpr (IsField<BelongsToFieldType>)
1257 if constexpr (BelongsToFieldType::IsPrimaryKey)
1259 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1260 record = std::move(result);
1263 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1267 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1268 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1269 record = std::move(result);
1272 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1278template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1279void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1281 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1282 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1284 using FieldType = HasMany<OtherRecord>;
1285 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1287 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1288 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1290 .Build([&](
auto& query) {
1291 Reflection::EnumerateMembers<ReferencedRecord>(
1292 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1293 if constexpr (FieldWithStorage<ReferencedFieldType>)
1295 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1299 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1300 callback(query, primaryKeyField);
1304template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1305void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1307 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1308 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1310 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1311 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1315template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1316void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1318 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1319 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
1322 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1324 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1326 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1328 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1333 _connection.
Query(RecordTableName<ReferencedRecord>)
1335 .Build([&](
auto& query) {
1336 Reflection::EnumerateMembers<ReferencedRecord>(
1337 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1338 if constexpr (FieldWithStorage<ReferencedFieldType>)
1340 query.Field(SqlQualifiedTableColumnName {
1341 RecordTableName<ReferencedRecord>,
1342 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1346 .InnerJoin(RecordTableName<ThroughRecord>,
1347 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1348 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1349 .InnerJoin(RecordTableName<Record>,
1350 FieldNameAt<PrimaryKeyIndex, Record>,
1351 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1352 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1354 SqlQualifiedTableColumnName {
1355 RecordTableName<Record>,
1356 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1359 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1361 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1369template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1370void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1372 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1375 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1377 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1378 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1379 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1382 CallOnBelongsTo<ThroughRecord>(
1383 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
1384 using ThroughBelongsToReferenceRecordFieldType =
1385 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1386 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1389 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1391 .Build([&](
auto& query) {
1392 Reflection::EnumerateMembers<ReferencedRecord>(
1393 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1394 if constexpr (FieldWithStorage<ReferencedFieldType>)
1396 query.Field(SqlQualifiedTableColumnName {
1397 RecordTableName<ReferencedRecord>,
1398 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1402 .InnerJoin(RecordTableName<ThroughRecord>,
1403 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1404 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1405 FieldNameAt<PrimaryKeyIndex, Record> })
1407 SqlQualifiedTableColumnName {
1408 RecordTableName<ThroughRecord>,
1409 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1412 callback(query, primaryKeyField);
1420template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1421void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
1423 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1425 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1426 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
1427 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
1431template <
typename Record>
1434 static_assert(!std::is_const_v<Record>);
1437#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1438 constexpr auto ctx = std::meta::access_context::current();
1439 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1441 using FieldType =
typename[:std::meta::type_of(el):];
1442 if constexpr (IsBelongsTo<FieldType>)
1444 auto& field = record.[:el:];
1445 field = LoadBelongsTo<FieldType>(field.Value());
1447 else if constexpr (IsHasMany<FieldType>)
1449 LoadHasMany<el>(record, record.[:el:]);
1451 else if constexpr (IsHasOneThrough<FieldType>)
1453 LoadHasOneThrough(record, record.[:el:]);
1455 else if constexpr (IsHasManyThrough<FieldType>)
1457 LoadHasManyThrough(record, record.[:el:]);
1461 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
1462 if constexpr (IsBelongsTo<FieldType>)
1464 field = LoadBelongsTo<FieldType>(field.Value());
1466 else if constexpr (IsHasMany<FieldType>)
1468 LoadHasMany<FieldIndex>(record, field);
1470 else if constexpr (IsHasOneThrough<FieldType>)
1472 LoadHasOneThrough(record, field);
1474 else if constexpr (IsHasManyThrough<FieldType>)
1476 LoadHasManyThrough(record, field);
1482template <
typename Record,
typename ValueType>
1483inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
1488#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)))
1493 using FieldType =
typename[:std::meta::type_of(el):];
1494 if constexpr (IsField<FieldType>)
1496 if constexpr (FieldType::IsPrimaryKey)
1498 record.[:el:] = std::forward<ValueType>(
id);
1503 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1504 if constexpr (IsField<FieldType>)
1506 if constexpr (FieldType::IsPrimaryKey)
1508 field = std::forward<FieldType>(
id);
1515template <
typename Record,
size_t InitialOffset>
1516inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1518 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1519 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
1523template <
typename Record,
size_t InitialOffset>
1524Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
1526 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
1530template <
typename ElementMask,
typename Record,
size_t InitialOffset>
1531inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1533 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1534 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
1537template <
typename ElementMask,
typename Record,
size_t InitialOffset>
1538Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
1540 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1541 static_assert(!std::is_const_v<Record>);
1542 assert(stmt !=
nullptr);
1544#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1545 auto constexpr ctx = std::meta::access_context::current();
1546 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
1547 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
1549 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
1550 using FieldType =
typename[:std::meta::type_of(el):];
1551 if constexpr (IsField<FieldType>)
1553 stmt->BindOutputColumn(i++, &record.[:el:].MutableValue());
1555 else if constexpr (SqlOutputColumnBinder<FieldType>)
1557 stmt->BindOutputColumn(i++, &record.[:el:]);
1561 Reflection::EnumerateMembers<ElementMask>(
1562 record, [stmt, i = SQLSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
1563 if constexpr (IsField<Field>)
1565 stmt->BindOutputColumn(i++, &field.MutableValue());
1567 else if constexpr (SqlOutputColumnBinder<Field>)
1569 stmt->BindOutputColumn(i++, &field);
1577template <
typename Record>
1582 auto self = shared_from_this();
1583 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
1584 if constexpr (IsBelongsTo<FieldType>)
1586 field.SetAutoLoader(
typename FieldType::Loader {
1587 .loadReference = [self, value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
1588 return self->LoadBelongsTo<FieldType>(value);
1592 else if constexpr (IsHasMany<FieldType>)
1594 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1597 .count = [self, &record]() ->
size_t {
1599 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1601 self->_stmt.Prepare(selectQuery.Count());
1602 self->_stmt.Execute(primaryKeyField.Value());
1603 if (self->_stmt.FetchRow())
1604 count = self->_stmt.GetColumn<
size_t>(1);
1605 self->_stmt.CloseCursor();
1609 .all = [self, &record, &hasMany]() { self->LoadHasMany<FieldIndex>(record, hasMany); },
1611 [self, &record](
auto const& each) {
1612 self->CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1615 stmt.
Prepare(selectQuery.All());
1616 stmt.Execute(primaryKeyField.Value());
1618 auto referencedRecord = ReferencedRecord {};
1619 self->BindOutputColumns(referencedRecord, &stmt);
1620 self->ConfigureRelationAutoLoading(referencedRecord);
1622 while (stmt.FetchRow())
1624 each(referencedRecord);
1625 self->BindOutputColumns(referencedRecord, &stmt);
1631 else if constexpr (IsHasOneThrough<FieldType>)
1633 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1634 using ThroughRecord =
typename FieldType::ThroughRecord;
1638 [self, &record, &hasOneThrough]() {
1639 self->LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
1643 else if constexpr (IsHasManyThrough<FieldType>)
1645 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1646 using ThroughRecord =
typename FieldType::ThroughRecord;
1649 .count = [self, &record]() ->
size_t {
1652 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1654 self->_stmt.Prepare(selectQuery.Count());
1655 self->_stmt.Execute(primaryKeyField.Value());
1656 if (self->_stmt.FetchRow())
1657 count = self->_stmt.GetColumn<
size_t>(1);
1658 self->_stmt.CloseCursor();
1663 [self, &record, &hasManyThrough]() {
1665 self->LoadHasManyThrough(record, hasManyThrough);
1668 [self, &record](
auto const& each) {
1670 self->CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1673 stmt.
Prepare(selectQuery.All());
1674 stmt.Execute(primaryKeyField.Value());
1676 auto referencedRecord = ReferencedRecord {};
1677 self->BindOutputColumns(referencedRecord, &stmt);
1678 self->ConfigureRelationAutoLoading(referencedRecord);
1680 while (stmt.FetchRow())
1682 each(referencedRecord);
1683 self->BindOutputColumns(referencedRecord, &stmt);
1691#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1692 constexpr auto ctx = std::meta::access_context::current();
1694 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
1695 constexpr auto localctx = std::meta::access_context::current();
1696 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
1697 using FieldType =
typename[:std::meta::type_of(members[I]):];
1698 callback.template operator()<I, FieldType>(record.[:members[I]:]);
1701 Reflection::EnumerateMembers(record, callback);
Main API for mapping records to and from the database using high level C++ syntax.
void Update(Record &record)
Updates the record in the database.
std::optional< Record > QuerySingleWithoutRelationAutoLoading(PrimaryKeyTypes &&... primaryKeys)
Queries a single record (based on primary key) from the database without auto-loading relations.
SqlConnection const & Connection() const noexcept
Returns the connection reference used by this data mapper.
bool IsModified(Record const &record) const noexcept
Checks if the record has any modified fields.
static std::shared_ptr< DataMapper > Create(SqlConnection &&connection)
Factory method to create a new data mapper with given connection.
DataMapper(SqlConnectionString connectionString, PrivateTag _)
Constructs a new data mapper, using the given connection string.
std::vector< std::string > CreateTableString(SqlServerType serverType)
Constructs a string list of SQL queries to create the table for the given record type.
void LoadRelations(Record &record)
Loads all direct relations to this record.
static std::shared_ptr< DataMapper > Create(SqlConnectionString connectionString)
Factory method to create a new data mapper with connection string.
static std::shared_ptr< DataMapper > Create()
Factory method to create a new data mapper with default connection.
std::size_t Delete(Record const &record)
Deletes the record from the database.
DataMapper(SqlConnection &&connection, PrivateTag _)
Constructs a new data mapper, using the given connection.
void CreateTable()
Creates the table for the given record type.
void ClearModifiedState(Record &record) noexcept
Clears the modified state of the record.
SqlConnection & Connection() noexcept
Returns the mutable connection reference used by this data mapper.
SqlAllFieldsQueryBuilder< Record > Query()
void CreateTables()
Creates the tables for the given record types.
static std::string Inspect(Record const &record)
Constructs a human readable string representation of the given record.
RecordPrimaryKeyType< Record > CreateExplicit(Record const &record)
Creates a new record in the database.
std::vector< Record > Query(SqlSelectQueryBuilder::ComposedQuery const &selectQuery, InputParameters &&... inputParameters)
Queries multiple records from the database, based on the given query.
std::vector< std::string > CreateTablesString(SqlServerType serverType)
Constructs a string list of SQL queries to create the tables for the given record types.
void ConfigureRelationAutoLoading(Record &record)
SqlQueryBuilder FromTable(std::string_view tableName)
Constructs an SQL query builder for the given table name.
std::optional< Record > QuerySingle(PrimaryKeyTypes &&... primaryKeys)
Queries a single record (based on primary key) from the database.
DataMapper(PrivateTag _)
Constructs a new data mapper, using the default connection.
std::vector< std::tuple< Records... > > QueryToTuple(SqlSelectQueryBuilder::ComposedQuery const &selectQuery)
This API represents a many-to-many relationship between two records through a third record.
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
This HasMany<OtherRecord> represents a simple one-to-many relationship between two records.
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
Represents a one-to-one relationship through a join table.
void SetAutoLoader(Loader loader)
Used internally to configure on-demand loading of the record.
Represents a query builder that retrieves all fields of a record.
Represents a connection to a SQL database.
SqlServerType ServerType() const noexcept
Retrieves the type of the server.
LIGHTWEIGHT_API SqlQueryBuilder Query(std::string_view const &table={}) const
static LIGHTWEIGHT_API SqlLogger & GetLogger()
Retrieves the currently configured logger.
virtual void OnWarning(std::string_view const &message)=0
Invoked on a warning.
LIGHTWEIGHT_API SqlCreateTableQueryBuilder CreateTable(std::string_view tableName)
Creates a new table.
API Entry point for building SQL queries.
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
Query builder for building SELECT ... queries.
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.
LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType< Record > GetPrimaryKeyField(Record const &record) noexcept
Represents a single column in a table.
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.