ores.cpp.repository.repository_impl
Table of Contents
CRUD + temporal queries over sqlgen; custom-method paste points. repository profile. Database access layer using sqlgen parameterised queries, tracking `valid_from`/`valid_to`.
See the Template variable reference for the complete list of available variables and their semantics.
1. Template
The full template source. Edit here and re-tangle with
compass build --direct tangle_codegen_templates to regenerate
library/templates/cpp_domain_type_repository.cpp.mustache.
{{! GENERATED FILE — tangled from projects/ores.codegen/library/templates/cpp_repository.org. Edit the org source. }}
{{{cpp_license}}}
{{#domain_entity}}
#include "ores.{{component_core}}/repository/{{entity_singular}}_repository.hpp"
#include "ores.utility/domain/protocol.hpp"
#include <sqlgen/postgres.hpp>
#include "ores.database/repository/helpers.hpp"
#include "ores.database/repository/bitemporal_operations.hpp"
{{#system_tenant_visible}}
#include "ores.database/service/tenant_context.hpp"
{{/system_tenant_visible}}
#include "ores.{{component_include}}/domain/{{entity_singular}}_json_io.hpp" // IWYU pragma: keep.
#include "ores.{{component_core}}/repository/{{entity_singular}}_entity.hpp"
#include "ores.{{component_core}}/repository/{{entity_singular}}_mapper.hpp"
{{#domain_entity.sql.has_list_by_as_of}}
#include "ores.platform/time/datetime.hpp"
{{/domain_entity.sql.has_list_by_as_of}}
{{#primary_key.has_timestamp_key}}
#include "ores.platform/time/datetime.hpp"
{{/primary_key.has_timestamp_key}}
{{#has_parent_id}}
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid_io.hpp>
{{/has_parent_id}}
{{#primary_key.is_compound}}
#include <stdexcept>
{{/primary_key.is_compound}}
{{#primary_key.is_compound}}
#include <set>
#include <tuple>
{{/primary_key.is_compound}}
<<paste:6141050C-0ED4-4680-B387-7DDDA3A69806>>
namespace ores::{{component}}::repository {
using namespace sqlgen;
using namespace sqlgen::literals;
using namespace ores::logging;
{{^component_is_database}}
using namespace ores::database::repository;
{{/component_is_database}}
std::string {{entity_singular}}_repository::sql() {
return generate_create_table_sql<{{entity_singular}}_entity>(lg());
}
ores::utility::domain::precondition
{{entity_singular}}_repository::replace_claim(context ctx,
const domain::{{entity_singular}}& v) {
const auto current = read_latest(ctx, {{{primary_key.v_args}}});
if (current.empty())
return {ores::utility::domain::precondition_kind::must_not_exist, std::nullopt};
{{#has_audit_columns}}
return {ores::utility::domain::precondition_kind::must_match_version,
static_cast<std::uint32_t>(current.front().version)};
{{/has_audit_columns}}
{{^has_audit_columns}}
// No version column to state, so a replace names no claim at all; the
// store replaces the row as it stands. A create over a live row is refused
// by the read in apply_claim.
return {ores::utility::domain::precondition_kind::any, std::nullopt};
{{/has_audit_columns}}
}
domain::{{entity_singular}} {{entity_singular}}_repository::apply_claim(
context ctx,
const domain::{{entity_singular}}& v,
const ores::utility::domain::precondition& claim) {
using ores::utility::domain::precondition_kind;
auto t = v;
{{#has_audit_columns}}
switch (claim.kind) {
case precondition_kind::must_not_exist:
// Zero states that no current row exists, which is the one meaning the
// store gives a zero version.
t.version = 0;
break;
case precondition_kind::must_match_version:
t.version = claim.version ? static_cast<int>(*claim.version) : 0;
break;
case precondition_kind::any: {
// A caller that claims nothing still has to say what it replaces, so
// the row is read and its version stated. A row that moved on between
// this read and the write is a conflict the trigger raises, never a
// silent overwrite.
const auto current = read_latest(ctx, {{{primary_key.v_args}}});
t.version = current.empty() ? 0 : current.front().version;
break;
}
}
{{/has_audit_columns}}
{{^has_audit_columns}}
// No version column to state, so the claim is honoured by the read alone.
if (claim.kind == precondition_kind::must_match_version)
throw std::invalid_argument(
"{{entity_singular}}_repository::write: this table keeps no version to match");
if (claim.kind == precondition_kind::must_not_exist
&& !read_latest(ctx, {{{primary_key.v_args}}}).empty())
throw std::invalid_argument(
"{{entity_singular}}_repository::write: a current row already exists");
{{/has_audit_columns}}
return t;
}
void {{entity_singular}}_repository::write(context ctx, const domain::{{entity_singular}}& v) {
write(ctx, v, replace_claim(ctx, v));
}
void {{entity_singular}}_repository::write(
context ctx, const std::vector<domain::{{entity_singular}}>& v) {
std::vector<ores::utility::domain::precondition> claims;
claims.reserve(v.size());
for (const auto& item : v)
claims.push_back(replace_claim(ctx, item));
write(ctx, v, claims);
}
void {{entity_singular}}_repository::write(context ctx, const domain::{{entity_singular}}& v,
const ores::utility::domain::precondition& claim) {
BOOST_LOG_SEV(lg(), debug) << "Writing {{entity_singular_words}}. " << {{{primary_key.value_log_fields}}};
{{#current_state}}
const auto t = apply_claim(ctx, v, claim);
const auto query = sqlgen::insert_or_replace({{entity_singular}}_mapper::map(t));
const auto r = sqlgen::session(ctx.connection_pool())
.and_then(sqlgen::begin_transaction)
.and_then(query)
.and_then(sqlgen::commit);
ensure_success(r, lg());
{{/current_state}}
{{^current_state}}
const auto t = apply_claim(ctx, v, claim);
execute_write_query(ctx, {{entity_singular}}_mapper::map(t),
lg(), "Writing {{entity_singular_words}} to database.");
{{/current_state}}
}
void {{entity_singular}}_repository::write(
context ctx, const std::vector<domain::{{entity_singular}}>& v,
const std::vector<ores::utility::domain::precondition>& claims) {
BOOST_LOG_SEV(lg(), debug) << "Writing {{entity_plural_words}}. Count: " << v.size();
std::vector<domain::{{entity_singular}}> batch;
batch.reserve(v.size());
for (std::size_t i = 0; i < v.size(); ++i)
batch.push_back(apply_claim(ctx, v[i], claims[i]));
{{#current_state}}
const auto query = sqlgen::insert_or_replace({{entity_singular}}_mapper::map(batch));
const auto r = sqlgen::session(ctx.connection_pool())
.and_then(sqlgen::begin_transaction)
.and_then(query)
.and_then(sqlgen::commit);
ensure_success(r, lg());
{{/current_state}}
{{^current_state}}
execute_write_query(ctx, {{entity_singular}}_mapper::map(batch),
lg(), "Writing {{entity_plural_words}} to database.");
{{/current_state}}
}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_latest(context ctx) {
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto& chain = ctx.workspace_resolution();
if (!chain.empty()) {
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c.in(chain){{domain_entity.temporal_filter}}) |
order_by({{{primary_key.order_by_cols}}});
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading latest {{entity_plural_words}} (workspace resolution chain).");
}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid{{domain_entity.temporal_filter}}) |
order_by({{{primary_key.order_by_cols}}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}}{{domain_entity.temporal_filter}}) |
order_by({{{primary_key.order_by_cols}}});
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto& chain = ctx.workspace_resolution();
if (!chain.empty()) {
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c.in(chain){{domain_entity.temporal_filter}}) |
order_by({{{primary_key.order_by_cols}}});
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading latest {{entity_plural_words}} (workspace resolution chain).");
}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid{{domain_entity.temporal_filter}}) |
order_by({{{primary_key.order_by_cols}}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
{{^current_state}}
where("valid_to"_c == max.value()) |
{{/current_state}}
order_by({{{primary_key.order_by_cols}}});
{{/has_workspace_id}}
{{/read_tenant_filtered}}
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading latest {{entity_plural_words}}");
}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_latest(context ctx, {{{primary_key.params}}}) {
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{entity_singular_words}}. " << {{{primary_key.log_fields}}};
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid && {{{primary_key.where_and}}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && {{{primary_key.where_and}}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid && {{{primary_key.where_and}}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{primary_key.where_and}}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{/read_tenant_filtered}}
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading latest {{entity_singular_words}} by {{primary_key.column}}.");
}
{{#key_finders}}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_latest_by_{{suffix}}(context ctx,
{{#parent_column}}
const std::string& {{parent_column}},
{{/parent_column}}
const std::string& {{column}}) {
{{#parent_column}}
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{entity_singular_words}} by {{parent_column}}/{{column}}: "
<< {{parent_column}} << "/" << {{column}};
{{/parent_column}}
{{^parent_column}}
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{entity_singular_words}} by {{column}}: " << {{column}};
{{/parent_column}}
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid && {{#parent_column}}"{{parent_column}}"_c == {{parent_column}} && {{/parent_column}}"{{column}}"_c == {{column}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && {{#parent_column}}"{{parent_column}}"_c == {{parent_column}} && {{/parent_column}}"{{column}}"_c == {{column}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid && {{#parent_column}}"{{parent_column}}"_c == {{parent_column}} && {{/parent_column}}"{{column}}"_c == {{column}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{#parent_column}}"{{parent_column}}"_c == {{parent_column}} && {{/parent_column}}"{{column}}"_c == {{column}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{/read_tenant_filtered}}
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading latest {{entity_singular_words}} by {{column}}.");
}
{{/key_finders}}
{{^current_state}}
{{#key_resolvers}}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_any_by_{{suffix}}(context ctx,
const std::string& {{column}}) {
BOOST_LOG_SEV(lg(), debug) << "Reading any {{entity_singular_words}} by {{column}}: " << {{column}};
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid && "{{column}}"_c == {{column}}) |
order_by("valid_from"_c.desc()) | sqlgen::limit(1);
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "{{column}}"_c == {{column}}) |
order_by("valid_from"_c.desc()) | sqlgen::limit(1);
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid && "{{column}}"_c == {{column}}) |
order_by("valid_from"_c.desc()) | sqlgen::limit(1);
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("{{column}}"_c == {{column}}) |
order_by("valid_from"_c.desc()) | sqlgen::limit(1);
{{/has_workspace_id}}
{{/read_tenant_filtered}}
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading any {{entity_singular_words}} by {{column}}.");
}
{{/key_resolvers}}
{{/current_state}}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_all(context ctx, {{{primary_key.params}}}) {
BOOST_LOG_SEV(lg(), debug) << "Reading all {{entity_singular_words}} versions. " << {{{primary_key.log_fields}}};
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid && {{{primary_key.where_and}}}) |
{{^current_state}}
{{#has_audit_columns}}
order_by("version"_c.desc(), "valid_from"_c.desc());
{{/has_audit_columns}}
{{^has_audit_columns}}
order_by("valid_from"_c.desc());
{{/has_audit_columns}}
{{/current_state}}
{{#current_state}}
order_by({{{primary_key.order_by_cols}}});
{{/current_state}}
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && {{{primary_key.where_and}}}) |
{{^current_state}}
{{#has_audit_columns}}
order_by("version"_c.desc(), "valid_from"_c.desc());
{{/has_audit_columns}}
{{^has_audit_columns}}
order_by("valid_from"_c.desc());
{{/has_audit_columns}}
{{/current_state}}
{{#current_state}}
order_by({{{primary_key.order_by_cols}}});
{{/current_state}}
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid && {{{primary_key.where_and}}}) |
{{^current_state}}
{{#has_audit_columns}}
order_by("version"_c.desc(), "valid_from"_c.desc());
{{/has_audit_columns}}
{{^has_audit_columns}}
order_by("valid_from"_c.desc());
{{/has_audit_columns}}
{{/current_state}}
{{#current_state}}
order_by({{{primary_key.order_by_cols}}});
{{/current_state}}
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{primary_key.where_and}}}) |
{{^current_state}}
{{#has_audit_columns}}
order_by("version"_c.desc(), "valid_from"_c.desc());
{{/has_audit_columns}}
{{^has_audit_columns}}
order_by("valid_from"_c.desc());
{{/has_audit_columns}}
{{/current_state}}
{{#current_state}}
order_by({{{primary_key.order_by_cols}}});
{{/current_state}}
{{/has_workspace_id}}
{{/read_tenant_filtered}}
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading all {{entity_singular_words}} versions by {{primary_key.column}}.");
}
{{#has_audit_columns}}
std::optional<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_at_version(context ctx, {{{primary_key.params}}},
std::uint32_t version) {
BOOST_LOG_SEV(lg(), debug) << "Reading {{entity_singular_words}} at version. " << {{{primary_key.log_fields}}}
<< " version: " << version;
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid && {{{primary_key.where_and}}} && "version"_c == version) |
sqlgen::limit(1);
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && {{{primary_key.where_and}}} && "version"_c == version) |
sqlgen::limit(1);
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid && {{{primary_key.where_and}}} && "version"_c == version) |
sqlgen::limit(1);
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{primary_key.where_and}}} && "version"_c == version) |
sqlgen::limit(1);
{{/has_workspace_id}}
{{/read_tenant_filtered}}
const auto entities = execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading {{entity_singular_words}} at version.");
if (entities.empty())
return std::nullopt;
return entities.front();
}
{{/has_audit_columns}}
{{#foreign_keys}}
{{#list_by}}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_latest_by_{{column}}(context ctx, const std::string& {{column}},
std::uint32_t offset, std::uint32_t limit) {
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{entity_plural_words}}. {{column}}: " << {{column}}
<< " offset: " << offset << " limit: " << limit;
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid && "{{column}}"_c == {{column}}{{domain_entity.temporal_filter}}) |
order_by("{{list_by_order_column}}"_c{{#list_by_order_desc}}.desc(){{/list_by_order_desc}}) | sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "{{column}}"_c == {{column}}{{domain_entity.temporal_filter}}) |
order_by("{{list_by_order_column}}"_c{{#list_by_order_desc}}.desc(){{/list_by_order_desc}}) | sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid && "{{column}}"_c == {{column}}{{domain_entity.temporal_filter}}) |
order_by("{{list_by_order_column}}"_c{{#list_by_order_desc}}.desc(){{/list_by_order_desc}}) | sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
{{^current_state}}
where("{{column}}"_c == {{column}} && "valid_to"_c == max.value()) |
{{/current_state}}
{{#current_state}}
where("{{column}}"_c == {{column}}) |
{{/current_state}}
order_by("{{list_by_order_column}}"_c{{#list_by_order_desc}}.desc(){{/list_by_order_desc}}) | sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_workspace_id}}
{{/read_tenant_filtered}}
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading latest {{entity_plural_words}} by {{column}}.");
}
std::uint32_t {{entity_singular}}_repository::get_total_{{entity_singular_short}}_count_by_{{column}}(
context ctx, const std::string& {{column}}) {
BOOST_LOG_SEV(lg(), debug) << "Retrieving total active {{entity_plural_words}} count. {{column}}: " << {{column}};
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
struct count_result { long long count; };
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::select_from<{{entity_singular}}_entity>(sqlgen::count().as<"count">()) |
where({{{tenant_where}}} && "workspace_id"_c == wid && "{{column}}"_c == {{column}}{{domain_entity.temporal_filter}}) |
sqlgen::to<count_result>;
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::select_from<{{entity_singular}}_entity>(sqlgen::count().as<"count">()) |
where({{{tenant_where}}} && "{{column}}"_c == {{column}}{{domain_entity.temporal_filter}}) |
sqlgen::to<count_result>;
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::select_from<{{entity_singular}}_entity>(sqlgen::count().as<"count">()) |
where("workspace_id"_c == wid && "{{column}}"_c == {{column}}{{domain_entity.temporal_filter}}) |
sqlgen::to<count_result>;
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::select_from<{{entity_singular}}_entity>(sqlgen::count().as<"count">()) |
{{^current_state}}
where("{{column}}"_c == {{column}} && "valid_to"_c == max.value()) |
{{/current_state}}
{{#current_state}}
where("{{column}}"_c == {{column}}) |
{{/current_state}}
sqlgen::to<count_result>;
{{/has_workspace_id}}
{{/read_tenant_filtered}}
const auto r = sqlgen::session(ctx.connection_pool()).and_then(query);
ensure_success(r, lg());
const auto count = static_cast<std::uint32_t>(r->count);
BOOST_LOG_SEV(lg(), debug) << "Total active {{entity_plural_words}} count by {{column}}: " << count;
return count;
}
{{/list_by}}
{{#list_by_as_of}}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_by_{{column}}_as_of(
context ctx, const std::string& {{column}},
std::chrono::system_clock::time_point valid_from_bound,
std::chrono::system_clock::time_point valid_to_bound) {
BOOST_LOG_SEV(lg(), debug) << "Reading {{entity_plural_words}} as of window. {{column}}: " << {{column}};
const auto vf(make_timestamp(ores::platform::time::datetime::to_db_string(valid_from_bound), lg()));
const auto vt(make_timestamp(ores::platform::time::datetime::to_db_string(valid_to_bound), lg()));
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "{{column}}"_c == {{column}} &&
"valid_from"_c < vt.value() && "valid_to"_c > vf.value()) |
order_by({{{primary_key.order_by_cols}}});
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("{{column}}"_c == {{column}} &&
"valid_from"_c < vt.value() && "valid_to"_c > vf.value()) |
order_by({{{primary_key.order_by_cols}}});
{{/read_tenant_filtered}}
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading {{entity_plural_words}} as of window by {{column}}.");
}
{{/list_by_as_of}}
{{/foreign_keys}}
{{entity_singular}}_repository::remove_status
{{entity_singular}}_repository::remove(context ctx, {{{primary_key.params}}},
std::optional<std::uint32_t> version) {
BOOST_LOG_SEV(lg(), debug) << "Removing {{entity_singular_words}}. " << {{{primary_key.log_fields}}};
{{^has_audit_columns}}
// The store keeps no version column, so a caller that stated a version
// asked a question this table cannot answer.
if (version)
return remove_status::unsupported;
{{/has_audit_columns}}
const auto current = read_latest(ctx, {{{primary_key.args}}});
if (current.empty())
return remove_status::missing;
{{#has_audit_columns}}
// The protocol states the version as a uint32 and the row carries it as an
// int, so the comparison states the conversion rather than relying on one.
if (version && static_cast<std::uint32_t>(current.front().version) != *version)
return remove_status::conflicting;
{{/has_audit_columns}}
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
{{#has_audit_columns}}
// The row is named by its version as well as by its key, so the removal
// cannot close a row that replaced the one the caller read between the
// read above and this statement.
const auto expected =
version ? static_cast<int>(*version) : current.front().version;
{{/has_audit_columns}}
{{#has_tenant_id}}
const auto tid = ctx.tenant_id().to_string();
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::delete_from<{{entity_singular}}_entity> |
where("tenant_id"_c == tid && "workspace_id"_c == wid && {{{primary_key.where_and}}}{{domain_entity.temporal_filter}}{{domain_entity.version_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::delete_from<{{entity_singular}}_entity> |
where("tenant_id"_c == tid && {{{primary_key.where_and}}}{{domain_entity.temporal_filter}}{{domain_entity.version_filter}});
{{/has_workspace_id}}
{{/has_tenant_id}}
{{^has_tenant_id}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::delete_from<{{entity_singular}}_entity> |
where("workspace_id"_c == wid && {{{primary_key.where_and}}}{{domain_entity.temporal_filter}}{{domain_entity.version_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::delete_from<{{entity_singular}}_entity> |
where({{{primary_key.where_and}}}{{domain_entity.temporal_filter}}{{domain_entity.version_filter}});
{{/has_workspace_id}}
{{/has_tenant_id}}
execute_delete_query(ctx, query, lg(), "Removing {{entity_singular_words}} from database.");
{{#has_audit_columns}}
// The delete reports no affected-row count, so the row is read back: a row
// still open after the statement means the store refused the removal, and
// the caller hears "conflicting" rather than "removed".
if (!read_latest(ctx, {{{primary_key.args}}}).empty())
return remove_status::conflicting;
{{/has_audit_columns}}
return remove_status::removed;
}
void {{entity_singular}}_repository::remove(context ctx, {{{primary_key.params}}}) {
static_cast<void>(remove(ctx, {{{primary_key.args}}}, std::nullopt));
}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_latest(context ctx, std::uint32_t offset, std::uint32_t limit) {
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{entity_plural_words}} with offset: " << offset
<< " and limit: " << limit;
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid{{domain_entity.temporal_filter}}) |
order_by({{{primary_key.order_by_cols}}}) |
sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}}{{domain_entity.temporal_filter}}) |
order_by({{{primary_key.order_by_cols}}}) |
sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid{{domain_entity.temporal_filter}}) |
order_by({{{primary_key.order_by_cols}}}) |
sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
{{^domain_entity.current_state}}
where("valid_to"_c == max.value()) |
{{/domain_entity.current_state}}
order_by({{{primary_key.order_by_cols}}}) |
sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_workspace_id}}
{{/read_tenant_filtered}}
return execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading latest {{entity_plural_words}} with pagination.");
}
{{#domain_entity.has_newest_read}}
std::optional<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_newest(context ctx) {
BOOST_LOG_SEV(lg(), debug) << "Reading newest {{entity_singular_words}}";
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}}) |
order_by("{{domain_entity.sql.newest_by}}"_c.desc()) | sqlgen::limit(1);
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
order_by("{{domain_entity.sql.newest_by}}"_c.desc()) | sqlgen::limit(1);
{{/read_tenant_filtered}}
const auto rows = execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading newest {{entity_singular_words}}.");
if (rows.empty())
return std::nullopt;
return rows.front();
}
{{/domain_entity.has_newest_read}}
std::uint32_t {{entity_singular}}_repository::get_total_{{entity_singular_short}}_count(context ctx) {
BOOST_LOG_SEV(lg(), debug) << "Retrieving total active {{entity_singular_words}} count";
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
struct count_result { long long count; };
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::select_from<{{entity_singular}}_entity>(sqlgen::count().as<"count">()) |
where({{{tenant_where}}} && "workspace_id"_c == wid{{domain_entity.temporal_filter}}) |
sqlgen::to<count_result>;
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::select_from<{{entity_singular}}_entity>(sqlgen::count().as<"count">()) |
where({{{tenant_where}}}{{domain_entity.temporal_filter}}) |
sqlgen::to<count_result>;
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::select_from<{{entity_singular}}_entity>(sqlgen::count().as<"count">()) |
where("workspace_id"_c == wid{{domain_entity.temporal_filter}}) |
sqlgen::to<count_result>;
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::select_from<{{entity_singular}}_entity>(sqlgen::count().as<"count">()) |
{{^domain_entity.current_state}}
where("valid_to"_c == max.value()) |
{{/domain_entity.current_state}}
sqlgen::to<count_result>;
{{/has_workspace_id}}
{{/read_tenant_filtered}}
const auto r = sqlgen::session(ctx.connection_pool()).and_then(query);
ensure_success(r, lg());
const auto count = static_cast<std::uint32_t>(r->count);
BOOST_LOG_SEV(lg(), debug) << "Total active {{entity_singular_words}} count: " << count;
return count;
}
std::vector<domain::{{entity_singular}}>
{{entity_singular}}_repository::read_latest(
context ctx, {{{primary_key.batch_params}}}) {
if ({{{primary_key.batch_empty_check}}}) return {};
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
{{#read_tenant_filtered}}
const auto tid = ctx.tenant_id().to_string();{{sys_decl}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && "workspace_id"_c == wid
&& {{{primary_key.batch_in_and}}}
{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{tenant_where}}} && {{{primary_key.batch_in_and}}}
{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{/read_tenant_filtered}}
{{^read_tenant_filtered}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where("workspace_id"_c == wid && {{{primary_key.batch_in_and}}}
{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::read<std::vector<{{entity_singular}}_entity>> |
where({{{primary_key.batch_in_and}}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{/read_tenant_filtered}}
auto result = execute_read_query<{{entity_singular}}_entity, domain::{{entity_singular}}>(
ctx, query,
[](const auto& entities) { return {{entity_singular}}_mapper::map(entities); },
lg(), "Reading latest {{entity_plural_words}} by ids.");
{{#primary_key.is_compound}}
// Compound key: the query above is a per-column .in() cross-product
// over-fetch (sqlgen has no tuple/composite IN), so filter down to the
// exact requested key-tuples here.
if ({{{primary_key.batch_size_mismatch_check}}})
throw std::invalid_argument(
"{{entity_singular}}_repository::read_latest: key column vectors must be the same length");
std::set<std::tuple<{{{primary_key.batch_tuple_type}}}>> requested;
for (std::size_t i = 0; i < {{primary_key.batch_requested_size_check}}; ++i)
{{{primary_key.batch_requested_insert}}}
std::vector<domain::{{entity_singular}}> filtered;
filtered.reserve(result.size());
for (auto& item : result) {
if (requested.contains(std::make_tuple({{{primary_key.batch_tuple_from_item}}})))
filtered.push_back(std::move(item));
}
return filtered;
{{/primary_key.is_compound}}
{{^primary_key.is_compound}}
return result;
{{/primary_key.is_compound}}
}
void {{entity_singular}}_repository::remove(
context ctx, {{{primary_key.batch_params}}}) {
{{#primary_key.is_compound}}
// Compound key: a per-column .in() DELETE would be a cross-product
// over-delete (rows outside the requested tuples), and a DELETE can't
// be filtered after the fact like a read -- remove one tuple at a time.
if ({{{primary_key.batch_size_mismatch_check}}})
throw std::invalid_argument(
"{{entity_singular}}_repository::remove: key column vectors must be the same length");
for (std::size_t i = 0; i < {{primary_key.batch_requested_size_check}}; ++i)
remove(ctx, {{{primary_key.batch_loop_args}}});
{{/primary_key.is_compound}}
{{^primary_key.is_compound}}
{{^current_state}}
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{/current_state}}
{{#has_tenant_id}}
const auto tid = ctx.tenant_id().to_string();
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::delete_from<{{entity_singular}}_entity> |
where("tenant_id"_c == tid && "workspace_id"_c == wid && {{{primary_key.batch_in_and}}}
{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::delete_from<{{entity_singular}}_entity> |
where("tenant_id"_c == tid && {{{primary_key.batch_in_and}}}
{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{/has_tenant_id}}
{{^has_tenant_id}}
{{#has_workspace_id}}
const auto wid = ctx.workspace_id();
const auto query = sqlgen::delete_from<{{entity_singular}}_entity> |
where("workspace_id"_c == wid && {{{primary_key.batch_in_and}}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{^has_workspace_id}}
const auto query = sqlgen::delete_from<{{entity_singular}}_entity> |
where({{{primary_key.batch_in_and}}}{{domain_entity.temporal_filter}});
{{/has_workspace_id}}
{{/has_tenant_id}}
execute_delete_query(ctx, query, lg(), "Batch removing {{entity_plural_words}}.");
{{/primary_key.is_compound}}
}
{{#has_parent_id}}
std::vector<ores::utility::domain::hierarchy_flat_row>
{{entity_singular}}_repository::get_hierarchy(context ctx,
const boost::uuids::uuid& root_id,
bool from_root) {
BOOST_LOG_SEV(lg(), debug) << "Reading {{entity_singular}} hierarchy. Root: " << root_id
<< " from_root: " << from_root;
const auto tenant_str = boost::uuids::to_string(ctx.tenant_id().to_uuid());
const auto root_str = boost::uuids::to_string(root_id);
const std::string sql = "SELECT * FROM {{sql_name_base}}_hierarchy_fn('" +
tenant_str + "'::uuid, '" + root_str + "'::uuid, " +
(from_root ? "true" : "false") + ")";
const auto rows =
execute_raw_multi_column_query(ctx, sql, lg(), "Reading {{entity_singular}} hierarchy");
std::vector<ores::utility::domain::hierarchy_flat_row> result;
result.reserve(rows.size());
for (const auto& row : rows) {
if (row.size() >= 3 && row[0]) {
ores::utility::domain::hierarchy_flat_row r;
r.id = boost::lexical_cast<boost::uuids::uuid>(*row[0]);
if (row[1])
r.parent_id = boost::lexical_cast<boost::uuids::uuid>(*row[1]);
if (row[2])
r.name = *row[2];
result.push_back(std::move(r));
}
}
BOOST_LOG_SEV(lg(), debug) << "Read " << result.size() << " {{entity_singular}} hierarchy rows.";
return result;
}
{{/has_parent_id}}
<<paste:F2EB1914-5E94-42CA-9C77-46BDB364BF9E>>
}
{{/domain_entity}}
{{#junction}}
#include "ores.{{component_core}}/repository/{{name_singular}}_repository.hpp"
#include <cstddef>
#include <optional>
#include <stdexcept>
#include <sqlgen/postgres.hpp>
{{#has_uuid_left_or_right}}
#include <boost/uuid/uuid_io.hpp>
{{/has_uuid_left_or_right}}
#include "ores.database/repository/helpers.hpp"
#include "ores.database/repository/bitemporal_operations.hpp"
#include "ores.{{component_include}}/domain/{{name_singular}}_json_io.hpp" // IWYU pragma: keep.
#include "ores.{{component_core}}/repository/{{name_singular}}_entity.hpp"
#include "ores.{{component_core}}/repository/{{name_singular}}_mapper.hpp"
{{#left.enrich_code}}
#include <unordered_map>
{{/left.enrich_code}}
{{#right.enrich_code}}
#include <unordered_map>
{{/right.enrich_code}}
namespace ores::{{component}}::repository {
using namespace sqlgen;
using namespace sqlgen::literals;
using namespace ores::logging;
{{^component_is_database}}
using namespace ores::database::repository;
{{/component_is_database}}
std::string {{name_singular}}_repository::sql() {
return generate_create_table_sql<{{name_singular}}_entity>(lg());
}
{{name_singular}}_repository::{{name_singular}}_repository(context ctx)
: ctx_(std::move(ctx)) {}
{{^read_only}}
ores::utility::domain::precondition
{{name_singular}}_repository::replace_claim(const domain::{{name_singular}}& v) {
const auto current = read_latest(v.{{left.column}}, v.{{right.column}});
if (current.empty())
return {ores::utility::domain::precondition_kind::must_not_exist, std::nullopt};
return {ores::utility::domain::precondition_kind::must_match_version,
static_cast<std::uint32_t>(current.front().version)};
}
domain::{{name_singular}} {{name_singular}}_repository::apply_claim(
const domain::{{name_singular}}& v,
const ores::utility::domain::precondition& claim) {
using ores::utility::domain::precondition_kind;
auto t = v;
switch (claim.kind) {
case precondition_kind::must_not_exist:
// Zero states that no current row exists, which is the one meaning the
// store gives a zero version.
t.version = 0;
break;
case precondition_kind::must_match_version:
t.version = claim.version ? static_cast<int>(*claim.version) : 0;
break;
case precondition_kind::any: {
// A caller that claims nothing still has to say what it replaces, so
// the row is read and its version stated. A row that moved on between
// this read and the write is a conflict the trigger raises, never a
// silent overwrite.
const auto current = read_latest(v.{{left.column}}, v.{{right.column}});
t.version = current.empty() ? 0 : current.front().version;
break;
}
}
return t;
}
void {{name_singular}}_repository::write(
const domain::{{name_singular}}& {{name_singular_short}}) {
write({{name_singular_short}}, replace_claim({{name_singular_short}}));
}
void {{name_singular}}_repository::write(
const std::vector<domain::{{name_singular}}>& {{name_short}}) {
std::vector<ores::utility::domain::precondition> claims;
claims.reserve({{name_short}}.size());
for (const auto& item : {{name_short}})
claims.push_back(replace_claim(item));
write({{name_short}}, claims);
}
void {{name_singular}}_repository::write(
const domain::{{name_singular}}& {{name_singular_short}},
const ores::utility::domain::precondition& claim) {
BOOST_LOG_SEV(lg(), debug) << "Writing {{name_singular_words}} to database: "
<< {{name_singular_short}}.{{left.column}} << "/" << {{name_singular_short}}.{{right.column}};
const auto t = apply_claim({{name_singular_short}}, claim);
execute_write_query(ctx_, {{name_singular}}_mapper::map(t),
lg(), "writing {{name_singular_words}} to database");
}
void {{name_singular}}_repository::write(
const std::vector<domain::{{name_singular}}>& {{name_short}},
const std::vector<ores::utility::domain::precondition>& claims) {
BOOST_LOG_SEV(lg(), debug) << "Writing {{name_words}} to database. Count: "
<< {{name_short}}.size();
std::vector<domain::{{name_singular}}> batch;
batch.reserve({{name_short}}.size());
for (std::size_t i = 0; i < {{name_short}}.size(); ++i)
batch.push_back(apply_claim({{name_short}}[i], claims[i]));
execute_write_query(ctx_, {{name_singular}}_mapper::map(batch),
lg(), "writing {{name_words}} to database");
}
{{/read_only}}
std::vector<domain::{{name_singular}}>
{{name_singular}}_repository::read_latest() {
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("tenant_id"_c == tid && "valid_to"_c == max.value()) |
order_by("{{left.column}}"_c, "{{order_column}}"_c);
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("valid_to"_c == max.value()) |
order_by("{{left.column}}"_c, "{{order_column}}"_c);
{{/has_tenant_id}}
return execute_read_query<{{name_singular}}_entity, domain::{{name_singular}}>(
ctx_, query,
[](const auto& entities) { return {{name_singular}}_mapper::map(entities); },
lg(), "Reading latest {{name_words}}");
}
std::vector<domain::{{name_singular}}>
{{name_singular}}_repository::read_latest(std::uint32_t offset, std::uint32_t limit) {
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{name_words}} with offset: " << offset
<< " and limit: " << limit;
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("tenant_id"_c == tid && "valid_to"_c == max.value()) |
order_by("{{left.column}}"_c, "{{order_column}}"_c) |
sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("valid_to"_c == max.value()) |
order_by("{{left.column}}"_c, "{{order_column}}"_c) |
sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_tenant_id}}
return execute_read_query<{{name_singular}}_entity, domain::{{name_singular}}>(
ctx_, query,
[](const auto& entities) { return {{name_singular}}_mapper::map(entities); },
lg(), "Reading latest {{name_words}} (paginated).");
}
std::vector<domain::{{name_singular}}>
{{name_singular}}_repository::read_latest(
{{#left.is_uuid}}
const boost::uuids::uuid& {{left.column}},
{{/left.is_uuid}}
{{^left.is_uuid}}
const std::string& {{left.column}},
{{/left.is_uuid}}
{{#right.is_uuid}}
const boost::uuids::uuid& {{right.column}}) {
{{/right.is_uuid}}
{{^right.is_uuid}}
const std::string& {{right.column}}) {
{{/right.is_uuid}}
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{name_singular_words}}. "
<< {{left.column}} << "/" << {{right.column}};
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{#left.is_uuid}}
const auto {{left.column}}_str = boost::uuids::to_string({{left.column}});
{{/left.is_uuid}}
{{#right.is_uuid}}
const auto {{right.column}}_str = boost::uuids::to_string({{right.column}});
{{/right.is_uuid}}
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("tenant_id"_c == tid &&
{{#left.is_uuid}}
"{{left.column}}"_c == {{left.column}}_str &&
{{/left.is_uuid}}
{{^left.is_uuid}}
"{{left.column}}"_c == {{left.column}} &&
{{/left.is_uuid}}
{{#right.is_uuid}}
"{{right.column}}"_c == {{right.column}}_str &&
{{/right.is_uuid}}
{{^right.is_uuid}}
"{{right.column}}"_c == {{right.column}} &&
{{/right.is_uuid}}
"valid_to"_c == max.value());
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where(
{{#left.is_uuid}}
"{{left.column}}"_c == {{left.column}}_str &&
{{/left.is_uuid}}
{{^left.is_uuid}}
"{{left.column}}"_c == {{left.column}} &&
{{/left.is_uuid}}
{{#right.is_uuid}}
"{{right.column}}"_c == {{right.column}}_str &&
{{/right.is_uuid}}
{{^right.is_uuid}}
"{{right.column}}"_c == {{right.column}} &&
{{/right.is_uuid}}
"valid_to"_c == max.value());
{{/has_tenant_id}}
return execute_read_query<{{name_singular}}_entity, domain::{{name_singular}}>(
ctx_, query,
[](const auto& entities) { return {{name_singular}}_mapper::map(entities); },
lg(), "Reading latest {{name_singular_words}} by key.");
}
std::uint32_t {{name_singular}}_repository::get_total_{{name_singular_short}}_count() {
BOOST_LOG_SEV(lg(), debug) << "Retrieving total active {{name_words}} count";
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
struct count_result { long long count; };
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::select_from<{{name_singular}}_entity>(sqlgen::count().as<"count">()) |
where("tenant_id"_c == tid && "valid_to"_c == max.value()) |
sqlgen::to<count_result>;
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::select_from<{{name_singular}}_entity>(sqlgen::count().as<"count">()) |
where("valid_to"_c == max.value()) |
sqlgen::to<count_result>;
{{/has_tenant_id}}
const auto r = sqlgen::session(ctx_.connection_pool()).and_then(query);
ensure_success(r, lg());
const auto count = static_cast<std::uint32_t>(r->count);
BOOST_LOG_SEV(lg(), debug) << "Total active {{name_words}} count: " << count;
return count;
}
std::vector<domain::{{name_singular}}>
{{name_singular}}_repository::read_latest_by_{{left.column_short}}(
{{#left.is_uuid}}
const boost::uuids::uuid& {{left.column}}) {
{{/left.is_uuid}}
{{^left.is_uuid}}
const std::string& {{left.column}}) {
{{/left.is_uuid}}
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{name_words}}. {{left.column_title}}: "
<< {{left.column}};
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{#left.is_uuid}}
const auto {{left.column}}_str = boost::uuids::to_string({{left.column}});
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("tenant_id"_c == tid && "{{left.column}}"_c == {{left.column}}_str && "valid_to"_c == max.value()) |
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("{{left.column}}"_c == {{left.column}}_str && "valid_to"_c == max.value()) |
{{/has_tenant_id}}
{{/left.is_uuid}}
{{^left.is_uuid}}
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("tenant_id"_c == tid && "{{left.column}}"_c == {{left.column}} && "valid_to"_c == max.value()) |
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("{{left.column}}"_c == {{left.column}} && "valid_to"_c == max.value()) |
{{/has_tenant_id}}
{{/left.is_uuid}}
order_by("{{order_column}}"_c);
auto rows = execute_read_query<{{name_singular}}_entity, domain::{{name_singular}}>(
ctx_, query,
[](const auto& entities) { return {{name_singular}}_mapper::map(entities); },
lg(), "Reading latest {{name_words}} by {{left.column_short}}.");
{{#right.enrich_code}}
if (!rows.empty()) {
std::unordered_map<std::string, std::string> code_by_id;
std::string ids = "{";
for (std::size_t i = 0; i < rows.size(); ++i) {
if (i > 0)
ids += ",";
ids += boost::uuids::to_string(rows[i].{{right.column}});
}
ids += "}";
const auto codes = execute_parameterized_multi_column_query(
ctx_,
"SELECT \"id\"::text, \"{{right.code_column}}\"::text"
" FROM {{right.references}}"
" WHERE \"valid_to\" = ores_utility_infinity_timestamp_fn()"
" AND \"id\" = ANY($1::uuid[])",
{ids},
lg(), "Reading {{right.column_title_lower}} codes for {{name_words}}");
code_by_id.reserve(codes.size());
for (const auto& code_row : codes) {
if (code_row[0] && code_row[1])
code_by_id.emplace(*code_row[0], *code_row[1]);
}
for (auto& row : rows) {
if (const auto it = code_by_id.find(boost::uuids::to_string(row.{{right.column}}));
it != code_by_id.end())
row.{{right.column_short}}_code = it->second;
}
}
{{/right.enrich_code}}
return rows;
}
std::vector<domain::{{name_singular}}>
{{name_singular}}_repository::read_latest_by_{{right.column_short}}(
{{#right.is_uuid}}
const boost::uuids::uuid& {{right.column}}) {
{{/right.is_uuid}}
{{^right.is_uuid}}
const std::string& {{right.column}}) {
{{/right.is_uuid}}
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{name_words}}. {{right.column_title}}: "
<< {{right.column}};
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{#right.is_uuid}}
const auto {{right.column}}_str = boost::uuids::to_string({{right.column}});
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("tenant_id"_c == tid && "{{right.column}}"_c == {{right.column}}_str && "valid_to"_c == max.value()) |
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("{{right.column}}"_c == {{right.column}}_str && "valid_to"_c == max.value()) |
{{/has_tenant_id}}
{{/right.is_uuid}}
{{^right.is_uuid}}
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("tenant_id"_c == tid && "{{right.column}}"_c == {{right.column}} && "valid_to"_c == max.value()) |
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
where("{{right.column}}"_c == {{right.column}} && "valid_to"_c == max.value()) |
{{/has_tenant_id}}
{{/right.is_uuid}}
order_by("{{left.column}}"_c);
auto rows = execute_read_query<{{name_singular}}_entity, domain::{{name_singular}}>(
ctx_, query,
[](const auto& entities) { return {{name_singular}}_mapper::map(entities); },
lg(), "Reading latest {{name_words}} by {{right.column_short}}.");
{{#left.enrich_code}}
if (!rows.empty()) {
std::unordered_map<std::string, std::string> code_by_id;
std::string ids = "{";
for (std::size_t i = 0; i < rows.size(); ++i) {
if (i > 0)
ids += ",";
ids += boost::uuids::to_string(rows[i].{{left.column}});
}
ids += "}";
const auto codes = execute_parameterized_multi_column_query(
ctx_,
"SELECT \"id\"::text, \"{{left.code_column}}\"::text"
" FROM {{left.references}}"
" WHERE \"valid_to\" = ores_utility_infinity_timestamp_fn()"
" AND \"id\" = ANY($1::uuid[])",
{ids},
lg(), "Reading {{left.column_title_lower}} codes for {{name_words}}");
code_by_id.reserve(codes.size());
for (const auto& code_row : codes) {
if (code_row[0] && code_row[1])
code_by_id.emplace(*code_row[0], *code_row[1]);
}
for (auto& row : rows) {
if (const auto it = code_by_id.find(boost::uuids::to_string(row.{{left.column}}));
it != code_by_id.end())
row.{{left.column_short}}_code = it->second;
}
}
{{/left.enrich_code}}
return rows;
}
{{#left.list_by}}
std::vector<domain::{{name_singular}}>
{{name_singular}}_repository::read_latest_by_{{left.column_short}}(
{{#left.is_uuid}}
const boost::uuids::uuid& {{left.column}}, std::uint32_t offset, std::uint32_t limit) {
const auto {{left.column}}_str = boost::uuids::to_string({{left.column}});
{{/left.is_uuid}}
{{^left.is_uuid}}
const std::string& {{left.column}}, std::uint32_t offset, std::uint32_t limit) {
{{/left.is_uuid}}
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{name_words}}. {{left.column_title}}: "
<< {{left.column}} << " offset: " << offset << " limit: " << limit;
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
{{#left.is_uuid}}
where("tenant_id"_c == tid && "{{left.column}}"_c == {{left.column}}_str && "valid_to"_c == max.value()) |
{{/left.is_uuid}}
{{^left.is_uuid}}
where("tenant_id"_c == tid && "{{left.column}}"_c == {{left.column}} && "valid_to"_c == max.value()) |
{{/left.is_uuid}}
order_by("{{order_column}}"_c) | sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
{{#left.is_uuid}}
where("{{left.column}}"_c == {{left.column}}_str && "valid_to"_c == max.value()) |
{{/left.is_uuid}}
{{^left.is_uuid}}
where("{{left.column}}"_c == {{left.column}} && "valid_to"_c == max.value()) |
{{/left.is_uuid}}
order_by("{{order_column}}"_c) | sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_tenant_id}}
auto rows = execute_read_query<{{name_singular}}_entity, domain::{{name_singular}}>(
ctx_, query,
[](const auto& entities) { return {{name_singular}}_mapper::map(entities); },
lg(), "Reading latest {{name_words}} by {{left.column_short}} (paginated).");
{{#right.enrich_code}}
if (!rows.empty()) {
std::unordered_map<std::string, std::string> code_by_id;
std::string ids = "{";
for (std::size_t i = 0; i < rows.size(); ++i) {
if (i > 0)
ids += ",";
ids += boost::uuids::to_string(rows[i].{{right.column}});
}
ids += "}";
const auto codes = execute_parameterized_multi_column_query(
ctx_,
"SELECT \"id\"::text, \"{{right.code_column}}\"::text"
" FROM {{right.references}}"
" WHERE \"valid_to\" = ores_utility_infinity_timestamp_fn()"
" AND \"id\" = ANY($1::uuid[])",
{ids},
lg(), "Reading {{right.column_title_lower}} codes for {{name_words}}");
code_by_id.reserve(codes.size());
for (const auto& code_row : codes) {
if (code_row[0] && code_row[1])
code_by_id.emplace(*code_row[0], *code_row[1]);
}
for (auto& row : rows) {
if (const auto it = code_by_id.find(boost::uuids::to_string(row.{{right.column}}));
it != code_by_id.end())
row.{{right.column_short}}_code = it->second;
}
}
{{/right.enrich_code}}
return rows;
}
{{/left.list_by}}
std::uint32_t {{name_singular}}_repository::get_total_{{name_singular_short}}_count_by_{{left.column_short}}(
{{#left.is_uuid}}
const boost::uuids::uuid& {{left.column}}) {
const auto {{left.column}}_str = boost::uuids::to_string({{left.column}});
{{/left.is_uuid}}
{{^left.is_uuid}}
const std::string& {{left.column}}) {
{{/left.is_uuid}}
BOOST_LOG_SEV(lg(), debug) << "Retrieving total active {{name_words}} count. {{left.column_title}}: " << {{left.column}};
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
struct count_result { long long count; };
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::select_from<{{name_singular}}_entity>(sqlgen::count().as<"count">()) |
{{#left.is_uuid}}
where("tenant_id"_c == tid && "{{left.column}}"_c == {{left.column}}_str && "valid_to"_c == max.value()) |
{{/left.is_uuid}}
{{^left.is_uuid}}
where("tenant_id"_c == tid && "{{left.column}}"_c == {{left.column}} && "valid_to"_c == max.value()) |
{{/left.is_uuid}}
sqlgen::to<count_result>;
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::select_from<{{name_singular}}_entity>(sqlgen::count().as<"count">()) |
{{#left.is_uuid}}
where("{{left.column}}"_c == {{left.column}}_str && "valid_to"_c == max.value()) |
{{/left.is_uuid}}
{{^left.is_uuid}}
where("{{left.column}}"_c == {{left.column}} && "valid_to"_c == max.value()) |
{{/left.is_uuid}}
sqlgen::to<count_result>;
{{/has_tenant_id}}
const auto r = sqlgen::session(ctx_.connection_pool()).and_then(query);
ensure_success(r, lg());
const auto count = static_cast<std::uint32_t>(r->count);
BOOST_LOG_SEV(lg(), debug) << "Total active {{name_words}} count by {{left.column_short}}: " << count;
return count;
}
{{#right.list_by}}
std::vector<domain::{{name_singular}}>
{{name_singular}}_repository::read_latest_by_{{right.column_short}}(
{{#right.is_uuid}}
const boost::uuids::uuid& {{right.column}}, std::uint32_t offset, std::uint32_t limit) {
const auto {{right.column}}_str = boost::uuids::to_string({{right.column}});
{{/right.is_uuid}}
{{^right.is_uuid}}
const std::string& {{right.column}}, std::uint32_t offset, std::uint32_t limit) {
{{/right.is_uuid}}
BOOST_LOG_SEV(lg(), debug) << "Reading latest {{name_words}}. {{right.column_title}}: "
<< {{right.column}} << " offset: " << offset << " limit: " << limit;
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
{{#right.is_uuid}}
where("tenant_id"_c == tid && "{{right.column}}"_c == {{right.column}}_str && "valid_to"_c == max.value()) |
{{/right.is_uuid}}
{{^right.is_uuid}}
where("tenant_id"_c == tid && "{{right.column}}"_c == {{right.column}} && "valid_to"_c == max.value()) |
{{/right.is_uuid}}
order_by("{{left.column}}"_c) | sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::read<std::vector<{{name_singular}}_entity>> |
{{#right.is_uuid}}
where("{{right.column}}"_c == {{right.column}}_str && "valid_to"_c == max.value()) |
{{/right.is_uuid}}
{{^right.is_uuid}}
where("{{right.column}}"_c == {{right.column}} && "valid_to"_c == max.value()) |
{{/right.is_uuid}}
order_by("{{left.column}}"_c) | sqlgen::offset(offset) | sqlgen::limit(limit);
{{/has_tenant_id}}
auto rows = execute_read_query<{{name_singular}}_entity, domain::{{name_singular}}>(
ctx_, query,
[](const auto& entities) { return {{name_singular}}_mapper::map(entities); },
lg(), "Reading latest {{name_words}} by {{right.column_short}} (paginated).");
{{#left.enrich_code}}
if (!rows.empty()) {
std::unordered_map<std::string, std::string> code_by_id;
std::string ids = "{";
for (std::size_t i = 0; i < rows.size(); ++i) {
if (i > 0)
ids += ",";
ids += boost::uuids::to_string(rows[i].{{left.column}});
}
ids += "}";
const auto codes = execute_parameterized_multi_column_query(
ctx_,
"SELECT \"id\"::text, \"{{left.code_column}}\"::text"
" FROM {{left.references}}"
" WHERE \"valid_to\" = ores_utility_infinity_timestamp_fn()"
" AND \"id\" = ANY($1::uuid[])",
{ids},
lg(), "Reading {{left.column_title_lower}} codes for {{name_words}}");
code_by_id.reserve(codes.size());
for (const auto& code_row : codes) {
if (code_row[0] && code_row[1])
code_by_id.emplace(*code_row[0], *code_row[1]);
}
for (auto& row : rows) {
if (const auto it = code_by_id.find(boost::uuids::to_string(row.{{left.column}}));
it != code_by_id.end())
row.{{left.column_short}}_code = it->second;
}
}
{{/left.enrich_code}}
return rows;
}
{{/right.list_by}}
std::uint32_t {{name_singular}}_repository::get_total_{{name_singular_short}}_count_by_{{right.column_short}}(
{{#right.is_uuid}}
const boost::uuids::uuid& {{right.column}}) {
const auto {{right.column}}_str = boost::uuids::to_string({{right.column}});
{{/right.is_uuid}}
{{^right.is_uuid}}
const std::string& {{right.column}}) {
{{/right.is_uuid}}
BOOST_LOG_SEV(lg(), debug) << "Retrieving total active {{name_words}} count. {{right.column_title}}: " << {{right.column}};
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
struct count_result { long long count; };
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::select_from<{{name_singular}}_entity>(sqlgen::count().as<"count">()) |
{{#right.is_uuid}}
where("tenant_id"_c == tid && "{{right.column}}"_c == {{right.column}}_str && "valid_to"_c == max.value()) |
{{/right.is_uuid}}
{{^right.is_uuid}}
where("tenant_id"_c == tid && "{{right.column}}"_c == {{right.column}} && "valid_to"_c == max.value()) |
{{/right.is_uuid}}
sqlgen::to<count_result>;
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::select_from<{{name_singular}}_entity>(sqlgen::count().as<"count">()) |
{{#right.is_uuid}}
where("{{right.column}}"_c == {{right.column}}_str && "valid_to"_c == max.value()) |
{{/right.is_uuid}}
{{^right.is_uuid}}
where("{{right.column}}"_c == {{right.column}} && "valid_to"_c == max.value()) |
{{/right.is_uuid}}
sqlgen::to<count_result>;
{{/has_tenant_id}}
const auto r = sqlgen::session(ctx_.connection_pool()).and_then(query);
ensure_success(r, lg());
const auto count = static_cast<std::uint32_t>(r->count);
BOOST_LOG_SEV(lg(), debug) << "Total active {{name_words}} count by {{right.column_short}}: " << count;
return count;
}
{{^read_only}}
void {{name_singular}}_repository::remove(
{{#left.is_uuid}}
const boost::uuids::uuid& {{left.column}},
{{/left.is_uuid}}
{{^left.is_uuid}}
const std::string& {{left.column}},
{{/left.is_uuid}}
{{#right.is_uuid}}
const boost::uuids::uuid& {{right.column}}) {
{{/right.is_uuid}}
{{^right.is_uuid}}
const std::string& {{right.column}}) {
{{/right.is_uuid}}
static_cast<void>(remove({{left.column}}, {{right.column}}, std::nullopt));
}
{{name_singular}}_repository::remove_status
{{name_singular}}_repository::remove(
{{#left.is_uuid}}
const boost::uuids::uuid& {{left.column}},
{{/left.is_uuid}}
{{^left.is_uuid}}
const std::string& {{left.column}},
{{/left.is_uuid}}
{{#right.is_uuid}}
const boost::uuids::uuid& {{right.column}},
{{/right.is_uuid}}
{{^right.is_uuid}}
const std::string& {{right.column}},
{{/right.is_uuid}}
std::optional<std::uint32_t> version) {
BOOST_LOG_SEV(lg(), debug) << "Removing {{name_singular_words}} from database: "
<< {{left.column}} << "/" << {{right.column}};
const auto current = read_latest({{left.column}}, {{right.column}});
if (current.empty())
return remove_status::missing;
// The protocol states the version as a uint32 and the row carries it as an
// int, so the comparison states the conversion rather than relying on one.
if (version && static_cast<std::uint32_t>(current.front().version) != *version)
return remove_status::conflicting;
static const auto max(make_timestamp(MAX_TIMESTAMP, lg()));
// The row is named by its version as well as by its key, so the removal
// cannot close a row that replaced the one the caller read between the
// read above and this statement.
const auto expected = version ? static_cast<int>(*version) : current.front().version;
{{#left.is_uuid}}
const auto {{left.column}}_str = boost::uuids::to_string({{left.column}});
{{/left.is_uuid}}
{{#right.is_uuid}}
const auto {{right.column}}_str = boost::uuids::to_string({{right.column}});
{{/right.is_uuid}}
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
{{/has_tenant_id}}
const auto query = sqlgen::delete_from<{{name_singular}}_entity> |
{{#has_tenant_id}}
where("tenant_id"_c == tid &&
{{#left.is_uuid}}
{{#right.is_uuid}}
"{{left.column}}"_c == {{left.column}}_str &&
"{{right.column}}"_c == {{right.column}}_str &&
{{/right.is_uuid}}
{{^right.is_uuid}}
"{{left.column}}"_c == {{left.column}}_str &&
"{{right.column}}"_c == {{right.column}} &&
{{/right.is_uuid}}
{{/left.is_uuid}}
{{^left.is_uuid}}
{{#right.is_uuid}}
"{{left.column}}"_c == {{left.column}} &&
"{{right.column}}"_c == {{right.column}}_str &&
{{/right.is_uuid}}
{{^right.is_uuid}}
"{{left.column}}"_c == {{left.column}} &&
"{{right.column}}"_c == {{right.column}} &&
{{/right.is_uuid}}
{{/left.is_uuid}}
"valid_to"_c == max.value() &&
"version"_c == expected);
{{/has_tenant_id}}
{{^has_tenant_id}}
{{#left.is_uuid}}
{{#right.is_uuid}}
where("{{left.column}}"_c == {{left.column}}_str &&
"{{right.column}}"_c == {{right.column}}_str &&
{{/right.is_uuid}}
{{^right.is_uuid}}
where("{{left.column}}"_c == {{left.column}}_str &&
"{{right.column}}"_c == {{right.column}} &&
{{/right.is_uuid}}
{{/left.is_uuid}}
{{^left.is_uuid}}
{{#right.is_uuid}}
where("{{left.column}}"_c == {{left.column}} &&
"{{right.column}}"_c == {{right.column}}_str &&
{{/right.is_uuid}}
{{^right.is_uuid}}
where("{{left.column}}"_c == {{left.column}} &&
"{{right.column}}"_c == {{right.column}} &&
{{/right.is_uuid}}
{{/left.is_uuid}}
"valid_to"_c == max.value() &&
"version"_c == expected);
{{/has_tenant_id}}
execute_delete_query(ctx_, query, lg(),
"removing {{name_singular_words}} from database");
// The delete reports no affected-row count, so the row is read back: a row
// still open after the statement means the store refused the removal, and
// the caller hears "conflicting" rather than "removed".
if (!read_latest({{left.column}}, {{right.column}}).empty())
return remove_status::conflicting;
return remove_status::removed;
}
void {{name_singular}}_repository::remove(
const std::vector<{{left.cpp_type}}>& {{left.column}}s,
const std::vector<{{right.cpp_type}}>& {{right.column}}s) {
// A junction's key is the pair of columns, and a per-column .in() DELETE
// would be a cross-product over-delete (rows outside the requested pairs),
// so each pair is removed on its own.
if ({{left.column}}s.size() != {{right.column}}s.size())
throw std::invalid_argument(
"{{name_singular}}_repository::remove: key column vectors must be the same length");
for (std::size_t i = 0; i < {{left.column}}s.size(); ++i)
static_cast<void>(remove({{left.column}}s[i], {{right.column}}s[i], std::nullopt));
}
void {{name_singular}}_repository::remove_by_{{left.column_short}}(
{{#left.is_uuid}}
const boost::uuids::uuid& {{left.column}}) {
{{/left.is_uuid}}
{{^left.is_uuid}}
const std::string& {{left.column}}) {
{{/left.is_uuid}}
BOOST_LOG_SEV(lg(), debug) << "Removing all {{name_words}} from database: "
<< {{left.column}};
{{#left.is_uuid}}
const auto {{left.column}}_str = boost::uuids::to_string({{left.column}});
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::delete_from<{{name_singular}}_entity> |
where("tenant_id"_c == tid && "{{left.column}}"_c == {{left.column}}_str);
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::delete_from<{{name_singular}}_entity> |
where("{{left.column}}"_c == {{left.column}}_str);
{{/has_tenant_id}}
{{/left.is_uuid}}
{{^left.is_uuid}}
{{#has_tenant_id}}
const auto tid = ctx_.tenant_id().to_string();
const auto query = sqlgen::delete_from<{{name_singular}}_entity> |
where("tenant_id"_c == tid && "{{left.column}}"_c == {{left.column}});
{{/has_tenant_id}}
{{^has_tenant_id}}
const auto query = sqlgen::delete_from<{{name_singular}}_entity> |
where("{{left.column}}"_c == {{left.column}});
{{/has_tenant_id}}
{{/left.is_uuid}}
execute_delete_query(ctx_, query, lg(),
"removing all {{name_words}} from database");
}
{{/read_only}}
}
{{/junction}}
2. See also
- Parent facet: ores.cpp.repository
- Template variable reference