ores.cpp.repository.mapper_impl
Domain ↔ entity conversions including timestamp handling. repository profile.
See the Template variable reference for the complete list of available variables and their semantics.
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}}
#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;
using namespace ores::database::repository;
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_identity_group}}
{{#has_audit_columns}}
r.identity.version = v.version;
{{/has_audit_columns}}
{{#has_tenant_id}}
r.identity.tenant_id = utility::uuid::tenant_id::from_string(v.tenant_id).value();
{{/has_tenant_id}}
{{#has_workspace_id}}
r.identity.workspace_id = boost::lexical_cast<boost::uuids::uuid>(v.workspace_id);
{{/has_workspace_id}}
{{#primary_key.columns}}
{{#is_text}}
r.identity.{{column}} = v.{{column}}.value();
{{/is_text}}
{{#is_uuid}}
r.identity.{{column}} = boost::lexical_cast<boost::uuids::uuid>(v.{{column}}.value());
{{/is_uuid}}
{{/primary_key.columns}}
{{/has_identity_group}}
{{^has_identity_group}}
{{#has_audit_columns}}
r.version = v.version;
{{/has_audit_columns}}
{{#has_tenant_id}}
r.tenant_id = utility::uuid::tenant_id::from_string(v.tenant_id).value();
{{/has_tenant_id}}
{{#has_workspace_id}}
r.workspace_id = boost::lexical_cast<boost::uuids::uuid>(v.workspace_id);
{{/has_workspace_id}}
{{#primary_key.columns}}
{{#is_text}}
r.{{column}} = v.{{column}}.value();
{{/is_text}}
{{#is_uuid}}
r.{{column}} = boost::lexical_cast<boost::uuids::uuid>(v.{{column}}.value());
{{/is_uuid}}
{{/primary_key.columns}}
{{/has_identity_group}}
{{! 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}}
{{#is_identity_group_column}}
{{#is_uuid}}
r.identity.{{name}} = boost::lexical_cast<boost::uuids::uuid>(v.{{name}});
{{/is_uuid}}
{{#is_optional_uuid}}
r.identity.{{name}} = v.{{name}}.has_value() ? std::optional(boost::lexical_cast<boost::uuids::uuid>(*v.{{name}})) : std::nullopt;
{{/is_optional_uuid}}
{{#is_optional_timestamp}}
r.identity.{{name}} = v.{{name}}.has_value() ? std::optional(timestamp_to_timepoint(*v.{{name}})) : std::nullopt;
{{/is_optional_timestamp}}
{{#is_required_timestamp}}
r.identity.{{name}} = timestamp_to_timepoint(std::string_view{v.{{name}}});
{{/is_required_timestamp}}
{{#is_nullable_string}}
r.identity.{{name}} = v.{{name}}.value_or("");
{{/is_nullable_string}}
{{#is_already_optional}}
r.identity.{{name}} = v.{{name}};
{{/is_already_optional}}
{{#is_enum}}
r.identity.{{name}} = rfl::string_to_enum<{{{cpp_type}}}>(v.{{name}}).value();
{{/is_enum}}
{{#is_simple}}
r.identity.{{name}} = v.{{name}};
{{/is_simple}}
{{/is_identity_group_column}}
{{^is_identity_group_column}}
{{#is_uuid}}
r.{{name}} = boost::lexical_cast<boost::uuids::uuid>(v.{{name}});
{{/is_uuid}}
{{#is_optional_uuid}}
r.{{name}} = v.{{name}}.has_value() ? std::optional(boost::lexical_cast<boost::uuids::uuid>(*v.{{name}})) : std::nullopt;
{{/is_optional_uuid}}
{{#is_optional_timestamp}}
r.{{name}} = v.{{name}}.has_value() ? std::optional(timestamp_to_timepoint(*v.{{name}})) : std::nullopt;
{{/is_optional_timestamp}}
{{#is_required_timestamp}}
r.{{name}} = timestamp_to_timepoint(std::string_view{v.{{name}}});
{{/is_required_timestamp}}
{{#is_nullable_string}}
r.{{name}} = v.{{name}}.value_or("");
{{/is_nullable_string}}
{{#is_already_optional}}
r.{{name}} = v.{{name}};
{{/is_already_optional}}
{{#is_enum}}
r.{{name}} = rfl::string_to_enum<{{{cpp_type}}}>(v.{{name}}).value();
{{/is_enum}}
{{#is_simple}}
r.{{name}} = v.{{name}};
{{/is_simple}}
{{/is_identity_group_column}}
{{/columns}}
{{#has_audit_group}}
{{#has_audit_columns}}
r.audit.modified_by = v.modified_by;
r.audit.performed_by = v.performed_by;
r.audit.change_reason_code = v.change_reason_code;
r.audit.change_commentary = v.change_commentary;
{{/has_audit_columns}}
r.audit.recorded_at = timestamp_to_timepoint(v.valid_from);
{{/has_audit_group}}
{{^has_audit_group}}
{{#has_audit_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;
{{/has_audit_columns}}
r.recorded_at = timestamp_to_timepoint(v.valid_from);
{{/has_audit_group}}
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;
{{#has_identity_group}}
{{#primary_key.columns}}
{{#is_text}}
r.{{column}} = v.identity.{{column}};
{{/is_text}}
{{#is_uuid}}
r.{{column}} = boost::uuids::to_string(v.identity.{{column}});
{{/is_uuid}}
{{/primary_key.columns}}
{{#has_tenant_id}}
r.tenant_id = v.identity.tenant_id.to_string();
{{/has_tenant_id}}
{{#has_workspace_id}}
r.workspace_id = boost::uuids::to_string(v.identity.workspace_id);
{{/has_workspace_id}}
{{#has_audit_columns}}
r.version = v.identity.version;
{{/has_audit_columns}}
{{/has_identity_group}}
{{^has_identity_group}}
{{#primary_key.columns}}
{{#is_text}}
r.{{column}} = v.{{column}};
{{/is_text}}
{{#is_uuid}}
r.{{column}} = boost::uuids::to_string(v.{{column}});
{{/is_uuid}}
{{/primary_key.columns}}
{{#has_tenant_id}}
r.tenant_id = v.tenant_id.to_string();
{{/has_tenant_id}}
{{#has_workspace_id}}
r.workspace_id = boost::uuids::to_string(v.workspace_id);
{{/has_workspace_id}}
{{#has_audit_columns}}
r.version = v.version;
{{/has_audit_columns}}
{{/has_identity_group}}
{{! 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}}
{{#is_identity_group_column}}
{{#is_uuid}}
r.{{name}} = boost::uuids::to_string(v.identity.{{name}});
{{/is_uuid}}
{{#is_optional_uuid}}
r.{{name}} = v.identity.{{name}}.has_value() ? std::optional(boost::uuids::to_string(*v.identity.{{name}})) : std::nullopt;
{{/is_optional_uuid}}
{{#is_optional_timestamp}}
r.{{name}} = v.identity.{{name}}.has_value() ? std::optional(ores::platform::time::datetime::to_db_string(*v.identity.{{name}})) : std::nullopt;
{{/is_optional_timestamp}}
{{#is_required_timestamp}}
r.{{name}} = ores::platform::time::datetime::to_iso8601_utc(v.identity.{{name}});
{{/is_required_timestamp}}
{{#is_nullable_string}}
r.{{name}} = v.identity.{{name}}.empty() ? std::nullopt : std::optional(v.identity.{{name}});
{{/is_nullable_string}}
{{#is_already_optional}}
r.{{name}} = v.identity.{{name}};
{{/is_already_optional}}
{{#is_enum}}
r.{{name}} = rfl::enum_to_string(v.identity.{{name}});
{{/is_enum}}
{{#is_simple}}
r.{{name}} = v.identity.{{name}};
{{/is_simple}}
{{/is_identity_group_column}}
{{^is_identity_group_column}}
{{#is_uuid}}
r.{{name}} = boost::uuids::to_string(v.{{name}});
{{/is_uuid}}
{{#is_optional_uuid}}
r.{{name}} = v.{{name}}.has_value() ? std::optional(boost::uuids::to_string(*v.{{name}})) : std::nullopt;
{{/is_optional_uuid}}
{{#is_optional_timestamp}}
r.{{name}} = v.{{name}}.has_value() ? std::optional(ores::platform::time::datetime::to_db_string(*v.{{name}})) : std::nullopt;
{{/is_optional_timestamp}}
{{#is_required_timestamp}}
r.{{name}} = ores::platform::time::datetime::to_iso8601_utc(v.{{name}});
{{/is_required_timestamp}}
{{#is_nullable_string}}
r.{{name}} = v.{{name}}.empty() ? std::nullopt : std::optional(v.{{name}});
{{/is_nullable_string}}
{{#is_already_optional}}
r.{{name}} = v.{{name}};
{{/is_already_optional}}
{{#is_enum}}
r.{{name}} = rfl::enum_to_string(v.{{name}});
{{/is_enum}}
{{#is_simple}}
r.{{name}} = v.{{name}};
{{/is_simple}}
{{/is_identity_group_column}}
{{/columns}}
{{#has_audit_group}}
{{#has_audit_columns}}
r.modified_by = v.audit.modified_by;
r.performed_by = v.audit.performed_by;
r.change_reason_code = v.audit.change_reason_code;
r.change_commentary = v.audit.change_commentary;
{{/has_audit_columns}}
{{/has_audit_group}}
{{^has_audit_group}}
{{#has_audit_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;
{{/has_audit_columns}}
{{/has_audit_group}}
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;
using namespace ores::database::repository;
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_date}}
r.{{name}} = ores::platform::time::datetime::from_iso8601_date(v.{{name}});
{{/is_date}}
{{^is_date}}
r.{{name}} = v.{{name}};
{{/is_date}}
{{/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_date}}
r.{{name}} = ores::platform::time::datetime::to_iso8601_date(v.{{name}});
{{/is_date}}
{{^is_date}}
r.{{name}} = v.{{name}};
{{/is_date}}
{{/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}}
See also
- Parent facet: ores.cpp.repository
- Template variable reference