134 template <
typename Record>
135 static std::string
Inspect(Record
const& record);
138 template <
typename Record>
142 template <
typename FirstRecord,
typename... MoreRecords>
146 template <
typename Record>
150 template <
typename FirstRecord,
typename... MoreRecords>
158 template <
typename Record>
159 RecordPrimaryKeyType<Record>
Create(Record& record);
166 template <
typename Record>
167 RecordPrimaryKeyType<Record>
CreateExplicit(Record
const& record);
173 template <
typename Record,
typename... PrimaryKeyTypes>
174 std::optional<Record>
QuerySingle(PrimaryKeyTypes&&... primaryKeys);
183 template <
typename Record,
typename... PrimaryKeyTypes>
187 template <
typename Record,
typename... InputParameters>
188 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
219 template <
typename Record,
typename... InputParameters>
220 std::vector<Record>
Query(std::string_view sqlQueryString, InputParameters&&... inputParameters);
246 template <
typename ElementMask,
typename Record,
typename... InputParameters>
247 std::vector<Record>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters);
273 template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions = {}>
275 std::vector<std::tuple<First, Second, Rest...>>
Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery);
278 template <
typename FirstRecord,
typename NextRecord,
DataMapperOptions QueryOptions = {}>
279 requires DataMapperRecord<FirstRecord> && DataMapperRecord<NextRecord>
280 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>, QueryOptions>
Query()
284 auto const emplaceRecordsFrom = [&fields]<
typename Record>() {
285 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
288 fields += std::format(R
"("{}"."{}")", RecordTableName<Record>, FieldNameAt<I, Record>);
292 emplaceRecordsFrom.template operator()<FirstRecord>();
293 emplaceRecordsFrom.template operator()<NextRecord>();
295 return SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, NextRecord>, QueryOptions>(*
this, std::move(fields));
310 template <
typename Record, DataMapperOptions QueryOptions = {}>
314 Reflection::EnumerateMembers<Record>([&fields]<
size_t I,
typename Field>() {
318 fields += RecordTableName<Record>;
320 fields += FieldNameAt<I, Record>;
336 template <
typename Record>
337 void Update(Record& record);
340 template <
typename Record>
341 std::size_t
Delete(Record
const& record);
346 return _connection.
Query(tableName);
350 template <
typename Record>
351 bool IsModified(Record
const& record)
const noexcept;
354 template <
typename Record>
358 template <
typename Record>
365 template <
typename Record>
372 template <
typename T>
373 [[nodiscard]] std::optional<T>
Execute(std::string_view sqlQueryString);
382 template <
typename Record,
typename... Args>
385 template <
typename Record,
typename ValueType>
386 void SetId(Record& record, ValueType&&
id);
388 template <
typename Record,
size_t InitialOffset = 1>
389 Record& BindOutputColumns(Record& record);
391 template <
typename Record,
size_t InitialOffset = 1>
392 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
394 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
395 Record& BindOutputColumns(Record& record);
397 template <
typename ElementMask,
typename Record,
size_t InitialOffset = 1>
398 Record& BindOutputColumns(Record& record,
SqlStatement* stmt);
400 template <
typename FieldType>
401 std::optional<typename FieldType::ReferencedRecord> LoadBelongsTo(FieldType::ValueType value);
403 template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
406 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
409 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
412 template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
413 void CallOnHasMany(Record& record, Callable
const& callback);
415 template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
416 void CallOnHasManyThrough(Record& record, Callable
const& callback);
426 template <
typename FieldType>
427 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType)
noexcept
429 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
436 if constexpr (IsField<FieldType>)
438 if constexpr (detail::OneOf<
typename FieldType::ValueType,
444 || IsSqlDynamicString<typename FieldType::ValueType>
445 || IsSqlDynamicBinary<typename FieldType::ValueType>)
454 template <DataMapperRecord Record>
455 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType)
noexcept
457 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
461 Reflection::EnumerateMembers<Record>([&result]<
size_t I,
typename Field>() {
462 if constexpr (IsField<Field>)
464 if constexpr (detail::OneOf<
typename Field::ValueType,
470 || IsSqlDynamicString<typename Field::ValueType>
471 || IsSqlDynamicBinary<typename Field::ValueType>)
481 template <
typename Record>
482 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLSMALLINT startOffset)
484 Reflection::EnumerateMembers(record,
485 [reader = &reader, i = startOffset]<
size_t I,
typename Field>(Field& field)
mutable {
486 if constexpr (IsField<Field>)
488 reader->BindOutputColumn(i++, &field.MutableValue());
490 else if constexpr (IsBelongsTo<Field>)
492 reader->BindOutputColumn(i++, &field.MutableValue());
494 else if constexpr (SqlOutputColumnBinder<Field>)
496 reader->BindOutputColumn(i++, &field);
501 template <
typename Record>
502 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
504 BindAllOutputColumnsWithOffset(reader, record, 1);
510 template <
typename ElementMask,
typename Record>
511 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
513 Reflection::EnumerateMembers<ElementMask>(
514 record, [reader = &reader, &indexFromQuery]<
size_t I,
typename Field>(Field& field)
mutable {
516 if constexpr (IsField<Field>)
519 field.MutableValue() =
520 reader->GetNullableColumn<
typename Field::ValueType::value_type>(indexFromQuery);
522 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(indexFromQuery);
524 else if constexpr (SqlGetColumnNativeType<Field>)
526 if constexpr (IsOptionalBelongsTo<Field>)
527 field = reader->GetNullableColumn<
typename Field::BaseType>(indexFromQuery);
529 field = reader->GetColumn<Field>(indexFromQuery);
534 template <
typename Record>
535 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
537 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(
538 reader, record, indexFromQuery);
541 template <
typename FirstRecord,
typename SecondRecord>
543 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
545 auto& [firstRecord, secondRecord] = record;
547 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
548 if constexpr (IsField<Field>)
551 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(I + 1);
553 field.MutableValue() = reader->GetColumn<
typename Field::ValueType>(I + 1);
555 else if constexpr (SqlGetColumnNativeType<Field>)
558 field = reader->GetNullableColumn<
typename Field::BaseType>(I + 1);
560 field = reader->GetColumn<Field>(I + 1);
564 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<
size_t I,
typename Field>(Field& field)
mutable {
565 if constexpr (IsField<Field>)
568 field.MutableValue() = reader->GetNullableColumn<
typename Field::ValueType::value_type>(
569 Reflection::CountMembers<FirstRecord> + I + 1);
571 field.MutableValue() =
572 reader->GetColumn<
typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
574 else if constexpr (SqlGetColumnNativeType<Field>)
578 reader->GetNullableColumn<
typename Field::BaseType>(Reflection::CountMembers<FirstRecord> + I + 1);
580 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
585 template <
typename Record>
586 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
588 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
590 if (outputColumnsBound)
591 BindAllOutputColumns(reader, record);
593 if (!reader.FetchRow())
596 if (!outputColumnsBound)
597 GetAllColumns(reader, record);
603template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
604inline SqlCoreDataMapperQueryBuilder<Record, Derived, QueryOptions>::SqlCoreDataMapperQueryBuilder(
605 DataMapper& dm, std::string fields)
noexcept:
607 _formatter { dm.Connection().QueryFormatter() },
608 _fields { std::move(fields) }
612template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
616 stmt.ExecuteDirect(_formatter.SelectCount(this->_query.distinct,
617 RecordTableName<Record>,
618 this->_query.searchCondition.tableAlias,
619 this->_query.searchCondition.tableJoins,
620 this->_query.searchCondition.condition));
621 auto reader = stmt.GetResultCursor();
622 if (reader.FetchRow())
623 return reader.GetColumn<
size_t>(1);
627template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
632 auto const query = _formatter.SelectFirst(this->_query.distinct,
634 RecordTableName<Record>,
635 this->_query.searchCondition.tableAlias,
636 this->_query.searchCondition.tableJoins,
637 this->_query.searchCondition.condition,
638 this->_query.orderBy,
641 stmt.ExecuteDirect(query);
642 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
647template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
652 auto const query = _formatter.Delete(RecordTableName<Record>,
653 this->_query.searchCondition.tableAlias,
654 this->_query.searchCondition.tableJoins,
655 this->_query.searchCondition.condition);
662template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
666 auto records = std::vector<Record> {};
668 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
670 RecordTableName<Record>,
671 this->_query.searchCondition.tableAlias,
672 this->_query.searchCondition.tableJoins,
673 this->_query.searchCondition.condition,
674 this->_query.orderBy,
675 this->_query.groupBy));
676 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
682 if constexpr (QueryOptions.loadRelations)
684 for (
auto& record: records)
686 _dm.ConfigureRelationAutoLoading(record);
693template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
695#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
696 requires(is_aggregate_type(parent_of(
Field)))
698 requires std::is_member_object_pointer_v<
decltype(
Field)>
703 auto result = std::vector<value_type> {};
706 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
707 FullyQualifiedNamesOf<Field>.string_view(),
708 RecordTableName<Record>,
709 this->_query.searchCondition.tableAlias,
710 this->_query.searchCondition.tableJoins,
711 this->_query.searchCondition.condition,
712 this->_query.orderBy,
713 this->_query.groupBy));
715 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(stmt.Connection().ServerType());
718 auto& value = result.emplace_back();
719 if (outputColumnsBound)
720 reader.BindOutputColumn(1, &value);
728 if (!outputColumnsBound)
735template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
736template <
auto... ReferencedFields>
737 requires(
sizeof...(ReferencedFields) >= 2)
740 auto records = std::vector<Record> {};
741 auto stmt = SqlStatement { _dm.Connection() };
743 stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
744 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
745 RecordTableName<Record>,
746 this->_query.searchCondition.tableAlias,
747 this->_query.searchCondition.tableJoins,
748 this->_query.searchCondition.condition,
749 this->_query.orderBy,
750 this->_query.groupBy));
752 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
753 SqlResultCursor reader = stmt.GetResultCursor();
756 auto& record = records.emplace_back();
757 if (outputColumnsBound)
758#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
759 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
761 reader.BindOutputColumns(&(record.*ReferencedFields)...);
763 if (!reader.FetchRow())
768 if (!outputColumnsBound)
770 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
771 detail::GetAllColumns<ElementMask>(reader, record);
778template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
781 std::optional<Record> record {};
783 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
785 RecordTableName<Record>,
786 this->_query.searchCondition.tableAlias,
787 this->_query.searchCondition.tableJoins,
788 this->_query.searchCondition.condition,
789 this->_query.orderBy,
791 Derived::ReadResult(stmt.Connection().ServerType(), stmt.GetResultCursor(), &record);
792 if constexpr (QueryOptions.loadRelations)
795 _dm.ConfigureRelationAutoLoading(record.value());
800template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
802#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
803 requires(is_aggregate_type(parent_of(
Field)))
805 requires std::is_member_object_pointer_v<
decltype(
Field)>
809 auto constexpr count = 1;
811 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
812 FullyQualifiedNamesOf<Field>.string_view(),
813 RecordTableName<Record>,
814 this->_query.searchCondition.tableAlias,
815 this->_query.searchCondition.tableJoins,
816 this->_query.searchCondition.condition,
817 this->_query.orderBy,
819 if (
SqlResultCursor reader = stmt.GetResultCursor(); reader.FetchRow())
824template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
825template <
auto... ReferencedFields>
826 requires(
sizeof...(ReferencedFields) >= 2)
829 auto optionalRecord = std::optional<Record> {};
831 auto stmt = SqlStatement { _dm.Connection() };
832 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
833 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
834 RecordTableName<Record>,
835 this->_query.searchCondition.tableAlias,
836 this->_query.searchCondition.tableJoins,
837 this->_query.searchCondition.condition,
838 this->_query.orderBy,
841 auto& record = optionalRecord.emplace();
842 SqlResultCursor reader = stmt.GetResultCursor();
843 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
844 if (outputColumnsBound)
845#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
846 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
848 reader.BindOutputColumns(&(record.*ReferencedFields)...);
850 if (!reader.FetchRow())
852 if (!outputColumnsBound)
854 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
855 detail::GetAllColumns<ElementMask>(reader, record);
858 if constexpr (QueryOptions.loadRelations)
859 _dm.ConfigureRelationAutoLoading(record);
861 return optionalRecord;
864template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
867 auto records = std::vector<Record> {};
870 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
872 RecordTableName<Record>,
873 this->_query.searchCondition.tableAlias,
874 this->_query.searchCondition.tableJoins,
875 this->_query.searchCondition.condition,
876 this->_query.orderBy,
878 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
880 if constexpr (QueryOptions.loadRelations)
882 for (
auto& record: records)
883 _dm.ConfigureRelationAutoLoading(record);
888template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
891 auto records = std::vector<Record> {};
893 records.reserve(limit);
895 _formatter.SelectRange(this->_query.distinct,
897 RecordTableName<Record>,
898 this->_query.searchCondition.tableAlias,
899 this->_query.searchCondition.tableJoins,
900 this->_query.searchCondition.condition,
901 !this->_query.orderBy.empty()
902 ? this->_query.orderBy
903 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
904 this->_query.groupBy,
907 Derived::ReadResults(stmt.Connection().ServerType(), stmt.GetResultCursor(), &records);
908 if constexpr (QueryOptions.loadRelations)
910 for (
auto& record: records)
911 _dm.ConfigureRelationAutoLoading(record);
916template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
917template <
auto... ReferencedFields>
920 auto records = std::vector<Record> {};
921 auto stmt = SqlStatement { _dm.Connection() };
922 records.reserve(limit);
924 _formatter.SelectRange(this->_query.distinct,
925 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
926 RecordTableName<Record>,
927 this->_query.searchCondition.tableAlias,
928 this->_query.searchCondition.tableJoins,
929 this->_query.searchCondition.condition,
930 !this->_query.orderBy.empty()
931 ? this->_query.orderBy
932 : std::format(
" ORDER BY \"{}\" ASC",
FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
933 this->_query.groupBy,
937 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
938 SqlResultCursor reader = stmt.GetResultCursor();
941 auto& record = records.emplace_back();
942 if (outputColumnsBound)
943#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
944 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
946 reader.BindOutputColumns(&(record.*ReferencedFields)...);
948 if (!reader.FetchRow())
953 if (!outputColumnsBound)
955 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
956 detail::GetAllColumns<ElementMask>(reader, record);
960 if constexpr (QueryOptions.loadRelations)
962 for (
auto& record: records)
963 _dm.ConfigureRelationAutoLoading(record);
969template <
typename Record,
typename Derived, DataMapperOptions QueryOptions>
970template <
auto... ReferencedFields>
973 auto records = std::vector<Record> {};
974 auto stmt = SqlStatement { _dm.Connection() };
976 stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
977 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
978 RecordTableName<Record>,
979 this->_query.searchCondition.tableAlias,
980 this->_query.searchCondition.tableJoins,
981 this->_query.searchCondition.condition,
982 this->_query.orderBy,
985 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(stmt.Connection().ServerType());
986 SqlResultCursor reader = stmt.GetResultCursor();
989 auto& record = records.emplace_back();
990 if (outputColumnsBound)
991#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
992 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
994 reader.BindOutputColumns(&(record.*ReferencedFields)...);
996 if (!reader.FetchRow())
1001 if (!outputColumnsBound)
1003 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
1004 detail::GetAllColumns<ElementMask>(reader, record);
1008 if constexpr (QueryOptions.loadRelations)
1010 for (
auto& record: records)
1011 _dm.ConfigureRelationAutoLoading(record);
1017template <
typename Record, DataMapperOptions QueryOptions>
1018void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResults(SqlServerType sqlServerType,
1019 SqlResultCursor reader,
1020 std::vector<Record>* records)
1024 Record& record = records->emplace_back();
1025 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1027 records->pop_back();
1033template <
typename Record, DataMapperOptions QueryOptions>
1034void SqlAllFieldsQueryBuilder<Record, QueryOptions>::ReadResult(SqlServerType sqlServerType,
1035 SqlResultCursor reader,
1036 std::optional<Record>* optionalRecord)
1038 Record& record = optionalRecord->emplace();
1039 if (!detail::ReadSingleResult(sqlServerType, reader, record))
1040 optionalRecord->reset();
1043template <
typename FirstRecord,
typename SecondRecord, DataMapperOptions QueryOptions>
1044void SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions>::ReadResults(
1045 SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
1049 auto& record = records->emplace_back();
1050 auto& [firstRecord, secondRecord] = record;
1052 using FirstRecordType = std::remove_cvref_t<
decltype(firstRecord)>;
1053 using SecondRecordType = std::remove_cvref_t<
decltype(secondRecord)>;
1055 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
1056 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
1057 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
1059 if (canSafelyBindAll)
1061 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
1062 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
1065 if (!reader.FetchRow())
1067 records->pop_back();
1071 if (!canSafelyBindAll)
1072 detail::GetAllColumns(reader, record);
1076template <
typename Record>
1082 Reflection::CallOnMembers(record, [&str]<
typename Name,
typename Value>(Name
const& name, Value
const& value) {
1088 if constexpr (Value::IsOptional)
1090 if (!value.Value().has_value())
1092 str += std::format(
"{} {} := <nullopt>", Reflection::TypeNameOf<Value>, name);
1096 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value().value());
1099 else if constexpr (IsBelongsTo<Value>)
1101 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.Value());
1103 else if constexpr (std::same_as<typename Value::ValueType, char>)
1108 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value.InspectValue());
1111 else if constexpr (!IsHasMany<Value> && !IsHasManyThrough<Value> && !IsHasOneThrough<Value> && !IsBelongsTo<Value>)
1112 str += std::format(
"{} {} := {}", Reflection::TypeNameOf<Value>, name, value);
1114 return "{\n" + std::move(str) +
"\n}";
1117template <
typename Record>
1123 auto createTable = migration.
CreateTable(RecordTableName<Record>);
1124#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1125 constexpr auto ctx = std::meta::access_context::current();
1126 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1128 using FieldType =
typename[:std::meta::type_of(el):];
1131 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1132 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameOf<el>),
1133 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1134 else if constexpr (FieldType::IsPrimaryKey)
1135 createTable.PrimaryKey(std::string(FieldNameOf<el>),
1136 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1137 else if constexpr (IsBelongsTo<FieldType>)
1139 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
1140 auto index = size_t(-1);
1141 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1142 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
1143 if constexpr (IsField<ReferencedFieldType>)
1144 if constexpr (ReferencedFieldType::IsPrimaryKey)
1149 createTable.ForeignKey(
1150 std::string(FieldNameOf<el>),
1151 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1153 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1154 .columnName = std::string { FieldNameOf<FieldType::ReferencedField> } });
1156 else if constexpr (FieldType::IsMandatory)
1157 createTable.RequiredColumn(std::string(FieldNameOf<el>),
1158 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1160 createTable.Column(std::string(FieldNameOf<el>), SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1165 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1168 if constexpr (IsAutoIncrementPrimaryKey<FieldType>)
1169 createTable.PrimaryKeyWithAutoIncrement(std::string(FieldNameAt<I, Record>),
1170 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1171 else if constexpr (FieldType::IsPrimaryKey)
1172 createTable.PrimaryKey(std::string(FieldNameAt<I, Record>),
1173 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1174 else if constexpr (IsBelongsTo<FieldType>)
1176 constexpr size_t referencedFieldIndex = []()
constexpr ->
size_t {
1177 auto index = size_t(-1);
1178 Reflection::EnumerateMembers<typename FieldType::ReferencedRecord>(
1179 [&index]<
size_t J,
typename ReferencedFieldType>()
constexpr ->
void {
1180 if constexpr (IsField<ReferencedFieldType>)
1181 if constexpr (ReferencedFieldType::IsPrimaryKey)
1186 createTable.ForeignKey(
1187 std::string(FieldNameAt<I, Record>),
1188 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>,
1190 .
tableName = std::string { RecordTableName<typename FieldType::ReferencedRecord> },
1192 std::string { FieldNameAt<referencedFieldIndex, typename FieldType::ReferencedRecord> } });
1194 else if constexpr (FieldType::IsMandatory)
1195 createTable.RequiredColumn(std::string(FieldNameAt<I, Record>),
1196 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1198 createTable.Column(std::string(FieldNameAt<I, Record>),
1199 SqlColumnTypeDefinitionOf<typename FieldType::ValueType>);
1203 return migration.GetPlan().ToSql();
1206template <
typename FirstRecord,
typename... MoreRecords>
1209 std::vector<std::string> output;
1210 auto const append = [&output](
auto const& sql) {
1211 output.insert(output.end(), sql.begin(), sql.end());
1213 append(CreateTableString<FirstRecord>(serverType));
1214 (append(CreateTableString<MoreRecords>(serverType)), ...);
1218template <
typename Record>
1223 auto const sqlQueryStrings = CreateTableString<Record>(_connection.
ServerType());
1224 for (
auto const& sqlQueryString: sqlQueryStrings)
1228template <
typename FirstRecord,
typename... MoreRecords>
1231 CreateTable<FirstRecord>();
1232 (CreateTable<MoreRecords>(), ...);
1235template <
typename Record>
1240 auto query = _connection.
Query(RecordTableName<Record>).
Insert(
nullptr);
1242#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1243 constexpr auto ctx = std::meta::access_context::current();
1244 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1246 using FieldType =
typename[:std::meta::type_of(el):];
1247 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1248 query.Set(FieldNameOf<el>, SqlWildcard);
1251 Reflection::EnumerateMembers(record, [&query]<
auto I,
typename FieldType>(FieldType
const& ) {
1252 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1253 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1259#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1261 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1263 using FieldType =
typename[:std::meta::type_of(el):];
1264 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1265 _stmt.BindInputParameter(i++, record.[:el:], std::meta::identifier_of(el));
1268 Reflection::CallOnMembers(
1270 [
this, i = SQLSMALLINT { 1 }]<
typename Name,
typename FieldType>(Name
const& name, FieldType
const& field)
mutable {
1271 if constexpr (SqlInputParameterBinder<FieldType> && !IsAutoIncrementPrimaryKey<FieldType>)
1272 _stmt.BindInputParameter(i++, field, name);
1277 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1278 return { _stmt.
LastInsertId(RecordTableName<Record>) };
1279 else if constexpr (HasPrimaryKey<Record>)
1281 RecordPrimaryKeyType<Record>
const* primaryKey =
nullptr;
1282#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1283 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1285 using FieldType =
typename[:std::meta::type_of(el):];
1286 if constexpr (IsField<FieldType>)
1288 if constexpr (FieldType::IsPrimaryKey)
1290 primaryKey = &record.[:el:].Value();
1295 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1296 if constexpr (IsField<FieldType>)
1298 if constexpr (FieldType::IsPrimaryKey)
1300 primaryKey = &field.Value();
1309template <
typename Record>
1312 static_assert(!std::is_const_v<Record>);
1317#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1318 constexpr auto ctx = std::meta::access_context::current();
1319 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1321 using FieldType =
typename[:std::meta::type_of(el):];
1322 if constexpr (IsField<FieldType>)
1323 if constexpr (FieldType::IsPrimaryKey)
1324 if constexpr (FieldType::IsAutoAssignPrimaryKey)
1326 if (!record.[:el:].IsModified())
1328 using ValueType =
typename FieldType::ValueType;
1329 if constexpr (std::same_as<ValueType, SqlGuid>)
1331 if (!record.[:el:].Value())
1334 else if constexpr (
requires { ValueType {} + 1; })
1336 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(std::format(
1337 R
"sql(SELECT MAX("{}") FROM "{}")sql", FieldNameOf<el>, RecordTableName<Record>));
1338 record.[:el:] = maxId.value_or(ValueType {}) + 1;
1344 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType& primaryKeyField) {
1345 if constexpr (PrimaryKeyType::IsAutoAssignPrimaryKey)
1347 if (!primaryKeyField.IsModified())
1349 using ValueType = PrimaryKeyType::ValueType;
1350 if constexpr (std::same_as<ValueType, SqlGuid>)
1352 if (!primaryKeyField.Value())
1355 else if constexpr (
requires { ValueType {} + 1; })
1357 auto maxId =
SqlStatement { _connection }.ExecuteDirectScalar<ValueType>(
1358 std::format(R
"sql(SELECT MAX("{}") FROM "{}")sql",
1359 FieldNameAt<PrimaryKeyIndex, Record>,
1360 RecordTableName<Record>));
1361 primaryKeyField = maxId.value_or(ValueType {}) + 1;
1370 if constexpr (HasAutoIncrementPrimaryKey<Record>)
1371 SetId(record, _stmt.
LastInsertId(RecordTableName<Record>));
1376 if constexpr (HasPrimaryKey<Record>)
1380template <
typename Record>
1385 bool modified =
false;
1387#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1388 auto constexpr ctx = std::meta::access_context::current();
1389 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1391 if constexpr (
requires { record.[:el:].IsModified(); })
1393 modified = modified || record.[:el:].IsModified();
1397 Reflection::CallOnMembers(record, [&modified](
auto const& ,
auto const& field) {
1398 if constexpr (
requires { field.IsModified(); })
1400 modified = modified || field.IsModified();
1408template <
typename Record>
1413 auto query = _connection.
Query(RecordTableName<Record>).
Update();
1415#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1416 auto constexpr ctx = std::meta::access_context::current();
1417 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1419 using FieldType =
typename[:std::meta::type_of(el):];
1422 if (record.[:el:].IsModified())
1423 query.Set(FieldNameOf<el>, SqlWildcard);
1424 if constexpr (IsPrimaryKey<FieldType>)
1425 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1429 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& field) {
1430 if (field.IsModified())
1431 query.Set(FieldNameAt<I, Record>, SqlWildcard);
1434 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1435 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1442#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1443 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1445 if (record.[:el:].IsModified())
1447 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1451 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1453 using FieldType =
typename[:std::meta::type_of(el):];
1454 if constexpr (FieldType::IsPrimaryKey)
1456 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1461 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1462 if (field.IsModified())
1463 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1467 Reflection::CallOnMembersWithoutName(record, [
this, &i]<
size_t I,
typename FieldType>(FieldType
const& field) {
1468 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1469 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1478template <
typename Record>
1483 auto query = _connection.
Query(RecordTableName<Record>).
Delete();
1485#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1486 auto constexpr ctx = std::meta::access_context::current();
1487 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1489 using FieldType =
typename[:std::meta::type_of(el):];
1490 if constexpr (FieldType::IsPrimaryKey)
1491 std::ignore = query.Where(FieldNameOf<el>, SqlWildcard);
1494 Reflection::CallOnMembersWithoutName(record, [&query]<
size_t I,
typename FieldType>(FieldType
const& ) {
1495 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1496 std::ignore = query.Where(FieldNameAt<I, Record>, SqlWildcard);
1502#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1504 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
1506 using FieldType =
typename[:std::meta::type_of(el):];
1507 if constexpr (FieldType::IsPrimaryKey)
1509 _stmt.BindInputParameter(i++, record.[:el:].Value(), FieldNameOf<el>);
1514 Reflection::CallOnMembersWithoutName(
1515 record, [
this, i = SQLSMALLINT { 1 }]<
size_t I,
typename FieldType>(FieldType
const& field)
mutable {
1516 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
1517 _stmt.BindInputParameter(i++, field.Value(), FieldNameAt<I, Record>);
1526template <
typename Record,
typename... PrimaryKeyTypes>
1531 auto queryBuilder = _connection.
Query(RecordTableName<Record>).
Select();
1533 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1536 queryBuilder.Field(FieldNameAt<I, Record>);
1538 if constexpr (FieldType::IsPrimaryKey)
1539 std::ignore = queryBuilder.Where(FieldNameAt<I, Record>, SqlWildcard);
1543 _stmt.
Prepare(queryBuilder.First());
1544 _stmt.
Execute(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1546 auto resultRecord = std::optional<Record> { Record {} };
1549 return std::nullopt;
1551 return resultRecord;
1554template <
typename Record,
typename... PrimaryKeyTypes>
1557 auto record = QuerySingleWithoutRelationAutoLoading<Record>(std::forward<PrimaryKeyTypes>(primaryKeys)...);
1563template <
typename Record,
typename... Args>
1568 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1570 selectQuery.
Field(SqlQualifiedTableColumnName { RecordTableName<Record>, FieldNameAt<I, Record> });
1573 _stmt.
Execute(std::forward<Args>(args)...);
1575 auto resultRecord = std::optional<Record> { Record {} };
1578 return std::nullopt;
1579 return resultRecord;
1584template <
typename Record,
typename... InputParameters>
1586 SqlSelectQueryBuilder::ComposedQuery
const& selectQuery, InputParameters&&... inputParameters)
1588 static_assert(DataMapperRecord<Record> || std::same_as<Record, SqlVariantRow>,
"Record must satisfy DataMapperRecord");
1590 return Query<Record>(selectQuery.ToSql(), std::forward<InputParameters>(inputParameters)...);
1593template <
typename Record,
typename... InputParameters>
1594std::vector<Record>
DataMapper::Query(std::string_view sqlQueryString, InputParameters&&... inputParameters)
1596 auto result = std::vector<Record> {};
1597 if constexpr (std::same_as<Record, SqlVariantRow>)
1599 _stmt.
Prepare(sqlQueryString);
1600 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1604 auto& record = result.emplace_back();
1605 record.reserve(numResultColumns);
1606 for (
auto const i: std::views::iota(1U, numResultColumns + 1))
1614 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1616 _stmt.
Prepare(sqlQueryString);
1617 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1623 auto& record = result.emplace_back();
1625 if (canSafelyBindOutputColumns)
1626 BindOutputColumns(record);
1628 if (!reader.FetchRow())
1631 if (!canSafelyBindOutputColumns)
1632 detail::GetAllColumns(reader, record);
1638 for (
auto& record: result)
1645template <
typename First,
typename Second,
typename... Rest,
DataMapperOptions QueryOptions>
1647std::vector<std::tuple<First, Second, Rest...>>
DataMapper::Query(SqlSelectQueryBuilder::ComposedQuery
const& selectQuery)
1649 using value_type = std::tuple<First, Second, Rest...>;
1650 auto result = std::vector<value_type> {};
1652 _stmt.
Prepare(selectQuery.ToSql());
1656 constexpr auto calculateOffset = []<
size_t I,
typename Tuple>() {
1659 if constexpr (I > 0)
1661 [&]<
size_t... Indices>(std::index_sequence<Indices...>) {
1662 ((Indices < I ? (offset += Reflection::CountMembers<std::tuple_element_t<Indices, Tuple>>) : 0), ...);
1663 }(std::make_index_sequence<I> {});
1668 auto const BindElements = [&](
auto& record) {
1669 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1670 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1671 auto& element = std::get<I>(record);
1672 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1673 this->BindOutputColumns<TupleElement, offset>(element);
1677 auto const GetElements = [&](
auto& record) {
1678 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1679 auto& element = std::get<I>(record);
1680 constexpr size_t offset = calculateOffset.template operator()<I, value_type>();
1681 detail::GetAllColumns(reader, element, offset - 1);
1685 bool const canSafelyBindOutputColumns = [&]() {
1687 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1688 using TupleElement = std::decay_t<std::tuple_element_t<I, value_type>>;
1696 auto& record = result.emplace_back();
1698 if (canSafelyBindOutputColumns)
1699 BindElements(record);
1701 if (!reader.FetchRow())
1704 if (!canSafelyBindOutputColumns)
1705 GetElements(record);
1711 if constexpr (QueryOptions.loadRelations)
1714 for (
auto& record: result)
1716 Reflection::template_for<0, std::tuple_size_v<value_type>>([&]<
auto I>() {
1717 auto& element = std::get<I>(record);
1726template <
typename ElementMask,
typename Record,
typename... InputParameters>
1728 InputParameters&&... inputParameters)
1732 _stmt.
Prepare(selectQuery.ToSql());
1733 _stmt.
Execute(std::forward<InputParameters>(inputParameters)...);
1735 auto records = std::vector<Record> {};
1738 bool const canSafelyBindOutputColumns = detail::CanSafelyBindOutputColumns<Record>(_stmt.
Connection().
ServerType());
1744 auto& record = records.emplace_back();
1746 if (canSafelyBindOutputColumns)
1747 BindOutputColumns<ElementMask>(record);
1749 if (!reader.FetchRow())
1752 if (!canSafelyBindOutputColumns)
1753 detail::GetAllColumns<ElementMask>(reader, record);
1759 for (
auto& record: records)
1765template <
typename Record>
1768 static_assert(!std::is_const_v<Record>);
1771 Reflection::EnumerateMembers(record, []<
size_t I,
typename FieldType>(FieldType& field) {
1772 if constexpr (
requires { field.SetModified(
false); })
1774 field.SetModified(
false);
1779template <
typename Record,
typename Callable>
1780inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Record& record, Callable
const& callable)
1784 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
1785 if constexpr (IsField<FieldType>)
1787 if constexpr (FieldType::IsPrimaryKey)
1789 return callable.template operator()<I, FieldType>(field);
1795template <
typename Record,
typename Callable>
1796inline LIGHTWEIGHT_FORCE_INLINE
void CallOnPrimaryKey(Callable
const& callable)
1798 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1800 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1801 if constexpr (IsField<FieldType>)
1803 if constexpr (FieldType::IsPrimaryKey)
1805 return callable.template operator()<I, FieldType>();
1811template <
typename Record,
typename Callable>
1812inline LIGHTWEIGHT_FORCE_INLINE
void CallOnBelongsTo(Callable
const& callable)
1814 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1816 Reflection::EnumerateMembers<Record>([&]<
size_t I,
typename FieldType>() {
1817 if constexpr (IsBelongsTo<FieldType>)
1819 return callable.template operator()<I, FieldType>();
1824template <
typename FieldType>
1825std::optional<typename FieldType::ReferencedRecord> DataMapper::LoadBelongsTo(FieldType::ValueType value)
1827 using ReferencedRecord = FieldType::ReferencedRecord;
1829 std::optional<ReferencedRecord> record { std::nullopt };
1831#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
1832 auto constexpr ctx = std::meta::access_context::current();
1833 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^ReferencedRecord, ctx)))
1835 using BelongsToFieldType =
typename[:std::meta::type_of(el):];
1836 if constexpr (IsField<BelongsToFieldType>)
1837 if constexpr (BelongsToFieldType::IsPrimaryKey)
1839 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1840 record = std::move(result);
1843 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1847 CallOnPrimaryKey<ReferencedRecord>([&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>() {
1848 if (
auto result = QuerySingle<ReferencedRecord>(value); result)
1849 record = std::move(result);
1852 std::format(
"Loading BelongsTo failed for {}", RecordTableName<ReferencedRecord>));
1858template <
size_t FieldIndex,
typename Record,
typename OtherRecord,
typename Callable>
1859void DataMapper::CallOnHasMany(Record& record, Callable
const& callback)
1861 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1862 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1864 using FieldType = HasMany<OtherRecord>;
1865 using ReferencedRecord = FieldType::ReferencedRecord;
1867 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1868 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1870 .Build([&](
auto& query) {
1871 Reflection::EnumerateMembers<ReferencedRecord>(
1872 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1873 if constexpr (FieldWithStorage<ReferencedFieldType>)
1875 query.Field(FieldNameAt<ReferencedFieldIndex, ReferencedRecord>);
1879 .Where(FieldNameAt<FieldIndex, ReferencedRecord>, SqlWildcard);
1880 callback(query, primaryKeyField);
1884template <
size_t FieldIndex,
typename Record,
typename OtherRecord>
1885void DataMapper::LoadHasMany(Record& record, HasMany<OtherRecord>& field)
1887 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1888 static_assert(DataMapperRecord<OtherRecord>,
"OtherRecord must satisfy DataMapperRecord");
1890 CallOnHasMany<FieldIndex, Record, OtherRecord>(record, [&](SqlSelectQueryBuilder selectQuery,
auto& primaryKeyField) {
1891 field.Emplace(detail::ToSharedPtrList(Query<OtherRecord>(selectQuery.All(), primaryKeyField.Value())));
1895template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
1896void DataMapper::LoadHasOneThrough(Record& record, HasOneThrough<ReferencedRecord, ThroughRecord>& field)
1898 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1899 static_assert(DataMapperRecord<ThroughRecord>,
"ThroughRecord must satisfy DataMapperRecord");
1902 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1904 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToIndex,
typename ThroughBelongsToType>() {
1906 CallOnPrimaryKey<ThroughRecord>([&]<
size_t ThroughPrimaryKeyIndex,
typename ThroughPrimaryKeyType>() {
1908 CallOnBelongsTo<ReferencedRecord>([&]<
size_t ReferencedKeyIndex,
typename ReferencedKeyType>() {
1913 _connection.
Query(RecordTableName<ReferencedRecord>)
1915 .Build([&](
auto& query) {
1916 Reflection::EnumerateMembers<ReferencedRecord>(
1917 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1918 if constexpr (FieldWithStorage<ReferencedFieldType>)
1920 query.Field(SqlQualifiedTableColumnName {
1921 RecordTableName<ReferencedRecord>,
1922 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1926 .InnerJoin(RecordTableName<ThroughRecord>,
1927 FieldNameAt<ThroughPrimaryKeyIndex, ThroughRecord>,
1928 FieldNameAt<ReferencedKeyIndex, ReferencedRecord>)
1929 .InnerJoin(RecordTableName<Record>,
1930 FieldNameAt<PrimaryKeyIndex, Record>,
1931 SqlQualifiedTableColumnName { RecordTableName<ThroughRecord>,
1932 FieldNameAt<ThroughBelongsToIndex, ThroughRecord> })
1934 SqlQualifiedTableColumnName {
1935 RecordTableName<Record>,
1936 FieldNameAt<PrimaryKeyIndex, ThroughRecord>,
1939 if (
auto link = QuerySingle<ReferencedRecord>(std::move(query), primaryKeyField.Value()); link)
1941 field.EmplaceRecord(std::make_shared<ReferencedRecord>(std::move(*link)));
1949template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record,
typename Callable>
1950void DataMapper::CallOnHasManyThrough(Record& record, Callable
const& callback)
1952 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
1955 CallOnPrimaryKey(record, [&]<
size_t PrimaryKeyIndex,
typename PrimaryKeyType>(PrimaryKeyType
const& primaryKeyField) {
1957 CallOnBelongsTo<ThroughRecord>([&]<
size_t ThroughBelongsToRecordIndex,
typename ThroughBelongsToRecordType>() {
1958 using ThroughBelongsToRecordFieldType = Reflection::MemberTypeOf<ThroughBelongsToRecordIndex, ThroughRecord>;
1959 if constexpr (std::is_same_v<typename ThroughBelongsToRecordFieldType::ReferencedRecord, Record>)
1962 CallOnBelongsTo<ThroughRecord>(
1963 [&]<
size_t ThroughBelongsToReferenceRecordIndex,
typename ThroughBelongsToReferenceRecordType>() {
1964 using ThroughBelongsToReferenceRecordFieldType =
1965 Reflection::MemberTypeOf<ThroughBelongsToReferenceRecordIndex, ThroughRecord>;
1966 if constexpr (std::is_same_v<
typename ThroughBelongsToReferenceRecordFieldType::ReferencedRecord,
1969 auto query = _connection.
Query(RecordTableName<ReferencedRecord>)
1971 .Build([&](
auto& query) {
1972 Reflection::EnumerateMembers<ReferencedRecord>(
1973 [&]<
size_t ReferencedFieldIndex,
typename ReferencedFieldType>() {
1974 if constexpr (FieldWithStorage<ReferencedFieldType>)
1976 query.Field(SqlQualifiedTableColumnName {
1977 RecordTableName<ReferencedRecord>,
1978 FieldNameAt<ReferencedFieldIndex, ReferencedRecord> });
1982 .InnerJoin(RecordTableName<ThroughRecord>,
1983 FieldNameAt<ThroughBelongsToReferenceRecordIndex, ThroughRecord>,
1984 SqlQualifiedTableColumnName { RecordTableName<ReferencedRecord>,
1985 FieldNameAt<PrimaryKeyIndex, Record> })
1987 SqlQualifiedTableColumnName {
1988 RecordTableName<ThroughRecord>,
1989 FieldNameAt<ThroughBelongsToRecordIndex, ThroughRecord>,
1992 callback(query, primaryKeyField);
2000template <
typename ReferencedRecord,
typename ThroughRecord,
typename Record>
2001void DataMapper::LoadHasManyThrough(Record& record, HasManyThrough<ReferencedRecord, ThroughRecord>& field)
2003 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2005 CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2006 record, [&](SqlSelectQueryBuilder& selectQuery,
auto& primaryKeyField) {
2007 field.Emplace(detail::ToSharedPtrList(Query<ReferencedRecord>(selectQuery.All(), primaryKeyField.Value())));
2011template <
typename Record>
2014 static_assert(!std::is_const_v<Record>);
2017#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2018 constexpr auto ctx = std::meta::access_context::current();
2019 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2021 using FieldType =
typename[:std::meta::type_of(el):];
2022 if constexpr (IsBelongsTo<FieldType>)
2024 auto& field = record.[:el:];
2025 field = LoadBelongsTo<FieldType>(field.Value());
2027 else if constexpr (IsHasMany<FieldType>)
2029 LoadHasMany<el>(record, record.[:el:]);
2031 else if constexpr (IsHasOneThrough<FieldType>)
2033 LoadHasOneThrough(record, record.[:el:]);
2035 else if constexpr (IsHasManyThrough<FieldType>)
2037 LoadHasManyThrough(record, record.[:el:]);
2041 Reflection::EnumerateMembers(record, [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2042 if constexpr (IsBelongsTo<FieldType>)
2044 field = LoadBelongsTo<FieldType>(field.Value());
2046 else if constexpr (IsHasMany<FieldType>)
2048 LoadHasMany<FieldIndex>(record, field);
2050 else if constexpr (IsHasOneThrough<FieldType>)
2052 LoadHasOneThrough(record, field);
2054 else if constexpr (IsHasManyThrough<FieldType>)
2056 LoadHasManyThrough(record, field);
2062template <
typename Record,
typename ValueType>
2063inline LIGHTWEIGHT_FORCE_INLINE
void DataMapper::SetId(Record& record, ValueType&&
id)
2068#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2070 auto constexpr ctx = std::meta::access_context::current();
2071 template for (
constexpr auto el: define_static_array(nonstatic_data_members_of(^^Record, ctx)))
2073 using FieldType =
typename[:std::meta::type_of(el):];
2074 if constexpr (IsField<FieldType>)
2076 if constexpr (FieldType::IsPrimaryKey)
2078 record.[:el:] = std::forward<ValueType>(
id);
2083 Reflection::EnumerateMembers(record, [&]<
size_t I,
typename FieldType>(FieldType& field) {
2084 if constexpr (IsField<FieldType>)
2086 if constexpr (FieldType::IsPrimaryKey)
2088 field = std::forward<FieldType>(
id);
2095template <
typename Record,
size_t InitialOffset>
2096inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2098 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2099 BindOutputColumns<Record, InitialOffset>(record, &_stmt);
2103template <
typename Record,
size_t InitialOffset>
2104Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2106 return BindOutputColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record, InitialOffset>(
2110template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2111inline LIGHTWEIGHT_FORCE_INLINE Record& DataMapper::BindOutputColumns(Record& record)
2113 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2114 return BindOutputColumns<ElementMask, Record, InitialOffset>(record, &_stmt);
2117template <
typename ElementMask,
typename Record,
size_t InitialOffset>
2118Record& DataMapper::BindOutputColumns(Record& record, SqlStatement* stmt)
2120 static_assert(DataMapperRecord<Record>,
"Record must satisfy DataMapperRecord");
2121 static_assert(!std::is_const_v<Record>);
2122 assert(stmt !=
nullptr);
2124#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2125 auto constexpr ctx = std::meta::access_context::current();
2126 SQLSMALLINT i = SQLSMALLINT { InitialOffset };
2127 template for (
constexpr auto index: define_static_array(template_arguments_of(^^ElementMask)) | std::views::drop(1))
2129 constexpr auto el = nonstatic_data_members_of(^^Record, ctx)[[:index:]];
2130 using FieldType =
typename[:std::meta::type_of(el):];
2131 if constexpr (IsField<FieldType>)
2133 stmt->BindOutputColumn(i++, &record.[:el:].MutableValue());
2135 else if constexpr (SqlOutputColumnBinder<FieldType>)
2137 stmt->BindOutputColumn(i++, &record.[:el:]);
2141 Reflection::EnumerateMembers<ElementMask>(
2142 record, [stmt, i = SQLSMALLINT { InitialOffset }]<
size_t I,
typename Field>(Field& field)
mutable {
2143 if constexpr (IsField<Field>)
2145 stmt->BindOutputColumn(i++, &field.MutableValue());
2147 else if constexpr (SqlOutputColumnBinder<Field>)
2149 stmt->BindOutputColumn(i++, &field);
2157template <
typename Record>
2162 auto const callback = [&]<
size_t FieldIndex,
typename FieldType>(FieldType& field) {
2163 if constexpr (IsBelongsTo<FieldType>)
2165 field.SetAutoLoader(
typename FieldType::Loader {
2166 .loadReference = [value = field.Value()]() -> std::optional<typename FieldType::ReferencedRecord> {
2168 return dm.LoadBelongsTo<FieldType>(value);
2172 else if constexpr (IsHasMany<FieldType>)
2174 using ReferencedRecord = FieldType::ReferencedRecord;
2177 .count = [&record]() ->
size_t {
2180 dm.CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2182 dm._stmt.
Prepare(selectQuery.Count());
2183 dm._stmt.
Execute(primaryKeyField.Value());
2191 [&record, &hasMany]() {
2193 dm.LoadHasMany<FieldIndex>(record, hasMany);
2196 [&record](
auto const& each) {
2198 dm.CallOnHasMany<FieldIndex, Record, ReferencedRecord>(
2201 stmt.Prepare(selectQuery.All());
2202 stmt.Execute(primaryKeyField.Value());
2204 auto referencedRecord = ReferencedRecord {};
2205 dm.BindOutputColumns(referencedRecord, &stmt);
2208 while (stmt.FetchRow())
2210 each(referencedRecord);
2211 dm.BindOutputColumns(referencedRecord, &stmt);
2217 else if constexpr (IsHasOneThrough<FieldType>)
2219 using ReferencedRecord = FieldType::ReferencedRecord;
2220 using ThroughRecord = FieldType::ThroughRecord;
2224 [&record, &hasOneThrough]() {
2226 dm.LoadHasOneThrough<ReferencedRecord, ThroughRecord>(record, hasOneThrough);
2230 else if constexpr (IsHasManyThrough<FieldType>)
2232 using ReferencedRecord = FieldType::ReferencedRecord;
2233 using ThroughRecord = FieldType::ThroughRecord;
2236 .count = [&record]() ->
size_t {
2240 dm.CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2242 dm._stmt.
Prepare(selectQuery.Count());
2243 dm._stmt.
Execute(primaryKeyField.Value());
2251 [&record, &hasManyThrough]() {
2254 dm.LoadHasManyThrough(record, hasManyThrough);
2257 [&record](
auto const& each) {
2260 dm.CallOnHasManyThrough<ReferencedRecord, ThroughRecord>(
2263 stmt.Prepare(selectQuery.All());
2264 stmt.Execute(primaryKeyField.Value());
2266 auto referencedRecord = ReferencedRecord {};
2267 dm.BindOutputColumns(referencedRecord, &stmt);
2270 while (stmt.FetchRow())
2272 each(referencedRecord);
2273 dm.BindOutputColumns(referencedRecord, &stmt);
2281#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
2282 constexpr auto ctx = std::meta::access_context::current();
2284 Reflection::template_for<0, nonstatic_data_members_of(^^Record, ctx).size()>([&callback, &record]<
auto I>() {
2285 constexpr auto localctx = std::meta::access_context::current();
2286 constexpr auto members = define_static_array(nonstatic_data_members_of(^^Record, localctx));
2287 using FieldType =
typename[:std::meta::type_of(members[I]):];
2288 callback.template operator()<I, FieldType>(record.[:members[I]:]);
2291 Reflection::EnumerateMembers(record, callback);
2295template <
typename T>