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"
18#include <reflection-cpp/reflection.hpp>
34 { field.Value() } -> std::convertible_to<typename T::ValueType const&>;
35 { mutableField.MutableValue() } -> std::convertible_to<typename T::ValueType&>;
36 { field.IsModified() } -> std::convertible_to<bool>;
37 { mutableField.SetModified(
bool {}) } -> std::convertible_to<void>;
43template <
typename Record>
45 Reflection::FoldMembers<Record>(
size_t { 0 }, []<
size_t I,
typename Field>(
size_t const accum)
constexpr {
52template <
typename Record>
53concept RecordWithStorageFields = (RecordStorageFieldCount<Record> > 0);
58template <
size_t... Ints>
64template <auto Test,
typename T>
65constexpr bool CheckFieldProperty = Reflection::FoldMembers<T>(
false, []<
size_t I,
typename Field>(
bool const accum) {
66 if constexpr (Test.template operator()<
Field>())
78constexpr bool HasPrimaryKey = detail::CheckFieldProperty<[]<typename Field>() {
return IsPrimaryKey<Field>; }, T>;
85 detail::CheckFieldProperty<[]<typename Field>() {
return IsAutoIncrementPrimaryKey<Field>; }, T>;
90template <
template <
typename>
class Allocator,
template <
typename,
typename>
class Container,
typename Object>
91auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
93 using SharedPtrRecord = std::shared_ptr<Object>;
94 auto sharedPtrContainer = Container<SharedPtrRecord, Allocator<SharedPtrRecord>> {};
95 for (
auto&
object: container)
96 sharedPtrContainer.emplace_back(std::make_shared<Object>(std::move(object)));
97 return sharedPtrContainer;
100template <
typename Record>
101constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType, Record
const& record)
103 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
110 Reflection::EnumerateMembers(record, [&result]<
size_t I,
typename Field>(
Field& ) {
111 if constexpr (IsField<Field>)
113 if constexpr (detail::OneOf<
typename Field::ValueType,
119 || IsSqlDynamicString<typename Field::ValueType>)
129template <
typename Record>
132 Reflection::EnumerateMembers(record, [reader = &reader]<
size_t I,
typename Field>(
Field& field)
mutable {
133 if constexpr (IsField<Field>)
137 else if constexpr (SqlGetColumnNativeType<Field>)
149template <
typename Record,
typename Derived>
160 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition()
noexcept
162 return this->_query.searchCondition;
165 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
SqlQueryFormatter const& Formatter()
const noexcept
174 _fields { std::move(fields) }
179 [[nodiscard]] std::vector<Record> All()
181 auto records = std::vector<Record> {};
184 RecordTableName<Record>,
185 this->_query.searchCondition.tableAlias,
186 this->_query.searchCondition.tableJoins,
187 this->_query.searchCondition.condition,
188 this->_query.orderBy,
189 this->_query.groupBy));
194 [[nodiscard]] std::vector<Record> First(
size_t n)
196 auto records = std::vector<Record> {};
200 RecordTableName<Record>,
201 this->_query.searchCondition.tableAlias,
202 this->_query.searchCondition.tableJoins,
203 this->_query.searchCondition.condition,
204 this->_query.orderBy,
210 [[nodiscard]] std::vector<Record> Range(
size_t offset,
size_t limit)
212 auto records = std::vector<Record> {};
213 records.reserve(limit);
215 this->_query.distinct,
217 RecordTableName<Record>,
218 this->_query.searchCondition.tableAlias,
219 this->_query.searchCondition.tableJoins,
220 this->_query.searchCondition.condition,
221 !this->_query.orderBy.empty()
222 ? this->_query.orderBy
223 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
224 this->_query.groupBy,
235template <
typename Record,
auto... ReferencedFields>
245 stmt, std::move(fields)
251 static void ReadResults(SqlServerType sqlServerType,
SqlResultCursor reader, std::vector<Record>* records)
255 auto& record = records->emplace_back();
257 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns(sqlServerType, record);
258 if (outputColumnsBound)
267 if (!outputColumnsBound)
268 detail::GetAllColumns(reader, record);
276template <
typename Record>
289 static void BindAllOutputColumns(
SqlResultCursor& reader, Record& record)
291 Reflection::EnumerateMembers(
292 record, [reader = &reader, i = SQLSMALLINT { 1 }]<
size_t I,
typename Field>(
Field& field)
mutable {
293 if constexpr (IsField<Field>)
295 reader->BindOutputColumn(i++, &field.MutableValue());
297 else if constexpr (IsBelongsTo<Field>)
299 reader->BindOutputColumn(i++, &field.MutableValue());
301 else if constexpr (SqlOutputColumnBinder<Field>)
303 reader->BindOutputColumn(i++, &field);
308 static void ReadResults(SqlServerType sqlServerType,
SqlResultCursor reader, std::vector<Record>* records)
312 auto& record = records->emplace_back();
314 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns(sqlServerType, record);
315 if (outputColumnsBound)
316 BindAllOutputColumns(reader, record);
324 if (!outputColumnsBound)
325 detail::GetAllColumns(reader, record);
335template <
typename Record>
343 SqlSearchCondition _searchCondition {};
348 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition()
noexcept
350 return _searchCondition;
353 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
SqlQueryFormatter const& Formatter()
const noexcept
358 static void BindAllOutputColumns(
SqlResultCursor& reader, Record& record)
360 Reflection::EnumerateMembers(
361 record, [reader = &reader, i = SQLSMALLINT { 1 }]<
size_t I,
typename Field>(
Field& field)
mutable {
362 if constexpr (IsField<Field>)
364 reader->BindOutputColumn(i++, &field.MutableValue());
366 else if constexpr (IsBelongsTo<Field>)
368 reader->BindOutputColumn(i++, &field.MutableValue());
370 else if constexpr (SqlOutputColumnBinder<Field>)
372 reader->BindOutputColumn(i++, &field);
381 _fields { std::move(fields) }
387 [[nodiscard]] std::optional<Record>
Get()
389 auto constexpr count = 1;
390 auto constexpr distinct =
false;
391 auto constexpr orderBy = std::string_view {};
394 RecordTableName<Record>,
395 _searchCondition.tableAlias,
396 _searchCondition.tableJoins,
397 _searchCondition.condition,
401 auto record = Record {};
402 auto canBindOutputColumns = detail::CanSafelyBindOutputColumns(_stmt.
Connection().
ServerType(), record);
403 if (canBindOutputColumns)
404 BindAllOutputColumns(reader, record);
407 if (!canBindOutputColumns)
408 detail::GetAllColumns(reader, record);
409 return std::optional { std::move(record) };
416template <
typename Record>
417inline LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType<Record>
GetPrimaryKeyField(Record
const& record)
noexcept
420 static_assert(HasPrimaryKey<Record>,
"Record must have a primary key");
422 auto result = RecordPrimaryKeyType<Record> {};
423 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType
const& field) {
424 if constexpr (IsPrimaryKey<FieldType> && std::same_as<FieldType, RecordPrimaryKeyType<Record>>)
445 _stmt { _connection }
451 _connection { std::move(connection) },
452 _stmt { _connection }
458 _connection { std::move(connectionString) },
459 _stmt { _connection }
482 template <
typename Record>
483 static std::string
Inspect(Record
const& record);
486 template <
typename Record>
490 template <
typename FirstRecord,
typename... MoreRecords>
494 template <
typename Record>
498 template <
typename FirstRecord,
typename... MoreRecords>
506 template <
typename Record>
507 RecordPrimaryKeyType<Record>
Create(Record& record);
514 template <
typename Record>
515 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
523 template <
typename Record,
typename... Args>
530 template <
typename Record,
typename... PrimaryKeyTypes>
531 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
544 template <
typename Record>
548 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
552 fields += RecordTableName<Record>;
554 fields += FieldNameAt<I, Record>;
566 template <
typename Record,
typename ColumnName,
typename T>
567 std::optional<Record>
QuerySingleBy(ColumnName
const& columnName, T
const& value);
570 template <
typename Record,
typename... InputParameters>
571 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery,
572 InputParameters&&... inputParameters);
575 template <
typename Record,
typename... InputParameters>
576 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
602 template <
typename ElementMask,
typename Record,
typename... InputParameters>
603 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery,
604 InputParameters&&... inputParameters);
616 template <
typename Record>
620 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
624 fields += RecordTableName<Record>;
626 fields += FieldNameAt<I, Record>;
645 template <
typename Record,
auto... ReferencedFields>
648 auto const appendFieldTo = []<
auto ReferencedField>(std::string& fields) {
649 using ReferencedRecord = Reflection::MemberClassType<ReferencedField>;
653 fields += RecordTableName<ReferencedRecord>;
655 fields += FieldNameOf<ReferencedField>;
659 (appendFieldTo.template operator()<ReferencedFields>(fields), ...);
665 template <
typename Record>
666 bool IsModified(Record
const& record)
const noexcept;
669 template <
typename Record>
670 void Update(Record& record);
673 template <
typename Record>
674 std::size_t
Delete(Record
const& record);
677 template <
typename Record>
681 template <
typename Record>
682 std::vector<Record>
All();
685 template <
typename Record>
688 return _connection.
Query(RecordTableName<Record>);
694 return _connection.
Query(tableName);
698 template <
typename Record>
702 template <
typename Record>
709 template <
typename Record>
713 template <
typename Record,
typename ValueType>
714 void SetId(Record& record, ValueType&&
id);
716 template <
typename Record>
717 Record& BindOutputColumns(Record& record);
719 template <
typename Record>
720 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
722 template <
typename ElementMask,
typename Record>
723 Record& BindOutputColumns(Record& record);
725 template <
typename ElementMask,
typename Record>
726 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
728 template <auto ReferencedRecordField, auto Be
longsToAlias>
731 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
734 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
737 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
740 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
741 void CallOnHasMany(Record& record, Callable
const& callback);
743 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
744 void CallOnHasManyThrough(Record& record, Callable
const& callback);
752template <
typename Record>
758 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
763 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
764 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value>
765 && !IsBelongsTo<Value>)
766 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
768 return "{" + std::move(str) +
"}";
771template <
typename Record>
777 auto createTable = migration.
CreateTable(RecordTableName<Record>);
779 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
782 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
783 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
784 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
785 else if constexpr (FieldType::IsPrimaryKey)
786 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
787 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
788 else if constexpr (IsBelongsTo<FieldType>)
790 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
791 auto index = size_t(-1);
792 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
793 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
794 if constexpr (IsField<ReferencedFieldType>)
795 if constexpr (ReferencedFieldType::IsPrimaryKey)
800 createTable.ForeignKey(
801 std::string(FieldNameAt<I, Record>),
802 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
804 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
806 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
808 else if constexpr (FieldType::IsMandatory)
809 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
810 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
812 createTable.Column(std::string(FieldNameAt<I, Record>),
813 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
817 return migration.GetPlan().ToSql();
820template <
typename FirstRecord,
typename... MoreRecords>
823 std::vector<std::string> output;
824 auto const append = [&output](
auto const& sql) {
825 output.insert(output.end(), sql.begin(), sql.end());
827 append(CreateTableString<FirstRecord>(serverType));
828 (append(CreateTableString<MoreRecords>(serverType)), ...);
832template <
typename Record>
837 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
838 for (
auto const& sqlQueryString: sqlQueryStrings)
842template <
typename FirstRecord,
typename... MoreRecords>
845 CreateTable<FirstRecord>();
846 (CreateTable<MoreRecords>(), ...);
849template <
typename Record>
854 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
856 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
857 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
858 query.Set(FieldNameAt<I, Record>, SqlWildcard);
863 Reflection::CallOnMembers(
865 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name,
866 FieldType
const& field)
mutable {
867 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
868 _stmt.BindInputParameter(i++, field, name);
873 if constexpr (HasAutoIncrementPrimaryKey<Record>)
875 else if constexpr (HasPrimaryKey<Record>)
877 RecordPrimaryKeyType<Record>
const* primaryKey =
nullptr;
878 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
879 if constexpr (IsField<FieldType>)
881 if constexpr (FieldType::IsPrimaryKey)
883 primaryKey = &field.Value();
891template <
typename Record>
894 static_assert(!std::is_const_v<Record>);
898 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
899 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
901 if (!primaryKeyField.IsModified())
903 using ValueType =
typename PrimaryKeyType::ValueType;
904 if constexpr (std::same_as<ValueType, SqlGuid>)
908 else if constexpr (
requires { ValueType {} + 1; })
910 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
911 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
912 FieldNameAt<PrimaryKeyIndex, Record>,
913 RecordTableName<Record>));
914 primaryKeyField = maxId.value_or(ValueType {}) + 1;
922 if constexpr (HasAutoIncrementPrimaryKey<Record>)
923 SetId(record, _stmt.
LastInsertId(RecordTableName<Record>));
928 if constexpr (HasPrimaryKey<Record>)
932template <
typename Record>
937 bool modified =
false;
939 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
940 if constexpr (
requires { field.IsModified(); })
942 modified = modified || field.IsModified();
949template <
typename Record>
954 auto query = _connection.
Query(RecordTableName<Record>).
Update();
956 Reflection::CallOnMembers(record,
957 [&query]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field) {
958 if (field.IsModified())
959 query.Set(name, SqlWildcard);
960 if constexpr (FieldType::IsPrimaryKey)
961 if (!field.IsModified())
962 std::ignore = query.Where(name, SqlWildcard);
969 Reflection::CallOnMembers(
970 record, [
this, &i]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
971 if (field.IsModified())
972 _stmt.BindInputParameter(i++, field.Value(), name);
976 Reflection::CallOnMembers(
977 record, [
this, &i]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
978 if constexpr (FieldType::IsPrimaryKey)
979 if (!field.IsModified())
980 _stmt.BindInputParameter(i++, field.Value(), name);
988template <
typename Record>
993 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
995 Reflection::CallOnMembers(
996 record, [&query]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& ) {
997 if constexpr (FieldType::IsPrimaryKey)
998 std::ignore = query.Where(name, SqlWildcard);
1004 Reflection::CallOnMembers(record,
1005 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
1006 Name
const& name, FieldType
const& field)
mutable {
1007 if constexpr (FieldType::IsPrimaryKey)
1008 _stmt.BindInputParameter(i++, field.Value(), name);
1016template <
typename Record>
1022 auto result =
size_t {};
1029template <
typename Record>
1032 return Query<Record>(_connection.
Query(RecordTableName<Record>).
Select().template Fields<Record>().
All());
1035template <
typename Record,
typename... PrimaryKeyTypes>
1040 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1042 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1045 queryBuilder.Field(FieldNameAt<I, Record>);
1047 if constexpr (FieldType::IsPrimaryKey)
1048 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1052 _stmt.
Prepare(queryBuilder.First());
1053 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1055 auto resultRecord = Record {};
1056 BindOutputColumns(resultRecord);
1059 return std::nullopt;
1065 return { std::move(resultRecord) };
1068template <
typename Record,
typename... Args>
1073 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1078 _stmt.
Execute(std::forward<Args>(args)...);
1080 auto resultRecord = Record {};
1081 BindOutputColumns(resultRecord);
1084 return std::nullopt;
1090 return { std::move(resultRecord) };
1095template <
typename Record,
typename... InputParameters>
1097 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1100 "Record must satisfy DataMapperRecord");
1102 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1105template <
typename Record,
typename... InputParameters>
1106std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1108 auto result = std::vector<Record> {};
1110 if constexpr (std::same_as<Record, SqlVariantRow>)
1112 _stmt.
Prepare(sqlQueryString);
1113 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1117 auto& record = result.emplace_back();
1118 record.reserve(numResultColumns);
1119 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1127 _stmt.
Prepare(sqlQueryString);
1128 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1130 auto record = Record {};
1131 BindOutputColumns(record);
1135 result.emplace_back(std::move(record));
1137 BindOutputColumns(record);
1145template <
typename ElementMask,
typename Record,
typename... InputParameters>
1147 InputParameters&&... inputParameters)
1151 _stmt.
Prepare(selectQuery.ToSql());
1152 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1154 auto result = std::vector<Record> {};
1163template <
typename Record>
1166 static_assert(!std::is_const_v<Record>);
1169 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1170 if constexpr (
requires { field.SetModified(
false); })
1172 field.SetModified(
false);
1177template <
typename Record,
typename Callable>
1178inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1182 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1183 if constexpr (IsField<FieldType>)
1185 if constexpr (FieldType::IsPrimaryKey)
1187 return callable.template operator()<I, FieldType>(field);
1193template <
typename Record,
typename Callable>
1194inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1198 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1199 if constexpr (IsField<FieldType>)
1201 if constexpr (FieldType::IsPrimaryKey)
1203 return callable.template operator()<I, FieldType>();
1209template <
typename Record,
typename Callable>
1210inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1214 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1215 if constexpr (IsBelongsTo<FieldType>)
1217 return callable.template operator()<I, FieldType>();
1222template <auto ReferencedRecordField, auto Be
longsToAlias>
1226 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1228 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1229 if (
auto result = QuerySingle<ReferencedRecord>(field.
Value()); result)
1233 std::format(
"Loading BelongsTo failed for {} ({})", RecordTableName<ReferencedRecord>, field.
Value()));
1237template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1238void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1244 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1247 record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1248 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1250 .Build([&](
auto& query) {
1251 Reflection::EnumerateMembers<ReferencedRecord>(
1252 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1255 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1259 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1260 callback(query, primaryKeyField);
1264template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1270 CallOnHasMany<FieldIndex, Record, OtherRecord>(
1272 field.
Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.
All(), primaryKeyField.Value())));
1276template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1284 record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1286 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1288 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1290 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1294 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1296 .Build([&](
auto& query) {
1297 Reflection::EnumerateMembers<ReferencedRecord>(
1298 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1302 RecordTableName<ReferencedRecord>,
1303 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1307 .InnerJoin(RecordTableName<ThroughRecord>,
1308 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1309 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1310 .InnerJoin(RecordTableName<Record>,
1311 FieldNameAt<PrimaryKeyIndex, Record>,
1313 RecordTableName<ThroughRecord>,
1314 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1317 RecordTableName<Record>,
1318 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1321 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1323 field.
EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1331template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1332void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1338 record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1340 CallOnBelongsTo<ThroughRecord>(
1341 [&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1342 using ThroughBelongsToRecordFieldType =
1343 Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1344 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1347 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToReferenceRecordIndex,
1348 typename ThroughBelongsToReferenceRecordType>() {
1349 using ThroughBelongsToReferenceRecordFieldType =
1350 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1351 if constexpr (std::is_same_v<
1352 typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1356 _connection.
Query(RecordTableName<ReferencedRecord>)
1358 .Build([&](
auto& query) {
1359 Reflection::EnumerateMembers<ReferencedRecord>(
1360 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1364 RecordTableName<ReferencedRecord>,
1365 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1369 .InnerJoin(RecordTableName<ThroughRecord>,
1370 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1372 FieldNameAt<PrimaryKeyIndex, Record> })
1375 RecordTableName<ThroughRecord>,
1376 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1379 callback(query, primaryKeyField);
1387template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1392 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1394 field.
Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.
All(), primaryKeyField.Value())));
1398template <
typename Record>
1401 static_assert(!std::is_const_v<Record>);
1404 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
1405 if constexpr (IsBelongsTo<FieldType>)
1407 LoadBelongsTo(field);
1409 else if constexpr (IsHasMany<FieldType>)
1411 LoadHasMany<FieldIndex>(record, field);
1413 else if constexpr (IsHasOneThrough<FieldType>)
1415 LoadHasOneThrough(record, field);
1417 else if constexpr (IsHasManyThrough<FieldType>)
1419 LoadHasManyThrough(record, field);
1424template <
typename Record,
typename ValueType>
1425inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
1430 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1431 if constexpr (IsField<FieldType>)
1433 if constexpr (FieldType::IsPrimaryKey)
1435 field = std::forward<FieldType>(
id);
1441template <
typename Record>
1442inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1445 BindOutputColumns(record, &_stmt);
1449template <
typename Record>
1450Record& DataMapper::BindOutputColumns(Record& record,
SqlStatement* stmt)
1452 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>>(record, stmt);
1455template <
typename ElementMask,
typename Record>
1456inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1459 return BindOutputColumns<ElementMask>(record, &_stmt);
1462template <
typename ElementMask,
typename Record>
1463Record& DataMapper::BindOutputColumns(Record& record,
SqlStatement* stmt)
1466 static_assert(!std::is_const_v<Record>);
1467 assert(stmt !=
nullptr);
1469 Reflection::EnumerateMembers<ElementMask>(
1470 record, [stmt, i = SQLSMALLINT { 1 }]<
size_t I,
typename Field>(
Field& field)
mutable {
1471 if constexpr (IsField<Field>)
1473 stmt->BindOutputColumn(i++, &field.MutableValue());
1475 else if constexpr (SqlOutputColumnBinder<Field>)
1477 stmt->BindOutputColumn(i++, &field);
1484template <
typename Record>
1489 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
1490 if constexpr (IsBelongsTo<FieldType>)
1492 field.SetAutoLoader(
typename FieldType::Loader {
1493 .loadReference = [
this, &field]() { LoadBelongsTo(field); },
1496 else if constexpr (IsHasMany<FieldType>)
1498 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1501 .count = [
this, &record]() ->
size_t {
1503 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1506 _stmt.
Execute(primaryKeyField.Value());
1513 .all = [
this, &record, &hasMany]() { LoadHasMany<FieldIndex>(record, hasMany); },
1515 [
this, &record](
auto const& each) {
1516 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1520 stmt.
Execute(primaryKeyField.Value());
1522 auto referencedRecord = ReferencedRecord {};
1523 BindOutputColumns(referencedRecord, &stmt);
1528 each(referencedRecord);
1529 BindOutputColumns(referencedRecord, &stmt);
1535 else if constexpr (IsHasOneThrough<FieldType>)
1537 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1538 using ThroughRecord =
typename FieldType::ThroughRecord;
1542 [
this, &record, &hasOneThrough]() {
1543 LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
1547 else if constexpr (IsHasManyThrough<FieldType>)
1549 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1550 using ThroughRecord =
typename FieldType::ThroughRecord;
1553 .count = [
this, &record]() ->
size_t {
1556 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1559 _stmt.
Execute(primaryKeyField.Value());
1567 [
this, &record, &hasManyThrough]() {
1569 LoadHasManyThrough(record, hasManyThrough);
1572 [
this, &record](
auto const& each) {
1574 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1578 stmt.
Execute(primaryKeyField.Value());
1580 auto referencedRecord = ReferencedRecord {};
1581 BindOutputColumns(referencedRecord, &stmt);
1586 each(referencedRecord);
1587 BindOutputColumns(referencedRecord, &stmt);
Represents a one-to-one relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & EmplaceRecord()
Emplaces a record into the relationship. This will mark the relationship as loaded.
LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const & Value() const noexcept
Retrieves the reference to the value of the field.
Main API for mapping records to and from the database using high level C++ syntax.
RecordPrimaryKeyType< Record > CreateExplicit(Record const &record)
Creates a new record in the database.
SqlSparseFieldQueryBuilder< Record, ReferencedFields... > QuerySparse()
std::vector< Record > Query(SqlSelectQueryBuilder::ComposedQuery const &selectQuery, InputParameters &&... inputParameters)
Queries multiple records from the database, based on the given query.
bool IsModified(Record const &record) const noexcept
Checks if the record has any modified fields.
DataMapper(SqlConnection &&connection)
Constructs a new data mapper, using the given connection.
void LoadRelations(Record &record)
Loads all direct relations to this record.
std::size_t Count()
Counts the total number of records in the database for the given record type.
std::vector< Record > All()
Loads all records from the database for the given record type.
std::optional< Record > QuerySingleBy(ColumnName const &columnName, T const &value)
Queries a single record by the given column name and value.
SqlConnection const & Connection() const noexcept
Returns the connection reference used by this data mapper.
std::vector< std::string > CreateTablesString(SqlServerType serverType)
Constructs a string list of SQL queries to create the tables for the given record types.
static std::string Inspect(Record const &record)
Constructs a human readable string representation of the given record.
DataMapper(SqlConnectionString connectionString)
Constructs a new data mapper, using the given connection string.
SqlQuerySingleBuilder< Record > QuerySingle()
Queries a single record from the database.
void CreateTables()
Creates the tables for the given record types.
void CreateTable()
Creates the table for the given record type.
std::vector< std::string > CreateTableString(SqlServerType serverType)
Constructs a string list of SQL queries to create the table for the given record type.
SqlQueryBuilder FromTable(std::string_view tableName)
Constructs an SQL query builder for the given table name.
RecordPrimaryKeyType< Record > Create(Record &record)
Creates a new record in the database.
void ConfigureRelationAutoLoading(Record &record)
SqlAllFieldsQueryBuilder< Record > Query()
auto BuildQuery() -> SqlQueryBuilder
Constructs an SQL query builder for the given record type.
void Update(Record &record)
Updates the record in the database.
std::size_t Delete(Record const &record)
Deletes the record from the database.
SqlConnection & Connection() noexcept
Returns the mutable connection reference used by this data mapper.
DataMapper()
Constructs a new data mapper, using the default connection.
void ClearModifiedState(Record &record) noexcept
Clears the modified state of the record.
This API represents a many-to-many relationship between two records through a third record.
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of records into this relationship.
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.
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of 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.
LIGHTWEIGHT_FORCE_INLINE constexpr void EmplaceRecord(std::shared_ptr< ReferencedRecord > record)
Emplaces the given record into this relationship.
Represents a query builder that retrieves all fields of a record.
Represents a binary data type.
Represents a connection to a SQL database.
SqlServerType ServerType() const noexcept
Retrieves the type of the server.
SqlQueryBuilder Query(std::string_view const &table={}) const
SqlQueryFormatter const & QueryFormatter() const noexcept
Retrieves a query formatter suitable for the SQL server being connected.
static 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 SqlDeleteQueryBuilder Delete() noexcept
Initiates DELETE query building.
LIGHTWEIGHT_API SqlSelectQueryBuilder Select() noexcept
Initiates SELECT query building.
LIGHTWEIGHT_API SqlUpdateQueryBuilder Update(std::vector< SqlVariant > *boundInputs=nullptr) noexcept
LIGHTWEIGHT_API SqlMigrationQueryBuilder Migration()
Initiates query for building database migrations.
Represents a query builder that retrieves only the first record found.
std::optional< Record > Get()
Executes the query and returns the first record found.
API for reading an SQL query result set.
LIGHTWEIGHT_FORCE_INLINE void BindOutputColumns(Args *... args)
LIGHTWEIGHT_FORCE_INLINE bool FetchRow()
Fetches the next row of the result set.
LIGHTWEIGHT_FORCE_INLINE bool GetColumn(SQLUSMALLINT column, T *result) const
Query builder for building SELECT ... queries.
LIGHTWEIGHT_API ComposedQuery Count()
Finalizes building the query as SELECT COUNT(*) ... query.
LIGHTWEIGHT_API ComposedQuery All()
Finalizes building the query as SELECT field names FROM ... query.
LIGHTWEIGHT_API SqlSelectQueryBuilder & Field(std::string_view const &fieldName)
Adds a single column to the SELECT clause.
LIGHTWEIGHT_API ComposedQuery First(size_t count=1)
Finalizes building the query as SELECT TOP n field names FROM ... query.
Represents a query builder that retrieves only the fields specified.
High level API for (prepared) raw SQL statements.
LIGHTWEIGHT_API SqlConnection & Connection() noexcept
Retrieves the connection associated with this statement.
SqlResultCursor GetResultCursor() noexcept
Retrieves the result cursor for reading an SQL query result.
LIGHTWEIGHT_API size_t NumRowsAffected() const
Retrieves the number of rows affected by the last query.
void Execute(Args const &... args)
Binds the given arguments to the prepared statement and executes it.
void CloseCursor() noexcept
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 NumColumnsAffected() const
Retrieves the number of columns affected by the last query.
LIGHTWEIGHT_API size_t LastInsertId(std::string_view tableName)
Retrieves the last insert ID of the given table.
LIGHTWEIGHT_API bool FetchRow()
void BindOutputColumns(Args *... args)
LIGHTWEIGHT_API void Prepare(std::string_view query) &
bool GetColumn(SQLUSMALLINT column, T *result) const
Represents a record type that can be used with the DataMapper.
LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType< Record > GetPrimaryKeyField(Record const &record) noexcept
constexpr size_t RecordStorageFieldCount
std::integer_sequence< size_t, Ints... > SqlElements
Represents a sequence of indexes that can be used alongside Query() to retrieve only part of the reco...
constexpr std::string_view FieldNameAt
Returns the SQL field name of the given field index in the record.
constexpr bool HasPrimaryKey
Tests if the given record type does contain a primary key.
constexpr bool HasAutoIncrementPrimaryKey
Tests if the given record type does contain an auto increment primary key.
Represents a single column in a table.
constexpr T & MutableValue() noexcept
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.
SqlQualifiedTableColumnName represents a column name qualified with a table name.
Represents a value that can be any of the supported SQL data types.