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"
{{#has_uuid_columns}}
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
{{/has_uuid_columns}}
{{#has_date_or_timestamp_natural_keys}}
#include "ores.platform/time/datetime.hpp"
#include <chrono>
#include <format>
#include <sstream>
{{/has_date_or_timestamp_natural_keys}}
{{#has_enum_columns}}
#include <rfl/enums.hpp>
{{/has_enum_columns}}
{{#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.{{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 = utility::uuid::tenant_id::from_string(*v.tenant_id).value();
{{/nullable_tenant_id}}
{{^nullable_tenant_id}}
r.{{{scaffold_prefix}}}tenant_id = utility::uuid::tenant_id::from_string(v.tenant_id).value();
{{/nullable_tenant_id}}
{{/has_tenant_id}}
{{#has_workspace_id}}
r.{{{scaffold_prefix}}}workspace_id = boost::lexical_cast<boost::uuids::uuid>(v.workspace_id);
{{/has_workspace_id}}
{{#primary_key.columns}}
{{#is_text}}
r.{{{group_prefix}}}{{column}} = v.{{column}}.value();
{{/is_text}}
{{#is_uuid}}
r.{{{group_prefix}}}{{column}} = boost::lexical_cast<boost::uuids::uuid>(v.{{column}}.value());
{{/is_uuid}}
{{#is_int}}
r.{{{group_prefix}}}{{column}} = boost::lexical_cast<int>(v.{{column}}.value());
{{/is_int}}
{{#is_timestamp}}
r.{{{group_prefix}}}{{column}} = timestamp_to_timepoint(std::string_view{v.{{column}}.value()});
{{/is_timestamp}}
{{/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_uuid}}
r.{{column}} = boost::lexical_cast<boost::uuids::uuid>(v.{{column}});
{{/is_uuid}}
{{#is_date}}
{
int yy{}, mm{}, dd{}; char s1{}, s2{};
std::istringstream ss(v.{{column}});
ss >> yy >> s1 >> mm >> s2 >> dd;
r.{{column}} = std::chrono::year{yy} / std::chrono::month{static_cast<unsigned>(mm)} /
std::chrono::day{static_cast<unsigned>(dd)};
}
{{/is_date}}
{{#is_timestamp}}
r.{{column}} = timestamp_to_timepoint(std::string_view{v.{{column}}});
{{/is_timestamp}}
{{^is_uuid}}{{^is_date}}{{^is_timestamp}}
r.{{column}} = v.{{column}};
{{/is_timestamp}}{{/is_date}}{{/is_uuid}}
{{/natural_keys}}
{{#columns}}
{{^sql_only}}
{{#is_uuid}}
r.{{{group_prefix}}}{{name}} = boost::lexical_cast<boost::uuids::uuid>(v.{{name}});
{{/is_uuid}}
{{#is_optional_uuid}}
{{#render_is_optional_uuid}}
r.{{{group_prefix}}}{{name}} = v.{{name}}.has_value() ? std::optional(boost::lexical_cast<boost::uuids::uuid>(*v.{{name}})) : std::nullopt;
{{/render_is_optional_uuid}}
{{#render_is_uuid}}
r.{{{group_prefix}}}{{name}} = v.{{name}}.has_value() ? boost::lexical_cast<boost::uuids::uuid>(*v.{{name}}) : boost::uuids::uuid{};
{{/render_is_uuid}}
{{/is_optional_uuid}}
{{#is_optional_timestamp}}
{{#render_is_optional_timestamp}}
r.{{{group_prefix}}}{{name}} = v.{{name}}.has_value() ? std::optional(timestamp_to_timepoint(*v.{{name}})) : std::nullopt;
{{/render_is_optional_timestamp}}
{{^render_is_optional_timestamp}}
{{^render_is_timestamp}}
r.{{{group_prefix}}}{{name}} = v.{{name}}.has_value() ? std::optional(v.{{name}}->str()) : std::nullopt;
{{/render_is_timestamp}}
{{/render_is_optional_timestamp}}
{{#render_is_timestamp}}
if (v.{{name}})
r.{{{group_prefix}}}{{name}} = timestamp_to_timepoint(*v.{{name}});
else
r.{{{group_prefix}}}{{name}} = {};
{{/render_is_timestamp}}
{{/is_optional_timestamp}}
{{#is_required_timestamp}}
r.{{{group_prefix}}}{{name}} = timestamp_to_timepoint(std::string_view{v.{{name}}});
{{/is_required_timestamp}}
{{#is_required_inet}}
r.{{{group_prefix}}}{{name}} = boost::asio::ip::make_address(v.{{name}});
{{/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}} = rfl::string_to_enum<{{{cpp_type}}}>(v.{{name}}).value();
{{/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() ?
rfl::string_to_enum<{{{render_cpp_type}}}>(*v.{{name}}).value_or({{{render_cpp_type}}}{}) :
{{{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 = timestamp_to_timepoint(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}}
{{#is_text}}
r.{{column}} = v.{{{group_prefix}}}{{column}};
{{/is_text}}
{{#is_uuid}}
r.{{column}} = boost::uuids::to_string(v.{{{group_prefix}}}{{column}});
{{/is_uuid}}
{{#is_int}}
r.{{column}} = std::to_string(v.{{{group_prefix}}}{{column}});
{{/is_int}}
{{#is_timestamp}}
r.{{column}} = ores::platform::time::datetime::to_db_string(v.{{{group_prefix}}}{{column}});
{{/is_timestamp}}
{{/primary_key.columns}}
{{#has_tenant_id}}
{{#nullable_tenant_id}}
if (v.{{{scaffold_prefix}}}tenant_id)
r.tenant_id = v.{{{scaffold_prefix}}}tenant_id->to_string();
{{/nullable_tenant_id}}
{{^nullable_tenant_id}}
r.tenant_id = v.{{{scaffold_prefix}}}tenant_id.to_string();
{{/nullable_tenant_id}}
{{/has_tenant_id}}
{{#has_workspace_id}}
r.workspace_id = boost::uuids::to_string(v.{{{scaffold_prefix}}}workspace_id);
{{/has_workspace_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_uuid}}
r.{{column}} = boost::uuids::to_string(v.{{column}});
{{/is_uuid}}
{{#is_date}}
r.{{column}} = std::format("{:%Y-%m-%d}", v.{{column}});
{{/is_date}}
{{#is_timestamp}}
r.{{column}} = ores::platform::time::datetime::to_iso8601_utc(v.{{column}});
{{/is_timestamp}}
{{^is_uuid}}{{^is_date}}{{^is_timestamp}}
r.{{column}} = v.{{column}};
{{/is_timestamp}}{{/is_date}}{{/is_uuid}}
{{/natural_keys}}
{{#columns}}
{{^sql_only}}
{{#is_uuid}}
r.{{name}} = boost::uuids::to_string(v.{{{group_prefix}}}{{name}});
{{/is_uuid}}
{{#is_optional_uuid}}
{{#render_is_optional_uuid}}
r.{{name}} = v.{{{group_prefix}}}{{name}}.has_value() ? std::optional(boost::uuids::to_string(*v.{{{group_prefix}}}{{name}})) : std::nullopt;
{{/render_is_optional_uuid}}
{{#render_is_uuid}}
r.{{name}} = v.{{{group_prefix}}}{{name}} == boost::uuids::uuid{} ? std::nullopt :
std::optional(boost::uuids::to_string(v.{{{group_prefix}}}{{name}}));
{{/render_is_uuid}}
{{/is_optional_uuid}}
{{#is_optional_timestamp}}
{{#render_is_optional_timestamp}}
r.{{name}} = v.{{{group_prefix}}}{{name}}.has_value() ? std::optional(ores::platform::time::datetime::to_db_string(*v.{{{group_prefix}}}{{name}})) : std::nullopt;
{{/render_is_optional_timestamp}}
{{#render_is_timestamp}}
r.{{name}} = v.{{{group_prefix}}}{{name}} != std::chrono::system_clock::time_point{} ?
std::optional(ores::platform::time::datetime::to_db_string(v.{{{group_prefix}}}{{name}})) :
std::nullopt;
{{/render_is_timestamp}}
{{^render_is_optional_timestamp}}
{{^render_is_timestamp}}
if (v.{{{group_prefix}}}{{name}}.has_value())
r.{{name}}.emplace(*v.{{{group_prefix}}}{{name}});
else
r.{{name}} = std::nullopt;
{{/render_is_timestamp}}
{{/render_is_optional_timestamp}}
{{/is_optional_timestamp}}
{{#is_required_timestamp}}
r.{{name}} = ores::platform::time::datetime::to_iso8601_utc(v.{{{group_prefix}}}{{name}});
{{/is_required_timestamp}}
{{#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}} = rfl::enum_to_string(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(std::string(rfl::enum_to_string(v.{{{group_prefix}}}{{name}})));
{{/is_enum}}
{{/render_is_enum}}
{{#is_simple}}
r.{{name}} = v.{{{group_prefix}}}{{name}};
{{/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"
{{#has_uuid_left_or_right}}
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
{{/has_uuid_left_or_right}}
{{#has_date_left_or_right_or_column}}
#include "ores.platform/time/datetime.hpp"
{{/has_date_left_or_right_or_column}}
#include "ores.database/repository/mapper_helpers.hpp"
#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}}
{{#left.is_uuid}}
r.{{left.column}} = boost::lexical_cast<boost::uuids::uuid>(v.{{left.column}}.value());
{{/left.is_uuid}}
{{^left.is_uuid}}
{{#left.is_date}}
r.{{left.column}} = ores::platform::time::datetime::from_iso8601_date(v.{{left.column}}.value());
{{/left.is_date}}
{{^left.is_date}}
r.{{left.column}} = v.{{left.column}}.value();
{{/left.is_date}}
{{/left.is_uuid}}
{{#right.is_uuid}}
r.{{right.column}} = boost::lexical_cast<boost::uuids::uuid>(v.{{right.column}});
{{/right.is_uuid}}
{{^right.is_uuid}}
{{#right.is_date}}
r.{{right.column}} = ores::platform::time::datetime::from_iso8601_date(v.{{right.column}});
{{/right.is_date}}
{{^right.is_date}}
r.{{right.column}} = v.{{right.column}};
{{/right.is_date}}
{{/right.is_uuid}}
{{#columns}}{{#is_required_timestamp}} r.{{name}} = timestamp_to_timepoint(std::string_view{v.{{name}}});
{{/is_required_timestamp}}{{#is_date}} r.{{name}} = ores::platform::time::datetime::from_iso8601_date(v.{{name}});
{{/is_date}}{{#is_base64}} r.{{name}} = utility::convert::base64_converter::convert(v.{{name}});
{{/is_base64}}{{^is_required_timestamp}}{{^is_date}}{{^is_base64}} r.{{name}} = v.{{name}};
{{/is_base64}}{{/is_date}}{{/is_required_timestamp}}{{/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 = timestamp_to_timepoint(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;
{{#left.is_uuid}}
r.{{left.column}} = boost::uuids::to_string(v.{{left.column}});
{{/left.is_uuid}}
{{^left.is_uuid}}
{{#left.is_date}}
r.{{left.column}} = ores::platform::time::datetime::to_iso8601_date(v.{{left.column}});
{{/left.is_date}}
{{^left.is_date}}
r.{{left.column}} = v.{{left.column}};
{{/left.is_date}}
{{/left.is_uuid}}
{{#has_tenant_id}}
r.tenant_id = v.tenant_id;
{{/has_tenant_id}}
{{#right.is_uuid}}
r.{{right.column}} = boost::uuids::to_string(v.{{right.column}});
{{/right.is_uuid}}
{{^right.is_uuid}}
{{#right.is_date}}
r.{{right.column}} = ores::platform::time::datetime::to_iso8601_date(v.{{right.column}});
{{/right.is_date}}
{{^right.is_date}}
r.{{right.column}} = v.{{right.column}};
{{/right.is_date}}
{{/right.is_uuid}}
r.version = v.version;
{{#columns}}{{#is_required_timestamp}} r.{{name}} = ores::platform::time::datetime::to_iso8601_utc(v.{{name}});
{{/is_required_timestamp}}{{#is_date}} r.{{name}} = ores::platform::time::datetime::to_iso8601_date(v.{{name}});
{{/is_date}}{{#is_base64}} r.{{name}} = utility::convert::base64_converter::convert(v.{{name}});
{{/is_base64}}{{^is_required_timestamp}}{{^is_date}}{{^is_base64}} r.{{name}} = v.{{name}};
{{/is_base64}}{{/is_date}}{{/is_required_timestamp}}{{/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
- Parent facet: ores.cpp.repository
- Template variable reference