ores.cpp.repository.mapper_impl

Table of Contents

Domain ↔ entity conversions including timestamp handling. repository profile.

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_mapper.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}}_mapper.hpp"
#include "ores.{{component_include}}/domain/{{entity_singular}}.hpp"
#include "ores.{{component_core}}/repository/{{entity_singular}}_entity.hpp"

{{#has_uuid_columns}}
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
{{/has_uuid_columns}}
{{#mapper_uses_datetime}}
#include "ores.platform/time/datetime.hpp"
{{/mapper_uses_datetime}}
{{#has_date_or_timestamp_natural_keys}}
#include <chrono>
{{/has_date_or_timestamp_natural_keys}}
{{#has_date_natural_keys}}
#include <format>
#include <sstream>
{{/has_date_natural_keys}}
{{#has_plain_date_columns}}
#include <chrono>
{{/has_plain_date_columns}}
{{#has_decimal_columns}}
#include "ores.utility/decimal/decimal.hpp"
{{/has_decimal_columns}}
{{#has_enum_columns}}
#include "ores.utility/codec/text_form.hpp"
{{/has_enum_columns}}
{{#mapper_enum_includes}}
#include "{{path}}"
{{/mapper_enum_includes}}
{{#has_inet_columns}}
#include <boost/asio/ip/address.hpp>
{{/has_inet_columns}}
{{#has_base64_columns}}
#include "ores.utility/convert/base64_converter.hpp"
{{/has_base64_columns}}
#include "ores.database/repository/mapper_helpers.hpp"
#include "ores.logging/boost_severity.hpp"
#include <boost/log/sources/severity_feature.hpp>
#include <vector>
{{#mapper_uses_optional}}
#include <optional>
{{/mapper_uses_optional}}
{{#mapper_uses_string}}
#include <string>
{{/mapper_uses_string}}
{{#mapper_uses_string_view}}
#include <string_view>
{{/mapper_uses_string_view}}
#include "ores.{{component_include}}/domain/{{entity_singular}}_json_io.hpp" // IWYU pragma: keep.

namespace ores::{{component}}::repository {

using namespace ores::logging;
{{^component_is_database}}
using namespace ores::database::repository;
{{/component_is_database}}

domain::{{entity_singular}}
{{entity_singular}}_mapper::map(const {{entity_singular}}_entity& v) {
    BOOST_LOG_SEV(lg(), trace) << "Mapping db entity: " << v;

    domain::{{entity_singular}} r;
{{#has_audit_columns}}
    r.{{{scaffold_prefix}}}version = v.version;
{{/has_audit_columns}}
{{#has_tenant_id}}
{{#nullable_tenant_id}}
    if (v.tenant_id)
        r.{{{scaffold_prefix}}}tenant_id = *v.tenant_id;
{{/nullable_tenant_id}}
{{^nullable_tenant_id}}
    r.{{{scaffold_prefix}}}tenant_id = v.tenant_id;
{{/nullable_tenant_id}}
{{/has_tenant_id}}
{{#primary_key.columns}}
    r.{{{group_prefix}}}{{column}} = v.{{column}};
{{/primary_key.columns}}
{{! Natural keys are not identity-group-column-aware: they always map flat.
    An entity with both has_identity_group and non-empty natural_keys would
    produce incorrect code if those keys live in the identity sub-struct. }}
{{#natural_keys}}
{{#is_optional_uuid}}
{{#render_is_uuid}}
    r.{{{group_prefix}}}{{column}} = v.{{column}}.has_value() ? *v.{{column}} : boost::uuids::uuid{};
{{/render_is_uuid}}
{{^render_is_uuid}}
    r.{{{group_prefix}}}{{column}} = v.{{column}};
{{/render_is_uuid}}
{{/is_optional_uuid}}
{{^is_optional_uuid}}
    r.{{{group_prefix}}}{{column}} = v.{{column}};
{{/is_optional_uuid}}
{{/natural_keys}}
{{#columns}}
{{^sql_only}}
{{#is_uuid}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/is_uuid}}
{{#is_optional_uuid}}
{{#render_is_uuid}}
    r.{{{group_prefix}}}{{name}} = v.{{name}}.has_value() ? *v.{{name}} : boost::uuids::uuid{};
{{/render_is_uuid}}
{{^render_is_uuid}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/render_is_uuid}}
{{/is_optional_uuid}}
{{#is_optional_timestamp}}
{{#render_is_timestamp}}
    r.{{{group_prefix}}}{{name}} = v.{{name}}.value_or(db_timestamp{});
{{/render_is_timestamp}}
{{^render_is_timestamp}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/render_is_timestamp}}
{{/is_optional_timestamp}}
{{#is_required_timestamp}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/is_required_timestamp}}
{{#is_optional_date}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/is_optional_date}}
{{#is_required_date}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/is_required_date}}
{{#is_optional_decimal}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/is_optional_decimal}}
{{#is_required_decimal}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/is_required_decimal}}
{{#is_required_inet}}
    // PostgreSQL renders an inet with its netmask, and the domain holds an
    // address, which has none: the mask is the part this type cannot carry.
    r.{{{group_prefix}}}{{name}} = boost::asio::ip::make_address(
        v.{{name}}.substr(0, v.{{name}}.find('/')));
{{/is_required_inet}}
{{#is_base64}}
    r.{{{group_prefix}}}{{name}} = utility::convert::base64_converter::convert(v.{{name}});
{{/is_base64}}
{{^render_is_enum}}
{{#is_nullable_string}}
{{#render_is_optional_string}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/render_is_optional_string}}
{{^render_is_optional_string}}
    r.{{{group_prefix}}}{{name}} = v.{{name}}.value_or("");
{{/render_is_optional_string}}
{{/is_nullable_string}}
{{/render_is_enum}}
{{#is_nullable_numeric}}
    r.{{{group_prefix}}}{{name}} = v.{{name}}.value_or(0);
{{/is_nullable_numeric}}
{{#is_already_optional}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/is_already_optional}}
{{#is_enum}}
    r.{{{group_prefix}}}{{name}} =
        ores::utility::codec::text_form<{{{cpp_type}}}>::from_text(v.{{name}});
{{/is_enum}}
{{#is_value_type}}
    r.{{{group_prefix}}}{{name}} = {{{cpp_type}}}::from_string(v.{{name}}).value();
{{/is_value_type}}
{{#render_is_enum}}
{{^is_enum}}
    r.{{{group_prefix}}}{{name}} = v.{{name}}.has_value() ?
        ores::utility::codec::text_form<{{{render_cpp_type}}}>::from_text(*v.{{name}}) :
        {{{render_cpp_type}}}{};
{{/is_enum}}
{{/render_is_enum}}
{{#is_simple}}
    r.{{{group_prefix}}}{{name}} = v.{{name}};
{{/is_simple}}
{{/sql_only}}
{{/columns}}
{{#has_audit_columns}}
    r.{{{audit_prefix}}}modified_by = v.modified_by;
    r.{{{audit_prefix}}}performed_by = v.performed_by;
    r.{{{audit_prefix}}}change_reason_code = v.change_reason_code;
    r.{{{audit_prefix}}}change_commentary = v.change_commentary;
    r.{{{audit_prefix}}}recorded_at = v.valid_from;
{{/has_audit_columns}}

    BOOST_LOG_SEV(lg(), trace) << "Mapped db entity. Result: " << r;
    return r;
}

{{entity_singular}}_entity
{{entity_singular}}_mapper::map(const domain::{{entity_singular}}& v) {
    BOOST_LOG_SEV(lg(), trace) << "Mapping domain entity: " << v;

    {{entity_singular}}_entity r;
{{#primary_key.columns}}
    r.{{column}} = v.{{{group_prefix}}}{{column}};
{{/primary_key.columns}}
{{#has_tenant_id}}
{{#nullable_tenant_id}}
    r.tenant_id = v.{{{scaffold_prefix}}}tenant_id;
{{/nullable_tenant_id}}
{{^nullable_tenant_id}}
    r.tenant_id = v.{{{scaffold_prefix}}}tenant_id;
{{/nullable_tenant_id}}
{{/has_tenant_id}}
{{#has_audit_columns}}
    r.version = v.{{{scaffold_prefix}}}version;
{{/has_audit_columns}}
{{! Natural keys are not identity-group-column-aware: they always map flat.
    An entity with both has_identity_group and non-empty natural_keys would
    produce incorrect code if those keys live in the identity sub-struct. }}
{{#natural_keys}}
{{#is_optional_uuid}}
{{#render_is_optional_uuid}}
    r.{{column}} = v.{{{group_prefix}}}{{column}};
{{/render_is_optional_uuid}}
{{^render_is_optional_uuid}}
    r.{{column}} = v.{{{group_prefix}}}{{column}}.value_or(boost::uuids::uuid{});
{{/render_is_optional_uuid}}
{{/is_optional_uuid}}
{{^is_optional_uuid}}
    r.{{column}} = v.{{{group_prefix}}}{{column}};
{{/is_optional_uuid}}
{{/natural_keys}}
{{#columns}}
{{^sql_only}}
{{#is_uuid}}
{{#render_is_optional_uuid}}
    r.{{name}} = v.{{{group_prefix}}}{{name}}.value_or(boost::uuids::uuid{});
{{/render_is_optional_uuid}}
{{^render_is_optional_uuid}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/render_is_optional_uuid}}
{{/is_uuid}}
{{#is_optional_uuid}}
{{#render_is_uuid}}
    r.{{name}} = v.{{{group_prefix}}}{{name}} == boost::uuids::uuid{} ?
                     std::nullopt :
                     std::optional(v.{{{group_prefix}}}{{name}});
{{/render_is_uuid}}
{{^render_is_uuid}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/render_is_uuid}}
{{/is_optional_uuid}}
{{#is_optional_timestamp}}
{{#render_is_timestamp}}
    r.{{name}} = v.{{{group_prefix}}}{{name}} == db_timestamp{} ?
                     std::nullopt :
                     std::optional(v.{{{group_prefix}}}{{name}});
{{/render_is_timestamp}}
{{^render_is_timestamp}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/render_is_timestamp}}
{{/is_optional_timestamp}}
{{#is_required_timestamp}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/is_required_timestamp}}
{{#is_optional_date}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/is_optional_date}}
{{#is_required_date}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/is_required_date}}
{{#is_optional_decimal}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/is_optional_decimal}}
{{#is_required_decimal}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/is_required_decimal}}
{{#is_required_inet}}
    r.{{name}} = v.{{{group_prefix}}}{{name}}.to_string();
{{/is_required_inet}}
{{#is_base64}}
    r.{{name}} = utility::convert::base64_converter::convert(v.{{{group_prefix}}}{{name}});
{{/is_base64}}
{{^render_is_enum}}
{{#is_nullable_string}}
{{#render_is_optional_string}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/render_is_optional_string}}
{{^render_is_optional_string}}
    r.{{name}} = v.{{{group_prefix}}}{{name}}.empty() ? std::nullopt : std::optional(v.{{{group_prefix}}}{{name}});
{{/render_is_optional_string}}
{{/is_nullable_string}}
{{/render_is_enum}}
{{#is_nullable_numeric}}
    r.{{name}} = v.{{{group_prefix}}}{{name}} == 0 ? std::nullopt : std::optional(v.{{{group_prefix}}}{{name}});
{{/is_nullable_numeric}}
{{#is_already_optional}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/is_already_optional}}
{{#is_enum}}
    r.{{name}} = ores::utility::codec::text_form<decltype(v.{{{group_prefix}}}{{name}})>
        ::to_text(v.{{{group_prefix}}}{{name}});
{{/is_enum}}
{{#is_value_type}}
    r.{{name}} = v.{{{group_prefix}}}{{name}}.to_string();
{{/is_value_type}}
{{#render_is_enum}}
{{^is_enum}}
    r.{{name}} = v.{{{group_prefix}}}{{name}} == {{{render_cpp_type}}}{} ?
        std::nullopt :
        std::optional(ores::utility::codec::text_form<decltype(v.{{{group_prefix}}}{{name}})>
            ::to_text(v.{{{group_prefix}}}{{name}}));
{{/is_enum}}
{{/render_is_enum}}
{{#is_simple}}
{{#no_wire}}
    r.{{name}} = v.{{{group_prefix}}}{{name}}.value();
{{/no_wire}}
{{^no_wire}}
    r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/no_wire}}
{{/is_simple}}
{{/sql_only}}
{{/columns}}
{{#has_audit_columns}}
    r.modified_by = v.{{{audit_prefix}}}modified_by;
    r.performed_by = v.{{{audit_prefix}}}performed_by;
    r.change_reason_code = v.{{{audit_prefix}}}change_reason_code;
    r.change_commentary = v.{{{audit_prefix}}}change_commentary;
{{/has_audit_columns}}

    BOOST_LOG_SEV(lg(), trace) << "Mapped domain entity. Result: " << r;
    return r;
}

std::vector<domain::{{entity_singular}}>
{{entity_singular}}_mapper::map(const std::vector<{{entity_singular}}_entity>& v) {
    return map_vector<{{entity_singular}}_entity, domain::{{entity_singular}}>(
        v,
        [](const auto& ve) { return map(ve); },
        lg(),
        "db entities");
}

std::vector<{{entity_singular}}_entity>
{{entity_singular}}_mapper::map(const std::vector<domain::{{entity_singular}}>& v) {
    return map_vector<domain::{{entity_singular}}, {{entity_singular}}_entity>(
        v,
        [](const auto& ve) { return map(ve); },
        lg(),
        "domain entities");
}

}
{{/domain_entity}}
{{#junction}}
#include "ores.{{component_core}}/repository/{{name_singular}}_mapper.hpp"
#include "ores.{{component_include}}/domain/{{name_singular}}.hpp"
#include "ores.{{component_core}}/repository/{{name_singular}}_entity.hpp"

{{#has_uuid_left_or_right}}
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
{{/has_uuid_left_or_right}}
{{#mapper_uses_datetime}}
#include "ores.platform/time/datetime.hpp"
{{/mapper_uses_datetime}}
#include "ores.database/repository/mapper_helpers.hpp"
#include "ores.logging/boost_severity.hpp"
#include <boost/log/sources/severity_feature.hpp>
#include <vector>
{{#mapper_uses_optional}}
#include <optional>
{{/mapper_uses_optional}}
{{#mapper_uses_string}}
#include <string>
{{/mapper_uses_string}}
{{#mapper_uses_string_view}}
#include <string_view>
{{/mapper_uses_string_view}}
#include "ores.{{component_include}}/domain/{{name_singular}}_json_io.hpp" // IWYU pragma: keep.

namespace ores::{{component}}::repository {

using namespace ores::logging;
{{^component_is_database}}
using namespace ores::database::repository;
{{/component_is_database}}

domain::{{name_singular}}
{{name_singular}}_mapper::map(const {{name_singular}}_entity& v) {
    BOOST_LOG_SEV(lg(), trace) << "Mapping db entity: " << v;

    domain::{{name_singular}} r;
    r.version = v.version;
{{#has_tenant_id}}
    r.tenant_id = v.tenant_id;
{{/has_tenant_id}}
    r.{{left.column}} = v.{{left.column}};
    r.{{right.column}} = v.{{right.column}};
{{#columns}}
{{#is_base64}}
    r.{{name}} = utility::convert::base64_converter::convert(v.{{name}});
{{/is_base64}}
{{^is_base64}}
    r.{{name}} = v.{{name}};
{{/is_base64}}
{{/columns}}
    r.modified_by = v.modified_by;
    r.performed_by = v.performed_by;
    r.change_reason_code = v.change_reason_code;
    r.change_commentary = v.change_commentary;
    r.recorded_at = v.valid_from;

    BOOST_LOG_SEV(lg(), trace) << "Mapped db entity. Result: " << r;
    return r;
}

{{name_singular}}_entity
{{name_singular}}_mapper::map(const domain::{{name_singular}}& v) {
    BOOST_LOG_SEV(lg(), trace) << "Mapping domain entity: " << v;

    {{name_singular}}_entity r;
    r.{{left.column}} = v.{{left.column}};
{{#has_tenant_id}}
    r.tenant_id = v.tenant_id;
{{/has_tenant_id}}
    r.{{right.column}} = v.{{right.column}};
    r.version = v.version;
{{#columns}}
{{#is_base64}}
    r.{{name}} = utility::convert::base64_converter::convert(v.{{name}});
{{/is_base64}}
{{^is_base64}}
    r.{{name}} = v.{{name}};
{{/is_base64}}
{{/columns}}
    r.modified_by = v.modified_by;
    r.performed_by = v.performed_by;
    r.change_reason_code = v.change_reason_code;
    r.change_commentary = v.change_commentary;

    BOOST_LOG_SEV(lg(), trace) << "Mapped domain entity. Result: " << r;
    return r;
}

std::vector<domain::{{name_singular}}>
{{name_singular}}_mapper::map(const std::vector<{{name_singular}}_entity>& v) {
    return map_vector<{{name_singular}}_entity, domain::{{name_singular}}>(
        v,
        [](const auto& ve) { return map(ve); },
        lg(),
        "db entities");
}

std::vector<{{name_singular}}_entity>
{{name_singular}}_mapper::map(const std::vector<domain::{{name_singular}}>& v) {
    return map_vector<domain::{{name_singular}}, {{name_singular}}_entity>(
        v,
        [](const auto& ve) { return map(ve); },
        lg(),
        "domain entities");
}

}
{{/junction}}

2. See also

Emacs 29.3 (Org mode 9.6.15)