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>
37 template <
template <
typename>
class Allocator,
template <
typename,
typename>
class Container,
typename Object>
38 auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
40 using SharedPtrRecord = std::shared_ptr<Object>;
41 auto sharedPtrContainer = Container<SharedPtrRecord, Allocator<SharedPtrRecord>> {};
42 for (
auto&
object: container)
43 sharedPtrContainer.emplace_back(std::make_shared<Object>(std::move(object)));
44 return sharedPtrContainer;
99 _connection { std::move(connection) },
100 _stmt { _connection }
106 _connection { std::move(connectionString) },
107 _stmt { _connection }
130 template <
typename Record>
131 static std::string
Inspect(Record
const& record);
134 template <
typename Record>
138 template <
typename FirstRecord,
typename... MoreRecords>
142 template <
typename Record>
146 template <
typename FirstRecord,
typename... MoreRecords>
154 template <
typename Record>
155 RecordPrimaryKeyType<Record>
Create(Record& record);
162 template <
typename Record>
163 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
169 template <
typename Record,
typename... PrimaryKeyTypes>
170 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
179 template <
typename Record,
typename... PrimaryKeyTypes>
183 template <
typename Record,
typename... InputParameters>
184 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
217 template <
typename... Records>
218 requires DataMapperRecords<Records...>
219 std::vector<std::tuple<Records...>>
QueryToTuple(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
250 template <
typename Record,
typename... InputParameters>
251 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
277 template <
typename ElementMask,
typename Record,
typename... InputParameters>
278 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
304 template <
typename FirstRecord,
typename NextRecord,
typename... InputParameters>
307 std::vector<std::tuple<FirstRecord, NextRecord>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery,
308 InputParameters&&... inputParameters);
311 template <
typename FirstRecord,
typename NextRecord>
318 auto const emplaceRecordsFrom = [&fields]<
typename Record>() {
319 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
322 fields += std::format(R
"("{}"."{}")", RecordTableName<Record>, FieldNameAt<I, Record>);
326 emplaceRecordsFrom.template operator()<FirstRecord>();
327 emplaceRecordsFrom.template operator()<NextRecord>();
329 return SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>>(_stmt, std::move(fields));
344 template <
typename Record>
348 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
352 fields += RecordTableName<Record>;
354 fields += FieldNameAt<I, Record>;
361 template <
typename Record>
362 void Update(Record& record);
365 template <
typename Record>
366 std::size_t
Delete(Record
const& record);
371 return _connection.
Query(tableName);
375 template <
typename Record>
376 bool IsModified(Record
const& record)
const noexcept;
379 template <
typename Record>
383 template <
typename Record>
390 template <
typename Record>
400 template <
typename Record,
typename... Args>
403 template <
typename Record,
typename ValueType>
404 void SetId(Record& record, ValueType&&
id);
406 template <
typename Record,
size_t InitialOffset = 1>
407 Record& BindOutputColumns(Record& record);
409 template <
typename Record,
size_t InitialOffset = 1>
410 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
412 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
413 Record& BindOutputColumns(Record& record);
415 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
416 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
418 template <
typename FieldType>
419 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(
typename FieldType::ValueType value);
421 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
424 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
427 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
430 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
431 void CallOnHasMany(Record& record, Callable
const& callback);
433 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
434 void CallOnHasManyThrough(Record& record, Callable
const& callback);
442template <
typename Record>
448 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
454 if constexpr (Value::IsOptional)
456 if (!value.Value().has_value())
458 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
462 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
465 else if constexpr (IsBelongsTo<Value>)
467 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
469 else if constexpr (std::same_as<typename Value::ValueType, char>)
474 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
477 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
478 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
480 return "{\n" + std::move(str) +
"\n}";
483template <
typename Record>
489 auto createTable = migration.
CreateTable(RecordTableName<Record>);
491 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
494 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
495 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
496 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
497 else if constexpr (FieldType::IsPrimaryKey)
498 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
499 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
500 else if constexpr (IsBelongsTo<FieldType>)
502 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
503 auto index = size_t(-1);
504 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
505 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
506 if constexpr (IsField<ReferencedFieldType>)
507 if constexpr (ReferencedFieldType::IsPrimaryKey)
512 createTable.ForeignKey(
513 std::string(FieldNameAt<I, Record>),
514 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
516 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
518 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
520 else if constexpr (FieldType::IsMandatory)
521 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
522 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
524 createTable.Column(std::string(FieldNameAt<I, Record>),
525 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
529 return migration.GetPlan().ToSql();
532template <
typename FirstRecord,
typename... MoreRecords>
535 std::vector<std::string> output;
536 auto const append = [&output](
auto const& sql) {
537 output.insert(output.end(), sql.begin(), sql.end());
539 append(CreateTableString<FirstRecord>(serverType));
540 (append(CreateTableString<MoreRecords>(serverType)), ...);
544template <
typename Record>
549 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
550 for (
auto const& sqlQueryString: sqlQueryStrings)
554template <
typename FirstRecord,
typename... MoreRecords>
557 CreateTable<FirstRecord>();
558 (CreateTable<MoreRecords>(), ...);
561template <
typename Record>
566 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
568 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
569 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
570 query.Set(FieldNameAt<I, Record>, SqlWildcard);
575 Reflection::CallOnMembers(
577 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
578 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
579 _stmt.BindInputParameter(i++, field, name);
584 if constexpr (HasAutoIncrementPrimaryKey<Record>)
586 else if constexpr (HasPrimaryKey<Record>)
588 RecordPrimaryKeyType<Record>
const* primaryKey =
nullptr;
589 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
590 if constexpr (IsField<FieldType>)
592 if constexpr (FieldType::IsPrimaryKey)
594 primaryKey = &field.Value();
602template <
typename Record>
605 static_assert(!std::is_const_v<Record>);
609 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
610 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
612 if (!primaryKeyField.IsModified())
614 using ValueType =
typename PrimaryKeyType::ValueType;
615 if constexpr (std::same_as<ValueType, SqlGuid>)
619 else if constexpr (
requires { ValueType {} + 1; })
621 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
622 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
623 FieldNameAt<PrimaryKeyIndex, Record>,
624 RecordTableName<Record>));
625 primaryKeyField = maxId.value_or(ValueType {}) + 1;
633 if constexpr (HasAutoIncrementPrimaryKey<Record>)
634 SetId(record, _stmt.
LastInsertId(RecordTableName<Record>));
639 if constexpr (HasPrimaryKey<Record>)
643template <
typename Record>
648 bool modified =
false;
650 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
651 if constexpr (
requires { field.IsModified(); })
653 modified = modified || field.IsModified();
660template <
typename Record>
665 auto query = _connection.
Query(RecordTableName<Record>).
Update();
667 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
668 if (field.IsModified())
669 query.Set(FieldNameAt<I, Record>, SqlWildcard);
672 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
673 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
680 Reflection::CallOnMembers(
681 record, [
this, &i]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
682 if (field.IsModified())
683 _stmt.BindInputParameter(i++, field.Value(), name);
687 Reflection::CallOnMembers(
688 record, [
this, &i]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
689 if constexpr (FieldType::IsPrimaryKey)
690 _stmt.BindInputParameter(i++, field.Value(), name);
698template <
typename Record>
703 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
705 Reflection::CallOnMembers(record,
706 [&query]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& ) {
707 if constexpr (FieldType::IsPrimaryKey)
708 std::ignore = query.Where(name, SqlWildcard);
714 Reflection::CallOnMembers(
716 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
717 if constexpr (FieldType::IsPrimaryKey)
718 _stmt.BindInputParameter(i++, field.Value(), name);
726template <
typename Record,
typename... PrimaryKeyTypes>
731 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
733 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
736 queryBuilder.Field(FieldNameAt<I, Record>);
738 if constexpr (FieldType::IsPrimaryKey)
739 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
743 _stmt.
Prepare(queryBuilder.First());
744 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
746 auto resultRecord = std::optional<Record> { Record {} };
754template <
typename Record,
typename... PrimaryKeyTypes>
757 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
763template <
typename Record,
typename... Args>
768 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
770 selectQuery.
Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
773 _stmt.
Execute(std::forward<Args>(args)...);
775 auto resultRecord = std::optional<Record> { Record {} };
784template <
typename Record,
typename... InputParameters>
786 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
788 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
790 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
793template <
typename Record,
typename... InputParameters>
794std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
796 auto result = std::vector<Record> {};
798 if constexpr (std::same_as<Record, SqlVariantRow>)
801 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
805 auto& record = result.emplace_back();
806 record.reserve(numResultColumns);
807 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
815 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
818 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
824 auto& record = result.emplace_back();
826 if (canSafelyBindOutputColumns)
827 BindOutputColumns(record);
829 if (!reader.FetchRow())
832 if (!canSafelyBindOutputColumns)
833 detail::GetAllColumns(reader, record);
839 for (
auto& record: result)
846template <
typename... Records>
847 requires DataMapperRecords<Records...>
850 using value_type = std::tuple<Records...>;
851 auto result = std::vector<value_type> {};
853 _stmt.
Prepare(selectQuery.ToSql());
857 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
862 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
863 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
864 }(std::make_index_sequence<I> {});
869 auto const BindElements = [&](
auto& record) {
870 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
871 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
872 auto& element = std::get<I>(record);
873 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
874 this->BindOutputColumns<TupleElement, offset>(element);
878 auto const GetElements = [&](
auto& record) {
879 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
880 auto& element = std::get<I>(record);
881 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
882 detail::GetAllColumns(reader, element, offset - 1);
886 bool const canSafelyBindOutputColumns = [&]() {
888 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
889 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
897 auto& record = result.emplace_back();
899 if (canSafelyBindOutputColumns)
900 BindElements(record);
902 if (!reader.FetchRow())
905 if (!canSafelyBindOutputColumns)
912 for (
auto& record: result)
914 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
915 auto& element = std::get<I>(record);
923template <
typename FirstRecord,
typename SecondRecord,
typename... InputParameters>
925std::vector<std::tuple<FirstRecord, SecondRecord>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery,
926 InputParameters&&... inputParameters)
928 auto result = std::vector<std::tuple<FirstRecord, SecondRecord>> {};
930 _stmt.
Prepare(selectQuery.ToSql());
931 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
933 auto const ConfigureFetchAndBind = [
this](
auto& record) {
934 auto& [recordFirst, recordSecond] = record;
938 this->BindOutputColumns<FirstRecord, 1>(recordFirst);
939 this->BindOutputColumns<SecondRecord, Reflection::CountMembers<FirstRecord> + 1>(recordSecond);
944 ConfigureFetchAndBind(result.emplace_back());
946 ConfigureFetchAndBind(result.emplace_back());
955template <
typename ElementMask,
typename Record,
typename... InputParameters>
957 InputParameters&&... inputParameters)
961 _stmt.
Prepare(selectQuery.ToSql());
962 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
964 auto records = std::vector<Record> {};
967 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
973 auto& record = records.emplace_back();
975 if (canSafelyBindOutputColumns)
976 BindOutputColumns<ElementMask>(record);
978 if (!reader.FetchRow())
981 if (!canSafelyBindOutputColumns)
982 detail::GetAllColumns<ElementMask>(reader, record);
988 for (
auto& record: records)
994template <
typename Record>
997 static_assert(!std::is_const_v<Record>);
1000 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1001 if constexpr (
requires { field.SetModified(
false); })
1003 field.SetModified(
false);
1008template <
typename Record,
typename Callable>
1009inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1013 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1014 if constexpr (IsField<FieldType>)
1016 if constexpr (FieldType::IsPrimaryKey)
1018 return callable.template operator()<I, FieldType>(field);
1024template <
typename Record,
typename Callable>
1025inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1027 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1029 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1030 if constexpr (IsField<FieldType>)
1032 if constexpr (FieldType::IsPrimaryKey)
1034 return callable.template operator()<I, FieldType>();
1040template <
typename Record,
typename Callable>
1041inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1043 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1045 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1046 if constexpr (IsBelongsTo<FieldType>)
1048 return callable.template operator()<I, FieldType>();
1053template <
typename FieldType>
1054std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(
typename FieldType::ValueType value)
1056 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1058 std::optional<ReferencedRecord> record { std::nullopt };
1059 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1060 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1061 record = std::move(result);
1064 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1069template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1070void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1072 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1073 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1075 using FieldType = HasMany<OtherRecord>;
1076 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1078 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1079 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1081 .Build([&](
auto& query) {
1082 Reflection::EnumerateMembers<ReferencedRecord>(
1083 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1084 if constexpr (FieldWithStorage<ReferencedFieldType>)
1086 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1090 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1091 callback(query, primaryKeyField);
1095template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1096void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1098 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1099 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1101 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1102 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1106template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1107void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1109 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1110 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
1113 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1115 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1117 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1119 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1124 _connection.
Query(RecordTableName<ReferencedRecord>)
1126 .Build([&](
auto& query) {
1127 Reflection::EnumerateMembers<ReferencedRecord>(
1128 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1129 if constexpr (FieldWithStorage<ReferencedFieldType>)
1131 query.Field(SqlQualifiedTableColumnName {
1132 RecordTableName<ReferencedRecord>,
1133 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1137 .InnerJoin(RecordTableName<ThroughRecord>,
1138 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1139 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1140 .InnerJoin(RecordTableName<Record>,
1141 FieldNameAt<PrimaryKeyIndex, Record>,
1142 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1143 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1145 SqlQualifiedTableColumnName {
1146 RecordTableName<Record>,
1147 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1150 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1152 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1160template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1161void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1163 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1166 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1168 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1169 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1170 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1173 CallOnBelongsTo<ThroughRecord>(
1174 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
1175 using ThroughBelongsToReferenceRecordFieldType =
1176 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1177 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1180 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1182 .Build([&](
auto& query) {
1183 Reflection::EnumerateMembers<ReferencedRecord>(
1184 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1185 if constexpr (FieldWithStorage<ReferencedFieldType>)
1187 query.Field(SqlQualifiedTableColumnName {
1188 RecordTableName<ReferencedRecord>,
1189 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1193 .InnerJoin(RecordTableName<ThroughRecord>,
1194 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1195 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1196 FieldNameAt<PrimaryKeyIndex, Record> })
1198 SqlQualifiedTableColumnName {
1199 RecordTableName<ThroughRecord>,
1200 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1203 callback(query, primaryKeyField);
1211template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1212void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
1214 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1216 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1217 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
1218 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
1222template <
typename Record>
1225 static_assert(!std::is_const_v<Record>);
1228 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
1229 if constexpr (IsBelongsTo<FieldType>)
1231 field = LoadBelongsTo<FieldType>(field.Value());
1233 else if constexpr (IsHasMany<FieldType>)
1235 LoadHasMany<FieldIndex>(record, field);
1237 else if constexpr (IsHasOneThrough<FieldType>)
1239 LoadHasOneThrough(record, field);
1241 else if constexpr (IsHasManyThrough<FieldType>)
1243 LoadHasManyThrough(record, field);
1248template <
typename Record,
typename ValueType>
1249inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
1254 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1255 if constexpr (IsField<FieldType>)
1257 if constexpr (FieldType::IsPrimaryKey)
1259 field = std::forward<FieldType>(
id);
1265template <
typename Record,
size_t InitialOffset>
1266inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1268 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1269 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
1273template <
typename Record,
size_t InitialOffset>
1274Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
1276 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
1280template <
typename ElementMask,
typename Record,
size_t InitialOffset>
1281inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1283 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1284 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
1287template <
typename ElementMask,
typename Record,
size_t InitialOffset>
1288Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
1290 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1291 static_assert(!std::is_const_v<Record>);
1292 assert(stmt !=
nullptr);
1294 Reflection::EnumerateMembers<ElementMask>(
1295 record, [stmt, i = SQLSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
1296 if constexpr (IsField<Field>)
1298 stmt->BindOutputColumn(i++, &field.MutableValue());
1300 else if constexpr (SqlOutputColumnBinder<Field>)
1302 stmt->BindOutputColumn(i++, &field);
1309template <
typename Record>
1314 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
1315 if constexpr (IsBelongsTo<FieldType>)
1317 field.SetAutoLoader(
typename FieldType::Loader {
1318 .loadReference = [
this, value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
1319 return LoadBelongsTo<FieldType>(value);
1323 else if constexpr (IsHasMany<FieldType>)
1325 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1328 .count = [
this, &record]() ->
size_t {
1330 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1333 _stmt.
Execute(primaryKeyField.Value());
1340 .all = [
this, &record, &hasMany]() { LoadHasMany<FieldIndex>(record, hasMany); },
1342 [
this, &record](
auto const& each) {
1343 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1346 stmt.Prepare(selectQuery.
All());
1347 stmt.Execute(primaryKeyField.Value());
1349 auto referencedRecord = ReferencedRecord {};
1350 BindOutputColumns(referencedRecord, &stmt);
1353 while (stmt.FetchRow())
1355 each(referencedRecord);
1356 BindOutputColumns(referencedRecord, &stmt);
1362 else if constexpr (IsHasOneThrough<FieldType>)
1364 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1365 using ThroughRecord =
typename FieldType::ThroughRecord;
1369 [
this, &record, &hasOneThrough]() {
1370 LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
1374 else if constexpr (IsHasManyThrough<FieldType>)
1376 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1377 using ThroughRecord =
typename FieldType::ThroughRecord;
1380 .count = [
this, &record]() ->
size_t {
1383 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1386 _stmt.
Execute(primaryKeyField.Value());
1394 [
this, &record, &hasManyThrough]() {
1396 LoadHasManyThrough(record, hasManyThrough);
1399 [
this, &record](
auto const& each) {
1401 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1404 stmt.Prepare(selectQuery.
All());
1405 stmt.Execute(primaryKeyField.Value());
1407 auto referencedRecord = ReferencedRecord {};
1408 BindOutputColumns(referencedRecord, &stmt);
1411 while (stmt.FetchRow())
1413 each(referencedRecord);
1414 BindOutputColumns(referencedRecord, &stmt);
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.
DataMapper()
Constructs a new data mapper, using the default connection.
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.
std::size_t Delete(Record const &record)
Deletes the record from the database.
DataMapper(SqlConnection &&connection)
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.
RecordPrimaryKeyType< Record > Create(Record &record)
Creates a new record in the database.
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(SqlConnectionString connectionString)
Constructs a new data mapper, using the given connection string.
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 ComposedQuery All()
Finalizes building the query as SELECT field names FROM ... query.
LIGHTWEIGHT_API ComposedQuery Count()
Finalizes building the query as SELECT COUNT(*) ... 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.
void CloseCursor() noexcept
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.