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>
36 { field.Value() } -> std::convertible_to<typename T::ValueType const&>;
37 { mutableField.MutableValue() } -> std::convertible_to<typename T::ValueType&>;
38 { field.IsModified() } -> std::convertible_to<bool>;
39 { mutableField.SetModified(
bool {}) } -> std::convertible_to<void>;
46template <
typename Record>
48 Reflection::FoldMembers<Record>(
size_t { 0 }, []<
size_t I,
typename Field>(
size_t const accum)
constexpr {
55template <
typename Record>
56concept RecordWithStorageFields = (RecordStorageFieldCount<Record> > 0);
61template <auto Test,
typename T>
62constexpr bool CheckFieldProperty = Reflection::FoldMembers<T>(
false, []<
size_t I,
typename Field>(
bool const accum) {
63 if constexpr (Test.template operator()<
Field>())
75constexpr bool HasPrimaryKey = detail::CheckFieldProperty<[]<typename Field>() {
return IsPrimaryKey<Field>; }, T>;
82 detail::CheckFieldProperty<[]<typename Field>() {
return IsAutoIncrementPrimaryKey<Field>; }, T>;
87template <
template <
typename>
class Allocator,
template <
typename,
typename>
class Container,
typename Object>
88auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
90 using SharedPtrRecord = std::shared_ptr<Object>;
91 auto sharedPtrContainer = Container<SharedPtrRecord, Allocator<SharedPtrRecord>> {};
92 for (
auto&
object: container)
93 sharedPtrContainer.emplace_back(std::make_shared<Object>(std::move(object)));
94 return sharedPtrContainer;
97template <
typename Record>
98constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
100 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
107 Reflection::EnumerateMembers<Record>([&result]<
size_t I,
typename Field>() {
108 if constexpr (IsField<Field>)
110 if constexpr (detail::OneOf<
typename Field::ValueType,
116 || IsSqlDynamicString<typename Field::ValueType> || IsSqlDynamicBinary<typename Field::ValueType>)
126template <
typename Record>
127void BindAllOutputColumnsWithOffset(
SqlResultCursor& reader, Record& record, SQLSMALLINT startOffset)
129 Reflection::EnumerateMembers(record,
130 [reader = &reader, i = startOffset]<
size_t I,
typename Field>(
Field& field)
mutable {
131 if constexpr (IsField<Field>)
135 else if constexpr (IsBelongsTo<Field>)
139 else if constexpr (SqlOutputColumnBinder<Field>)
141 reader->BindOutputColumn(i++, &field);
146template <
typename Record>
149 BindAllOutputColumnsWithOffset(reader, record, 1);
155template <
typename ElementMask,
typename Record>
158 SQLUSMALLINT indexFromQuery = 0;
159 Reflection::EnumerateMembers<ElementMask>(
160 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(
Field& field)
mutable {
162 if constexpr (IsField<Field>)
166 else if constexpr (SqlGetColumnNativeType<Field>)
173template <
typename Record>
176 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(reader, record);
179template <
typename FirstRecord,
typename SecondRecord>
180void GetAllColumns(
SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
182 auto& [firstRecord, secondRecord] = record;
184 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<
size_t I,
typename Field>(
Field& field)
mutable {
185 if constexpr (IsField<Field>)
189 else if constexpr (SqlGetColumnNativeType<Field>)
195 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<
size_t I,
typename Field>(
Field& field)
mutable {
196 if constexpr (IsField<Field>)
199 reader->
GetColumn<
typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
201 else if constexpr (SqlGetColumnNativeType<Field>)
203 field = reader->
GetColumn<
Field>(Reflection::CountMembers<FirstRecord> + I + 1);
208template <
typename Record>
209bool ReadSingleResult(SqlServerType sqlServerType,
SqlResultCursor& reader, Record& record)
211 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
213 if (outputColumnsBound)
214 BindAllOutputColumns(reader, record);
219 if (!outputColumnsBound)
220 GetAllColumns(reader, record);
230template <
typename Record,
typename Derived>
239 friend class SqlWhereClauseBuilder<Derived>;
241 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition()
noexcept
243 return this->_query.searchCondition;
246 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
SqlQueryFormatter const& Formatter()
const noexcept
255 _fields { std::move(fields) }
260 [[nodiscard]] std::vector<Record> All()
262 auto records = std::vector<Record> {};
265 RecordTableName<Record>,
266 this->_query.searchCondition.tableAlias,
267 this->_query.searchCondition.tableJoins,
268 this->_query.searchCondition.condition,
269 this->_query.orderBy,
270 this->_query.groupBy));
275 [[nodiscard]] std::optional<Record> First()
277 std::optional<Record> record {};
280 RecordTableName<Record>,
281 this->_query.searchCondition.tableAlias,
282 this->_query.searchCondition.tableJoins,
283 this->_query.searchCondition.condition,
284 this->_query.orderBy,
290 [[nodiscard]] std::vector<Record> First(
size_t n)
292 auto records = std::vector<Record> {};
296 RecordTableName<Record>,
297 this->_query.searchCondition.tableAlias,
298 this->_query.searchCondition.tableJoins,
299 this->_query.searchCondition.condition,
300 this->_query.orderBy,
306 [[nodiscard]] std::vector<Record> Range(
size_t offset,
size_t limit)
308 auto records = std::vector<Record> {};
309 records.reserve(limit);
311 this->_query.distinct,
313 RecordTableName<Record>,
314 this->_query.searchCondition.tableAlias,
315 this->_query.searchCondition.tableJoins,
316 this->_query.searchCondition.condition,
317 !this->_query.orderBy.empty()
318 ? this->_query.orderBy
319 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
320 this->_query.groupBy,
331template <
typename Record,
auto... ReferencedFields>
335 using ElementMask = std::integer_sequence<size_t, Reflection::MemberIndexOf<ReferencedFields>...>;
349 static void ReadResults(SqlServerType sqlServerType,
SqlResultCursor reader, std::vector<Record>* records)
353 auto& record = records->emplace_back();
354 if (!ReadResultImpl(sqlServerType, reader, record))
362 static void ReadResult(SqlServerType sqlServerType,
SqlResultCursor reader, std::optional<Record>* optionalRecord)
364 auto& record = optionalRecord->emplace();
365 if (!ReadResultImpl(sqlServerType, reader, record))
366 optionalRecord->reset();
369 static bool ReadResultImpl(SqlServerType sqlServerType,
SqlResultCursor& reader, Record& record)
371 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(sqlServerType);
372 if (outputColumnsBound)
378 if (!outputColumnsBound)
379 detail::GetAllColumns<ElementMask>(reader, record);
388template <
typename Record>
401 static void ReadResults(SqlServerType sqlServerType,
SqlResultCursor reader, std::vector<Record>* records)
405 Record& record = records->emplace_back();
406 if (!detail::ReadSingleResult(sqlServerType, reader, record))
414 static void ReadResult(SqlServerType sqlServerType,
SqlResultCursor reader, std::optional<Record>* optionalRecord)
416 Record& record = optionalRecord->emplace();
417 if (!detail::ReadSingleResult(sqlServerType, reader, record))
418 optionalRecord->reset();
426template <
typename FirstRecord,
typename SecondRecord>
429 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>>>
432 using RecordType = std::tuple<FirstRecord, SecondRecord>;
441 static void ReadResults(SqlServerType sqlServerType,
SqlResultCursor reader, std::vector<RecordType>* records)
445 auto& record = records->emplace_back();
446 auto& [firstRecord, secondRecord] = record;
448 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
449 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
451 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
452 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
453 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
455 if (canSafelyBindAll)
457 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
458 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
467 if (!canSafelyBindAll)
468 detail::GetAllColumns(reader, record);
478template <
typename Record>
486 SqlSearchCondition _searchCondition {};
491 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition()
noexcept
493 return _searchCondition;
496 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
SqlQueryFormatter const& Formatter()
const noexcept
505 _fields { std::move(fields) }
511 [[nodiscard]] std::optional<Record>
Get()
513 auto constexpr count = 1;
514 auto constexpr distinct =
false;
515 auto constexpr orderBy = std::string_view {};
518 RecordTableName<Record>,
519 _searchCondition.tableAlias,
520 _searchCondition.tableJoins,
521 _searchCondition.condition,
524 auto record = std::optional<Record> { Record {} };
535template <
typename Record>
536inline LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType<Record>
GetPrimaryKeyField(Record
const& record)
noexcept
539 static_assert(HasPrimaryKey<Record>,
"Record must have a primary key");
541 auto result = RecordPrimaryKeyType<Record> {};
542 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType
const& field) {
543 if constexpr (IsPrimaryKey<FieldType> && std::same_as<FieldType, RecordPrimaryKeyType<Record>>)
564 _stmt { _connection }
570 _connection { std::move(connection) },
571 _stmt { _connection }
577 _connection { std::move(connectionString) },
578 _stmt { _connection }
601 template <
typename Record>
602 static std::string
Inspect(Record
const& record);
605 template <
typename Record>
609 template <
typename FirstRecord,
typename... MoreRecords>
613 template <
typename Record>
617 template <
typename FirstRecord,
typename... MoreRecords>
625 template <
typename Record>
626 RecordPrimaryKeyType<Record>
Create(Record& record);
633 template <
typename Record>
634 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
642 template <
typename Record,
typename... Args>
649 template <
typename Record,
typename... PrimaryKeyTypes>
650 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
659 template <
typename Record,
typename... PrimaryKeyTypes>
673 template <
typename Record>
677 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
681 fields += RecordTableName<Record>;
683 fields += FieldNameAt<I, Record>;
695 template <
typename Record,
typename ColumnName,
typename T>
696 std::optional<Record>
QuerySingleBy(ColumnName
const& columnName, T
const& value);
699 template <
typename Record,
typename... InputParameters>
700 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
703 template <
typename Record,
typename... InputParameters>
704 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
730 template <
typename ElementMask,
typename Record,
typename... InputParameters>
731 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
757 template <
typename FirstRecord,
typename NextRecord,
typename... InputParameters>
759 std::vector<std::tuple<FirstRecord, NextRecord>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery,
760 InputParameters&&... inputParameters);
763 template <
typename FirstRecord,
typename NextRecord>
770 auto const emplaceRecordsFrom = [&fields]<
typename Record>() {
771 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
774 fields += std::format(R
"("{}"."{}")", RecordTableName<Record>, FieldNameAt<I, Record>);
778 emplaceRecordsFrom.template operator()<FirstRecord>();
779 emplaceRecordsFrom.template operator()<NextRecord>();
795 template <
typename Record>
799 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
803 fields += RecordTableName<Record>;
805 fields += FieldNameAt<I, Record>;
824 template <
typename Record,
auto... ReferencedFields>
827 auto const appendFieldTo = []<
auto ReferencedField>(std::string& fields) {
828 using ReferencedRecord = Reflection::MemberClassType<ReferencedField>;
832 fields += RecordTableName<ReferencedRecord>;
834 fields += FieldNameOf<ReferencedField>;
838 (appendFieldTo.template operator()<ReferencedFields>(fields), ...);
844 template <
typename Record>
845 bool IsModified(Record
const& record)
const noexcept;
848 template <
typename Record>
849 void Update(Record& record);
852 template <
typename Record>
853 std::size_t
Delete(Record
const& record);
856 template <
typename Record>
860 template <
typename Record>
861 std::vector<Record>
All();
864 template <
typename Record>
867 return _connection.
Query(RecordTableName<Record>);
873 return _connection.
Query(tableName);
877 template <
typename Record>
881 template <
typename Record>
888 template <
typename Record>
892 template <
typename Record,
typename ValueType>
893 void SetId(Record& record, ValueType&&
id);
895 template <
typename Record,
size_t InitialOffset = 1>
896 Record& BindOutputColumns(Record& record);
898 template <
typename Record,
size_t InitialOffset = 1>
899 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
901 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
902 Record& BindOutputColumns(Record& record);
904 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
905 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
907 template <auto ReferencedRecordField, auto Be
longsToAlias>
910 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
913 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
916 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
919 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
920 void CallOnHasMany(Record& record, Callable
const& callback);
922 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
923 void CallOnHasManyThrough(Record& record, Callable
const& callback);
931template <
typename Record>
937 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
942 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
943 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
944 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
946 return "{" + std::move(str) +
"}";
949template <
typename Record>
955 auto createTable = migration.
CreateTable(RecordTableName<Record>);
957 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
960 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
961 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
962 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
963 else if constexpr (FieldType::IsPrimaryKey)
964 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
965 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
966 else if constexpr (IsBelongsTo<FieldType>)
968 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
969 auto index = size_t(-1);
970 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
971 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
972 if constexpr (IsField<ReferencedFieldType>)
973 if constexpr (ReferencedFieldType::IsPrimaryKey)
978 createTable.ForeignKey(
979 std::string(FieldNameAt<I, Record>),
980 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
982 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
984 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
986 else if constexpr (FieldType::IsMandatory)
987 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
988 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
990 createTable.Column(std::string(FieldNameAt<I, Record>),
991 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
995 return migration.GetPlan().ToSql();
998template <
typename FirstRecord,
typename... MoreRecords>
1001 std::vector<std::string> output;
1002 auto const append = [&output](
auto const& sql) {
1003 output.insert(output.end(), sql.begin(), sql.end());
1005 append(CreateTableString<FirstRecord>(serverType));
1006 (append(CreateTableString<MoreRecords>(serverType)), ...);
1010template <
typename Record>
1015 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1016 for (
auto const& sqlQueryString: sqlQueryStrings)
1020template <
typename FirstRecord,
typename... MoreRecords>
1023 CreateTable<FirstRecord>();
1024 (CreateTable<MoreRecords>(), ...);
1027template <
typename Record>
1032 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1034 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1035 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1036 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1041 Reflection::CallOnMembers(
1043 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
1044 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1045 _stmt.BindInputParameter(i++, field, name);
1050 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1051 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1052 else if constexpr (HasPrimaryKey<Record>)
1054 RecordPrimaryKeyType<Record>
const* primaryKey =
nullptr;
1055 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1056 if constexpr (IsField<FieldType>)
1058 if constexpr (FieldType::IsPrimaryKey)
1060 primaryKey = &field.Value();
1068template <
typename Record>
1071 static_assert(!std::is_const_v<Record>);
1075 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
1076 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
1078 if (!primaryKeyField.IsModified())
1080 using ValueType =
typename PrimaryKeyType::ValueType;
1081 if constexpr (std::same_as<ValueType, SqlGuid>)
1085 else if constexpr (
requires { ValueType {} + 1; })
1087 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1088 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1089 FieldNameAt<PrimaryKeyIndex, Record>,
1090 RecordTableName<Record>));
1091 primaryKeyField = maxId.value_or(ValueType {}) + 1;
1099 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1100 SetId(record, _stmt.
LastInsertId(RecordTableName<Record>));
1105 if constexpr (HasPrimaryKey<Record>)
1109template <
typename Record>
1114 bool modified =
false;
1116 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1117 if constexpr (
requires { field.
IsModified(); })
1126template <
typename Record>
1131 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1133 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1134 if (field.IsModified())
1135 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1138 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1139 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1146 Reflection::CallOnMembers(
1147 record, [
this, &i]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
1148 if (field.IsModified())
1149 _stmt.BindInputParameter(i++, field.Value(), name);
1153 Reflection::CallOnMembers(
1154 record, [
this, &i]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
1155 if constexpr (FieldType::IsPrimaryKey)
1156 _stmt.BindInputParameter(i++, field.Value(), name);
1164template <
typename Record>
1169 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1171 Reflection::CallOnMembers(record,
1172 [&query]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& ) {
1173 if constexpr (FieldType::IsPrimaryKey)
1174 std::ignore = query.Where(name, SqlWildcard);
1180 Reflection::CallOnMembers(
1182 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
1183 if constexpr (FieldType::IsPrimaryKey)
1184 _stmt.BindInputParameter(i++, field.
Value(), name);
1192template <
typename Record>
1198 auto result =
size_t {};
1205template <
typename Record>
1208 return Query<Record>(_connection.
Query(RecordTableName<Record>).
Select().template Fields<Record>().
All());
1211template <
typename Record,
typename... PrimaryKeyTypes>
1216 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1218 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1221 queryBuilder.Field(FieldNameAt<I, Record>);
1223 if constexpr (FieldType::IsPrimaryKey)
1224 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1228 _stmt.
Prepare(queryBuilder.First());
1229 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1231 auto resultRecord = std::optional<Record> { Record {} };
1234 return std::nullopt;
1236 return resultRecord;
1239template <
typename Record,
typename... PrimaryKeyTypes>
1242 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1248template <
typename Record,
typename... Args>
1253 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1258 _stmt.
Execute(std::forward<Args>(args)...);
1260 auto resultRecord = std::optional<Record> { Record {} };
1263 return std::nullopt;
1264 return resultRecord;
1269template <
typename Record,
typename... InputParameters>
1271 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1273 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1275 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1278template <
typename Record,
typename... InputParameters>
1279std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1281 auto result = std::vector<Record> {};
1283 if constexpr (std::same_as<Record, SqlVariantRow>)
1285 _stmt.
Prepare(sqlQueryString);
1286 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1290 auto& record = result.emplace_back();
1291 record.reserve(numResultColumns);
1292 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1300 _stmt.
Prepare(sqlQueryString);
1301 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1303 auto record = Record {};
1304 BindOutputColumns(record);
1308 result.emplace_back(std::move(record));
1310 BindOutputColumns(record);
1318template <
typename FirstRecord,
typename SecondRecord,
typename... InputParameters>
1320std::vector<std::tuple<FirstRecord, SecondRecord>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery,
1321 InputParameters&&... inputParameters)
1323 auto result = std::vector<std::tuple<FirstRecord, SecondRecord>> {};
1325 _stmt.
Prepare(selectQuery.ToSql());
1326 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1328 auto const ConfigureFetchAndBind = [
this](
auto& record) {
1329 auto& [recordFirst, recordSecond] = record;
1333 this->BindOutputColumns<FirstRecord, 1>(recordFirst);
1334 this->BindOutputColumns<SecondRecord, Reflection::CountMembers<FirstRecord> + 1>(recordSecond);
1339 ConfigureFetchAndBind(result.emplace_back());
1341 ConfigureFetchAndBind(result.emplace_back());
1344 if (!result.empty())
1350template <
typename ElementMask,
typename Record,
typename... InputParameters>
1352 InputParameters&&... inputParameters)
1356 _stmt.
Prepare(selectQuery.ToSql());
1357 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1359 auto records = std::vector<Record> {};
1362 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1368 auto& record = records.emplace_back();
1370 if (canSafelyBindOutputColumns)
1371 BindOutputColumns<ElementMask>(record);
1376 if (!canSafelyBindOutputColumns)
1377 detail::GetAllColumns<ElementMask>(reader, record);
1383 for (
auto& record: records)
1389template <
typename Record>
1392 static_assert(!std::is_const_v<Record>);
1395 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1396 if constexpr (
requires { field.SetModified(
false); })
1398 field.SetModified(
false);
1403template <
typename Record,
typename Callable>
1404inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1408 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1409 if constexpr (IsField<FieldType>)
1411 if constexpr (FieldType::IsPrimaryKey)
1413 return callable.template operator()<I, FieldType>(field);
1419template <
typename Record,
typename Callable>
1420inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1424 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1425 if constexpr (IsField<FieldType>)
1427 if constexpr (FieldType::IsPrimaryKey)
1429 return callable.template operator()<I, FieldType>();
1435template <
typename Record,
typename Callable>
1436inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1440 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1441 if constexpr (IsBelongsTo<FieldType>)
1443 return callable.template operator()<I, FieldType>();
1448template <auto ReferencedRecordField, auto Be
longsToAlias>
1452 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1454 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1455 if (
auto result = QuerySingle<ReferencedRecord>(field.
Value()); result)
1459 std::format(
"Loading BelongsTo failed for {} ({})", RecordTableName<ReferencedRecord>, field.
Value()));
1463template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1464void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1470 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1472 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1473 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1475 .Build([&](
auto& query) {
1476 Reflection::EnumerateMembers<ReferencedRecord>(
1477 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1480 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1484 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1485 callback(query, primaryKeyField);
1489template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1495 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](
SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1496 field.
Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.
All(), primaryKeyField.Value())));
1500template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1507 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1509 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1511 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1513 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1518 _connection.
Query(RecordTableName<ReferencedRecord>)
1520 .Build([&](
auto& query) {
1521 Reflection::EnumerateMembers<ReferencedRecord>(
1522 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1526 RecordTableName<ReferencedRecord>,
1527 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1531 .InnerJoin(RecordTableName<ThroughRecord>,
1532 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1533 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1534 .InnerJoin(RecordTableName<Record>,
1535 FieldNameAt<PrimaryKeyIndex, Record>,
1537 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1540 RecordTableName<Record>,
1541 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1544 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1546 field.
EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1554template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1555void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1560 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1562 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1563 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1564 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1567 CallOnBelongsTo<ThroughRecord>(
1568 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
1569 using ThroughBelongsToReferenceRecordFieldType =
1570 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1571 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1574 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1576 .Build([&](
auto& query) {
1577 Reflection::EnumerateMembers<ReferencedRecord>(
1578 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1582 RecordTableName<ReferencedRecord>,
1583 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1587 .InnerJoin(RecordTableName<ThroughRecord>,
1588 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1590 FieldNameAt<PrimaryKeyIndex, Record> })
1593 RecordTableName<ThroughRecord>,
1594 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1597 callback(query, primaryKeyField);
1605template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1610 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1612 field.
Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.
All(), primaryKeyField.Value())));
1616template <
typename Record>
1619 static_assert(!std::is_const_v<Record>);
1622 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
1623 if constexpr (IsBelongsTo<FieldType>)
1625 LoadBelongsTo(field);
1627 else if constexpr (IsHasMany<FieldType>)
1629 LoadHasMany<FieldIndex>(record, field);
1631 else if constexpr (IsHasOneThrough<FieldType>)
1633 LoadHasOneThrough(record, field);
1635 else if constexpr (IsHasManyThrough<FieldType>)
1637 LoadHasManyThrough(record, field);
1642template <
typename Record,
typename ValueType>
1643inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
1648 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1649 if constexpr (IsField<FieldType>)
1651 if constexpr (FieldType::IsPrimaryKey)
1653 field = std::forward<FieldType>(
id);
1659template <
typename Record,
size_t InitialOffset>
1660inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1663 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
1667template <
typename Record,
size_t InitialOffset>
1668Record& DataMapper::BindOutputColumns(Record& record,
SqlStatement* stmt)
1670 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
1674template <
typename ElementMask,
typename Record,
size_t InitialOffset>
1675inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
1678 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
1681template <
typename ElementMask,
typename Record,
size_t InitialOffset>
1682Record& DataMapper::BindOutputColumns(Record& record,
SqlStatement* stmt)
1685 static_assert(!std::is_const_v<Record>);
1686 assert(stmt !=
nullptr);
1688 Reflection::EnumerateMembers<ElementMask>(
1689 record, [stmt, i = SQLSMALLINT { InitialOffset }]<
size_t I,
typename Field>(
Field& field)
mutable {
1690 if constexpr (IsField<Field>)
1692 stmt->BindOutputColumn(i++, &field.MutableValue());
1694 else if constexpr (SqlOutputColumnBinder<Field>)
1696 stmt->BindOutputColumn(i++, &field);
1703template <
typename Record>
1708 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
1709 if constexpr (IsBelongsTo<FieldType>)
1711 field.SetAutoLoader(
typename FieldType::Loader {
1712 .loadReference = [
this, &field]() { LoadBelongsTo(field); },
1715 else if constexpr (IsHasMany<FieldType>)
1717 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1720 .count = [
this, &record]() ->
size_t {
1722 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1725 _stmt.
Execute(primaryKeyField.Value());
1732 .all = [
this, &record, &hasMany]() { LoadHasMany<FieldIndex>(record, hasMany); },
1734 [
this, &record](
auto const& each) {
1735 CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
1739 stmt.
Execute(primaryKeyField.Value());
1741 auto referencedRecord = ReferencedRecord {};
1742 BindOutputColumns(referencedRecord, &stmt);
1747 each(referencedRecord);
1748 BindOutputColumns(referencedRecord, &stmt);
1754 else if constexpr (IsHasOneThrough<FieldType>)
1756 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1757 using ThroughRecord =
typename FieldType::ThroughRecord;
1761 [
this, &record, &hasOneThrough]() {
1762 LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
1766 else if constexpr (IsHasManyThrough<FieldType>)
1768 using ReferencedRecord =
typename FieldType::ReferencedRecord;
1769 using ThroughRecord =
typename FieldType::ThroughRecord;
1772 .count = [
this, &record]() ->
size_t {
1775 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1778 _stmt.
Execute(primaryKeyField.Value());
1786 [
this, &record, &hasManyThrough]() {
1788 LoadHasManyThrough(record, hasManyThrough);
1791 [
this, &record](
auto const& each) {
1793 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
1797 stmt.
Execute(primaryKeyField.Value());
1799 auto referencedRecord = ReferencedRecord {};
1800 BindOutputColumns(referencedRecord, &stmt);
1805 each(referencedRecord);
1806 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.
std::optional< Record > QuerySingleWithoutRelationAutoLoading(PrimaryKeyTypes &&... primaryKeys)
Queries a single record (based on primary key) from the database without auto-loading relations.
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.
LIGHTWEIGHT_API SqlQueryBuilder Query(std::string_view const &table={}) const
SqlServerType ServerType() const noexcept
Retrieves the type of the server.
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
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
constexpr T const & Value() const noexcept
Returns the value of the field.
constexpr bool IsModified() const noexcept
Checks if the field has been modified.
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.