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>());
456 template <
typename Record>
457 requires HasPrimaryKey<Record>
458 void Update(Record& record);
476 template <std::ranges::range Records>
486 template <
typename Record>
487 std::size_t
Delete(Record
const& record);
492 return _connection.
Query(tableName);
500 template <
typename Record>
501 bool IsModified(Record
const& record)
const noexcept;
516 template <ModifiedState state,
typename Record>
523 template <
typename Record>
533 template <
typename Record>
542 template <
typename T>
543 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
563 [[nodiscard]] Async::Task<RecordPrimaryKeyType<Record>>
CreateAsync(Record& record);
570 template <
typename Record, DataMapperOptions QueryOptions = {},
typename... PrimaryKeyTypes>
571 [[nodiscard]] Async::Task<std::optional<Record>>
QuerySingleAsync(PrimaryKeyTypes... primaryKeys);
574 template <
typename Record>
575 [[nodiscard]] Async::Task<void>
UpdateAsync(Record& record);
578 template <
typename Record>
579 [[nodiscard]] Async::Task<std::size_t>
DeleteAsync(Record
const& record);
582 template <
typename Record>
592 template <
typename Record>
593 [[nodiscard]]
static std::string BuildFullyQualifiedFieldList()
596 EnumerateRecordMembers<Record>([&fields]<
size_t I,
typename FieldType>() {
598 if constexpr (RecordColumnMember<FieldType>)
603 fields += RecordTableName<Record>;
605 fields += FieldNameAt<I, Record>;
618 template <
typename Record,
typename... Args>
619 std::optional<Record>
QuerySingle(SqlSelectQueryBuilder selectQuery, Args&&... args);
621 template <
typename Record,
typename ValueType>
622 void SetId(Record& record, ValueType&&
id);
624 template <
typename Record,
size_t InitialOffset = 1>
625 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
627 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
628 Record& BindOutputColumns(Record& record, SqlResultCursor& cursor);
630 template <
typename FieldType>
631 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
648 template <
typename FieldType>
649 std::shared_ptr<typename FieldType::ReferencedRecord> LoadCompositeForeignKeyRecord(
650 typename FieldType::OrderedValueType
const& keys);
656 template <
typename Record,
typename FieldType>
657 void LoadCompositeForeignKey(Record
const& record, FieldType& field);
659 template <
typename Record,
typename OtherRecord, auto InverseSelector>
660 void LoadHasMany(Record& record, HasMany<OtherRecord, InverseSelector>& field);
662 template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ThroughSelector>
663 void LoadHasOneThrough(Record& record,
664 HasOneThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ThroughSelector>& field);
666 template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ReferencedSelector>
667 void LoadHasManyThrough(Record& record,
668 HasManyThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ReferencedSelector>& field);
670 template <
typename Record,
typename OtherRecord, auto InverseSelector,
typename Callable>
671 void CallOnHasMany(Record& record, Callable
const& callback);
673 template <
typename OwnerRecord,
typename OtherRecord, auto InverseSelector>
674 SqlSelectQueryBuilder BuildHasManySelectQuery();
676 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
677 SqlSelectQueryBuilder BuildHasOneThroughSelectQuery();
679 template <
typename ReferencedRecord,
680 typename ThroughRecord,
683 auto ReferencedSelector>
684 SqlSelectQueryBuilder BuildHasManyThroughSelectQuery();
686 template <
typename ReferencedRecord,
687 typename ThroughRecord,
690 auto ReferencedSelector,
692 void CallOnHasManyThrough(Record& record, Callable
const& callback);
694 template <
typename ReferencedRecord,
695 typename ThroughRecord,
698 auto ReferencedSelector,
701 void CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback);
703 template <
typename ReferencedRecord,
704 typename ThroughRecord,
707 auto ThroughSelector,
709 std::shared_ptr<ReferencedRecord> LoadHasOneThroughByPK(PKValue
const& pkValue);
711 enum class PrimaryKeySource : std::uint8_t
717 template <
typename Record>
718 std::optional<RecordPrimaryKeyType<Record>> GenerateAutoAssignPrimaryKey(Record
const& record);
720 template <PrimaryKeySource UsePkOverr
ide,
typename Record>
721 RecordPrimaryKeyType<Record> CreateInternal(
722 Record
const& record,
723 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
724 pkOverride = std::nullopt);
726 SqlConnection _connection;
739 template <
typename T>
740 inline constexpr bool IsGrowableColumnType =
741 detail::OneOf<T, std::string, std::wstring, std::u16string, std::u32string, SqlBinary> || IsSqlDynamicString<T>
742 || IsSqlDynamicBinary<T>;
757 template <
typename T>
758 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
760 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
766 return !IsGrowableColumnType<T>;
777 template <DataMapperRecord Record>
778 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
780 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
784 EnumerateRecordMembers<Record>([&result]<
size_t I,
typename Field>() {
785 if constexpr (IsField<Field>)
786 if constexpr (IsGrowableColumnType<typename Field::ValueType>)
793 template <
typename Record>
794 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLUSMALLINT startOffset)
796 EnumerateRecordMembers(record, [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
797 if constexpr (IsField<Field>)
799 reader->BindOutputColumn(i++, &field.MutableValue());
801 else if constexpr (IsBelongsTo<Field>)
803 reader->BindOutputColumn(i++, &field.MutableValue());
805 else if constexpr (SqlOutputColumnBinder<Field>)
807 reader->BindOutputColumn(i++, &field);
812 template <
typename Record>
813 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
815 BindAllOutputColumnsWithOffset(reader, record, 1);
820 constexpr std::size_t kDefaultRowArrayFetchDepth = 1024;
825 template <std::
size_t I>
826 struct MutableFieldValueAccessor
828 template <
typename Record>
829 decltype(
auto)
operator()(Record& record)
const
831 return GetRecordMemberAt<I>(record).MutableValue();
837 template <
typename FieldType>
838 using RowWiseColumnValueType = std::remove_cvref_t<decltype(std::declval<FieldType&>().MutableValue())>;
842 template <
typename FieldType>
843 constexpr bool RowWiseIsColumn()
845 return IsField<FieldType> || IsBelongsTo<FieldType> || SqlOutputColumnBinder<FieldType>;
852 template <
typename FieldType>
853 constexpr bool RowWiseColumnAcceptable()
855 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
856 return SqlRowWiseFetchableColumn<RowWiseColumnValueType<FieldType>>;
857 else if constexpr (SqlOutputColumnBinder<FieldType>)
863 template <
typename Record, std::size_t... Is>
864 constexpr bool CanRowWiseFetchRecordImpl(std::index_sequence<Is...> )
869 return (
sizeof(Record) %
alignof(SQLLEN) == 0) && (RowWiseColumnAcceptable<RecordMemberTypeOf<Is, Record>>() && ...)
870 && (RowWiseIsColumn<RecordMemberTypeOf<Is, Record>>() || ...);
877 template <
typename Record>
878 constexpr bool CanRowWiseFetchRecord()
880 return CanRowWiseFetchRecordImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
885 template <std::
size_t I,
typename Record>
886 auto MakeOutputColumnAccessor()
888 using FieldType = RecordMemberTypeOf<I, Record>;
889 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
890 return std::tuple<MutableFieldValueAccessor<I>> {};
892 return std::tuple<> {};
898 template <
typename Record>
899 void ReadAllRowWise(SqlResultCursor& reader, std::vector<Record>* records)
901 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
903 [&](
auto const&... accessors) {
904 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
906 std::tuple_cat(MakeOutputColumnAccessor<Is, Record>()...));
907 }(std::make_index_sequence<RecordMemberCount<Record>> {});
913 template <
typename FieldType>
914 constexpr bool ColumnIsNarrowFixedString()
916 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
918 using V = RowWiseColumnValueType<FieldType>;
919 if constexpr (SqlIsStdOptional<V>)
920 return IsSqlFixedString<typename V::value_type>;
922 return IsSqlFixedString<V>;
928 template <
typename Record, std::size_t... Is>
929 constexpr bool RecordHasNarrowFixedStringColumnImpl(std::index_sequence<Is...> )
931 return (ColumnIsNarrowFixedString<RecordMemberTypeOf<Is, Record>>() || ...);
937 template <
typename Record>
938 constexpr bool RecordHasNarrowFixedStringColumn()
940 return RecordHasNarrowFixedStringColumnImpl<Record>(std::make_index_sequence<RecordMemberCount<Record>> {});
947 template <
typename Record>
948 bool CanRowWiseFetchOn(SqlServerType serverType)
950 if constexpr (!CanRowWiseFetchRecord<Record>())
954 && (!RecordHasNarrowFixedStringColumn<Record>()
962 template <std::
size_t TupleIndex, std::
size_t I>
963 struct MutableTupleFieldAccessor
965 template <
typename TupleType>
966 decltype(
auto)
operator()(TupleType& row)
const
968 return GetRecordMemberAt<I>(std::get<TupleIndex>(row)).MutableValue();
972 template <
typename First,
typename Second, std::size_t... Fs, std::size_t... Ss>
973 constexpr bool CanRowWiseFetchTupleImpl(std::index_sequence<Fs...> ,
974 std::index_sequence<Ss...> )
976 return (
sizeof(std::tuple<First, Second>) %
alignof(SQLLEN) == 0)
977 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Fs, First>>() && ...)
978 && (RowWiseColumnAcceptable<RecordMemberTypeOf<Ss, Second>>() && ...)
979 && ((RowWiseIsColumn<RecordMemberTypeOf<Fs, First>>() || ...)
980 || (RowWiseIsColumn<RecordMemberTypeOf<Ss, Second>>() || ...));
986 template <
typename First,
typename Second>
987 constexpr bool CanRowWiseFetchTuple()
989 return CanRowWiseFetchTupleImpl<First, Second>(std::make_index_sequence<RecordMemberCount<First>> {},
990 std::make_index_sequence<RecordMemberCount<Second>> {});
996 template <
typename First,
typename Second>
997 bool CanRowWiseFetchTupleOn(SqlServerType serverType)
999 if constexpr (!CanRowWiseFetchTuple<First, Second>())
1003 && ((!RecordHasNarrowFixedStringColumn<First>() && !RecordHasNarrowFixedStringColumn<Second>())
1008 template <std::
size_t TupleIndex, std::
size_t I,
typename SubRecord>
1009 auto MakeTupleColumnAccessor()
1011 using FieldType = RecordMemberTypeOf<I, SubRecord>;
1012 if constexpr (IsField<FieldType> || IsBelongsTo<FieldType>)
1013 return std::tuple<MutableTupleFieldAccessor<TupleIndex, I>> {};
1015 return std::tuple<> {};
1021 template <
typename First,
typename Second>
1022 void ReadAllRowWiseTuple(SqlResultCursor& reader, std::vector<std::tuple<First, Second>>* records)
1024 [&]<std::size_t... Fs, std::size_t... Ss>(std::index_sequence<Fs...>, std::index_sequence<Ss...>) {
1026 [&](
auto const&... accessors) {
1027 reader.FetchAllRowWise(*records, kDefaultRowArrayFetchDepth, accessors...);
1029 std::tuple_cat(MakeTupleColumnAccessor<0, Fs, First>()..., MakeTupleColumnAccessor<1, Ss, Second>()...));
1030 }(std::make_index_sequence<RecordMemberCount<First>> {}, std::make_index_sequence<RecordMemberCount<Second>> {});
1036 template <
typename ElementMask,
typename Record>
1037 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
1039 EnumerateRecordMembers<ElementMask>(
1040 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
1049 static_assert(RecordColumnMember<Field> == (IsField<Field> || SqlGetColumnNativeType<Field>),
1050 "Record member is projected but not readable (or readable but not projected). "
1051 "A SqlDataBinder<T> used as a record member must provide both OutputColumn() "
1052 "and GetColumn().");
1053 if constexpr (IsField<Field>)
1057 field.MutableValue() =
1058 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
1060 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
1062 else if constexpr (SqlGetColumnNativeType<Field>)
1065 if constexpr (IsOptionalBelongsTo<Field>)
1066 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
1068 field = reader->GetColumn<Field>(indexFromQuery);
1073 template <
typename Record>
1074 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
1076 return GetAllColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record>(
1077 reader, record, indexFromQuery);
1080 template <
typename FirstRecord,
typename SecondRecord>
1082 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
1084 auto& [firstRecord, secondRecord] = record;
1089 GetAllColumns(reader, firstRecord, 0);
1090 GetAllColumns(reader, secondRecord,
static_cast<SQLUSMALLINT
>(RecordColumnCount<FirstRecord>));
1093 template <
typename Record>
1094 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
1096 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
1098 if (outputColumnsBound)
1099 BindAllOutputColumns(reader, record);
1101 if (!reader.FetchRow())
1104 if (!outputColumnsBound)
1105 GetAllColumns(reader, record);
1111template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1112template <
typename Finisher>
1113auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RunFinisher(Finisher finisher)
1115 if constexpr (Derived::QueryExecution == SqlQueryExecutionMode::Asynchronous)
1116 return Async::RunAsync(_dm.Connection().AsyncBackend(), std::move(finisher));
1121template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1123 DataMapper& dm, std::string fields)
noexcept:
1125 _formatter { dm.Connection().QueryFormatter() },
1126 _fields { std::move(fields) }
1128 this->_query.searchCondition.inputBindings = &_boundInputs;
1131template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1132size_t SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::CountImpl()
1134 auto stmt = SqlStatement { _dm.Connection() };
1135 stmt.Prepare(_formatter.SelectCount(this->_query.distinct,
1136 RecordTableName<Record>,
1137 this->_query.searchCondition.tableAlias,
1138 this->_query.searchCondition.tableJoins,
1139 this->_query.searchCondition.condition,
1140 this->_query.groupBy));
1141 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1142 if (reader.FetchRow())
1143 return reader.template GetColumn<size_t>(1);
1147template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1148bool SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::ExistImpl()
1150 auto stmt = SqlStatement { _dm.Connection() };
1152 auto const query = _formatter.SelectFirst(this->_query.distinct,
1154 RecordTableName<Record>,
1155 this->_query.searchCondition.tableAlias,
1156 this->_query.searchCondition.tableJoins,
1157 this->_query.searchCondition.condition,
1158 this->_query.orderBy,
1159 this->_query.groupBy,
1162 stmt.Prepare(query);
1163 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1168template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1169void SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::DeleteImpl()
1171 auto stmt = SqlStatement { _dm.Connection() };
1173 auto const query = _formatter.Delete(RecordTableName<Record>,
1174 this->_query.searchCondition.tableAlias,
1175 this->_query.searchCondition.tableJoins,
1176 this->_query.searchCondition.condition);
1178 stmt.Prepare(query);
1179 [[maybe_unused]]
auto cursor = stmt.ExecuteWithVariants(_boundInputs);
1182template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1183std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl()
1186 auto records = std::vector<Record> {};
1187 auto stmt = SqlStatement { _dm.Connection() };
1188 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1190 RecordTableName<Record>,
1191 this->_query.searchCondition.tableAlias,
1192 this->_query.searchCondition.tableJoins,
1193 this->_query.searchCondition.condition,
1194 this->_query.orderBy,
1195 this->_query.groupBy));
1196 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1197 if constexpr (DataMapperRecord<Record>)
1202 if constexpr (QueryOptions.loadRelations)
1204 for (
auto& record: records)
1206 _dm.ConfigureRelationAutoLoading(record);
1213template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1214template <auto Field>
1215#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1216 requires(is_aggregate_type(parent_of(Field)))
1218 requires std::is_member_object_pointer_v<
decltype(Field)>
1220auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<ReferencedFieldTypeOf<Field>>
1222 using value_type = ReferencedFieldTypeOf<Field>;
1223 auto result = std::vector<value_type> {};
1225 auto stmt = SqlStatement { _dm.Connection() };
1226 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1227 detail::FullyQualifiedNamesOf<Field>,
1228 RecordTableName<Record>,
1229 this->_query.searchCondition.tableAlias,
1230 this->_query.searchCondition.tableJoins,
1231 this->_query.searchCondition.condition,
1232 this->_query.orderBy,
1233 this->_query.groupBy));
1234 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1235 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
1238 auto& value = result.emplace_back();
1239 if (outputColumnsBound)
1240 reader.BindOutputColumn(1, &value);
1242 if (!reader.FetchRow())
1248 if (!outputColumnsBound)
1249 value = reader.template GetColumn<value_type>(1);
1255template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1256template <
auto... ReferencedFields>
1257 requires(
sizeof...(ReferencedFields) >= 2)
1258auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::AllImpl() -> std::vector<Record>
1260 auto records = std::vector<Record> {};
1261 auto stmt = SqlStatement { _dm.Connection() };
1263 stmt.Prepare(_formatter.SelectAll(this->_query.distinct,
1264 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1265 RecordTableName<Record>,
1266 this->_query.searchCondition.tableAlias,
1267 this->_query.searchCondition.tableJoins,
1268 this->_query.searchCondition.condition,
1269 this->_query.orderBy,
1270 this->_query.groupBy));
1272 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1273 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1276 auto& record = records.emplace_back();
1277 if (outputColumnsBound)
1278#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1279 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1281 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1283 if (!reader.FetchRow())
1288 if (!outputColumnsBound)
1290 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1291 detail::GetAllColumns<ElementMask>(reader, record);
1298template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1299std::optional<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl()
1301 std::optional<Record> record {};
1302 auto stmt = SqlStatement { _dm.Connection() };
1303 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1305 RecordTableName<Record>,
1306 this->_query.searchCondition.tableAlias,
1307 this->_query.searchCondition.tableJoins,
1308 this->_query.searchCondition.condition,
1309 this->_query.orderBy,
1310 this->_query.groupBy,
1312 Derived::ReadResult(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &record);
1313 if constexpr (QueryOptions.loadRelations)
1316 _dm.ConfigureRelationAutoLoading(record.value());
1321template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1322template <auto Field>
1323#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1324 requires(is_aggregate_type(parent_of(Field)))
1326 requires std::is_member_object_pointer_v<
decltype(Field)>
1328auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<ReferencedFieldTypeOf<Field>>
1330 auto constexpr count = 1;
1331 auto stmt = SqlStatement { _dm.Connection() };
1332 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1333 detail::FullyQualifiedNamesOf<Field>,
1334 RecordTableName<Record>,
1335 this->_query.searchCondition.tableAlias,
1336 this->_query.searchCondition.tableJoins,
1337 this->_query.searchCondition.condition,
1338 this->_query.orderBy,
1339 this->_query.groupBy,
1341 if (
auto reader = stmt.ExecuteWithVariants(_boundInputs); reader.FetchRow())
1342 return reader.template GetColumn<ReferencedFieldTypeOf<Field>>(1);
1343 return std::nullopt;
1346template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1347template <
auto... ReferencedFields>
1348 requires(
sizeof...(ReferencedFields) >= 2)
1349auto SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl() -> std::optional<Record>
1351 auto optionalRecord = std::optional<Record> {};
1353 auto stmt = SqlStatement { _dm.Connection() };
1354 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1355 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1356 RecordTableName<Record>,
1357 this->_query.searchCondition.tableAlias,
1358 this->_query.searchCondition.tableJoins,
1359 this->_query.searchCondition.condition,
1360 this->_query.orderBy,
1361 this->_query.groupBy,
1364 auto& record = optionalRecord.emplace();
1365 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1366 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1367 if (outputColumnsBound)
1368#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1369 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1371 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1379 if (reader.FetchRow())
1381 if (!outputColumnsBound)
1383 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1384 detail::GetAllColumns<ElementMask>(reader, record);
1387 if constexpr (QueryOptions.loadRelations)
1388 _dm.ConfigureRelationAutoLoading(record);
1392 optionalRecord.reset();
1395 return optionalRecord;
1398template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1399std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1401 auto records = std::vector<Record> {};
1402 auto stmt = SqlStatement { _dm.Connection() };
1404 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1406 RecordTableName<Record>,
1407 this->_query.searchCondition.tableAlias,
1408 this->_query.searchCondition.tableJoins,
1409 this->_query.searchCondition.condition,
1410 this->_query.orderBy,
1411 this->_query.groupBy,
1413 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1415 if constexpr (QueryOptions.loadRelations)
1417 for (
auto& record: records)
1418 _dm.ConfigureRelationAutoLoading(record);
1423template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1424std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1426 auto records = std::vector<Record> {};
1427 auto stmt = SqlStatement { _dm.Connection() };
1428 records.reserve(limit);
1430 _formatter.SelectRange(this->_query.distinct,
1432 RecordTableName<Record>,
1433 this->_query.searchCondition.tableAlias,
1434 this->_query.searchCondition.tableJoins,
1435 this->_query.searchCondition.condition,
1436 !this->_query.orderBy.empty()
1437 ? this->_query.orderBy
1438 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1439 this->_query.groupBy,
1442 Derived::ReadResults(stmt.Connection().ServerType(), stmt.ExecuteWithVariants(_boundInputs), &records);
1443 if constexpr (QueryOptions.loadRelations)
1445 for (
auto& record: records)
1446 _dm.ConfigureRelationAutoLoading(record);
1451template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1452template <
auto... ReferencedFields>
1453std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::RangeImpl(
size_t offset,
size_t limit)
1455 auto records = std::vector<Record> {};
1456 auto stmt = SqlStatement { _dm.Connection() };
1457 records.reserve(limit);
1459 _formatter.SelectRange(this->_query.distinct,
1460 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1461 RecordTableName<Record>,
1462 this->_query.searchCondition.tableAlias,
1463 this->_query.searchCondition.tableJoins,
1464 this->_query.searchCondition.condition,
1465 !this->_query.orderBy.empty()
1466 ? this->_query.orderBy
1467 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
1468 this->_query.groupBy,
1472 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1473 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1476 auto& record = records.emplace_back();
1477 if (outputColumnsBound)
1478#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1479 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1481 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1483 if (!reader.FetchRow())
1488 if (!outputColumnsBound)
1490 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1491 detail::GetAllColumns<ElementMask>(reader, record);
1495 if constexpr (QueryOptions.loadRelations)
1497 for (
auto& record: records)
1498 _dm.ConfigureRelationAutoLoading(record);
1504template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
1505template <
auto... ReferencedFields>
1506[[nodiscard]] std::vector<Record> SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::FirstImpl(
size_t n)
1508 auto records = std::vector<Record> {};
1509 auto stmt = SqlStatement { _dm.Connection() };
1511 stmt.Prepare(_formatter.SelectFirst(this->_query.distinct,
1512 detail::FullyQualifiedNamesOf<ReferencedFields...>,
1513 RecordTableName<Record>,
1514 this->_query.searchCondition.tableAlias,
1515 this->_query.searchCondition.tableJoins,
1516 this->_query.searchCondition.condition,
1517 this->_query.orderBy,
1518 this->_query.groupBy,
1521 auto reader = stmt.ExecuteWithVariants(_boundInputs);
1522 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
1525 auto& record = records.emplace_back();
1526 if (outputColumnsBound)
1527#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1528 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
1530 reader.BindOutputColumns(&(record.*ReferencedFields)...);
1532 if (!reader.FetchRow())
1537 if (!outputColumnsBound)
1539 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1540 detail::GetAllColumns<ElementMask>(reader, record);
1544 if constexpr (QueryOptions.loadRelations)
1546 for (
auto& record: records)
1547 _dm.ConfigureRelationAutoLoading(record);
1553template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1554void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResults(SqlServerType sqlServerType,
1555 SqlResultCursor reader,
1556 std::vector<Record>* records)
1562 if constexpr (detail::CanRowWiseFetchRecord<Record>())
1564 if (detail::CanRowWiseFetchOn<Record>(sqlServerType))
1566 detail::ReadAllRowWise(reader, records);
1573 Record& record = records->emplace_back();
1574 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1576 records->pop_back();
1582template <
typename Record, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1583void SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>::ReadResult(SqlServerType sqlServerType,
1584 SqlResultCursor reader,
1585 std::optional<Record>* optionalRecord)
1587 Record& record = optionalRecord->emplace();
1588 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1589 optionalRecord->reset();
1592template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
1593void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution>::ReadResults(
1594 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1598 if constexpr (detail::CanRowWiseFetchTuple<FirstRecord, SecondRecord>())
1600 if (detail::CanRowWiseFetchTupleOn<FirstRecord, SecondRecord>(sqlServerType))
1602 detail::ReadAllRowWiseTuple<FirstRecord, SecondRecord>(reader, records);
1609 auto& record = records->emplace_back();
1610 auto& [firstRecord, secondRecord] = record;
1612 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1613 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1615 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1616 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1617 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1619 if (canSafelyBindAll)
1621 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1624 detail::BindAllOutputColumnsWithOffset(
1625 reader, secondRecord,
static_cast<SQLUSMALLINT
>(1 + RecordColumnCount<FirstRecord>));
1628 if (!reader.FetchRow())
1630 records->pop_back();
1634 if (!canSafelyBindAll)
1635 detail::GetAllColumns(reader, record);
1639template <
typename Record>
1645 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1651 if constexpr (Value::IsOptional)
1653 if (!value.Value().has_value())
1655 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1659 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1662 else if constexpr (IsBelongsTo<Value>)
1664 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1666 else if constexpr (std::same_as<typename Value::ValueType, char>)
1671 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1674 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>
1675 && !IsCompositeForeignKey<Value>)
1676 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1678 return "{\n" + std::move(str) +
"\n}";
1681template <
typename Record>
1687 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1688 detail::PopulateCreateTableBuilder<Record>(createTable);
1689 return migration.GetPlan().ToSql();
1692template <
typename FirstRecord,
typename... MoreRecords>
1695 std::vector<std::string> output;
1696 auto const append = [&output](
auto const& sql) {
1697 output.insert(output.end(), sql.begin(), sql.end());
1699 append(CreateTableString<FirstRecord>(serverType));
1700 (append(CreateTableString<MoreRecords>(serverType)), ...);
1704template <
typename Record>
1709 ZoneScopedN(
"DataMapper::CreateTable");
1710 ZoneTextObject(RecordTableName<Record>);
1712 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1713 for (
auto const& sqlQueryString: sqlQueryStrings) [[maybe_unused]]
1717template <
typename FirstRecord,
typename... MoreRecords>
1720 CreateTable<FirstRecord>();
1721 (CreateTable<MoreRecords>(), ...);
1724template <
typename Record>
1725std::optional<RecordPrimaryKeyType<Record>> DataMapper::GenerateAutoAssignPrimaryKey(Record
const& record)
1732 static_assert(detail::AutoAssignPrimaryKeyFieldCount<Record> <= 1,
1733 "A record may declare at most one auto-assigned primary key member. Auto-assignment yields a "
1734 "single value that would be written into every key member, so a composite key cannot be "
1735 "generated - declare the key members without PrimaryKey::AutoAssign and set their values "
1736 "yourself before calling Create().");
1738 std::optional<RecordPrimaryKeyType<Record>> result;
1740 record, [
this, &result]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1741 if constexpr (IsField<PrimaryKeyType> && IsPrimaryKey<PrimaryKeyType>
1742 && detail::IsAutoAssignPrimaryKeyField<PrimaryKeyType>::value)
1744 using ValueType = PrimaryKeyType::ValueType;
1745 if constexpr (std::same_as<ValueType, SqlGuid>)
1747 if (!primaryKeyField.Value())
1752 else if constexpr (
requires { ValueType {} + 1; })
1754 if (primaryKeyField.Value() == ValueType {})
1756 auto maxId = SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1757 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1758 FieldNameAt<PrimaryKeyIndex, Record>,
1759 RecordTableName<Record>));
1760 result = maxId.value_or(ValueType {}) + 1;
1768template <DataMapper::PrimaryKeySource UsePkOverr
ide,
typename Record>
1769RecordPrimaryKeyType<Record> DataMapper::CreateInternal(
1770 Record
const& record,
1771 std::optional<std::conditional_t<std::is_void_v<RecordPrimaryKeyType<Record>>,
int, RecordPrimaryKeyType<Record>>>
1774 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1776 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1778#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1779 constexpr auto ctx = std::meta::access_context::current();
1780 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1782 using FieldType =
typename[:std::meta::type_of(el):];
1783 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1784 query.Set(FieldNameOf<el>, SqlWildcard);
1788 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1789 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1795#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1797 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1799 using FieldType =
typename[:std::meta::type_of(el):];
1800 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1802 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1809 Reflection::CallOnMembers(record,
1810 [
this, &pkOverride, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(
1811 Name
const& name, FieldType
const& field)
mutable {
1812 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1814 if constexpr (IsPrimaryKey<FieldType> && UsePkOverride == PrimaryKeySource::Override)
1821 [[maybe_unused]]
auto cursor = _stmt.
Execute();
1823 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1829 return static_cast<RecordPrimaryKeyType<Record>
>(_stmt.
LastInsertId(RecordTableName<Record>));
1830 else if constexpr (HasPrimaryKey<Record>)
1832 if constexpr (UsePkOverride == PrimaryKeySource::Override)
1835 return RecordPrimaryKeyOf(record).Value();
1841template <
typename Record>
1845 return CreateInternal<PrimaryKeySource::Record>(record);
1854 template <
typename FieldType>
1855 constexpr bool IsBatchInsertColumn = SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>;
1858 template <
typename FieldType>
1862 template <
typename FieldType>
1863 constexpr bool IsBatchUpdateWhereColumn = IsPrimaryKey<FieldType>;
1867 template <std::
size_t I>
1868 struct FieldValueAccessor
1870 template <
typename Record>
1871 decltype(
auto)
operator()(Record
const& record)
const
1873 return GetRecordMemberAt<I>(record).Value();
1879 template <std::
size_t I,
typename Record>
1880 auto MakeCreateColumnAccessor()
1882 using FieldType = RecordMemberTypeOf<I, Record>;
1883 if constexpr (IsBatchInsertColumn<FieldType>)
1884 return std::tuple<FieldValueAccessor<I>> {};
1886 return std::tuple<> {};
1890 template <std::
size_t I,
typename Record>
1891 auto MakeUpdateSetAccessor()
1893 using FieldType = RecordMemberTypeOf<I, Record>;
1894 if constexpr (IsBatchUpdateSetColumn<FieldType>)
1895 return std::tuple<FieldValueAccessor<I>> {};
1897 return std::tuple<> {};
1901 template <std::
size_t I,
typename Record>
1902 auto MakeUpdateWhereAccessor()
1904 using FieldType = RecordMemberTypeOf<I, Record>;
1905 if constexpr (IsBatchUpdateWhereColumn<FieldType>)
1906 return std::tuple<FieldValueAccessor<I>> {};
1908 return std::tuple<> {};
1912template <std::ranges::range Records>
1915 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
1916 "CreateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
1917 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
1918 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
1921 ZoneScopedN(
"DataMapper::CreateAll");
1922 ZoneTextObject(RecordTableName<Record>);
1924 if (std::ranges::empty(records))
1928 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1929 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
1930 if constexpr (detail::IsBatchInsertColumn<FieldType>)
1931 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1936 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
1937 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
1938 std::tuple_cat(detail::MakeCreateColumnAccessor<Is, Record>()...));
1939 }(std::make_index_sequence<RecordMemberCount<Record>> {});
1942template <DataMapperOptions QueryOptions,
typename Record>
1946 static_assert(HasPrimaryKey<Record>,
"CreateCopyOf requires a record type with a primary key");
1948 auto generatedKey = GenerateAutoAssignPrimaryKey(originalRecord);
1950 return CreateInternal<PrimaryKeySource::Override>(originalRecord, generatedKey);
1952 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1953 return CreateInternal<PrimaryKeySource::Record>(originalRecord);
1955 return CreateInternal<PrimaryKeySource::Override>(originalRecord, RecordPrimaryKeyType<Record> {});
1958template <DataMapperOptions QueryOptions,
typename Record>
1961 static_assert(!std::is_const_v<Record>);
1964 ZoneScopedN(
"DataMapper::Create");
1965 ZoneTextObject(RecordTableName<Record>);
1967 auto generatedKey = GenerateAutoAssignPrimaryKey(record);
1969 SetId(record, *generatedKey);
1971 auto pk = CreateInternal<PrimaryKeySource::Record>(record);
1973 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1976 SetModifiedState<ModifiedState::NotModified>(record);
1978 if constexpr (QueryOptions.loadRelations)
1981 if constexpr (HasPrimaryKey<Record>)
1985template <
typename Record>
1990 bool modified =
false;
1992#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1993 auto constexpr ctx = std::meta::access_context::current();
1994 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1996 if constexpr (
requires { record.[:el:].IsModified(); })
1998 modified = modified || record.[:el:].IsModified();
2002 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
2003 if constexpr (
requires { field.IsModified(); })
2005 modified = modified || field.IsModified();
2013template <
typename Record>
2014 requires HasPrimaryKey<Record>
2019 ZoneScopedN(
"DataMapper::Update");
2020 ZoneTextObject(RecordTableName<Record>);
2022 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2027 bool anyFieldModified =
false;
2029#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2030 auto constexpr ctx = std::meta::access_context::current();
2031 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2033 using FieldType =
typename[:std::meta::type_of(el):];
2036 if (record.[:el:].IsModified())
2038 query.Set(FieldNameOf<el>, SqlWildcard);
2039 anyFieldModified =
true;
2041 if constexpr (IsPrimaryKey<FieldType>)
2042 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2046 EnumerateRecordMembers(record, [&query, &anyFieldModified]<
size_t I,
typename FieldType>(FieldType
const& field) {
2053 if (field.IsModified())
2055 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2056 anyFieldModified =
true;
2058 if constexpr (IsPrimaryKey<MemberType>)
2059 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2065 if (!anyFieldModified)
2072#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2073 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2075 using FieldType =
typename[:std::meta::type_of(el):];
2079 if (record.[:el:].IsModified())
2086 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2088 using FieldType =
typename[:std::meta::type_of(el):];
2091 if constexpr (FieldType::IsPrimaryKey)
2103 if (field.IsModified())
2110 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2115 [[maybe_unused]]
auto cursor = _stmt.
Execute();
2117 SetModifiedState<ModifiedState::NotModified>(record);
2120template <std::ranges::range Records>
2123 static_assert(std::ranges::contiguous_range<Records> && std::ranges::sized_range<Records>,
2124 "UpdateAll requires a contiguous, sized range of records (e.g. std::vector, std::array, "
2125 "std::span, or a C array); native row-wise array binding needs the records laid out contiguously.");
2126 using Record = std::remove_cvref_t<std::ranges::range_value_t<Records>>;
2128 static_assert(HasPrimaryKey<Record>,
"UpdateAll requires a record type with a primary key");
2130 ZoneScopedN(
"DataMapper::UpdateAll");
2131 ZoneTextObject(RecordTableName<Record>);
2133 if (std::ranges::empty(records))
2137 auto query = _connection.
Query(RecordTableName<Record>).
Update();
2138 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2139 if constexpr (detail::IsBatchUpdateSetColumn<FieldType>)
2140 query.Set(FieldNameAt<I, Record>, SqlWildcard);
2142 EnumerateRecordMembers<Record>([&query]<
auto I,
typename FieldType>() {
2143 if constexpr (detail::IsBatchUpdateWhereColumn<FieldType>)
2144 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2149 [&]<std::size_t... Is>(std::index_sequence<Is...>) {
2150 std::apply([&](
auto const&... accessors) { std::ignore = _stmt.
ExecuteBatch(records, accessors...); },
2151 std::tuple_cat(detail::MakeUpdateSetAccessor<Is, Record>()...,
2152 detail::MakeUpdateWhereAccessor<Is, Record>()...));
2153 }(std::make_index_sequence<RecordMemberCount<Record>> {});
2156template <
typename Record>
2161 ZoneScopedN(
"DataMapper::Delete");
2162 ZoneTextObject(RecordTableName<Record>);
2164 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
2166#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2167 auto constexpr ctx = std::meta::access_context::current();
2168 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2170 using FieldType =
typename[:std::meta::type_of(el):];
2173 if constexpr (FieldType::IsPrimaryKey)
2174 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
2178 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2179 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
2185#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2187 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2189 using FieldType =
typename[:std::meta::type_of(el):];
2192 if constexpr (FieldType::IsPrimaryKey)
2201 [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
2202 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
2207 auto cursor = _stmt.
Execute();
2212template <
typename Record,
DataMapperOptions QueryOptions,
typename... PrimaryKeyTypes>
2217 ZoneScopedN(
"DataMapper::QuerySingle(PK)");
2218 ZoneTextObject(RecordTableName<Record>);
2224 auto selectStarter = _connection.
Query(RecordTableName<Record>).
Select();
2228 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2231 if (queryBuilder ==
nullptr)
2232 queryBuilder = &selectStarter.
Field(FieldNameAt<I, Record>);
2234 queryBuilder->
Field(FieldNameAt<I, Record>);
2238 if constexpr (FieldType::IsPrimaryKey)
2239 std::ignore = queryBuilder->
Where(FieldNameAt<I, Record>, SqlWildcard);
2245 auto reader = _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
2251 auto resultRecord = std::optional<Record> { Record {} };
2254 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2256 if constexpr (QueryOptions.loadRelations)
2261 resultRecord.reset();
2264 return resultRecord;
2267template <
typename Record,
typename... Args>
2272 ZoneScopedN(
"DataMapper::QuerySingle(Builder)");
2273 ZoneTextObject(RecordTableName<Record>);
2276 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2280 auto const composedSql = selectQuery.
First().ToSql();
2281 ZoneTextObject(composedSql);
2283 auto reader = _stmt.
Execute(std::forward<Args>(args)...);
2285 auto resultRecord = std::optional<Record> { Record {} };
2287 return std::nullopt;
2290 SetModifiedState<ModifiedState::NotModified>(resultRecord.value());
2292 return resultRecord;
2298template <
typename Record, DataMapperOptions QueryOptions,
typename... InputParameters>
2300 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
2302 static_assert(
DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
2304 ZoneScopedN(
"DataMapper::Query(ComposedQuery)");
2305 return Query<Record, QueryOptions>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
2308template <
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2309std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
2311 ZoneScopedN(
"DataMapper::Query(string)");
2312 ZoneTextObject(sqlQueryString);
2314 auto result = std::vector<Record> {};
2315 if constexpr (std::same_as<Record, SqlVariantRow>)
2317 _stmt.
Prepare(sqlQueryString);
2322 auto& record = result.emplace_back();
2323 record.reserve(numResultColumns);
2324 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
2332 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2334 _stmt.
Prepare(sqlQueryString);
2335 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2339 auto& record = result.emplace_back();
2341 if (canSafelyBindOutputColumns)
2342 BindOutputColumns(record, reader);
2344 if (!reader.FetchRow())
2347 if (!canSafelyBindOutputColumns)
2348 detail::GetAllColumns(reader, record);
2354 for (
auto& record: result)
2356 SetModifiedState<ModifiedState::NotModified>(record);
2357 if constexpr (QueryOptions.loadRelations)
2365template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
2367std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
2369 using value_type = std::tuple<First, Second, Rest...>;
2370 auto result = std::vector<value_type> {};
2372 ZoneScopedN(
"DataMapper::Query(ComposedQuery -> tuple)");
2373 auto const tupleSql = selectQuery.ToSql();
2374 ZoneTextObject(tupleSql);
2376 auto reader = _stmt.
Execute();
2381 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
2384 if constexpr (I > 0)
2386 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
2387 ((Indices < I ? (offset += RecordColumnCount<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
2388 }(std::make_index_sequence<I> {});
2393 auto const BindElements = [&](
auto& record) {
2394 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2395 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2396 auto& element = std::get<I>(record);
2397 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2398 this->BindOutputColumns<TupleElement, offset>(element, reader);
2402 auto const GetElements = [&](
auto& record) {
2403 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2404 auto& element = std::get<I>(record);
2405 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
2406 detail::GetAllColumns(reader, element, offset - 1);
2410 bool const canSafelyBindOutputColumns = [&]() {
2412 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2413 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
2421 auto& record = result.emplace_back();
2423 if (canSafelyBindOutputColumns)
2424 BindElements(record);
2426 if (!reader.FetchRow())
2429 if (!canSafelyBindOutputColumns)
2430 GetElements(record);
2436 for (
auto& record: result)
2438 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
2439 auto& element = std::get<I>(record);
2440 SetModifiedState<ModifiedState::NotModified>(element);
2441 if constexpr (QueryOptions.loadRelations)
2451template <
typename ElementMask,
typename Record,
DataMapperOptions QueryOptions,
typename... InputParameters>
2453 InputParameters&&... inputParameters)
2457 ZoneScopedN(
"DataMapper::Query(ComposedQuery, ElementMask)");
2458 auto const maskedSql = selectQuery.ToSql();
2459 ZoneTextObject(maskedSql);
2462 auto records = std::vector<Record> {};
2465 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
2467 auto reader = _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
2471 auto& record = records.emplace_back();
2473 if (canSafelyBindOutputColumns)
2474 BindOutputColumns<ElementMask>(record, reader);
2476 if (!reader.FetchRow())
2479 if (!canSafelyBindOutputColumns)
2480 detail::GetAllColumns<ElementMask>(reader, record);
2486 for (
auto& record: records)
2488 SetModifiedState<ModifiedState::NotModified>(record);
2489 if constexpr (QueryOptions.loadRelations)
2496template <DataMapper::ModifiedState state,
typename Record>
2499 static_assert(!std::is_const_v<Record>);
2503 if constexpr (
requires { field.SetModified(
false); })
2505 if constexpr (state == ModifiedState::Modified)
2506 field.SetModified(
true);
2508 field.SetModified(
false);
2513template <
typename Record,
typename Callable>
2514inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
2519 if constexpr (IsField<FieldType>)
2521 if constexpr (FieldType::IsPrimaryKey)
2523 return callable.template operator()<I, FieldType>(field);
2529template <
typename Record,
typename Callable>
2530inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
2532 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2534 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2535 if constexpr (IsField<FieldType>)
2537 if constexpr (FieldType::IsPrimaryKey)
2539 return callable.template operator()<I, FieldType>();
2545template <
typename Record,
typename Callable>
2546inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
2548 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2550 EnumerateRecordMembers<Record>([&]<
size_t I,
typename FieldType>() {
2551 if constexpr (IsBelongsTo<FieldType>)
2553 return callable.template operator()<I, FieldType>();
2558template <
typename FieldType>
2559std::shared_ptr<typename FieldType::ReferencedRecord> DataMapper::LoadCompositeForeignKeyRecord(
2560 typename FieldType::OrderedValueType
const& keys)
2562 using ReferencedRecord =
typename FieldType::ReferencedRecord;
2565 std::apply([
this](
auto const&... key) {
return this->
template QuerySingle<ReferencedRecord>(key...); }, keys);
2568 return std::make_shared<ReferencedRecord>(std::move(*loaded));
2571template <
typename Record,
typename FieldType>
2572void DataMapper::LoadCompositeForeignKey(Record
const& record, FieldType& field)
2574 using ReferencedRecord =
typename FieldType::ReferencedRecord;
2576 ZoneScopedN(
"DataMapper::LoadCompositeForeignKey");
2577 ZoneTextObject(RecordTableName<ReferencedRecord>);
2583 auto loaded = LoadCompositeForeignKeyRecord<FieldType>(FieldType::OrderedValuesOf(record));
2591 std::format(
"Loading composite foreign key failed for {}", RecordTableName<ReferencedRecord>));
2595 field.EmplaceRecord(std::move(loaded));
2598template <
typename FieldType>
2599std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
2601 using ReferencedRecord = FieldType::ReferencedRecord;
2603 ZoneScopedN(
"DataMapper::LoadBelongsTo");
2604 ZoneTextObject(RecordTableName<ReferencedRecord>);
2606 std::optional<ReferencedRecord> record { std::nullopt };
2609 if constexpr (FieldType::IsOptional)
2610 if (!value.has_value())
2613#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2614 auto constexpr ctx = std::meta::access_context::current();
2615 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
2617 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
2618 if constexpr (IsField<BelongsToFieldType>)
2619 if constexpr (BelongsToFieldType::IsPrimaryKey)
2621 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
2622 record = std::move(result);
2625 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
2629 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
2630 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
2631 record = std::move(result);
2634 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
2640template <
typename Record,
typename OtherRecord, auto InverseSelector,
typename Callable>
2641void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
2643 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2644 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2646 using FieldType = HasMany<OtherRecord, InverseSelector>;
2647 using ReferencedRecord = FieldType::ReferencedRecord;
2649 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2650 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
2652 .Build([&](
auto& query) {
2653 EnumerateRecordMembers<ReferencedRecord>(
2654 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2655 if constexpr (FieldWithStorage<ReferencedFieldType>)
2657 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
2661 .Where(InverseBelongsToFieldNameOf<Record, ReferencedRecord, InverseSelector>, SqlWildcard)
2662 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord>);
2663 callback(query, primaryKeyField);
2667template <
typename OwnerRecord,
typename OtherRecord, auto InverseSelector>
2668SqlSelectQueryBuilder DataMapper::BuildHasManySelectQuery()
2670 return _connection.
Query(RecordTableName<OtherRecord>)
2672 .
Build([](
auto& q) {
2673 EnumerateRecordMembers<OtherRecord>([&]<
size_t I,
typename F>() {
2674 if constexpr (FieldWithStorage<F>)
2675 q.Field(FieldNameAt<I, OtherRecord>);
2678 .Where(InverseBelongsToFieldNameOf<OwnerRecord, OtherRecord, InverseSelector>, SqlWildcard)
2679 .OrderBy(
FieldNameAt<RecordPrimaryKeyIndex<OtherRecord>, OtherRecord>);
2682template <
typename Record,
typename OtherRecord, auto InverseSelector>
2683void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord, InverseSelector>& field)
2685 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2686 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
2688 ZoneScopedN(
"DataMapper::LoadHasMany");
2689 ZoneTextObject(RecordTableName<OtherRecord>);
2691 CallOnHasMany<Record, OtherRecord, InverseSelector>(
2692 record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
2693 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
2697template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ThroughSelector>
2698SqlSelectQueryBuilder DataMapper::BuildHasOneThroughSelectQuery()
2700 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2701 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2704 constexpr size_t ThroughToOwnerIndex = InverseBelongsToIndexOf<Record, ThroughRecord, OwnerSelector>;
2707 constexpr size_t ReferencedToThroughIndex = InverseBelongsToIndexOf<ThroughRecord, ReferencedRecord, ThroughSelector>;
2712 return _connection.
Query(RecordTableName<ReferencedRecord>)
2714 .
Build([&](
auto& query) {
2715 EnumerateRecordMembers<ReferencedRecord>([&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2716 if constexpr (FieldWithStorage<ReferencedFieldType>)
2718 query.Field(SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2719 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2723 .InnerJoin(RecordTableName<ThroughRecord>,
2724 FieldNameAt<RecordPrimaryKeyIndex<ThroughRecord>, ThroughRecord>,
2725 FieldNameAt<ReferencedToThroughIndex, ReferencedRecord>)
2727 SqlQualifiedTableColumnName {
2728 RecordTableName<ThroughRecord>,
2729 FieldNameAt<ThroughToOwnerIndex, ThroughRecord>,
2734template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ThroughSelector>
2735void DataMapper::LoadHasOneThrough(Record& record,
2736 HasOneThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ThroughSelector>& field)
2738 using ThroughRecord = ThroughRecordOf<ThroughSpec>;
2740 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2741 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2743 ZoneScopedN(
"DataMapper::LoadHasOneThrough");
2744 ZoneTextObject(RecordTableName<ReferencedRecord>);
2746 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2748 BuildHasOneThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ThroughSelector>();
2749 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
2750 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
2754template <
typename ReferencedRecord,
2755 typename ThroughRecord,
2758 auto ThroughSelector,
2760std::shared_ptr<ReferencedRecord> DataMapper::LoadHasOneThroughByPK(PKValue
const& pkValue)
2762 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2764 auto query = BuildHasOneThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ThroughSelector>();
2766 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), pkValue); link)
2767 return std::make_shared<ReferencedRecord>(std::move(*link));
2772template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record, auto OwnerSelector, auto ReferencedSelector>
2773SqlSelectQueryBuilder DataMapper::BuildHasManyThroughSelectQuery()
2775 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2776 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
2779 constexpr size_t ThroughToOwnerIndex = InverseBelongsToIndexOf<Record, ThroughRecord, OwnerSelector>;
2782 constexpr size_t ThroughToReferencedIndex = InverseBelongsToIndexOf<ReferencedRecord, ThroughRecord, ReferencedSelector>;
2784 return _connection.
Query(RecordTableName<ReferencedRecord>)
2786 .
Build([&](
auto& query) {
2787 EnumerateRecordMembers<ReferencedRecord>([&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
2788 if constexpr (FieldWithStorage<ReferencedFieldType>)
2790 query.Field(SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2791 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
2795 .InnerJoin(RecordTableName<ThroughRecord>,
2796 FieldNameAt<ThroughToReferencedIndex, ThroughRecord>,
2797 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
2798 FieldNameAt<RecordPrimaryKeyIndex<ReferencedRecord>, ReferencedRecord> })
2800 SqlQualifiedTableColumnName {
2801 RecordTableName<ThroughRecord>,
2802 FieldNameAt<ThroughToOwnerIndex, ThroughRecord>,
2807template <
typename ReferencedRecord,
2808 typename ThroughRecord,
2811 auto ReferencedSelector,
2813void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
2815 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2817 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
2819 BuildHasManyThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>();
2820 callback(query, primaryKeyField);
2824template <
typename ReferencedRecord,
2825 typename ThroughRecord,
2828 auto ReferencedSelector,
2831void DataMapper::CallOnHasManyThroughByPK(PKValue
const& pkValue, Callable
const& callback)
2833 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2836 BuildHasManyThroughSelectQuery<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>();
2837 callback(query, pkValue);
2840template <
typename ReferencedRecord,
typename ThroughSpec,
typename Record, auto OwnerSelector, auto ReferencedSelector>
2841void DataMapper::LoadHasManyThrough(Record& record,
2842 HasManyThrough<ReferencedRecord, ThroughSpec, OwnerSelector, ReferencedSelector>& field)
2844 using ThroughRecord = ThroughRecordOf<ThroughSpec>;
2846 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2848 ZoneScopedN(
"DataMapper::LoadHasManyThrough");
2849 ZoneTextObject(RecordTableName<ReferencedRecord>);
2851 CallOnHasManyThrough<ReferencedRecord, ThroughRecord, Record, OwnerSelector, ReferencedSelector>(
2852 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2853 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2857template <
typename Record>
2860 static_assert(!std::is_const_v<Record>);
2863 ZoneScopedN(
"DataMapper::LoadRelations");
2864 ZoneTextObject(RecordTableName<Record>);
2866#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2867 constexpr auto ctx = std::meta::access_context::current();
2868 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2870 using FieldType =
typename[:std::meta::type_of(el):];
2871 if constexpr (IsBelongsTo<FieldType>)
2873 auto& field = record.[:el:];
2874 field.AdoptFetchedRecord(LoadBelongsTo<FieldType>(field.Value()));
2876 else if constexpr (IsCompositeForeignKey<FieldType>)
2878 LoadCompositeForeignKey(record, record.[:el:]);
2880 else if constexpr (IsHasMany<FieldType>)
2882 LoadHasMany(record, record.[:el:]);
2884 else if constexpr (IsHasOneThrough<FieldType>)
2886 LoadHasOneThrough(record, record.[:el:]);
2888 else if constexpr (IsHasManyThrough<FieldType>)
2890 LoadHasManyThrough(record, record.[:el:]);
2895 if constexpr (IsBelongsTo<FieldType>)
2897 field.AdoptFetchedRecord(LoadBelongsTo<FieldType>(field.Value()));
2899 else if constexpr (IsCompositeForeignKey<FieldType>)
2901 LoadCompositeForeignKey(record, field);
2903 else if constexpr (IsHasMany<FieldType>)
2905 LoadHasMany(record, field);
2907 else if constexpr (IsHasOneThrough<FieldType>)
2909 LoadHasOneThrough(record, field);
2911 else if constexpr (IsHasManyThrough<FieldType>)
2913 LoadHasManyThrough(record, field);
2920template <
typename Record,
typename ValueType>
2921inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2926#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2928 auto constexpr ctx = std::meta::access_context::current();
2929 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2931 using FieldType =
typename[:std::meta::type_of(el):];
2932 if constexpr (IsField<FieldType>)
2934 if constexpr (FieldType::IsPrimaryKey)
2936 record.[:el:] = std::forward<ValueType>(
id);
2942 if constexpr (IsField<FieldType>)
2944 if constexpr (FieldType::IsPrimaryKey)
2946 field = std::forward<FieldType>(
id);
2954template <
typename Record,
size_t InitialOffset>
2955inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2958 return BindOutputColumns<std::make_integer_sequence<size_t, RecordMemberCount<Record>>, Record, InitialOffset>(record,
2962template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2963Record& DataMapper::BindOutputColumns(Record& record,
SqlResultCursor& cursor)
2966 static_assert(!std::is_const_v<Record>);
2968#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2969 auto constexpr ctx = std::meta::access_context::current();
2970 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2971 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2973 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2974 using FieldType =
typename[:std::meta::type_of(el):];
2975 if constexpr (IsField<FieldType>)
2979 else if constexpr (SqlOutputColumnBinder<FieldType>)
2985 EnumerateRecordMembers<ElementMask>(
2986 record, [&cursor, i = SQLUSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2987 if constexpr (IsField<Field>)
2991 else if constexpr (SqlOutputColumnBinder<Field>)
3000template <
typename Record>
3006 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
3007 if constexpr (IsBelongsTo<FieldType>)
3009 field.SetAutoLoader(
typename FieldType::Loader {
3010 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
3012 return dm.LoadBelongsTo<FieldType>(value);
3016 if constexpr (IsCompositeForeignKey<FieldType>)
3018 using ReferencedRecord =
typename FieldType::ReferencedRecord;
3034 field.SetAutoLoader(
typename FieldType::Loader {
3035 .loadReference = [keys = FieldType::OrderedValuesOf(record)]() -> std::shared_ptr<ReferencedRecord> {
3037 return dm.LoadCompositeForeignKeyRecord<FieldType>(keys);
3041 if constexpr (IsHasMany<FieldType>)
3043 if constexpr (HasPrimaryKey<Record>)
3045 using ReferencedRecord = FieldType::ReferencedRecord;
3050 .count = [pkValue]() ->
size_t {
3053 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3054 dm._stmt.
Prepare(selectQuery.Count());
3061 .all = [pkValue]() -> FieldType::ReferencedRecordList {
3064 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3065 return detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pkValue));
3068 [pkValue](
auto const& each) {
3071 dm.BuildHasManySelectQuery<Record, ReferencedRecord, FieldType::InverseSelector>();
3073 stmt.Prepare(selectQuery.All());
3074 auto cursor = stmt.Execute(pkValue);
3076 auto referencedRecord = ReferencedRecord {};
3077 dm.BindOutputColumns(referencedRecord, cursor);
3082 each(referencedRecord);
3090 referencedRecord = ReferencedRecord {};
3091 dm.BindOutputColumns(referencedRecord, cursor);
3098 if constexpr (IsHasOneThrough<FieldType> && HasPrimaryKey<Record>)
3100 using ReferencedRecord = FieldType::ReferencedRecord;
3101 using ThroughRecord = FieldType::ThroughRecord;
3104 field.SetAutoLoader(
typename FieldType::Loader {
3105 .loadReference = [pkValue]() -> std::shared_ptr<ReferencedRecord> {
3107 return dm.LoadHasOneThroughByPK<ReferencedRecord,
3110 FieldType::OwnerSelector,
3111 FieldType::ThroughSelector>(pkValue);
3115 if constexpr (IsHasManyThrough<FieldType> && HasPrimaryKey<Record>)
3117 using ReferencedRecord = FieldType::ReferencedRecord;
3118 using ThroughRecord = FieldType::ThroughRecord;
3121 field.SetAutoLoader(
typename FieldType::Loader {
3122 .count = [pkValue]() ->
size_t {
3126 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3129 FieldType::OwnerSelector,
3130 FieldType::ReferencedSelector>(
3132 dm._stmt.
Prepare(selectQuery.Count());
3139 .all = [pkValue]() -> FieldType::ReferencedRecordList {
3142 typename FieldType::ReferencedRecordList result;
3143 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3146 FieldType::OwnerSelector,
3147 FieldType::ReferencedSelector>(
3149 result = detail::ToSharedPtrList(dm.
Query<ReferencedRecord>(selectQuery.All(), pk));
3154 [pkValue](
auto const& each) {
3157 dm.CallOnHasManyThroughByPK<ReferencedRecord,
3160 FieldType::OwnerSelector,
3161 FieldType::ReferencedSelector>(
3164 stmt.Prepare(selectQuery.All());
3165 auto cursor = stmt.Execute(pk);
3166 auto referencedRecord = ReferencedRecord {};
3167 dm.BindOutputColumns(referencedRecord, cursor);
3172 each(referencedRecord);
3178 referencedRecord = ReferencedRecord {};
3179 dm.BindOutputColumns(referencedRecord, cursor);
3188#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
3189 constexpr auto ctx = std::meta::access_context::current();
3191 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
3192 constexpr auto localctx = std::meta::access_context::current();
3193 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
3194 using FieldType =
typename[:std::meta::type_of(members[I]):];
3195 callback.template operator()<I, FieldType>(record.[:members[I]:]);
3202template <
typename T>
3205 ZoneScopedN(
"DataMapper::Execute(string)");
3206 ZoneTextObject(sqlQueryString);
3212#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.
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()
void Update(Record &record)
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 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 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
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.