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"
13#include "CompositeForeignKey.hpp"
16#include "HasManyThrough.hpp"
17#include "HasOneThrough.hpp"
18#include "QueryBuilders.hpp"
21#include <reflection-cpp/reflection.hpp>
42 template <
template <
typename>
class Allocator,
template <
typename,
typename>
class Container,
typename Object>
43 auto ToSharedPtrList(Container<Object, Allocator<Object>> container)
45 using SharedPtrRecord = std::shared_ptr<Object>;
46 auto sharedPtrContainer = Container<SharedPtrRecord, Allocator<SharedPtrRecord>> {};
47 for (
auto&
object: container)
48 sharedPtrContainer.emplace_back(std::make_shared<Object>(std::move(object)));
49 return sharedPtrContainer;
101 _stmt { _connection }
107 _connection { std::move(connection) },
108 _stmt { _connection }
113 explicit DataMapper(std::optional<SqlConnectionString> connectionString):
114 _connection { std::move(connectionString) },
115 _stmt { _connection }
124 _connection(std::move(other._connection)),
136 _connection = std::move(other._connection);
157#if defined(BUILD_TESTS)
159 [[nodiscard]]
SqlStatement& Statement(
this auto&& self)
noexcept
167 template <
typename Record>
168 static std::string
Inspect(Record
const& record);
171 template <
typename Record>
175 template <
typename FirstRecord,
typename... MoreRecords>
179 template <
typename Record>
183 template <
typename FirstRecord,
typename... MoreRecords>
194 template <DataMapperOptions QueryOptions = {},
typename Record>
195 RecordPrimaryKeyType<Record>
Create(Record& record);
204 template <
typename Record>
205 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
226 template <std::ranges::range Records>
237 template <DataMapperOptions QueryOptions = {},
typename Record>
238 [[nodiscard]] RecordPrimaryKeyType<Record>
CreateCopyOf(Record
const& originalRecord);
263 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
264 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
274 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... InputParameters>
275 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
306 template <
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
307 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
347 template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions = {},
typename... InputParameters>
348 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
381 template <
typename First,
typename Second,
typename... Rest, DataMapperOptions QueryOptions = {}>
382 requires DataMapperRecord<First> && DataMapperRecord<Second> && DataMapperRecords<Rest...>
383 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
399 template <
typename Record, DataMapperOptions QueryOptions = {}>
428 *
this, BuildFullyQualifiedFieldList<Record>());
449 template <
typename Record>
450 void Update(Record& record);
468 template <std::ranges::range Records>
478 template <
typename Record>
479 std::size_t
Delete(Record
const& record);
484 return _connection.
Query(tableName);
492 template <
typename Record>
493 bool IsModified(Record
const& record)
const noexcept;
508 template <ModifiedState state,
typename Record>
515 template <
typename Record>
525 template <
typename Record>
534 template <
typename T>
535 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
555 [[nodiscard]] Async::Task<RecordPrimaryKeyType<Record>>
CreateAsync(Record& record);
562 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
563 [[nodiscard]] Async::Task<std::optional<Record>>
QuerySingleAsync(PrimaryKeyTypes... primaryKeys);
566 template <
typename Record>
567 [[nodiscard]] Async::Task<void>
UpdateAsync(Record& record);
570 template <
typename Record>
571 [[nodiscard]] Async::Task<std::size_t>
DeleteAsync(Record
const& record);
574 template <
typename Record>
584 template <
typename Record>
585 [[nodiscard]]
static std::string BuildFullyQualifiedFieldList()
588 EnumerateRecordMembers<Record>([&fields]<
size_t I,
typename FieldType>() {
590 if constexpr (RecordColumnMember<FieldType>)
595 fields += RecordTableName<Record>;
597 fields += FieldNameAt<I, Record>;
610 template <
typename Record,
typename... Args>
611 std::optional<Record>
QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args);
613 template <
typename Record,
typename ValueType>
614 void SetId(Record& record, ValueType&&
id);
616 template <
typename Record,
size_t InitialOffset = 1>
617 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
619 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
620 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
622 template <
typename FieldType>
623 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
640 template <
typename FieldType>
641 std::shared_ptr<typename FieldType::ReferencedRecord> LoadCompositeForeignKeyRecord(
642 typename FieldType::OrderedValueType
const& keys);
648 template <
typename Record,
typename FieldType>
649 void LoadCompositeForeignKey(Record
const& record, FieldType& field);
651 template <
typename Record,
typename OtherRecord, auto InverseSelector>
652 void LoadHasMany(Record& record, HasMany<OtherRecord, InverseSelector>& field);
654 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
655 void LoadHasOneThrough(Record& record,
656 HasOneThrough<ReferencedRecord, ThroughRecord, OwnerSelector, ThroughSelector>& field);
658 template <
typename ReferencedRecord,
659 typename ThroughRecord,
662 auto ReferencedSelector>
663 void LoadHasManyThrough(Record& record,
664 HasManyThrough<ReferencedRecord, ThroughRecord, OwnerSelector, ReferencedSelector>& field);
666 template <
typename Record,
typename OtherRecord, auto InverseSelector,
typename Callable>
667 void CallOnHasMany(Record& record, Callable
const& callback);
669 template <
typename OwnerRecord,
typename OtherRecord, auto InverseSelector>
670 SqlSelectQueryBuilder BuildHasManySelectQuery();
672 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
673 SqlSelectQueryBuilder BuildHasOneThroughSelectQuery();
675 template <
typename ReferencedRecord,
676 typename ThroughRecord,
679 auto ReferencedSelector>
680 SqlSelectQueryBuilder BuildHasManyThroughSelectQuery();
682 template <
typename ReferencedRecord,
683 typename ThroughRecord,
686 auto ReferencedSelector,
688 void CallOnHasManyThrough(Record& record, Callable
const& callback);
690 template <
typename ReferencedRecord,
691 typename ThroughRecord,
694 auto ReferencedSelector,
697 void CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback);
699 template <
typename ReferencedRecord,
700 typename ThroughRecord,
703 auto ThroughSelector,
705 std::shared_ptr<ReferencedRecord> LoadHasOneThroughByPK(PKValue
const& pkValue);
707 enum class PrimaryKeySource : std::uint8_t
713 template <
typename Record>
714 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record
const& record);
716 template <PrimaryKeySource UsePkOverr
ide,
typename Record>
717 RecordPrimaryKeyType<Record> CreateInternal(
718 Record
const& record,
719 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
720 pkOverride = std::nullopt);
722 SqlConnection _connection;
730 template <
typename FieldType>
731 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
733 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
740 if constexpr (IsField<FieldType>)
742 if constexpr (detail::OneOf<
typename FieldType::ValueType,
748 || IsSqlDynamicString<typename FieldType::ValueType>
749 || IsSqlDynamicBinary<typename FieldType::ValueType>)
758 template <DataMapperRecord Record>
759 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
761 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
765 EnumerateRecordMembers<Record>([&result]<
size_t I,
typename Field>() {
766 if constexpr (IsField<Field>)
774 || IsSqlDynamicString<typename Field::ValueType>
775 || IsSqlDynamicBinary<typename Field::ValueType>)
785 template <
typename Record>
786 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
788 EnumerateRecordMembers(record, [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
789 if constexpr (IsField<Field>)
791 reader->BindOutputColumn(i++, &field.MutableValue());
793 else if constexpr (IsBelongsTo<Field>)
795 reader->BindOutputColumn(i++, &field.MutableValue());
797 else if constexpr (SqlOutputColumnBinder<Field>)
799 reader->BindOutputColumn(i++, &field);
804 template <
typename Record>
805 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
807 BindAllOutputColumnsWithOffset(reader, record, 1);
812 constexpr std::size_t kDefaultRowArrayFetchDepth = 1024;
817 template <std::
size_t I>
818 struct MutableFieldValueAccessor
820 template <
typename Record>
821 decltype(
auto)
operator()(Record& record)
const
823 return GetRecordMemberAt<I>(record).MutableValue();
829 template <
typename FieldType>
830 using RowWiseColumnValueType = std::remove_cvref_t<decltype(std::declval<FieldType&>().MutableValue())>;
834 template <
typename FieldType>
835 constexpr bool RowWiseIsColumn()
837 return IsField<FieldType> || IsBelongsTo<FieldType> || SqlOutputColumnBinder<FieldType>;
844 template <
typename FieldType>
845 constexpr bool RowWiseColumnAcceptable()
847 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
848 return SqlRowWiseFetchableColumn<RowWiseColumnValueType<FieldType>>;
849 else if constexpr (SqlOutputColumnBinder<FieldType>)
855 template <
typename Record, std::size_t... Is>
856 constexpr bool CanRowWiseFetchRecordImpl(std::index_sequence<Is...> )
861 return (
sizeof(Record) %
alignof(SQLLEN) == 0) && (RowWiseColumnAcceptable<RecordMemberTypeOf<Is, Record>>() && ...)
862 && (RowWiseIsColumn<RecordMemberTypeOf<Is, Record>>() || ...);
869 template <
typename Record>
870 constexpr bool CanRowWiseFetchRecord()
872 return CanRowWiseFetchRecordImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
877 template <std::
size_t I,
typename Record>
878 auto MakeOutputColumnAccessor()
880 using FieldType = RecordMemberTypeOf<I, Record>;
881 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
882 return std::tuple<MutableFieldValueAccessor<I>> {};
884 return std::tuple<> {};
890 template <
typename Record>
891 void ReadAllRowWise(SqlResultCursor& reader, std::vector<Record>* records)
893 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
895 [&](
auto const&... accessors) {
896 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
898 std::tuple_cat(MakeOutputColumnAccessor<Is, Record>()...));
899 }(std::make_index_sequence<RecordMemberCount<Record>> {});
905 template <
typename FieldType>
906 constexpr bool ColumnIsNarrowFixedString()
908 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
910 using V = RowWiseColumnValueType<FieldType>;
911 if constexpr (SqlIsStdOptional<V>)
912 return IsSqlFixedString<typename V::value_type>;
914 return IsSqlFixedString<V>;
920 template <
typename Record, std::size_t... Is>
921 constexpr bool RecordHasNarrowFixedStringColumnImpl(std::index_sequence<Is...> )
923 return (ColumnIsNarrowFixedString<RecordMemberTypeOf<Is, Record>>() || ...);
929 template <
typename Record>
930 constexpr bool RecordHasNarrowFixedStringColumn()
932 return RecordHasNarrowFixedStringColumnImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
939 template <
typename Record>
940 bool CanRowWiseFetchOn(SqlServerType serverType)
942 if constexpr (!CanRowWiseFetchRecord<Record>())
946 && (!RecordHasNarrowFixedStringColumn<Record>()
954 template <std::
size_t TupleIndex, std::
size_t I>
955 struct MutableTupleFieldAccessor
957 template <
typename TupleType>
958 decltype(
auto)
operator()(TupleType& row)
const
960 return GetRecordMemberAt<I>(std::get<TupleIndex>(row)).MutableValue();
964 template <
typename First,
typename Second, std::size_t... Fs, std::size_t... Ss>
965 constexpr bool CanRowWiseFetchTupleImpl(std::index_sequence<Fs...> ,
966 std::index_sequence<Ss...> )
968 return (
sizeof(std::tuple<First, Second>) %
alignof(SQLLEN) == 0)
969 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Fs, First>>() && ...)
970 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Ss, Second>>() && ...)
971 && ((RowWiseIsColumn<RecordMemberTypeOf<Fs, First>>() || ...)
972 || (RowWiseIsColumn<RecordMemberTypeOf<Ss, Second>>() || ...));
978 template <
typename First,
typename Second>
979 constexpr bool CanRowWiseFetchTuple()
981 return CanRowWiseFetchTupleImpl<First, Second>(std::make_index_sequence<RecordMemberCount<First>> {},
982 std::make_index_sequence<RecordMemberCount<Second>> {});
988 template <
typename First,
typename Second>
989 bool CanRowWiseFetchTupleOn(SqlServerType serverType)
991 if constexpr (!CanRowWiseFetchTuple<First, Second>())
995 && ((!RecordHasNarrowFixedStringColumn<First>() && !RecordHasNarrowFixedStringColumn<Second>())
1000 template <std::
size_t TupleIndex, std::
size_t I,
typename SubRecord>
1001 auto MakeTupleColumnAccessor()
1003 using FieldType = RecordMemberTypeOf<I, SubRecord>;
1004 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1005 return std::tuple<MutableTupleFieldAccessor<TupleIndex, I>> {};
1007 return std::tuple<> {};
1013 template <
typename First,
typename Second>
1014 void ReadAllRowWiseTuple(SqlResultCursor& reader, std::vector<std::tuple<First, Second>>* records)
1016 [&]<std::size_t... Fs, std::size_t... Ss>(std::index_sequence<Fs...>, std::index_sequence<Ss...>) {
1018 [&](
auto const&... accessors) {
1019 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
1021 std::tuple_cat(MakeTupleColumnAccessor<0, Fs, First>()..., MakeTupleColumnAccessor<1, Ss, Second>()...));
1022 }(std::make_index_sequence<RecordMemberCount<First>> {}, std::make_index_sequence<RecordMemberCount<Second>> {});
1028 template <
typename ElementMask,
typename Record>
1029 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
1031 EnumerateRecordMembers<ElementMask>(
1032 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
1041 static_assert(RecordColumnMember<Field> == (IsField<Field> || SqlGetColumnNativeType<Field>),
1042 "Record member is projected but not readable (or readable but not projected). "
1043 "A SqlDataBinder<T> used as a record member must provide both OutputColumn() "
1044 "and GetColumn().");
1045 if constexpr (IsField<Field>)
1049 field.MutableValue() =
1050 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
1052 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
1054 else if constexpr (SqlGetColumnNativeType<Field>)
1057 if constexpr (IsOptionalBelongsTo<Field>)
1058 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
1060 field = reader->GetColumn<Field>(indexFromQuery);
1065 template <
typename Record>
1066 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
1068 return GetAllColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record>(
1069 reader, record, indexFromQuery);
1072 template <
typename FirstRecord,
typename SecondRecord>
1074 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
1076 auto& [firstRecord, secondRecord] = record;
1081 GetAllColumns(reader, firstRecord, 0);
1082 GetAllColumns(reader, secondRecord,
static_cast<SQLUSMALLINT
>(RecordColumnCount<FirstRecord>));
1085 template <
typename Record>
1086 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
1088 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
1090 if (outputColumnsBound)
1091 BindAllOutputColumns(reader, record);
1093 if (!reader.FetchRow())
1096 if (!outputColumnsBound)
1097 GetAllColumns(reader, record);
1103template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1104template <
typename Finisher>
1105auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RunFinisher(Finisher finisher)
1107 if constexpr (Derived::QueryExecution == SqlQueryExecutionMode::Asynchronous)
1108 return Async::RunAsync(_dm.Connection().AsyncBackend(), std::move(finisher));
1113template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1115 DataMapper& dm, std::string fields)
noexcept:
1117 _formatter { dm.Connection().QueryFormatter() },
1118 _fields { std::move(fields) }
1120 this->_query.searchCondition.inputBindings = &_boundInputs;
1123template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1124size_t SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::CountImpl()
1126 auto stmt = SqlStatement { _dm.Connection() };
1127 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
1128 RecordTableName<Record>,
1129 this->_query.searchCondition.tableAlias,
1130 this->_query.searchCondition.tableJoins,
1131 this->_query.searchCondition.condition));
1132 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1133 if (reader.FetchRow())
1134 return reader.template GetColumn<size_t>(1);
1138template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1139bool SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::ExistImpl()
1141 auto stmt = SqlStatement { _dm.Connection() };
1143 auto const query = _formatter.SelectFirst(this->_query.distinct,
1145 RecordTableName<Record>,
1146 this->_query.searchCondition.tableAlias,
1147 this->_query.searchCondition.tableJoins,
1148 this->_query.searchCondition.condition,
1149 this->_query.orderBy,
1152 stmt.Prepare(query);
1153 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1158template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1159void SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::DeleteImpl()
1161 auto stmt = SqlStatement { _dm.Connection() };
1163 auto const query = _formatter.Delete(RecordTableName<Record>,
1164 this->_query.searchCondition.tableAlias,
1165 this->_query.searchCondition.tableJoins,
1166 this->_query.searchCondition.condition);
1168 stmt.Prepare(query);
1169 [[maybe_unused]]
auto cursor = stmt.ExecuteWithVariants(_boundInputs);
1172template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1173std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl()
1176 auto records = std::vector<Record> {};
1177 auto stmt = SqlStatement { _dm.Connection() };
1178 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1180 RecordTableName<Record>,
1181 this->_query.searchCondition.tableAlias,
1182 this->_query.searchCondition.tableJoins,
1183 this->_query.searchCondition.condition,
1184 this->_query.orderBy,
1185 this->_query.groupBy));
1186 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1187 if constexpr (DataMapperRecord<Record>)
1192 if constexpr (QueryOptions.loadRelations)
1194 for (
auto& record: records)
1196 _dm.ConfigureRelationAutoLoading(record);
1203template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1204template <auto Field>
1205#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1206 requires(is_aggregate_type(parent_of(Field)))
1208 requires std::is_member_object_pointer_v<
decltype(Field)>
1210auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<ReferencedFieldTypeOf<Field>>
1212 using value_type = ReferencedFieldTypeOf<Field>;
1213 auto result = std::vector<value_type> {};
1215 auto stmt = SqlStatement { _dm.Connection() };
1216 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1217 detail::FullyQualifiedNamesOf<Field>,
1218 RecordTableName<Record>,
1219 this->_query.searchCondition.tableAlias,
1220 this->_query.searchCondition.tableJoins,
1221 this->_query.searchCondition.condition,
1222 this->_query.orderBy,
1223 this->_query.groupBy));
1224 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1225 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
1228 auto& value = result.emplace_back();
1229 if (outputColumnsBound)
1230 reader.BindOutputColumn(1, &value);
1232 if (!reader.FetchRow())
1238 if (!outputColumnsBound)
1239 value = reader.template GetColumn<value_type>(1);
1245template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1246template <
auto... ReferencedFields>
1247 requires(
sizeof...(ReferencedFields) >= 2)
1248auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<Record>
1250 auto records = std::vector<Record> {};
1251 auto stmt = SqlStatement { _dm.Connection() };
1253 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1254 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1255 RecordTableName<Record>,
1256 this->_query.searchCondition.tableAlias,
1257 this->_query.searchCondition.tableJoins,
1258 this->_query.searchCondition.condition,
1259 this->_query.orderBy,
1260 this->_query.groupBy));
1262 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1263 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1266 auto& record = records.emplace_back();
1267 if (outputColumnsBound)
1268#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1269 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1271 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1273 if (!reader.FetchRow())
1278 if (!outputColumnsBound)
1280 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1281 detail::GetAllColumns<ElementMask>(reader, record);
1288template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1289std::optional<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl()
1291 std::optional<Record> record {};
1292 auto stmt = SqlStatement { _dm.Connection() };
1293 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1295 RecordTableName<Record>,
1296 this->_query.searchCondition.tableAlias,
1297 this->_query.searchCondition.tableJoins,
1298 this->_query.searchCondition.condition,
1299 this->_query.orderBy,
1301 Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record);
1302 if constexpr (QueryOptions.loadRelations)
1305 _dm.ConfigureRelationAutoLoading(record.value());
1310template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1311template <auto Field>
1312#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1313 requires(is_aggregate_type(parent_of(Field)))
1315 requires std::is_member_object_pointer_v<
decltype(Field)>
1317auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<ReferencedFieldTypeOf<Field>>
1319 auto constexpr count = 1;
1320 auto stmt = SqlStatement { _dm.Connection() };
1321 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1322 detail::FullyQualifiedNamesOf<Field>,
1323 RecordTableName<Record>,
1324 this->_query.searchCondition.tableAlias,
1325 this->_query.searchCondition.tableJoins,
1326 this->_query.searchCondition.condition,
1327 this->_query.orderBy,
1329 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1330 return reader.template GetColumn<ReferencedFieldTypeOf<Field>>(1);
1331 return std::nullopt;
1334template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1335template <
auto... ReferencedFields>
1336 requires(
sizeof...(ReferencedFields) >= 2)
1337auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<Record>
1339 auto optionalRecord = std::optional<Record> {};
1341 auto stmt = SqlStatement { _dm.Connection() };
1342 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1343 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1344 RecordTableName<Record>,
1345 this->_query.searchCondition.tableAlias,
1346 this->_query.searchCondition.tableJoins,
1347 this->_query.searchCondition.condition,
1348 this->_query.orderBy,
1351 auto& record = optionalRecord.emplace();
1352 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1353 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1354 if (outputColumnsBound)
1355#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1356 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1358 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1366 if (reader.FetchRow())
1368 if (!outputColumnsBound)
1370 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1371 detail::GetAllColumns<ElementMask>(reader, record);
1374 if constexpr (QueryOptions.loadRelations)
1375 _dm.ConfigureRelationAutoLoading(record);
1379 optionalRecord.reset();
1382 return optionalRecord;
1385template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1386std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1388 auto records = std::vector<Record> {};
1389 auto stmt = SqlStatement { _dm.Connection() };
1391 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1393 RecordTableName<Record>,
1394 this->_query.searchCondition.tableAlias,
1395 this->_query.searchCondition.tableJoins,
1396 this->_query.searchCondition.condition,
1397 this->_query.orderBy,
1399 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1401 if constexpr (QueryOptions.loadRelations)
1403 for (
auto& record: records)
1404 _dm.ConfigureRelationAutoLoading(record);
1409template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1410std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1412 auto records = std::vector<Record> {};
1413 auto stmt = SqlStatement { _dm.Connection() };
1414 records.reserve(limit);
1416 _formatter.SelectRange(this->_query.distinct,
1418 RecordTableName<Record>,
1419 this->_query.searchCondition.tableAlias,
1420 this->_query.searchCondition.tableJoins,
1421 this->_query.searchCondition.condition,
1422 !this->_query.orderBy.empty()
1423 ? this->_query.orderBy
1424 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1425 this->_query.groupBy,
1428 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1429 if constexpr (QueryOptions.loadRelations)
1431 for (
auto& record: records)
1432 _dm.ConfigureRelationAutoLoading(record);
1437template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1438template <
auto... ReferencedFields>
1439std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1441 auto records = std::vector<Record> {};
1442 auto stmt = SqlStatement { _dm.Connection() };
1443 records.reserve(limit);
1445 _formatter.SelectRange(this->_query.distinct,
1446 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1447 RecordTableName<Record>,
1448 this->_query.searchCondition.tableAlias,
1449 this->_query.searchCondition.tableJoins,
1450 this->_query.searchCondition.condition,
1451 !this->_query.orderBy.empty()
1452 ? this->_query.orderBy
1453 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1454 this->_query.groupBy,
1458 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1459 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1462 auto& record = records.emplace_back();
1463 if (outputColumnsBound)
1464#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1465 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1467 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1469 if (!reader.FetchRow())
1474 if (!outputColumnsBound)
1476 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1477 detail::GetAllColumns<ElementMask>(reader, record);
1481 if constexpr (QueryOptions.loadRelations)
1483 for (
auto& record: records)
1484 _dm.ConfigureRelationAutoLoading(record);
1490template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1491template <
auto... ReferencedFields>
1492[[nodiscard]] std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1494 auto records = std::vector<Record> {};
1495 auto stmt = SqlStatement { _dm.Connection() };
1497 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1498 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1499 RecordTableName<Record>,
1500 this->_query.searchCondition.tableAlias,
1501 this->_query.searchCondition.tableJoins,
1502 this->_query.searchCondition.condition,
1503 this->_query.orderBy,
1506 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1507 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1510 auto& record = records.emplace_back();
1511 if (outputColumnsBound)
1512#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1513 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1515 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1517 if (!reader.FetchRow())
1522 if (!outputColumnsBound)
1524 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1525 detail::GetAllColumns<ElementMask>(reader, record);
1529 if constexpr (QueryOptions.loadRelations)
1531 for (
auto& record: records)
1532 _dm.ConfigureRelationAutoLoading(record);
1538template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1539void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResults(SqlServerType sqlServerType,
1540 SqlResultCursor reader,
1541 std::vector<Record>* records)
1547 if constexpr (detail::CanRowWiseFetchRecord<Record>())
1549 if (detail::CanRowWiseFetchOn<Record>(sqlServerType))
1551 detail::ReadAllRowWise(reader, records);
1558 Record& record = records->emplace_back();
1559 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1561 records->pop_back();
1567template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1568void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResult(SqlServerType sqlServerType,
1569 SqlResultCursor reader,
1570 std::optional<Record>* optionalRecord)
1572 Record& record = optionalRecord->emplace();
1573 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1574 optionalRecord->reset();
1577template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1578void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution>::ReadResults(
1579 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1583 if constexpr (detail::CanRowWiseFetchTuple<FirstRecord, SecondRecord>())
1585 if (detail::CanRowWiseFetchTupleOn<FirstRecord, SecondRecord>(sqlServerType))
1587 detail::ReadAllRowWiseTuple<FirstRecord, SecondRecord>(reader, records);
1594 auto& record = records->emplace_back();
1595 auto& [firstRecord, secondRecord] = record;
1597 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1598 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1600 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1601 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1602 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1604 if (canSafelyBindAll)
1606 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1609 detail::BindAllOutputColumnsWithOffset(
1610 reader, secondRecord,
static_cast<SQLUSMALLINT
>(1 + RecordColumnCount<FirstRecord>));
1613 if (!reader.FetchRow())
1615 records->pop_back();
1619 if (!canSafelyBindAll)
1620 detail::GetAllColumns(reader, record);
1624template <
typename Record>
1630 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1636 if constexpr (Value::IsOptional)
1638 if (!value.Value().has_value())
1640 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1644 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1647 else if constexpr (IsBelongsTo<Value>)
1649 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1651 else if constexpr (std::same_as<typename Value::ValueType, char>)
1656 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1659 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>
1660 && !IsCompositeForeignKey<Value>)
1661 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1663 return "{\n" + std::move(str) +
"\n}";
1666template <
typename Record>
1672 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1673 detail::PopulateCreateTableBuilder<Record>(createTable);
1674 return migration.GetPlan().ToSql();
1677template <
typename FirstRecord,
typename... MoreRecords>
1680 std::vector<std::string> output;
1681 auto const append = [&output](
auto const& sql) {
1682 output.insert(output.end(), sql.begin(), sql.end());
1684 append(CreateTableString<FirstRecord>(serverType));
1685 (append(CreateTableString<MoreRecords>(serverType)), ...);
1689template <
typename Record>
1694 ZoneScopedN(
"DataMapper::CreateTable");
1695 ZoneTextObject(RecordTableName<Record>);
1697 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1698 for (
auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
1702template <
typename FirstRecord,
typename... MoreRecords>
1705 CreateTable<FirstRecord>();
1706 (CreateTable<MoreRecords>(), ...);
1709template <
typename Record>
1710std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
1717 static_assert(detail::AutoAssignPrimaryKeyFieldCount<Record> <= 1,
1718 "A record may declare at most one auto-assigned primary key member. Auto-assignment yields a "
1719 "single value that would be written into every key member, so a composite key cannot be "
1720 "generated - declare the key members without PrimaryKey::AutoAssign and set their values "
1721 "yourself before calling Create().");
1723 std::optional<RecordPrimaryKeyType<Record>> result;
1725 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1726 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1727 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1729 using ValueType = PrimaryKeyType::ValueType;
1730 if constexpr (std::same_as<ValueType, SqlGuid>)
1732 if (!primaryKeyField.Value())
1737 else if constexpr (
requires { ValueType {} + 1; })
1739 if (primaryKeyField.Value() == ValueType {})
1741 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1742 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1743 FieldNameAt<PrimaryKeyIndex, Record>,
1744 RecordTableName<Record>));
1745 result = maxId.value_or(ValueType {}) + 1;
1753template <DataMapper::PrimaryKeySource UsePkOverr
ide,
typename Record>
1754RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
1755 Record
const& record,
1756 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
1759 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1761 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1763#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1764 constexpr auto ctx = std::meta::access_context::current();
1765 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1767 using FieldType =
typename[:std::meta::type_of(el):];
1768 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1769 query.Set(FieldNameOf<el>, SqlWildcard);
1773 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1774 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1780#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1782 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1784 using FieldType =
typename[:std::meta::type_of(el):];
1785 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1787 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1794 Reflection::CallOnMembers(record,
1795 [
this, &pkOverride, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
1796 Name
const& name, FieldType
const& field)
mutable {
1797 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1799 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1806 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1808 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1809 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1810 else if constexpr (HasPrimaryKey<Record>)
1812 if constexpr (UsePkOverride == PrimaryKeySource::Override)
1815 return RecordPrimaryKeyOf(record).Value();
1821template <
typename Record>
1825 return CreateInternal<PrimaryKeySource::Record>(record);
1834 template <
typename FieldType>
1835 constexpr bool IsBatchInsertColumn = SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>;
1838 template <
typename FieldType>
1842 template <
typename FieldType>
1843 constexpr bool IsBatchUpdateWhereColumn = IsPrimaryKey<FieldType>;
1847 template <std::
size_t I>
1848 struct FieldValueAccessor
1850 template <
typename Record>
1851 decltype(
auto)
operator()(Record
const& record)
const
1853 return GetRecordMemberAt<I>(record).Value();
1859 template <std::
size_t I,
typename Record>
1860 auto MakeCreateColumnAccessor()
1862 using FieldType = RecordMemberTypeOf<I, Record>;
1863 if constexpr (IsBatchInsertColumn<FieldType>)
1864 return std::tuple<FieldValueAccessor<I>> {};
1866 return std::tuple<> {};
1870 template <std::
size_t I,
typename Record>
1871 auto MakeUpdateSetAccessor()
1873 using FieldType = RecordMemberTypeOf<I, Record>;
1874 if constexpr (IsBatchUpdateSetColumn<FieldType>)
1875 return std::tuple<FieldValueAccessor<I>> {};
1877 return std::tuple<> {};
1881 template <std::
size_t I,
typename Record>
1882 auto MakeUpdateWhereAccessor()
1884 using FieldType = RecordMemberTypeOf<I, Record>;
1885 if constexpr (IsBatchUpdateWhereColumn<FieldType>)
1886 return std::tuple<FieldValueAccessor<I>> {};
1888 return std::tuple<> {};
1892template <std::ranges::range Records>
1895 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
1896 "CreateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
1897 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
1898 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
1901 ZoneScopedN(
"DataMapper::CreateAll");
1902 ZoneTextObject(RecordTableName<Record>);
1904 if (std::ranges::empty(records))
1908 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1909 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
1910 if constexpr (detail::IsBatchInsertColumn<FieldType>)
1911 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1916 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
1917 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
1918 std::tuple_cat(detail::MakeCreateColumnAccessor<Is, Record>()...));
1919 }(std::make_index_sequence<RecordMemberCount<Record>> {});
1922template <DataMapperOptions QueryOptions,
typename Record>
1926 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
1928 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
1930 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
1932 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1933 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
1935 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
1938template <DataMapperOptions QueryOptions,
typename Record>
1941 static_assert(!std::is_const_v<Record>);
1944 ZoneScopedN(
"DataMapper::Create");
1945 ZoneTextObject(RecordTableName<Record>);
1947 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
1949 SetId(record, *generatedKey);
1951 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
1953 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1956 SetModifiedState<ModifiedState::NotModified>(record);
1958 if constexpr (QueryOptions.loadRelations)
1961 if constexpr (HasPrimaryKey<Record>)
1965template <
typename Record>
1970 bool modified =
false;
1972#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1973 auto constexpr ctx = std::meta::access_context::current();
1974 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1976 if constexpr (
requires { record.[:el:].IsModified(); })
1978 modified = modified || record.[:el:].IsModified();
1982 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1983 if constexpr (
requires { field.IsModified(); })
1985 modified = modified || field.IsModified();
1993template <
typename Record>
1998 ZoneScopedN(
"DataMapper::Update");
1999 ZoneTextObject(RecordTableName<Record>);
2001 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2003#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2004 auto constexpr ctx = std::meta::access_context::current();
2005 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2007 using FieldType =
typename[:std::meta::type_of(el):];
2010 if (record.[:el:].IsModified())
2011 query.Set(FieldNameOf<el>, SqlWildcard);
2012 if constexpr (IsPrimaryKey<FieldType>)
2013 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2024 if (field.IsModified())
2025 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2026 if constexpr (IsPrimaryKey<MemberType>)
2027 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2035#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2036 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2038 using FieldType =
typename[:std::meta::type_of(el):];
2042 if (record.[:el:].IsModified())
2049 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2051 using FieldType =
typename[:std::meta::type_of(el):];
2054 if constexpr (FieldType::IsPrimaryKey)
2066 if (field.IsModified())
2073 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2078 [[maybe_unused]]
auto cursor = _stmt.
Execute();
2080 SetModifiedState<ModifiedState::NotModified>(record);
2083template <std::ranges::range Records>
2086 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
2087 "UpdateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
2088 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
2089 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
2091 static_assert(HasPrimaryKey<Record>,
"UpdateAll requires a record type with a primary key");
2093 ZoneScopedN(
"DataMapper::UpdateAll");
2094 ZoneTextObject(RecordTableName<Record>);
2096 if (std::ranges::empty(records))
2100 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2101 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2102 if constexpr (detail::IsBatchUpdateSetColumn<FieldType>)
2103 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2105 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2106 if constexpr (detail::IsBatchUpdateWhereColumn<FieldType>)
2107 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2112 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
2113 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
2114 std::tuple_cat(detail::MakeUpdateSetAccessor<Is, Record>()...,
2115 detail::MakeUpdateWhereAccessor<Is, Record>()...));
2116 }(std::make_index_sequence<RecordMemberCount<Record>> {});
2119template <
typename Record>
2124 ZoneScopedN(
"DataMapper::Delete");
2125 ZoneTextObject(RecordTableName<Record>);
2127 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
2129#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2130 auto constexpr ctx = std::meta::access_context::current();
2131 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2133 using FieldType =
typename[:std::meta::type_of(el):];
2136 if constexpr (FieldType::IsPrimaryKey)
2137 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2141 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2142 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2148#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2150 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2152 using FieldType =
typename[:std::meta::type_of(el):];
2155 if constexpr (FieldType::IsPrimaryKey)
2164 [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
2165 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2170 auto cursor = _stmt.
Execute();
2175template <
typename Record,
DataMapperOptions QueryOptions,
typename... PrimaryKeyTypes>
2180 ZoneScopedN(
"DataMapper::QuerySingle(PK)");
2181 ZoneTextObject(RecordTableName<Record>);
2187 auto selectStarter = _connection.
Query(RecordTableName<Record>).
Select();
2191 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2194 if (queryBuilder ==
nullptr)
2195 queryBuilder = &selectStarter.
Field(FieldNameAt<I, Record>);
2197 queryBuilder->
Field(FieldNameAt<I, Record>);
2201 if constexpr (FieldType::IsPrimaryKey)
2202 std::ignore = queryBuilder->
Where(FieldNameAt<I, Record>, SqlWildcard);
2208 auto reader = _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
2214 auto resultRecord = std::optional<Record> { Record {} };
2217 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2219 if constexpr (QueryOptions.loadRelations)
2224 resultRecord.reset();
2227 return resultRecord;
2230template <
typename Record,
typename... Args>
2235 ZoneScopedN(
"DataMapper::QuerySingle(Builder)");
2236 ZoneTextObject(RecordTableName<Record>);
2239 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2243 auto const composedSql = selectQuery.
First().ToSql();
2244 ZoneTextObject(composedSql);
2246 auto reader = _stmt.
Execute(std::forward<Args>(args)...);
2248 auto resultRecord = std::optional<Record> { Record {} };
2250 return std::nullopt;
2253 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2255 return resultRecord;
2261template <
typename Record, DataMapperOptions QueryOptions,
typename... InputParameters>
2263 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
2265 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
2267 ZoneScopedN(
"DataMapper::Query(ComposedQuery)");
2268 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
2271template <
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2272std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
2274 ZoneScopedN(
"DataMapper::Query(string)");
2275 ZoneTextObject(sqlQueryString);
2277 auto result = std::vector<Record> {};
2278 if constexpr (std::same_as<Record, SqlVariantRow>)
2280 _stmt.
Prepare(sqlQueryString);
2285 auto& record = result.emplace_back();
2286 record.reserve(numResultColumns);
2287 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
2295 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2297 _stmt.
Prepare(sqlQueryString);
2298 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2302 auto& record = result.emplace_back();
2304 if (canSafelyBindOutputColumns)
2305 BindOutputColumns(record, reader);
2307 if (!reader.FetchRow())
2310 if (!canSafelyBindOutputColumns)
2311 detail::GetAllColumns(reader, record);
2317 for (
auto& record: result)
2319 SetModifiedState<ModifiedState::NotModified>(record);
2320 if constexpr (QueryOptions.loadRelations)
2328template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
2330std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
2332 using value_type = std::tuple<First, Second, Rest...>;
2333 auto result = std::vector<value_type> {};
2335 ZoneScopedN(
"DataMapper::Query(ComposedQuery -> tuple)");
2336 auto const tupleSql = selectQuery.ToSql();
2337 ZoneTextObject(tupleSql);
2339 auto reader = _stmt.
Execute();
2344 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
2347 if constexpr (I > 0)
2349 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
2350 ((Indices < I ? (offset += RecordColumnCount<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
2351 }(std::make_index_sequence<I> {});
2356 auto const BindElements = [&](
auto& record) {
2357 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2358 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2359 auto& element = std::get<I>(record);
2360 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2361 this->BindOutputColumns<TupleElement, offset>(element, reader);
2365 auto const GetElements = [&](
auto& record) {
2366 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2367 auto& element = std::get<I>(record);
2368 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2369 detail::GetAllColumns(reader, element, offset - 1);
2373 bool const canSafelyBindOutputColumns = [&]() {
2375 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2376 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2384 auto& record = result.emplace_back();
2386 if (canSafelyBindOutputColumns)
2387 BindElements(record);
2389 if (!reader.FetchRow())
2392 if (!canSafelyBindOutputColumns)
2393 GetElements(record);
2399 for (
auto& record: result)
2401 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2402 auto& element = std::get<I>(record);
2403 SetModifiedState<ModifiedState::NotModified>(element);
2404 if constexpr (QueryOptions.loadRelations)
2414template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2416 InputParameters&&... inputParameters)
2420 ZoneScopedN(
"DataMapper::Query(ComposedQuery, ElementMask)");
2421 auto const maskedSql = selectQuery.ToSql();
2422 ZoneTextObject(maskedSql);
2425 auto records = std::vector<Record> {};
2428 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2430 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2434 auto& record = records.emplace_back();
2436 if (canSafelyBindOutputColumns)
2437 BindOutputColumns<ElementMask>(record, reader);
2439 if (!reader.FetchRow())
2442 if (!canSafelyBindOutputColumns)
2443 detail::GetAllColumns<ElementMask>(reader, record);
2449 for (
auto& record: records)
2451 SetModifiedState<ModifiedState::NotModified>(record);
2452 if constexpr (QueryOptions.loadRelations)
2459template <DataMapper::ModifiedState state,
typename Record>
2462 static_assert(!std::is_const_v<Record>);
2466 if constexpr (
requires { field.SetModified(
false); })
2468 if constexpr (state == ModifiedState::Modified)
2469 field.SetModified(
true);
2471 field.SetModified(
false);
2476template <
typename Record,
typename Callable>
2477inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
2482 if constexpr (IsField<FieldType>)
2484 if constexpr (FieldType::IsPrimaryKey)
2486 return callable.template operator()<I, FieldType>(field);
2492template <
typename Record,
typename Callable>
2493inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
2495 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2497 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2498 if constexpr (IsField<FieldType>)
2500 if constexpr (FieldType::IsPrimaryKey)
2502 return callable.template operator()<I, FieldType>();
2508template <
typename Record,
typename Callable>
2509inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
2511 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2513 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2514 if constexpr (IsBelongsTo<FieldType>)
2516 return callable.template operator()<I, FieldType>();
2521template <
typename FieldType>
2522std::shared_ptr<typename FieldType::ReferencedRecord> DataMapper::LoadCompositeForeignKeyRecord(
2523 typename FieldType::OrderedValueType
const& keys)
2525 using ReferencedRecord =
typename FieldType::ReferencedRecord;
2528 std::apply([
this](
auto const&... key) {
return this->
template QuerySingle<ReferencedRecord>(key...); }, keys);
2531 return std::make_shared<ReferencedRecord>(std::move(*loaded));
2534template <
typename Record,
typename FieldType>
2535void DataMapper::LoadCompositeForeignKey(Record
const& record, FieldType& field)
2537 using ReferencedRecord =
typename FieldType::ReferencedRecord;
2539 ZoneScopedN(
"DataMapper::LoadCompositeForeignKey");
2540 ZoneTextObject(RecordTableName<ReferencedRecord>);
2546 auto loaded = LoadCompositeForeignKeyRecord<FieldType>(FieldType::OrderedValuesOf(record));
2554 std::format(
"Loading composite foreign key failed for {}", RecordTableName<ReferencedRecord>));
2558 field.EmplaceRecord(std::move(loaded));
2561template <
typename FieldType>
2562std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
2564 using ReferencedRecord = FieldType::ReferencedRecord;
2566 ZoneScopedN(
"DataMapper::LoadBelongsTo");
2567 ZoneTextObject(RecordTableName<ReferencedRecord>);
2569 std::optional<ReferencedRecord> record { std::nullopt };
2572 if constexpr (FieldType::IsOptional)
2573 if (!value.has_value())
2576#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2577 auto constexpr ctx = std::meta::access_context::current();
2578 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
2580 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
2581 if constexpr (IsField<BelongsToFieldType>)
2582 if constexpr (BelongsToFieldType::IsPrimaryKey)
2584 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
2585 record = std::move(result);
2588 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
2592 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
2593 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
2594 record = std::move(result);
2597 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
2603template <
typename Record,
typename OtherRecord, auto InverseSelector,
typename Callable>
2604void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
2606 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2607 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2609 using FieldType = HasMany<OtherRecord, InverseSelector>;
2610 using ReferencedRecord = FieldType::ReferencedRecord;
2612 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2613 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2615 .Build([&](
auto& query) {
2616 EnumerateRecordMembers<ReferencedRecord>(
2617 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2618 if constexpr (FieldWithStorage<ReferencedFieldType>)
2620 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
2624 .Where(InverseBelongsToFieldNameOf<Record, ReferencedRecord, InverseSelector>, SqlWildcard)
2625 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
2626 callback(query, primaryKeyField);
2630template <
typename OwnerRecord,
typename OtherRecord, auto InverseSelector>
2631SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
2633 return _connection.
Query(RecordTableName<OtherRecord>)
2635 .
Build([](
auto& q) {
2636 EnumerateRecordMembers<OtherRecord>([&]<
size_t I,
typename F>() {
2637 if constexpr (FieldWithStorage<F>)
2638 q.Field(FieldNameAt<I, OtherRecord>);
2641 .Where(InverseBelongsToFieldNameOf<OwnerRecord, OtherRecord, InverseSelector>, SqlWildcard)
2642 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
2645template <
typename Record,
typename OtherRecord, auto InverseSelector>
2646void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord, InverseSelector>& field)
2648 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2649 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2651 ZoneScopedN(
"DataMapper::LoadHasMany");
2652 ZoneTextObject(RecordTableName<OtherRecord>);
2654 CallOnHasMany<Record, OtherRecord, InverseSelector>(
2655 record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
2656 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
2660template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
2661SqlSelectQueryBuilder DataMapper::BuildHasOneThroughSelectQuery()
2663 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2664 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2667 constexpr size_t ThroughToOwnerIndex = InverseBelongsToIndexOf<Record, ThroughRecord, OwnerSelector>;
2670 constexpr size_t ReferencedToThroughIndex = InverseBelongsToIndexOf<ThroughRecord, ReferencedRecord, ThroughSelector>;
2675 return _connection.
Query(RecordTableName<ReferencedRecord>)
2677 .
Build([&](
auto& query) {
2678 EnumerateRecordMembers<ReferencedRecord>([&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2679 if constexpr (FieldWithStorage<ReferencedFieldType>)
2681 query.Field(SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2682 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2686 .InnerJoin(RecordTableName<ThroughRecord>,
2687 FieldNameAt<RecordPrimaryKeyIndex<ThroughRecord>, ThroughRecord>,
2688 FieldNameAt<ReferencedToThroughIndex, ReferencedRecord>)
2690 SqlQualifiedTableColumnName {
2691 RecordTableName<ThroughRecord>,
2692 FieldNameAt<ThroughToOwnerIndex, ThroughRecord>,
2697template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
2698void DataMapper::LoadHasOneThrough(Record& record,
2699 HasOneThrough<ReferencedRecord, ThroughRecord, OwnerSelector, ThroughSelector>& field)
2701 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2702 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2704 ZoneScopedN(
"DataMapper::LoadHasOneThrough");
2705 ZoneTextObject(RecordTableName<ReferencedRecord>);
2707 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2709 BuildHasOneThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ThroughSelector>();
2710 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
2711 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
2715template <
typename ReferencedRecord,
2716 typename ThroughRecord,
2719 auto ThroughSelector,
2721std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue
const& pkValue)
2723 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2725 auto query = BuildHasOneThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ThroughSelector>();
2727 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
2728 return std::make_shared<ReferencedRecord>(std::move(*link));
2733template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ReferencedSelector>
2734SqlSelectQueryBuilder DataMapper::BuildHasManyThroughSelectQuery()
2736 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2737 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2740 constexpr size_t ThroughToOwnerIndex = InverseBelongsToIndexOf<Record, ThroughRecord, OwnerSelector>;
2743 constexpr size_t ThroughToReferencedIndex = InverseBelongsToIndexOf<ReferencedRecord, ThroughRecord, ReferencedSelector>;
2745 return _connection.
Query(RecordTableName<ReferencedRecord>)
2747 .
Build([&](
auto& query) {
2748 EnumerateRecordMembers<ReferencedRecord>([&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2749 if constexpr (FieldWithStorage<ReferencedFieldType>)
2751 query.Field(SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2752 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2756 .InnerJoin(RecordTableName<ThroughRecord>,
2757 FieldNameAt<ThroughToReferencedIndex, ThroughRecord>,
2758 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2759 FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord> })
2761 SqlQualifiedTableColumnName {
2762 RecordTableName<ThroughRecord>,
2763 FieldNameAt<ThroughToOwnerIndex, ThroughRecord>,
2768template <
typename ReferencedRecord,
2769 typename ThroughRecord,
2772 auto ReferencedSelector,
2774void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
2776 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2778 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2780 BuildHasManyThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>();
2781 callback(query, primaryKeyField);
2785template <
typename ReferencedRecord,
2786 typename ThroughRecord,
2789 auto ReferencedSelector,
2792void DataMapper::CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback)
2794 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2797 BuildHasManyThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>();
2798 callback(query, pkValue);
2801template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ReferencedSelector>
2802void DataMapper::LoadHasManyThrough(
2803 Record& record, HasManyThrough<ReferencedRecord, ThroughRecord, OwnerSelector, ReferencedSelector>& field)
2805 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2807 ZoneScopedN(
"DataMapper::LoadHasManyThrough");
2808 ZoneTextObject(RecordTableName<ReferencedRecord>);
2810 CallOnHasManyThrough<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>(
2811 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2812 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2816template <
typename Record>
2819 static_assert(!std::is_const_v<Record>);
2822 ZoneScopedN(
"DataMapper::LoadRelations");
2823 ZoneTextObject(RecordTableName<Record>);
2825#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2826 constexpr auto ctx = std::meta::access_context::current();
2827 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2829 using FieldType =
typename[:std::meta::type_of(el):];
2830 if constexpr (IsBelongsTo<FieldType>)
2832 auto& field = record.[:el:];
2833 field.AdoptFetchedRecord(LoadBelongsTo<FieldType>(field.Value()));
2835 else if constexpr (IsCompositeForeignKey<FieldType>)
2837 LoadCompositeForeignKey(record, record.[:el:]);
2839 else if constexpr (IsHasMany<FieldType>)
2841 LoadHasMany(record, record.[:el:]);
2843 else if constexpr (IsHasOneThrough<FieldType>)
2845 LoadHasOneThrough(record, record.[:el:]);
2847 else if constexpr (IsHasManyThrough<FieldType>)
2849 LoadHasManyThrough(record, record.[:el:]);
2854 if constexpr (IsBelongsTo<FieldType>)
2856 field.AdoptFetchedRecord(LoadBelongsTo<FieldType>(field.Value()));
2858 else if constexpr (IsCompositeForeignKey<FieldType>)
2860 LoadCompositeForeignKey(record, field);
2862 else if constexpr (IsHasMany<FieldType>)
2864 LoadHasMany(record, field);
2866 else if constexpr (IsHasOneThrough<FieldType>)
2868 LoadHasOneThrough(record, field);
2870 else if constexpr (IsHasManyThrough<FieldType>)
2872 LoadHasManyThrough(record, field);
2879template <
typename Record,
typename ValueType>
2880inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2885#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2887 auto constexpr ctx = std::meta::access_context::current();
2888 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2890 using FieldType =
typename[:std::meta::type_of(el):];
2891 if constexpr (IsField<FieldType>)
2893 if constexpr (FieldType::IsPrimaryKey)
2895 record.[:el:] = std::forward<ValueType>(
id);
2901 if constexpr (IsField<FieldType>)
2903 if constexpr (FieldType::IsPrimaryKey)
2905 field = std::forward<FieldType>(
id);
2913template <
typename Record,
size_t InitialOffset>
2914inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2917 return BindOutputColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record, InitialOffset>(record,
2921template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2922Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2925 static_assert(!std::is_const_v<Record>);
2927#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2928 auto constexpr ctx = std::meta::access_context::current();
2929 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2930 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2932 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2933 using FieldType =
typename[:std::meta::type_of(el):];
2934 if constexpr (IsField<FieldType>)
2938 else if constexpr (SqlOutputColumnBinder<FieldType>)
2944 EnumerateRecordMembers<ElementMask>(
2945 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2946 if constexpr (IsField<Field>)
2950 else if constexpr (SqlOutputColumnBinder<Field>)
2959template <
typename Record>
2965 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2966 if constexpr (IsBelongsTo<FieldType>)
2968 field.SetAutoLoader(
typename FieldType::Loader {
2969 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2971 return dm.LoadBelongsTo<FieldType>(value);
2975 if constexpr (IsCompositeForeignKey<FieldType>)
2977 using ReferencedRecord =
typename FieldType::ReferencedRecord;
2993 field.SetAutoLoader(
typename FieldType::Loader {
2994 .loadReference = [keys = FieldType::OrderedValuesOf(record)]() -> std::shared_ptr<ReferencedRecord> {
2996 return dm.LoadCompositeForeignKeyRecord<FieldType>(keys);
3000 if constexpr (IsHasMany<FieldType>)
3002 if constexpr (HasPrimaryKey<Record>)
3004 using ReferencedRecord = FieldType::ReferencedRecord;
3009 .count = [pkValue]() ->
size_t {
3012 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3013 dm._stmt.
Prepare(selectQuery.Count());
3020 .all = [pkValue]() -> FieldType::ReferencedRecordList {
3023 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3024 return detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pkValue));
3027 [pkValue](
auto const& each) {
3030 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3032 stmt.Prepare(selectQuery.All());
3033 auto cursor = stmt.Execute(pkValue);
3035 auto referencedRecord = ReferencedRecord {};
3036 dm.BindOutputColumns(referencedRecord, cursor);
3041 each(referencedRecord);
3049 referencedRecord = ReferencedRecord {};
3050 dm.BindOutputColumns(referencedRecord, cursor);
3057 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
3059 using ReferencedRecord = FieldType::ReferencedRecord;
3060 using ThroughRecord = FieldType::ThroughRecord;
3062 hasOneThrough = field;
3066 .loadReference = [pkValue]() -> std::shared_ptr<ReferencedRecord> {
3068 return dm.LoadHasOneThroughByPK<ReferencedRecord,
3071 FieldType::OwnerSelector,
3072 FieldType::ThroughSelector>(pkValue);
3076 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
3078 using ReferencedRecord = FieldType::ReferencedRecord;
3079 using ThroughRecord = FieldType::ThroughRecord;
3081 hasManyThrough = field;
3085 .count = [pkValue]() ->
size_t {
3089 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3092 FieldType::OwnerSelector,
3093 FieldType::ReferencedSelector>(
3095 dm._stmt.
Prepare(selectQuery.Count());
3102 .all = [pkValue]() -> FieldType::ReferencedRecordList {
3105 typename FieldType::ReferencedRecordList result;
3106 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3109 FieldType::OwnerSelector,
3110 FieldType::ReferencedSelector>(
3112 result = detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pk));
3117 [pkValue](
auto const& each) {
3120 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3123 FieldType::OwnerSelector,
3124 FieldType::ReferencedSelector>(
3127 stmt.Prepare(selectQuery.All());
3128 auto cursor = stmt.Execute(pk);
3129 auto referencedRecord = ReferencedRecord {};
3130 dm.BindOutputColumns(referencedRecord, cursor);
3135 each(referencedRecord);
3141 referencedRecord = ReferencedRecord {};
3142 dm.BindOutputColumns(referencedRecord, cursor);
3151#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3152 constexpr auto ctx = std::meta::access_context::current();
3154 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
3155 constexpr auto localctx = std::meta::access_context::current();
3156 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
3157 using FieldType =
typename[:std::meta::type_of(members[I]):];
3158 callback.template operator()<I, FieldType>(record.[:members[I]:]);
3165template <
typename T>
3168 ZoneScopedN(
"DataMapper::Execute(string)");
3169 ZoneTextObject(sqlQueryString);
3175#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.
Requires that T maps onto a column of its record's table.
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.
detail::RecordMemberTypeOfDispatch< I, std::remove_cvref_t< Record >, HasDescription< Record > >::type RecordMemberTypeOf
Type of the member at index I — from the descriptor if present, else via reflection.
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.