4#include "../Async/Backend.hpp"
5#include "../SqlConnection.hpp"
6#include "../SqlDataBinder.hpp"
7#include "../SqlLogger.hpp"
8#include "../SqlRealName.hpp"
9#include "../SqlStatement.hpp"
10#include "../Utils.hpp"
11#include "BelongsTo.hpp"
12#include "CollectDifferences.hpp"
15#include "HasManyThrough.hpp"
16#include "HasOneThrough.hpp"
17#include "QueryBuilders.hpp"
20#include <reflection-cpp/reflection.hpp>
41 template <
template <
typename>
class Allocator,
template <
typename,
typename>
class Container,
typename Object>
42 auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
44 using SharedPtrRecord = std::shared_ptr<Object>;
45 auto sharedPtrContainer = Container<SharedPtrRecord, Allocator<SharedPtrRecord>> {};
46 for (
auto&
object: container)
47 sharedPtrContainer.emplace_back(std::make_shared<Object>(std::move(object)));
48 return sharedPtrContainer;
100 _stmt { _connection }
106 _connection { std::move(connection) },
107 _stmt { _connection }
112 explicit DataMapper(std::optional<SqlConnectionString> connectionString):
113 _connection { std::move(connectionString) },
114 _stmt { _connection }
123 _connection(std::move(other._connection)),
135 _connection = std::move(other._connection);
156#if defined(BUILD_TESTS)
158 [[nodiscard]]
SqlStatement& Statement(
this auto&& self)
noexcept
166 template <
typename Record>
167 static std::string
Inspect(Record
const& record);
170 template <
typename Record>
174 template <
typename FirstRecord,
typename... MoreRecords>
178 template <
typename Record>
182 template <
typename FirstRecord,
typename... MoreRecords>
193 template <DataMapperOptions QueryOptions = {},
typename Record>
194 RecordPrimaryKeyType<Record>
Create(Record& record);
203 template <
typename Record>
204 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
225 template <std::ranges::range Records>
236 template <DataMapperOptions QueryOptions = {},
typename Record>
237 [[nodiscard]] RecordPrimaryKeyType<Record>
CreateCopyOf(Record
const& originalRecord);
262 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
263 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
273 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... InputParameters>
274 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
305 template <
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
306 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
346 template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
347 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
380 template <
typename First,
typename Second,
typename... Rest, DataMapperOptions QueryOptions = {}>
381 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
382 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
398 template <
typename Record, DataMapperOptions QueryOptions = {}>
427 *
this, BuildFullyQualifiedFieldList<Record>());
448 template <
typename Record>
449 void Update(Record& record);
467 template <std::ranges::range Records>
477 template <
typename Record>
478 std::size_t
Delete(Record
const& record);
483 return _connection.
Query(tableName);
491 template <
typename Record>
492 bool IsModified(Record
const& record)
const noexcept;
507 template <ModifiedState state,
typename Record>
514 template <
typename Record>
524 template <
typename Record>
533 template <
typename T>
534 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
554 [[nodiscard]] Async::Task<RecordPrimaryKeyType<Record>>
CreateAsync(Record& record);
561 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
562 [[nodiscard]] Async::Task<std::optional<Record>>
QuerySingleAsync(PrimaryKeyTypes... primaryKeys);
565 template <
typename Record>
566 [[nodiscard]] Async::Task<void>
UpdateAsync(Record& record);
569 template <
typename Record>
570 [[nodiscard]] Async::Task<std::size_t>
DeleteAsync(Record
const& record);
573 template <
typename Record>
583 template <
typename Record>
584 [[nodiscard]]
static std::string BuildFullyQualifiedFieldList()
587 EnumerateRecordMembers<Record>([&fields]<
size_t I,
typename Field>() {
591 fields += RecordTableName<Record>;
593 fields += FieldNameAt<I, Record>;
605 template <
typename Record,
typename... Args>
606 std::optional<Record>
QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args);
608 template <
typename Record,
typename ValueType>
609 void SetId(Record& record, ValueType&&
id);
611 template <
typename Record,
size_t InitialOffset = 1>
612 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
614 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
615 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
617 template <
typename FieldType>
618 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
620 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
621 void LoadHasMany(Record& record, HasMany<OtherRecord>& field);
623 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
624 void LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field);
626 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
627 void LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field);
629 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
630 void CallOnHasMany(Record& record, Callable
const& callback);
632 template <
size_t FieldIndex,
typename OtherRecord>
633 SqlSelectQueryBuilder BuildHasManySelectQuery();
635 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
636 void CallOnHasManyThrough(Record& record, Callable
const& callback);
638 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue,
typename Callable>
639 void CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback);
641 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue>
642 std::shared_ptr<ReferencedRecord> LoadHasOneThroughByPK(PKValue
const& pkValue);
644 enum class PrimaryKeySource : std::uint8_t
650 template <
typename Record>
651 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record
const& record);
653 template <PrimaryKeySource UsePkOverr
ide,
typename Record>
654 RecordPrimaryKeyType<Record> CreateInternal(
655 Record
const& record,
656 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
657 pkOverride = std::nullopt);
659 SqlConnection _connection;
667 template <
typename FieldType>
668 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
670 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
677 if constexpr (IsField<FieldType>)
679 if constexpr (detail::OneOf<
typename FieldType::ValueType,
685 || IsSqlDynamicString<typename FieldType::ValueType>
686 || IsSqlDynamicBinary<typename FieldType::ValueType>)
695 template <DataMapperRecord Record>
696 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
698 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
702 EnumerateRecordMembers<Record>([&result]<
size_t I,
typename Field>() {
703 if constexpr (IsField<Field>)
711 || IsSqlDynamicString<typename Field::ValueType>
712 || IsSqlDynamicBinary<typename Field::ValueType>)
722 template <
typename Record>
723 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
725 EnumerateRecordMembers(record, [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
726 if constexpr (IsField<Field>)
728 reader->BindOutputColumn(i++, &field.MutableValue());
730 else if constexpr (IsBelongsTo<Field>)
732 reader->BindOutputColumn(i++, &field.MutableValue());
734 else if constexpr (SqlOutputColumnBinder<Field>)
736 reader->BindOutputColumn(i++, &field);
741 template <
typename Record>
742 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
744 BindAllOutputColumnsWithOffset(reader, record, 1);
749 constexpr std::size_t kDefaultRowArrayFetchDepth = 1024;
754 template <std::
size_t I>
755 struct MutableFieldValueAccessor
757 template <
typename Record>
758 decltype(
auto)
operator()(Record& record)
const
760 return GetRecordMemberAt<I>(record).MutableValue();
766 template <
typename FieldType>
767 using RowWiseColumnValueType = std::remove_cvref_t<decltype(std::declval<FieldType&>().MutableValue())>;
771 template <
typename FieldType>
772 constexpr bool RowWiseIsColumn()
774 return IsField<FieldType> || IsBelongsTo<FieldType> || SqlOutputColumnBinder<FieldType>;
781 template <
typename FieldType>
782 constexpr bool RowWiseColumnAcceptable()
784 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
785 return SqlRowWiseFetchableColumn<RowWiseColumnValueType<FieldType>>;
786 else if constexpr (SqlOutputColumnBinder<FieldType>)
792 template <
typename Record, std::size_t... Is>
793 constexpr bool CanRowWiseFetchRecordImpl(std::index_sequence<Is...>)
798 return (
sizeof(Record) %
alignof(SQLLEN) == 0) && (RowWiseColumnAcceptable<RecordMemberTypeOf<Is, Record>>() && ...)
799 && (RowWiseIsColumn<RecordMemberTypeOf<Is, Record>>() || ...);
806 template <
typename Record>
807 constexpr bool CanRowWiseFetchRecord()
809 return CanRowWiseFetchRecordImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
814 template <std::
size_t I,
typename Record>
815 auto MakeOutputColumnAccessor()
817 using FieldType = RecordMemberTypeOf<I, Record>;
818 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
819 return std::tuple<MutableFieldValueAccessor<I>> {};
821 return std::tuple<> {};
827 template <
typename Record>
828 void ReadAllRowWise(SqlResultCursor& reader, std::vector<Record>* records)
830 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
832 [&](
auto const&... accessors) {
833 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
835 std::tuple_cat(MakeOutputColumnAccessor<Is, Record>()...));
836 }(std::make_index_sequence<RecordMemberCount<Record>> {});
842 template <
typename FieldType>
843 constexpr bool ColumnIsNarrowFixedString()
845 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
847 using V = RowWiseColumnValueType<FieldType>;
848 if constexpr (SqlIsStdOptional<V>)
849 return IsSqlFixedString<typename V::value_type>;
851 return IsSqlFixedString<V>;
857 template <
typename Record, std::size_t... Is>
858 constexpr bool RecordHasNarrowFixedStringColumnImpl(std::index_sequence<Is...>)
860 return (ColumnIsNarrowFixedString<RecordMemberTypeOf<Is, Record>>() || ...);
866 template <
typename Record>
867 constexpr bool RecordHasNarrowFixedStringColumn()
869 return RecordHasNarrowFixedStringColumnImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
876 template <
typename Record>
877 bool CanRowWiseFetchOn(SqlServerType serverType)
879 if constexpr (!CanRowWiseFetchRecord<Record>())
883 && (!RecordHasNarrowFixedStringColumn<Record>()
891 template <std::
size_t TupleIndex, std::
size_t I>
892 struct MutableTupleFieldAccessor
894 template <
typename TupleType>
895 decltype(
auto)
operator()(TupleType& row)
const
897 return GetRecordMemberAt<I>(std::get<TupleIndex>(row)).MutableValue();
901 template <
typename First,
typename Second, std::size_t... Fs, std::size_t... Ss>
902 constexpr bool CanRowWiseFetchTupleImpl(std::index_sequence<Fs...>, std::index_sequence<Ss...>)
904 return (
sizeof(std::tuple<First, Second>) %
alignof(SQLLEN) == 0)
905 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Fs, First>>() && ...)
906 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Ss, Second>>() && ...)
907 && ((RowWiseIsColumn<RecordMemberTypeOf<Fs, First>>() || ...)
908 || (RowWiseIsColumn<RecordMemberTypeOf<Ss, Second>>() || ...));
914 template <
typename First,
typename Second>
915 constexpr bool CanRowWiseFetchTuple()
917 return CanRowWiseFetchTupleImpl<First, Second>(std::make_index_sequence<RecordMemberCount<First>> {},
918 std::make_index_sequence<RecordMemberCount<Second>> {});
924 template <
typename First,
typename Second>
925 bool CanRowWiseFetchTupleOn(SqlServerType serverType)
927 if constexpr (!CanRowWiseFetchTuple<First, Second>())
931 && ((!RecordHasNarrowFixedStringColumn<First>() && !RecordHasNarrowFixedStringColumn<Second>())
936 template <std::
size_t TupleIndex, std::
size_t I,
typename SubRecord>
937 auto MakeTupleColumnAccessor()
939 using FieldType = RecordMemberTypeOf<I, SubRecord>;
940 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
941 return std::tuple<MutableTupleFieldAccessor<TupleIndex, I>> {};
943 return std::tuple<> {};
949 template <
typename First,
typename Second>
950 void ReadAllRowWiseTuple(SqlResultCursor& reader, std::vector<std::tuple<First, Second>>* records)
952 [&]<std::size_t... Fs, std::size_t... Ss>(std::index_sequence<Fs...>, std::index_sequence<Ss...>) {
954 [&](
auto const&... accessors) {
955 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
957 std::tuple_cat(MakeTupleColumnAccessor<0, Fs, First>()..., MakeTupleColumnAccessor<1, Ss, Second>()...));
958 }(std::make_index_sequence<RecordMemberCount<First>> {}, std::make_index_sequence<RecordMemberCount<Second>> {});
964 template <
typename ElementMask,
typename Record>
965 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
967 EnumerateRecordMembers<ElementMask>(
968 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
970 if constexpr (IsField<Field>)
973 field.MutableValue() =
974 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
976 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
978 else if constexpr (SqlGetColumnNativeType<Field>)
980 if constexpr (IsOptionalBelongsTo<Field>)
981 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
983 field = reader->GetColumn<Field>(indexFromQuery);
988 template <
typename Record>
989 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
991 return GetAllColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record>(
992 reader, record, indexFromQuery);
995 template <
typename FirstRecord,
typename SecondRecord>
997 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
999 auto& [firstRecord, secondRecord] = record;
1002 if constexpr (IsField<Field>)
1005 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(I + 1);
1007 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(I + 1);
1009 else if constexpr (SqlGetColumnNativeType<Field>)
1012 field = reader->GetNullableColumn<
typename Field::BaseType>(I + 1);
1014 field = reader->GetColumn<Field>(I + 1);
1018 EnumerateRecordMembers(secondRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
1019 if constexpr (IsField<Field>)
1022 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(
1023 RecordMemberCount<FirstRecord> + I + 1);
1025 field.MutableValue() =
1026 reader->GetColumn<
typename Field::ValueType>(RecordMemberCount<FirstRecord> + I + 1);
1028 else if constexpr (SqlGetColumnNativeType<Field>)
1031 field = reader->GetNullableColumn<
typename Field::BaseType>(RecordMemberCount<FirstRecord> + I + 1);
1033 field = reader->GetColumn<Field>(RecordMemberCount<FirstRecord> + I + 1);
1038 template <
typename Record>
1039 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
1041 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
1043 if (outputColumnsBound)
1044 BindAllOutputColumns(reader, record);
1046 if (!reader.FetchRow())
1049 if (!outputColumnsBound)
1050 GetAllColumns(reader, record);
1056template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1057template <
typename Finisher>
1058auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RunFinisher(Finisher finisher)
1060 if constexpr (Derived::QueryExecution == SqlQueryExecutionMode::Asynchronous)
1061 return Async::RunAsync(_dm.Connection().AsyncBackend(), std::move(finisher));
1066template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1068 DataMapper& dm, std::string fields)
noexcept:
1070 _formatter { dm.Connection().QueryFormatter() },
1071 _fields { std::move(fields) }
1073 this->_query.searchCondition.inputBindings = &_boundInputs;
1076template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1077size_t SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::CountImpl()
1079 auto stmt = SqlStatement { _dm.Connection() };
1080 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
1081 RecordTableName<Record>,
1082 this->_query.searchCondition.tableAlias,
1083 this->_query.searchCondition.tableJoins,
1084 this->_query.searchCondition.condition));
1085 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1086 if (reader.FetchRow())
1087 return reader.GetColumn<
size_t>(1);
1091template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1092bool SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::ExistImpl()
1094 auto stmt = SqlStatement { _dm.Connection() };
1096 auto const query = _formatter.SelectFirst(this->_query.distinct,
1098 RecordTableName<Record>,
1099 this->_query.searchCondition.tableAlias,
1100 this->_query.searchCondition.tableJoins,
1101 this->_query.searchCondition.condition,
1102 this->_query.orderBy,
1105 stmt.Prepare(query);
1106 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1111template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1112void SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::DeleteImpl()
1114 auto stmt = SqlStatement { _dm.Connection() };
1116 auto const query = _formatter.Delete(RecordTableName<Record>,
1117 this->_query.searchCondition.tableAlias,
1118 this->_query.searchCondition.tableJoins,
1119 this->_query.searchCondition.condition);
1121 stmt.Prepare(query);
1122 [[maybe_unused]]
auto cursor = stmt.ExecuteWithVariants(_boundInputs);
1125template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1126std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl()
1129 auto records = std::vector<Record> {};
1130 auto stmt = SqlStatement { _dm.Connection() };
1131 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1133 RecordTableName<Record>,
1134 this->_query.searchCondition.tableAlias,
1135 this->_query.searchCondition.tableJoins,
1136 this->_query.searchCondition.condition,
1137 this->_query.orderBy,
1138 this->_query.groupBy));
1139 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1140 if constexpr (DataMapperRecord<Record>)
1145 if constexpr (QueryOptions.loadRelations)
1147 for (
auto& record: records)
1149 _dm.ConfigureRelationAutoLoading(record);
1156template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1157template <auto Field>
1158#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1159 requires(is_aggregate_type(parent_of(Field)))
1161 requires std::is_member_object_pointer_v<
decltype(Field)>
1163auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<ReferencedFieldTypeOf<Field>>
1165 using value_type = ReferencedFieldTypeOf<Field>;
1166 auto result = std::vector<value_type> {};
1168 auto stmt = SqlStatement { _dm.Connection() };
1169 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1170 detail::FullyQualifiedNamesOf<Field>,
1171 RecordTableName<Record>,
1172 this->_query.searchCondition.tableAlias,
1173 this->_query.searchCondition.tableJoins,
1174 this->_query.searchCondition.condition,
1175 this->_query.orderBy,
1176 this->_query.groupBy));
1177 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1178 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
1181 auto& value = result.emplace_back();
1182 if (outputColumnsBound)
1183 reader.BindOutputColumn(1, &value);
1185 if (!reader.FetchRow())
1191 if (!outputColumnsBound)
1192 value = reader.GetColumn<value_type>(1);
1198template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1199template <
auto... ReferencedFields>
1200 requires(
sizeof...(ReferencedFields) >= 2)
1201auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<Record>
1203 auto records = std::vector<Record> {};
1204 auto stmt = SqlStatement { _dm.Connection() };
1206 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1207 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1208 RecordTableName<Record>,
1209 this->_query.searchCondition.tableAlias,
1210 this->_query.searchCondition.tableJoins,
1211 this->_query.searchCondition.condition,
1212 this->_query.orderBy,
1213 this->_query.groupBy));
1215 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1216 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1219 auto& record = records.emplace_back();
1220 if (outputColumnsBound)
1221#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1222 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1224 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1226 if (!reader.FetchRow())
1231 if (!outputColumnsBound)
1233 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1234 detail::GetAllColumns<ElementMask>(reader, record);
1241template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1242std::optional<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl()
1244 std::optional<Record> record {};
1245 auto stmt = SqlStatement { _dm.Connection() };
1246 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1248 RecordTableName<Record>,
1249 this->_query.searchCondition.tableAlias,
1250 this->_query.searchCondition.tableJoins,
1251 this->_query.searchCondition.condition,
1252 this->_query.orderBy,
1254 Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record);
1255 if constexpr (QueryOptions.loadRelations)
1258 _dm.ConfigureRelationAutoLoading(record.value());
1263template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1264template <auto Field>
1265#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1266 requires(is_aggregate_type(parent_of(Field)))
1268 requires std::is_member_object_pointer_v<
decltype(Field)>
1270auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<ReferencedFieldTypeOf<Field>>
1272 auto constexpr count = 1;
1273 auto stmt = SqlStatement { _dm.Connection() };
1274 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1275 detail::FullyQualifiedNamesOf<Field>,
1276 RecordTableName<Record>,
1277 this->_query.searchCondition.tableAlias,
1278 this->_query.searchCondition.tableJoins,
1279 this->_query.searchCondition.condition,
1280 this->_query.orderBy,
1282 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1283 return reader.template GetColumn<ReferencedFieldTypeOf<Field>>(1);
1284 return std::nullopt;
1287template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1288template <
auto... ReferencedFields>
1289 requires(
sizeof...(ReferencedFields) >= 2)
1290auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<Record>
1292 auto optionalRecord = std::optional<Record> {};
1294 auto stmt = SqlStatement { _dm.Connection() };
1295 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1296 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1297 RecordTableName<Record>,
1298 this->_query.searchCondition.tableAlias,
1299 this->_query.searchCondition.tableJoins,
1300 this->_query.searchCondition.condition,
1301 this->_query.orderBy,
1304 auto& record = optionalRecord.emplace();
1305 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1306 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1307 if (outputColumnsBound)
1308#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1309 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1311 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1313 if (!reader.FetchRow())
1314 return std::nullopt;
1315 if (!outputColumnsBound)
1317 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1318 detail::GetAllColumns<ElementMask>(reader, record);
1321 if constexpr (QueryOptions.loadRelations)
1322 _dm.ConfigureRelationAutoLoading(record);
1324 return optionalRecord;
1327template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1328std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1330 auto records = std::vector<Record> {};
1331 auto stmt = SqlStatement { _dm.Connection() };
1333 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1335 RecordTableName<Record>,
1336 this->_query.searchCondition.tableAlias,
1337 this->_query.searchCondition.tableJoins,
1338 this->_query.searchCondition.condition,
1339 this->_query.orderBy,
1341 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1343 if constexpr (QueryOptions.loadRelations)
1345 for (
auto& record: records)
1346 _dm.ConfigureRelationAutoLoading(record);
1351template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1352std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1354 auto records = std::vector<Record> {};
1355 auto stmt = SqlStatement { _dm.Connection() };
1356 records.reserve(limit);
1358 _formatter.SelectRange(this->_query.distinct,
1360 RecordTableName<Record>,
1361 this->_query.searchCondition.tableAlias,
1362 this->_query.searchCondition.tableJoins,
1363 this->_query.searchCondition.condition,
1364 !this->_query.orderBy.empty()
1365 ? this->_query.orderBy
1366 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1367 this->_query.groupBy,
1370 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1371 if constexpr (QueryOptions.loadRelations)
1373 for (
auto& record: records)
1374 _dm.ConfigureRelationAutoLoading(record);
1379template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1380template <
auto... ReferencedFields>
1381std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1383 auto records = std::vector<Record> {};
1384 auto stmt = SqlStatement { _dm.Connection() };
1385 records.reserve(limit);
1387 _formatter.SelectRange(this->_query.distinct,
1388 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1389 RecordTableName<Record>,
1390 this->_query.searchCondition.tableAlias,
1391 this->_query.searchCondition.tableJoins,
1392 this->_query.searchCondition.condition,
1393 !this->_query.orderBy.empty()
1394 ? this->_query.orderBy
1395 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1396 this->_query.groupBy,
1400 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1401 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1404 auto& record = records.emplace_back();
1405 if (outputColumnsBound)
1406#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1407 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1409 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1411 if (!reader.FetchRow())
1416 if (!outputColumnsBound)
1418 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1419 detail::GetAllColumns<ElementMask>(reader, record);
1423 if constexpr (QueryOptions.loadRelations)
1425 for (
auto& record: records)
1426 _dm.ConfigureRelationAutoLoading(record);
1432template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1433template <
auto... ReferencedFields>
1434[[nodiscard]] std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1436 auto records = std::vector<Record> {};
1437 auto stmt = SqlStatement { _dm.Connection() };
1439 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1440 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1441 RecordTableName<Record>,
1442 this->_query.searchCondition.tableAlias,
1443 this->_query.searchCondition.tableJoins,
1444 this->_query.searchCondition.condition,
1445 this->_query.orderBy,
1448 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1449 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1452 auto& record = records.emplace_back();
1453 if (outputColumnsBound)
1454#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1455 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1457 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1459 if (!reader.FetchRow())
1464 if (!outputColumnsBound)
1466 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1467 detail::GetAllColumns<ElementMask>(reader, record);
1471 if constexpr (QueryOptions.loadRelations)
1473 for (
auto& record: records)
1474 _dm.ConfigureRelationAutoLoading(record);
1480template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1481void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResults(SqlServerType sqlServerType,
1482 SqlResultCursor reader,
1483 std::vector<Record>* records)
1489 if constexpr (detail::CanRowWiseFetchRecord<Record>())
1491 if (detail::CanRowWiseFetchOn<Record>(sqlServerType))
1493 detail::ReadAllRowWise(reader, records);
1500 Record& record = records->emplace_back();
1501 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1503 records->pop_back();
1509template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1510void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResult(SqlServerType sqlServerType,
1511 SqlResultCursor reader,
1512 std::optional<Record>* optionalRecord)
1514 Record& record = optionalRecord->emplace();
1515 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1516 optionalRecord->reset();
1519template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1520void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution>::ReadResults(
1521 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1525 if constexpr (detail::CanRowWiseFetchTuple<FirstRecord, SecondRecord>())
1527 if (detail::CanRowWiseFetchTupleOn<FirstRecord, SecondRecord>(sqlServerType))
1529 detail::ReadAllRowWiseTuple<FirstRecord, SecondRecord>(reader, records);
1536 auto& record = records->emplace_back();
1537 auto& [firstRecord, secondRecord] = record;
1539 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1540 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1542 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1543 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1544 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1546 if (canSafelyBindAll)
1548 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1549 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + RecordMemberCount<FirstRecord>);
1552 if (!reader.FetchRow())
1554 records->pop_back();
1558 if (!canSafelyBindAll)
1559 detail::GetAllColumns(reader, record);
1563template <
typename Record>
1569 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1575 if constexpr (Value::IsOptional)
1577 if (!value.Value().has_value())
1579 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1583 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1586 else if constexpr (IsBelongsTo<Value>)
1588 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1590 else if constexpr (std::same_as<typename Value::ValueType, char>)
1595 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1598 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1599 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1601 return "{\n" + std::move(str) +
"\n}";
1604template <
typename Record>
1610 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1611 detail::PopulateCreateTableBuilder<Record>(createTable);
1612 return migration.GetPlan().ToSql();
1615template <
typename FirstRecord,
typename... MoreRecords>
1618 std::vector<std::string> output;
1619 auto const append = [&output](
auto const& sql) {
1620 output.insert(output.end(), sql.begin(), sql.end());
1622 append(CreateTableString<FirstRecord>(serverType));
1623 (append(CreateTableString<MoreRecords>(serverType)), ...);
1627template <
typename Record>
1632 ZoneScopedN(
"DataMapper::CreateTable");
1633 ZoneTextObject(RecordTableName<Record>);
1635 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1636 for (
auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
1640template <
typename FirstRecord,
typename... MoreRecords>
1643 CreateTable<FirstRecord>();
1644 (CreateTable<MoreRecords>(), ...);
1647template <
typename Record>
1648std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
1650 std::optional<RecordPrimaryKeyType<Record>> result;
1652 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1653 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1654 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1656 using ValueType = PrimaryKeyType::ValueType;
1657 if constexpr (std::same_as<ValueType, SqlGuid>)
1659 if (!primaryKeyField.Value())
1664 else if constexpr (
requires { ValueType {} + 1; })
1666 if (primaryKeyField.Value() == ValueType {})
1668 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1669 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1670 FieldNameAt<PrimaryKeyIndex, Record>,
1671 RecordTableName<Record>));
1672 result = maxId.value_or(ValueType {}) + 1;
1680template <DataMapper::PrimaryKeySource UsePkOverr
ide,
typename Record>
1681RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
1682 Record
const& record,
1683 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
1686 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1688 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1690#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1691 constexpr auto ctx = std::meta::access_context::current();
1692 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1694 using FieldType =
typename[:std::meta::type_of(el):];
1695 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1696 query.Set(FieldNameOf<el>, SqlWildcard);
1700 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1701 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1707#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1709 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1711 using FieldType =
typename[:std::meta::type_of(el):];
1712 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1714 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1721 Reflection::CallOnMembers(record,
1722 [
this, &pkOverride, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
1723 Name
const& name, FieldType
const& field)
mutable {
1724 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1726 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1733 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1735 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1736 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1737 else if constexpr (HasPrimaryKey<Record>)
1739 if constexpr (UsePkOverride == PrimaryKeySource::Override)
1742 return RecordPrimaryKeyOf(record).Value();
1748template <
typename Record>
1752 return CreateInternal<PrimaryKeySource::Record>(record);
1761 template <
typename FieldType>
1762 constexpr bool IsBatchInsertColumn = SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>;
1765 template <
typename FieldType>
1769 template <
typename FieldType>
1770 constexpr bool IsBatchUpdateWhereColumn = IsPrimaryKey<FieldType>;
1774 template <std::
size_t I>
1775 struct FieldValueAccessor
1777 template <
typename Record>
1778 decltype(
auto)
operator()(Record
const& record)
const
1780 return GetRecordMemberAt<I>(record).Value();
1786 template <std::
size_t I,
typename Record>
1787 auto MakeCreateColumnAccessor()
1789 using FieldType = RecordMemberTypeOf<I, Record>;
1790 if constexpr (IsBatchInsertColumn<FieldType>)
1791 return std::tuple<FieldValueAccessor<I>> {};
1793 return std::tuple<> {};
1797 template <std::
size_t I,
typename Record>
1798 auto MakeUpdateSetAccessor()
1800 using FieldType = RecordMemberTypeOf<I, Record>;
1801 if constexpr (IsBatchUpdateSetColumn<FieldType>)
1802 return std::tuple<FieldValueAccessor<I>> {};
1804 return std::tuple<> {};
1808 template <std::
size_t I,
typename Record>
1809 auto MakeUpdateWhereAccessor()
1811 using FieldType = RecordMemberTypeOf<I, Record>;
1812 if constexpr (IsBatchUpdateWhereColumn<FieldType>)
1813 return std::tuple<FieldValueAccessor<I>> {};
1815 return std::tuple<> {};
1819template <std::ranges::range Records>
1822 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
1823 "CreateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
1824 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
1825 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
1828 ZoneScopedN(
"DataMapper::CreateAll");
1829 ZoneTextObject(RecordTableName<Record>);
1831 if (std::ranges::empty(records))
1835 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1836 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
1837 if constexpr (detail::IsBatchInsertColumn<FieldType>)
1838 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1843 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
1844 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
1845 std::tuple_cat(detail::MakeCreateColumnAccessor<Is, Record>()...));
1846 }(std::make_index_sequence<RecordMemberCount<Record>> {});
1849template <DataMapperOptions QueryOptions,
typename Record>
1853 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
1855 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
1857 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
1859 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1860 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
1862 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
1865template <DataMapperOptions QueryOptions,
typename Record>
1868 static_assert(!std::is_const_v<Record>);
1871 ZoneScopedN(
"DataMapper::Create");
1872 ZoneTextObject(RecordTableName<Record>);
1874 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
1876 SetId(record, *generatedKey);
1878 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
1880 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1883 SetModifiedState<ModifiedState::NotModified>(record);
1885 if constexpr (QueryOptions.loadRelations)
1888 if constexpr (HasPrimaryKey<Record>)
1892template <
typename Record>
1897 bool modified =
false;
1899#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1900 auto constexpr ctx = std::meta::access_context::current();
1901 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1903 if constexpr (
requires { record.[:el:].IsModified(); })
1905 modified = modified || record.[:el:].IsModified();
1909 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1910 if constexpr (
requires { field.IsModified(); })
1912 modified = modified || field.IsModified();
1920template <
typename Record>
1925 ZoneScopedN(
"DataMapper::Update");
1926 ZoneTextObject(RecordTableName<Record>);
1928 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1930#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1931 auto constexpr ctx = std::meta::access_context::current();
1932 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1934 using FieldType =
typename[:std::meta::type_of(el):];
1937 if (record.[:el:].IsModified())
1938 query.Set(FieldNameOf<el>, SqlWildcard);
1939 if constexpr (IsPrimaryKey<FieldType>)
1940 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1945 if (field.IsModified())
1946 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1949 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
1950 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1957#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1958 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1960 if (record.[:el:].IsModified())
1966 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1968 using FieldType =
typename[:std::meta::type_of(el):];
1969 if constexpr (FieldType::IsPrimaryKey)
1977 if (field.IsModified())
1983 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
1988 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1990 SetModifiedState<ModifiedState::NotModified>(record);
1993template <std::ranges::range Records>
1996 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
1997 "UpdateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
1998 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
1999 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
2001 static_assert(HasPrimaryKey<Record>,
"UpdateAll requires a record type with a primary key");
2003 ZoneScopedN(
"DataMapper::UpdateAll");
2004 ZoneTextObject(RecordTableName<Record>);
2006 if (std::ranges::empty(records))
2010 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2011 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2012 if constexpr (detail::IsBatchUpdateSetColumn<FieldType>)
2013 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2015 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2016 if constexpr (detail::IsBatchUpdateWhereColumn<FieldType>)
2017 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2022 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
2023 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
2024 std::tuple_cat(detail::MakeUpdateSetAccessor<Is, Record>()...,
2025 detail::MakeUpdateWhereAccessor<Is, Record>()...));
2026 }(std::make_index_sequence<RecordMemberCount<Record>> {});
2029template <
typename Record>
2034 ZoneScopedN(
"DataMapper::Delete");
2035 ZoneTextObject(RecordTableName<Record>);
2037 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
2039#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2040 auto constexpr ctx = std::meta::access_context::current();
2041 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2043 using FieldType =
typename[:std::meta::type_of(el):];
2044 if constexpr (FieldType::IsPrimaryKey)
2045 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2049 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2050 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2056#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2058 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2060 using FieldType =
typename[:std::meta::type_of(el):];
2061 if constexpr (FieldType::IsPrimaryKey)
2069 [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
2070 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2075 auto cursor = _stmt.
Execute();
2080template <
typename Record,
DataMapperOptions QueryOptions,
typename... PrimaryKeyTypes>
2085 ZoneScopedN(
"DataMapper::QuerySingle(PK)");
2086 ZoneTextObject(RecordTableName<Record>);
2092 auto selectStarter = _connection.
Query(RecordTableName<Record>).
Select();
2094 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2097 if (queryBuilder ==
nullptr)
2098 queryBuilder = &selectStarter.
Field(FieldNameAt<I, Record>);
2100 queryBuilder->
Field(FieldNameAt<I, Record>);
2102 if constexpr (FieldType::IsPrimaryKey)
2103 std::ignore = queryBuilder->
Where(FieldNameAt<I, Record>, SqlWildcard);
2108 auto reader = _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
2110 auto resultRecord = std::optional<Record> { Record {} };
2112 return std::nullopt;
2115 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2117 if constexpr (QueryOptions.loadRelations)
2123 return resultRecord;
2126template <
typename Record,
typename... Args>
2131 ZoneScopedN(
"DataMapper::QuerySingle(Builder)");
2132 ZoneTextObject(RecordTableName<Record>);
2134 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2138 auto const composedSql = selectQuery.
First().ToSql();
2139 ZoneTextObject(composedSql);
2141 auto reader = _stmt.
Execute(std::forward<Args>(args)...);
2143 auto resultRecord = std::optional<Record> { Record {} };
2145 return std::nullopt;
2148 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2150 return resultRecord;
2156template <
typename Record, DataMapperOptions QueryOptions,
typename... InputParameters>
2158 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
2160 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
2162 ZoneScopedN(
"DataMapper::Query(ComposedQuery)");
2163 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
2166template <
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2167std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
2169 ZoneScopedN(
"DataMapper::Query(string)");
2170 ZoneTextObject(sqlQueryString);
2172 auto result = std::vector<Record> {};
2173 if constexpr (std::same_as<Record, SqlVariantRow>)
2175 _stmt.
Prepare(sqlQueryString);
2180 auto& record = result.emplace_back();
2181 record.reserve(numResultColumns);
2182 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
2190 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2192 _stmt.
Prepare(sqlQueryString);
2193 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2197 auto& record = result.emplace_back();
2199 if (canSafelyBindOutputColumns)
2200 BindOutputColumns(record, reader);
2202 if (!reader.FetchRow())
2205 if (!canSafelyBindOutputColumns)
2206 detail::GetAllColumns(reader, record);
2212 for (
auto& record: result)
2214 SetModifiedState<ModifiedState::NotModified>(record);
2215 if constexpr (QueryOptions.loadRelations)
2223template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
2225std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
2227 using value_type = std::tuple<First, Second, Rest...>;
2228 auto result = std::vector<value_type> {};
2230 ZoneScopedN(
"DataMapper::Query(ComposedQuery -> tuple)");
2231 auto const tupleSql = selectQuery.ToSql();
2232 ZoneTextObject(tupleSql);
2234 auto reader = _stmt.
Execute();
2236 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
2239 if constexpr (I > 0)
2241 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
2242 ((Indices < I ? (offset += RecordMemberCount<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
2243 }(std::make_index_sequence<I> {});
2248 auto const BindElements = [&](
auto& record) {
2249 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2250 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2251 auto& element = std::get<I>(record);
2252 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2253 this->BindOutputColumns<TupleElement, offset>(element, reader);
2257 auto const GetElements = [&](
auto& record) {
2258 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2259 auto& element = std::get<I>(record);
2260 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2261 detail::GetAllColumns(reader, element, offset - 1);
2265 bool const canSafelyBindOutputColumns = [&]() {
2267 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2268 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2276 auto& record = result.emplace_back();
2278 if (canSafelyBindOutputColumns)
2279 BindElements(record);
2281 if (!reader.FetchRow())
2284 if (!canSafelyBindOutputColumns)
2285 GetElements(record);
2291 for (
auto& record: result)
2293 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2294 auto& element = std::get<I>(record);
2295 SetModifiedState<ModifiedState::NotModified>(element);
2296 if constexpr (QueryOptions.loadRelations)
2306template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2308 InputParameters&&... inputParameters)
2312 ZoneScopedN(
"DataMapper::Query(ComposedQuery, ElementMask)");
2313 auto const maskedSql = selectQuery.ToSql();
2314 ZoneTextObject(maskedSql);
2317 auto records = std::vector<Record> {};
2320 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2322 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2326 auto& record = records.emplace_back();
2328 if (canSafelyBindOutputColumns)
2329 BindOutputColumns<ElementMask>(record, reader);
2331 if (!reader.FetchRow())
2334 if (!canSafelyBindOutputColumns)
2335 detail::GetAllColumns<ElementMask>(reader, record);
2341 for (
auto& record: records)
2343 SetModifiedState<ModifiedState::NotModified>(record);
2344 if constexpr (QueryOptions.loadRelations)
2351template <DataMapper::ModifiedState state,
typename Record>
2354 static_assert(!std::is_const_v<Record>);
2358 if constexpr (
requires { field.SetModified(
false); })
2360 if constexpr (state == ModifiedState::Modified)
2361 field.SetModified(
true);
2363 field.SetModified(
false);
2368template <
typename Record,
typename Callable>
2369inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
2374 if constexpr (IsField<FieldType>)
2376 if constexpr (FieldType::IsPrimaryKey)
2378 return callable.template operator()<I, FieldType>(field);
2384template <
typename Record,
typename Callable>
2385inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
2387 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2389 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2390 if constexpr (IsField<FieldType>)
2392 if constexpr (FieldType::IsPrimaryKey)
2394 return callable.template operator()<I, FieldType>();
2400template <
typename Record,
typename Callable>
2401inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
2403 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2405 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2406 if constexpr (IsBelongsTo<FieldType>)
2408 return callable.template operator()<I, FieldType>();
2413template <
typename FieldType>
2414std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
2416 using ReferencedRecord = FieldType::ReferencedRecord;
2418 ZoneScopedN(
"DataMapper::LoadBelongsTo");
2419 ZoneTextObject(RecordTableName<ReferencedRecord>);
2421 std::optional<ReferencedRecord> record { std::nullopt };
2423#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2424 auto constexpr ctx = std::meta::access_context::current();
2425 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
2427 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
2428 if constexpr (IsField<BelongsToFieldType>)
2429 if constexpr (BelongsToFieldType::IsPrimaryKey)
2431 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
2432 record = std::move(result);
2435 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
2439 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
2440 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
2441 record = std::move(result);
2444 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
2450template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
2451void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
2453 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2454 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2456 using FieldType = HasMany<OtherRecord>;
2457 using ReferencedRecord = FieldType::ReferencedRecord;
2459 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2460 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2462 .Build([&](
auto& query) {
2463 EnumerateRecordMembers<ReferencedRecord>(
2464 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2465 if constexpr (FieldWithStorage<ReferencedFieldType>)
2467 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
2471 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard)
2472 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
2473 callback(query, primaryKeyField);
2477template <
size_t FieldIndex,
typename OtherRecord>
2478SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
2480 return _connection.
Query(RecordTableName<OtherRecord>)
2482 .
Build([](
auto& q) {
2483 EnumerateRecordMembers<OtherRecord>([&]<
size_t I,
typename F>() {
2484 if constexpr (FieldWithStorage<F>)
2485 q.Field(FieldNameAt<I, OtherRecord>);
2488 .Where(FieldNameAt<FieldIndex, OtherRecord>, SqlWildcard)
2489 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
2492template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
2493void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
2495 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2496 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2498 ZoneScopedN(
"DataMapper::LoadHasMany");
2499 ZoneTextObject(RecordTableName<OtherRecord>);
2501 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
2502 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
2506template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2507void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
2509 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2510 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2512 ZoneScopedN(
"DataMapper::LoadHasOneThrough");
2513 ZoneTextObject(RecordTableName<ReferencedRecord>);
2516 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2518 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
2520 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
2522 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
2527 _connection.
Query(RecordTableName<ReferencedRecord>)
2529 .Build([&](
auto& query) {
2530 EnumerateRecordMembers<ReferencedRecord>(
2531 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2532 if constexpr (FieldWithStorage<ReferencedFieldType>)
2534 query.Field(SqlQualifiedTableColumnName {
2535 RecordTableName<ReferencedRecord>,
2536 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2540 .InnerJoin(RecordTableName<ThroughRecord>,
2541 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
2542 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2543 .InnerJoin(RecordTableName<Record>,
2544 FieldNameAt<PrimaryKeyIndex, Record>,
2545 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2546 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2548 SqlQualifiedTableColumnName {
2549 RecordTableName<Record>,
2550 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2553 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
2555 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
2563template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue>
2564std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue
const& pkValue)
2566 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2568 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2569 std::shared_ptr<ReferencedRecord> result;
2572 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
2574 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
2576 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
2578 _connection.
Query(RecordTableName<ReferencedRecord>)
2580 .Build([&](
auto& query) {
2581 EnumerateRecordMembers<ReferencedRecord>(
2582 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2583 if constexpr (FieldWithStorage<ReferencedFieldType>)
2585 query.Field(SqlQualifiedTableColumnName {
2586 RecordTableName<ReferencedRecord>,
2587 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2591 .InnerJoin(RecordTableName<ThroughRecord>,
2592 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
2593 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
2594 .InnerJoin(RecordTableName<Record>,
2595 FieldNameAt<PrimaryKeyIndex, Record>,
2596 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
2597 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
2599 SqlQualifiedTableColumnName {
2600 RecordTableName<Record>,
2601 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
2604 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
2605 result = std::make_shared<ReferencedRecord>(std::move(*link));
2613template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
2614void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
2616 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2619 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2621 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
2622 using ThroughBelongsToRecordFieldType = RecordMemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2623 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2626 CallOnBelongsTo<ThroughRecord>(
2627 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
2628 using ThroughBelongsToReferenceRecordFieldType =
2629 RecordMemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2630 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2633 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2635 .
Build([&](
auto& query) {
2636 EnumerateRecordMembers<ReferencedRecord>(
2637 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2638 if constexpr (FieldWithStorage<ReferencedFieldType>)
2640 query.Field(SqlQualifiedTableColumnName {
2641 RecordTableName<ReferencedRecord>,
2642 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2646 .InnerJoin(RecordTableName<ThroughRecord>,
2647 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2648 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2649 FieldNameAt<PrimaryKeyIndex, Record> })
2651 SqlQualifiedTableColumnName {
2652 RecordTableName<ThroughRecord>,
2653 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2656 callback(query, primaryKeyField);
2664template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename PKValue,
typename Callable>
2665void DataMapper::CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback)
2667 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2669 constexpr size_t PrimaryKeyIndex = RecordPrimaryKeyIndex<Record>;
2672 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
2673 using ThroughBelongsToRecordFieldType = RecordMemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
2674 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
2677 CallOnBelongsTo<ThroughRecord>(
2678 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
2679 using ThroughBelongsToReferenceRecordFieldType =
2680 RecordMemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
2681 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
2684 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2686 .
Build([&](
auto& query) {
2687 EnumerateRecordMembers<ReferencedRecord>(
2688 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2689 if constexpr (FieldWithStorage<ReferencedFieldType>)
2691 query.Field(SqlQualifiedTableColumnName {
2692 RecordTableName<ReferencedRecord>,
2693 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2697 .InnerJoin(RecordTableName<ThroughRecord>,
2698 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
2699 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2700 FieldNameAt<PrimaryKeyIndex, Record> })
2702 SqlQualifiedTableColumnName {
2703 RecordTableName<ThroughRecord>,
2704 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
2707 callback(query, pkValue);
2714template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2715void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2717 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2719 ZoneScopedN(
"DataMapper::LoadHasManyThrough");
2720 ZoneTextObject(RecordTableName<ReferencedRecord>);
2722 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2723 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2724 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2728template <
typename Record>
2731 static_assert(!std::is_const_v<Record>);
2734 ZoneScopedN(
"DataMapper::LoadRelations");
2735 ZoneTextObject(RecordTableName<Record>);
2737#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2738 constexpr auto ctx = std::meta::access_context::current();
2739 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2741 using FieldType =
typename[:std::meta::type_of(el):];
2742 if constexpr (IsBelongsTo<FieldType>)
2744 auto& field = record.[:el:];
2745 field = LoadBelongsTo<FieldType>(field.Value());
2747 else if constexpr (IsHasMany<FieldType>)
2749 LoadHasMany<el>(record, record.[:el:]);
2751 else if constexpr (IsHasOneThrough<FieldType>)
2753 LoadHasOneThrough(record, record.[:el:]);
2755 else if constexpr (IsHasManyThrough<FieldType>)
2757 LoadHasManyThrough(record, record.[:el:]);
2762 if constexpr (IsBelongsTo<FieldType>)
2764 field = LoadBelongsTo<FieldType>(field.Value());
2766 else if constexpr (IsHasMany<FieldType>)
2768 LoadHasMany<FieldIndex>(record, field);
2770 else if constexpr (IsHasOneThrough<FieldType>)
2772 LoadHasOneThrough(record, field);
2774 else if constexpr (IsHasManyThrough<FieldType>)
2776 LoadHasManyThrough(record, field);
2783template <
typename Record,
typename ValueType>
2784inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2789#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2791 auto constexpr ctx = std::meta::access_context::current();
2792 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2794 using FieldType =
typename[:std::meta::type_of(el):];
2795 if constexpr (IsField<FieldType>)
2797 if constexpr (FieldType::IsPrimaryKey)
2799 record.[:el:] = std::forward<ValueType>(
id);
2805 if constexpr (IsField<FieldType>)
2807 if constexpr (FieldType::IsPrimaryKey)
2809 field = std::forward<FieldType>(
id);
2817template <
typename Record,
size_t InitialOffset>
2818inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2821 return BindOutputColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record, InitialOffset>(record,
2825template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2826Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2829 static_assert(!std::is_const_v<Record>);
2831#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2832 auto constexpr ctx = std::meta::access_context::current();
2833 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2834 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2836 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2837 using FieldType =
typename[:std::meta::type_of(el):];
2838 if constexpr (IsField<FieldType>)
2842 else if constexpr (SqlOutputColumnBinder<FieldType>)
2848 EnumerateRecordMembers<ElementMask>(
2849 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2850 if constexpr (IsField<Field>)
2854 else if constexpr (SqlOutputColumnBinder<Field>)
2863template <
typename Record>
2869 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2870 if constexpr (IsBelongsTo<FieldType>)
2872 field.SetAutoLoader(
typename FieldType::Loader {
2873 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2875 return dm.LoadBelongsTo<FieldType>(value);
2879 if constexpr (IsHasMany<FieldType>)
2881 if constexpr (HasPrimaryKey<Record>)
2883 using ReferencedRecord = FieldType::ReferencedRecord;
2888 .count = [pkValue]() ->
size_t {
2890 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2891 dm._stmt.
Prepare(selectQuery.Count());
2898 .all = [pkValue]() -> FieldType::ReferencedRecordList {
2900 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2901 return detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pkValue));
2904 [pkValue](
auto const& each) {
2906 auto selectQuery = dm.BuildHasManySelectQuery<FieldIndex, ReferencedRecord>();
2908 stmt.Prepare(selectQuery.All());
2909 auto cursor = stmt.Execute(pkValue);
2911 auto referencedRecord = ReferencedRecord {};
2912 dm.BindOutputColumns(referencedRecord, cursor);
2917 each(referencedRecord);
2918 dm.BindOutputColumns(referencedRecord, cursor);
2924 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
2926 using ReferencedRecord = FieldType::ReferencedRecord;
2927 using ThroughRecord = FieldType::ThroughRecord;
2932 .loadReference = [pkValue]() -> std::shared_ptr<ReferencedRecord> {
2934 return dm.LoadHasOneThroughByPK<ReferencedRecord, ThroughRecord, Record>(pkValue);
2938 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
2940 using ReferencedRecord = FieldType::ReferencedRecord;
2941 using ThroughRecord = FieldType::ThroughRecord;
2946 .count = [pkValue]() ->
size_t {
2950 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2952 dm._stmt.
Prepare(selectQuery.Count());
2959 .all = [pkValue]() -> FieldType::ReferencedRecordList {
2962 typename FieldType::ReferencedRecordList result;
2963 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2965 result = detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pk));
2970 [pkValue](
auto const& each) {
2973 dm.CallOnHasManyThroughByPK<ReferencedRecord, ThroughRecord, Record>(
2976 stmt.Prepare(selectQuery.All());
2977 auto cursor = stmt.Execute(pk);
2978 auto referencedRecord = ReferencedRecord {};
2979 dm.BindOutputColumns(referencedRecord, cursor);
2984 each(referencedRecord);
2985 dm.BindOutputColumns(referencedRecord, cursor);
2993#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2994 constexpr auto ctx = std::meta::access_context::current();
2996 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2997 constexpr auto localctx = std::meta::access_context::current();
2998 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2999 using FieldType =
typename[:std::meta::type_of(members[I]):];
3000 callback.template operator()<I, FieldType>(record.[:members[I]:]);
3007template <
typename T>
3010 ZoneScopedN(
"DataMapper::Execute(string)");
3011 ZoneTextObject(sqlQueryString);
3017#include "../Async/DataMapperAsync.hpp"
Main API for mapping records to and from the database using high level C++ syntax.
DataMapper(DataMapper &&other) noexcept
Move constructor.
void Update(Record &record)
SqlConnection const & Connection() const noexcept
Returns the connection reference used by this data mapper.
bool IsModified(Record const &record) const noexcept
Async::Task< void > LoadRelationsAsync(Record &record)
Asynchronously loads record's relations.
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)
DataMapper & operator=(DataMapper &&other) noexcept
Move assignment operator.
static LIGHTWEIGHT_API DataMapper & AcquireThreadLocal()
Acquires a thread-local DataMapper instance that is safe for reuse within that thread.
void SetModifiedState(Record &record) noexcept
void UpdateAll(Records const &records)
Batch-updates a span of records with a single prepared statement.
Async::Task< std::optional< Record > > QuerySingleAsync(PrimaryKeyTypes... primaryKeys)
Async::Task< void > UpdateAsync(Record &record)
Asynchronously updates record's modified fields.
std::optional< T > Execute(std::string_view sqlQueryString)
DataMapper(std::optional< SqlConnectionString > connectionString)
Constructs a new data mapper, using the given connection string.
void CreateAll(Records const &records)
Batch-inserts a span of records with a single prepared statement.
std::size_t Delete(Record const &record)
RecordPrimaryKeyType< Record > CreateCopyOf(Record const &originalRecord)
Creates a copy of an existing record in the database.
DataMapper(SqlConnection &&connection)
Constructs a new data mapper, using the given connection.
void CreateTable()
Creates the table for the given record type.
SqlAllFieldsQueryBuilder< Record, QueryOptions > Query()
std::optional< Record > QuerySingle(PrimaryKeyTypes &&... primaryKeys)
Queries a single record (based on primary key) from the database.
SqlConnection & Connection() noexcept
Returns the mutable connection reference used by this data mapper.
void CreateTables()
Creates the tables for the given record types.
static std::string Inspect(Record const &record)
Constructs a human readable string representation of the given record.
RecordPrimaryKeyType< Record > CreateExplicit(Record const &record)
Creates a new record in the database.
std::vector< std::string > CreateTablesString(SqlServerType serverType)
Constructs a string list of SQL queries to create the tables for the given record types.
Async::Task< RecordPrimaryKeyType< Record > > CreateAsync(Record &record)
Asynchronously inserts record, updating its primary key in place.
void ConfigureRelationAutoLoading(Record &record)
SqlAllFieldsQueryBuilder< Record, QueryOptions, SqlQueryExecutionMode::Asynchronous > QueryAsync()
SqlQueryBuilder FromTable(std::string_view tableName)
Constructs an SQL query builder for the given table name.
ModifiedState
Enum to set the modified state of a record.
RecordPrimaryKeyType< Record > Create(Record &record)
Creates a new record in the database.
Async::Task< std::size_t > DeleteAsync(Record const &record)
Asynchronously deletes record.
std::vector< Record > Query(SqlSelectQueryBuilder::ComposedQuery const &selectQuery, InputParameters &&... inputParameters)
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 bool RoundTripsNarrowTextByteExact(SqlServerType serverType) noexcept
Whether serverType's driver round-trips narrow (SQL_C_CHAR) character data byte-exact,...
SqlQueryFormatter const & QueryFormatter() const noexcept
Retrieves a query formatter suitable for the SQL server being connected.
bool SupportsNativeRowArrayFetch() const noexcept
Whether this connection's ODBC driver supports native row-array fetching (SQL_ATTR_ROW_ARRAY_SIZE > 1...
LIGHTWEIGHT_FORCE_INLINE SqlCoreDataMapperQueryBuilder(DataMapper &dm, std::string fields) noexcept
Constructs a query builder with the given data mapper and field list.
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 SqlSelectQueryStarter Select() noexcept
LIGHTWEIGHT_API SqlInsertQueryBuilder Insert(std::vector< SqlVariant > *boundInputs=nullptr) noexcept
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
API for reading an SQL query result set.
LIGHTWEIGHT_FORCE_INLINE bool GetColumn(SQLUSMALLINT column, T *result) const
LIGHTWEIGHT_FORCE_INLINE size_t NumColumnsAffected() const
Retrieves the number of columns affected by the last query.
LIGHTWEIGHT_FORCE_INLINE size_t NumRowsAffected() const
Retrieves the number of rows affected by the last query.
LIGHTWEIGHT_FORCE_INLINE void BindOutputColumn(SQLUSMALLINT columnIndex, T *arg)
Binds a single output column at the given index to store fetched data.
LIGHTWEIGHT_FORCE_INLINE bool FetchRow()
Fetches the next row of the result set.
Query builder for building SELECT ... queries.
SqlSelectQueryBuilder & Build(Callable const &callable)
Builds the query using a callable.
LIGHTWEIGHT_API ComposedQuery First(size_t count=1)
Finalizes building the query as SELECT TOP n field names FROM ... query.
LIGHTWEIGHT_API SqlSelectQueryBuilder & Field(std::string_view const &fieldName)
Adds a single column to the SELECT clause.
High level API for (prepared) raw SQL statements.
LIGHTWEIGHT_API void Prepare(std::string_view query) &
LIGHTWEIGHT_API size_t LastInsertId(std::string_view tableName)
Retrieves the last insert ID of the given table.
LIGHTWEIGHT_API SqlConnection & Connection() noexcept
Retrieves the connection associated with this statement.
SqlResultCursor Execute(Args const &... args)
Binds the given arguments to the prepared statement and executes it.
SqlResultCursor ExecuteBatch(FirstColumnBatch const &firstColumnBatch, MoreColumnBatches const &... moreColumnBatches)
void BindInputParameter(SQLSMALLINT columnIndex, Arg const &arg)
Binds an input parameter to the prepared statement at the given column index.
LIGHTWEIGHT_API SqlResultCursor ExecuteDirect(std::string_view const &query, std::source_location location=std::source_location::current())
Executes the given query directly.
std::optional< T > ExecuteDirectScalar(std::string_view const &query, std::source_location location=std::source_location::current())
Derived & Where(ColumnName const &columnName, std::string_view binaryOp, T const &value)
Constructs or extends a WHERE clause to test for a binary operation.
Represents a record type that can be used with the DataMapper.
LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType< Record > GetPrimaryKeyField(Record const &record) noexcept
constexpr std::string_view FieldNameAt
Returns the SQL field name of the given field index in the record.
constexpr void EnumerateRecordMembers(Record &record, Callable &&callable)
Invokes callable as callable<I>(member) for each member of record.
T ValueType
The underlying value type of this field.
static constexpr auto IsOptional
Indicates if the field is optional, i.e., it can be NULL.
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.