91 auto connect(
const std::string& connectionString) -> void;
96 auto connect(db::BackendType requestedBackend,
const std::string& connectionString) -> void;
111 template <
typename SchemaType,
typename T>
114 constexpr auto model = model::modelView<SchemaType, T>();
115 ensureQuerySupported(model, query.getData());
116 std::vector<T> result;
117 const auto statement = getCommandGenerator().select(model, query.getData());
118 ensureStatementWithinBindLimit(statement.parameters.size(),
"select");
122 soci::values parameterValues;
123 detail::bindStatementParameters(getBackend().runtime(), parameterValues, statement.parameters);
125 auto readRows = [&]<
bool JoinedValues>()
127 soci::rowset<db::binding::BindingPayload<T, SchemaType, JoinedValues>> preparedRowSet =
128 (sql.prepare << statement.sql, soci::use(parameterValues));
130 for (
auto& payload : preparedRowSet)
132 result.push_back(std::move(payload.value));
136 if (query.getData().shouldJoin)
138 readRows.template operator()<
true>();
142 readRows.template operator()<
false>();
145 loadIncludedCollections<SchemaType>(model, query.getData(), result);
147 catch (
const db::binding::ConversionError&)
149 throw DatabaseError{DatabaseErrorCode::Conversion, backendType,
"select",
150 "A database result cannot be represented by the requested model"};
152 catch (
const soci::soci_error& error)
154 throwTranslatedError(error, DatabaseErrorCode::Statement,
"select");
168 template <
typename SchemaType,
typename Source,
typename Result>
171 constexpr auto model = model::modelView<SchemaType, Source>();
172 ensureQuerySupported(model, query.getData());
173 std::vector<Result> result;
174 const auto statement = getCommandGenerator().select(model, query.getData());
175 ensureStatementWithinBindLimit(statement.parameters.size(),
"select projection");
179 soci::values parameterValues;
180 detail::bindStatementParameters(getBackend().runtime(), parameterValues, statement.parameters);
182 soci::rowset<db::binding::ProjectionPayload<Result>> preparedRowSet =
183 (sql.prepare << statement.sql, soci::use(parameterValues));
185 for (
auto& payload : preparedRowSet)
187 result.push_back(std::move(payload.value));
190 catch (
const db::binding::ConversionError&)
192 throw DatabaseError{DatabaseErrorCode::Conversion, backendType,
"select projection",
193 "A database result cannot be represented by the requested projection"};
195 catch (
const soci::soci_error& error)
197 throwTranslatedError(error, DatabaseErrorCode::Statement,
"select projection");
209 template <
typename SchemaType,
typename T>
212 for (
const auto&
object : objects)
224 template <
typename SchemaType,
typename T>
227 constexpr auto model = model::modelView<SchemaType, T>();
228 ensureModelSupported(model,
"insert");
230 const auto command = getCommandGenerator().insert(model);
232 const db::binding::BindingPayload<T, SchemaType> payload{};
234 payload.value = std::move(
object);
238 soci::values serializedModel;
239 auto indicator = soci::i_ok;
240 soci::type_conversion<db::binding::BindingPayload<T, SchemaType>>::to_base(payload, serializedModel,
243 soci::values parameterValues;
244 const auto parameterCount =
245 detail::bindModelParameters(getBackend().runtime(), parameterValues, serializedModel, model);
246 ensureStatementWithinBindLimit(parameterCount,
"insert");
248 if (parameterCount == 0)
254 sql << command, soci::use(parameterValues);
257 catch (
const db::binding::ConversionError&)
259 throw DatabaseError{DatabaseErrorCode::Conversion, backendType,
"insert",
260 "A model value cannot be represented by the selected backend"};
262 catch (
const soci::soci_error& error)
264 throwTranslatedError(error, DatabaseErrorCode::Statement,
"insert");
275 template <
typename SchemaType,
typename T>
278 constexpr auto model = model::modelView<SchemaType, T>();
279 ensureModelSupported(model,
"update");
281 const auto statement = getCommandGenerator().update(model, update.getData());
283 return executeMutation(statement,
"update");
293 template <
typename SchemaType,
typename T>
294 auto removeImpl(
const query::Predicate& predicate) -> std::size_t
296 constexpr auto model = model::modelView<SchemaType, T>();
297 ensureModelSupported(model,
"remove");
299 const auto statement = getCommandGenerator().remove(model, predicate);
301 return executeMutation(statement,
"remove");
309 template <
typename SchemaType,
typename T>
312 constexpr auto model = model::modelView<SchemaType, T>();
313 ensureModelSupported(model,
"create table");
315 "idempotent table creation is not supported");
316 const auto command = getCommandGenerator().createTable(model);
317 executeSql(command,
"create table");
325 template <
typename SchemaType,
typename T>
328 constexpr auto model = model::modelView<SchemaType, T>();
329 ensureModelSupported(model,
"drop table");
331 "idempotent table removal is not supported");
332 const auto command = getCommandGenerator().dropTable(model);
333 executeSql(command,
"drop table");
342 template <
typename SchemaType,
typename T>
345 const auto owner = model::modelView<SchemaType, T>();
346 const auto ownsJunctionTable = detail::hasOwningJunction(owner);
348 if (not ownsJunctionTable)
353 ensureModelSupported(owner,
"create relation tables");
354 ensureRelationTableEndpointsExist(owner);
356 for (
const auto& command : db::relations::createTableStatements(getBackend().dialect(), owner))
358 executeSql(command,
"create relation table");
365 template <
typename SchemaType,
typename T>
368 const auto owner = model::modelView<SchemaType, T>();
369 const auto ownsJunctionTable = detail::hasOwningJunction(owner);
371 if (not ownsJunctionTable)
376 ensureModelSupported(owner,
"drop relation tables");
378 "junction tables are not supported");
380 "idempotent table removal is not supported");
382 for (
const auto& command : db::relations::dropTableStatements(getBackend().dialect(), owner))
384 executeSql(command,
"drop relation table");
392 template <
typename SchemaType,
typename Owner,
typename Target>
393 auto linkImpl(
const Owner& owner, std::string_view relationField,
const Target& target) -> std::size_t
395 constexpr auto ownerDescriptor = model::modelView<SchemaType, Owner>();
396 const auto* relation = ownerDescriptor.findRelation(relationField);
398 if (relation ==
nullptr or relation->kind == model::RelationKind::ToOne)
400 throw std::invalid_argument{
"Unknown collection relation: " + std::string{relationField}};
403 const auto targetDescriptor = ownerDescriptor.resolveTarget(*relation);
404 if (targetDescriptor ==
nullptr or targetDescriptor->type != model::typeId<Target>())
406 throw std::invalid_argument{
"Relation target type does not match mapping: " + std::string{relationField}};
409 ensureModelSupported(ownerDescriptor,
"link relation");
410 ensureModelSupported(*targetDescriptor,
"link relation");
411 const auto ownerKey = db::binding::getPrimaryKey<SchemaType>(owner);
412 const auto targetKey = db::binding::getPrimaryKey<SchemaType>(target);
414 if (relation->kind == model::RelationKind::OneToMany)
417 "one-to-many relations are not supported");
422 "many-to-many relations are not supported");
424 "idempotent relation links are not supported");
426 requireCapability(relation->kind == model::RelationKind::OneToMany ?
getBackendCapabilities().mutations.update :
428 "link relation",
"relation mutations are not supported");
430 if (not relationEndpointExists(ownerDescriptor, ownerKey) or
431 not relationEndpointExists(*targetDescriptor, targetKey))
433 throw std::invalid_argument{
"Cannot link relation endpoints that do not exist"};
436 return executeMutation(
437 db::relations::linkStatement(getBackend().dialect(), ownerDescriptor, *relation, ownerKey, targetKey),
445 template <
typename SchemaType,
typename Owner,
typename Target>
446 auto unlinkImpl(
const Owner& owner, std::string_view relationField,
const Target& target) -> std::size_t
448 constexpr auto ownerDescriptor = model::modelView<SchemaType, Owner>();
449 const auto* relation = ownerDescriptor.findRelation(relationField);
451 if (relation ==
nullptr or relation->kind == model::RelationKind::ToOne)
453 throw std::invalid_argument{
"Unknown collection relation: " + std::string{relationField}};
456 const auto targetDescriptor = ownerDescriptor.resolveTarget(*relation);
457 if (targetDescriptor ==
nullptr or targetDescriptor->type != model::typeId<Target>())
459 throw std::invalid_argument{
"Relation target type does not match mapping: " + std::string{relationField}};
462 ensureModelSupported(ownerDescriptor,
"unlink relation");
463 ensureModelSupported(*targetDescriptor,
"unlink relation");
464 requireCapability(relation->kind == model::RelationKind::OneToMany ?
467 "unlink relation",
"the requested collection relation is not supported");
468 requireCapability(relation->kind == model::RelationKind::OneToMany ?
getBackendCapabilities().mutations.update :
470 "unlink relation",
"relation mutations are not supported");
472 return executeMutation(db::relations::unlinkStatement(getBackend().dialect(), ownerDescriptor, *relation,
473 db::binding::getPrimaryKey<SchemaType>(owner),
474 db::binding::getPrimaryKey<SchemaType>(target)),
513 template <typename SchemaType, typename Owner, typename Target,
bool JoinedValues>
514 auto appendCollectionRows(const db::Statement& statement,
515 std::map<db::binding::PrimaryKey, std::vector<Target>>& groupedTargets) ->
void
517 ensureStatementWithinBindLimit(statement.parameters.size(),
"include collection");
518 soci::values parameterValues;
519 detail::bindStatementParameters(getBackend().runtime(), parameterValues, statement.parameters);
520 soci::rowset<db::binding::CollectionPayload<Owner, Target, SchemaType, JoinedValues>> preparedRowSet =
521 (sql.prepare << statement.sql, soci::use(parameterValues));
523 for (
auto& payload : preparedRowSet)
525 groupedTargets[payload.ownerKey].push_back(std::move(payload.value));
529 template <
typename SchemaType,
typename Owner>
530 auto loadIncludedCollections(model::ModelView ownerDescriptor,
const query::SelectSpec& queryData,
531 std::vector<Owner>& owners) ->
void
533 if (queryData.includes.empty())
538 for (
const auto& includedRelation : queryData.includes)
540 const auto* relation = ownerDescriptor.findRelation(includedRelation);
542 if (relation ==
nullptr or relation->kind == model::RelationKind::ToOne)
544 throw std::invalid_argument{
"Unknown collection relation: " + includedRelation};
553 constexpr auto reflectedFields = reflection::fields<Owner>();
554 auto ownerFields = reflection::fieldPointers(owners.front());
555 auto loadField = [
this, &queryData, &owners, ownerDescriptor, &reflectedFields](
auto fieldIndex,
auto* field)
557 using collection_t = std::decay_t<
decltype(*field)>;
559 if constexpr (orm::is_relation_collection_v<collection_t>)
561 const auto relationName = std::string{reflectedFields[fieldIndex].name};
563 if (std::ranges::find(queryData.includes, relationName) == queryData.includes.end())
568 const auto* relation = ownerDescriptor.findRelation(relationName);
569 assert(relation !=
nullptr);
571 loadCollectionField<SchemaType, decltype(fieldIndex)::value, Owner, collection_t>(
572 ownerDescriptor, queryData, owners, *relation);
576 utils::constexpr_for_tuple(ownerFields, loadField);
579 template <
typename SchemaType, std::
size_t FieldIndex,
typename Owner,
typename Collection>
580 auto loadCollectionField(model::ModelView ownerDescriptor,
const query::SelectSpec& queryData,
581 std::vector<Owner>& owners,
const model::RelationView& relation) ->
void
583 using target_t = orm::relation_target_t<Collection>;
585 const auto targetDescriptor =
586 detail::requireCollectionTarget(ownerDescriptor, relation, model::typeId<target_t>());
588 const auto ownerPrimaryKey = db::binding::getPrimaryKeyColumns(ownerDescriptor);
589 const auto runtimeLimits = getBackendRuntimeLimits();
590 const auto parameterBudget = runtimeLimits.maxBindParameters.value_or(std::numeric_limits<std::size_t>::max());
592 if (parameterBudget < ownerPrimaryKey.size())
594 throw DatabaseError{DatabaseErrorCode::UnsupportedFeature, backendType,
"include collection",
595 "The backend bind-parameter limit is too small for the relation primary key"};
598 const auto batchSize = runtimeLimits.maxBindParameters.has_value() ?
599 std::max<std::size_t>(1, parameterBudget / ownerPrimaryKey.size()) :
601 std::map<db::binding::PrimaryKey, std::vector<target_t>> groupedTargets;
603 orm::Query<target_t> targetQuery;
605 if (not queryData.shouldJoin)
607 targetQuery.disableJoining();
610 const auto baseTargetStatement = getCommandGenerator().select(*targetDescriptor, targetQuery.getData());
612 assert(baseTargetStatement.parameters.empty());
614 for (std::size_t batchStart = 0; batchStart < owners.size(); batchStart += batchSize)
616 const auto batchEnd = std::min(owners.size(), batchStart + batchSize);
617 std::vector<db::binding::PrimaryKey> ownerKeys;
618 ownerKeys.reserve(batchEnd - batchStart);
620 for (
auto ownerIndex = batchStart; ownerIndex < batchEnd; ++ownerIndex)
622 ownerKeys.push_back(db::binding::getPrimaryKey<SchemaType>(owners[ownerIndex]));
625 const auto statement =
626 db::relations::collectionSelectStatement(getBackend().dialect(), ownerDescriptor, relation,
627 baseTargetStatement.sql, ownerKeys, queryData.shouldJoin);
628 if (queryData.shouldJoin)
630 appendCollectionRows<SchemaType, Owner, target_t, true>(statement, groupedTargets);
634 appendCollectionRows<SchemaType, Owner, target_t, false>(statement, groupedTargets);
638 for (
auto& owner : owners)
640 const auto ownerKey = db::binding::getPrimaryKey<SchemaType>(owner);
641 auto ownerFields = reflection::fieldPointers(owner);
642 auto* collection = std::get<FieldIndex>(ownerFields);
643 const auto targets = groupedTargets.find(ownerKey);
645 if (targets == groupedTargets.end())
647 collection->setLoaded({});
651 collection->setLoaded(targets->second);
656 auto executeMutation(
const db::Statement& statement, std::string_view operation) -> std::size_t;
657 auto executeSql(std::string_view statement, std::string_view operation) -> void;
658 auto relationEndpointExists(model::ModelView model,
const db::binding::PrimaryKey& key) -> bool;
659 auto tableExists(std::string_view tableName) -> bool;
660 auto ensureRelationTableEndpointsExist(model::ModelView owner) -> void;
661 [[nodiscard]]
auto getBackend() const -> const db::BackendProvider&;
662 [[nodiscard]] auto getCommandGenerator() const -> const db::CommandGenerator&;
663 [[nodiscard]] auto getBackendRuntimeLimits() -> db::BackendRuntimeLimits;
664 auto ensureStatementWithinBindLimit(std::
size_t parameterCount, std::string_view operation) ->
void;
665 auto ensureModelSupported(model::ModelView model, std::string_view operation) const ->
void;
666 auto ensureQuerySupported(model::ModelView model, const query::SelectSpec& spec) const ->
void;
667 auto ensureAffectedRowsAvailable(std::string_view operation) const ->
void;
668 auto requireCapability(
bool supported, std::string_view operation, std::string_view message) const ->
void;
669 [[noreturn]] auto throwTranslatedError(const soci::soci_error& error, DatabaseErrorCode fallback,
670 std::string_view operation) ->
void;
673 std::unique_ptr<soci::transaction> transaction;
674 bool transactionFailed = false;
675 db::BackendType backendType;
676 db::CommandGeneratorFactory commandGeneratorFactory;
677 const db::BackendProvider* backend =
nullptr;