// file : libbrep/common.hxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #ifndef LIBBREP_COMMON_HXX #define LIBBREP_COMMON_HXX #include #include #include #include // static_assert #include #include #include #include #include #include // The uint16_t value range is not fully covered by SMALLINT PostgreSQL type // to which uint16_t is mapped by default. // #pragma db value(uint16_t) type("INTEGER") namespace brep { // Use an image type to map bpkg::version to the database since there // is no way to modify individual components directly. // #pragma db value struct _version { uint16_t epoch; string canonical_upstream; string canonical_release; optional revision; string upstream; optional release; }; } #include namespace brep { using optional_version = optional; using _optional_version = optional<_version>; } // Prevent assert() macro expansion in get/set expressions. This should // appear after all #include directives since the assert() macro is // redefined in each inclusion. // #ifdef ODB_COMPILER # undef assert # define assert assert void assert (int); #endif // We have to keep these mappings at the global scope instead of inside // the brep namespace because they need to be also effective in the // bpkg namespace from which we "borrow" types (and some of them use version). // #pragma db map type(bpkg::version) as(brep::_version) \ to(brep::_version{(?).epoch, \ (?).canonical_upstream, \ (?).canonical_release, \ (?).revision, \ (?).upstream, \ (?).release}) \ from(bpkg::version ((?).epoch, \ std::move ((?).upstream), \ std::move ((?).release), \ (?).revision, \ 0)) #pragma db map type(brep::optional_version) as(brep::_optional_version) \ to((?) \ ? brep::_version{(?)->epoch, \ (?)->canonical_upstream, \ (?)->canonical_release, \ (?)->revision, \ (?)->upstream, \ (?)->release} \ : brep::_optional_version ()) \ from((?) \ ? bpkg::version ((?)->epoch, \ std::move ((?)->upstream), \ std::move ((?)->release), \ (?)->revision, \ 0) \ : brep::optional_version ()) namespace brep { // path // #pragma db map type(path) as(string) to((?).string ()) from(brep::path (?)) using optional_path = optional; using optional_string = optional; #pragma db map type(optional_path) as(brep::optional_string) \ to((?) ? (?)->string () : brep::optional_string ()) \ from((?) ? brep::path (*(?)) : brep::optional_path ()) #pragma db map type(dir_path) as(string) \ to((?).string ()) from(brep::dir_path (?)) // Make sure that timestamp can be represented in nonoseconds without loss // of accuracy, so the following ODB mapping is adequate. // static_assert( std::ratio_greater_equal::value, "The following timestamp ODB mapping is invalid"); // As it pointed out in libbutl/timestamp.hxx we will overflow in year 2262, // but by that time some larger basic type will be available for mapping. // #pragma db map type(timestamp) as(uint64_t) \ to(std::chrono::duration_cast ( \ (?).time_since_epoch ()).count ()) \ from(brep::timestamp ( \ std::chrono::duration_cast ( \ std::chrono::nanoseconds (?)))) using optional_timestamp = optional; using optional_uint64 = optional; #pragma db map type(optional_timestamp) as(brep::optional_uint64) \ to((?) \ ? std::chrono::duration_cast ( \ (?)->time_since_epoch ()).count () \ : brep::optional_uint64 ()) \ from((?) \ ? brep::timestamp ( \ std::chrono::duration_cast ( \ std::chrono::nanoseconds (*(?)))) \ : brep::optional_timestamp ()) #pragma db map type(duration) as(uint64_t) \ to(std::chrono::duration_cast (?).count ()) \ from(brep::duration (std::chrono::nanoseconds (?))) using optional_duration = optional; #pragma db map type(optional_duration) as(brep::optional_uint64) \ to((?) \ ? std::chrono::duration_cast (*(?)).count () \ : brep::optional_uint64 ()) \ from((?) \ ? brep::duration (std::chrono::nanoseconds (*(?))) \ : brep::optional_duration ()) // version // using bpkg::version; // Sometimes we need to split the version into two parts: the part // that goes into the object id (epoch, canonical upstream, canonical // release, revision) and the original upstream and release. This is what // the canonical_version and upstream_version value types are for. Note that // upstream_version derives from version and uses it as storage. The idea // here is this: when we split the version, we often still want to have the // "whole" version object readily accessible and that's exactly what this // strange contraption is for. See package for an example on how everything // fits together. // // Note that the object id cannot contain an optional member which is why we // make the revision type uint16_t and represent nullopt as zero. This // should be ok for package object ids referencing the package manifest // version values because an absent revision and zero revision mean the // same thing. // #pragma db value struct canonical_version { uint16_t epoch; string canonical_upstream; string canonical_release; uint16_t revision; canonical_version () = default; explicit canonical_version (const version& v) : epoch (v.epoch), canonical_upstream (v.canonical_upstream), canonical_release (v.canonical_release), revision (v.effective_revision ()) {} bool empty () const noexcept { // Note that an empty canonical_upstream doesn't denote an empty // canonical_version. Remeber, that canonical_upstream doesn't include // rightmost digit-only zero components? So non-empty version("0") has // an empty canonical_upstream. // return epoch == 0 && canonical_upstream.empty () && canonical_release.empty () && revision == 0; } // Change collation to ensure the proper comparison of the "absent" release // with a specified one. // // The default collation for UTF8-encoded TEXT columns in PostgreSQL is // UCA-compliant. This makes the statement 'a' < '~' to be false, which // in turn makes the statement 2.1-alpha < 2.1 to be false as well. // // Unicode Collation Algorithm (UCA): http://unicode.org/reports/tr10/ // #pragma db member(canonical_release) options("COLLATE \"C\"") }; #pragma db value transient struct upstream_version: version { #pragma db member(upstream_) virtual(string) \ get(this.upstream) \ set(this = brep::version ( \ 0, std::move (?), std::string (), brep::nullopt, 0)) #pragma db member(release_) virtual(optional_string) \ get(this.release) \ set(this = brep::version ( \ 0, std::move (this.upstream), std::move (?), brep::nullopt, 0)) upstream_version () = default; upstream_version (version v): version (move (v)) {} upstream_version& operator= (version v) {version& b (*this); b = v; return *this;} void init (const canonical_version& cv, const upstream_version& uv) { // Note: revert the zero revision mapping (see above). // *this = version (cv.epoch, uv.upstream, uv.release, (cv.revision != 0 ? optional (cv.revision) : nullopt), 0); assert (cv.canonical_upstream == canonical_upstream && cv.canonical_release == canonical_release); } }; // Wildcard version. Satisfies any dependency constraint and is represented // as 0+0 (which is also the "stub version"; since a real version is always // greater than the stub version, we reuse it to signify a special case). // extern const version wildcard_version; // target_triplet // using butl::target_triplet; #pragma db value(target_triplet) type("TEXT") // package_name // using bpkg::package_name; #pragma db value(package_name) type("CITEXT") #pragma db map type("CITEXT") as("TEXT") to("(?)::CITEXT") from("(?)::TEXT") // package_id // #pragma db value struct package_id { string tenant; package_name name; canonical_version version; package_id () = default; package_id (string t, package_name n, const brep::version& v) : tenant (move (t)), name (move (n)), version (v) {} }; // repository_type // using bpkg::repository_type; using bpkg::to_repository_type; #pragma db map type(repository_type) as(string) \ to(to_string (?)) \ from(brep::to_repository_type (?)) // repository_url // using bpkg::repository_url; #pragma db map type(repository_url) as(string) \ to((?).string ()) \ from((?).empty () ? brep::repository_url () : brep::repository_url (?)) // repository_location // using bpkg::repository_location; #pragma db value struct _repository_location { repository_url url; repository_type type; }; // Note that the type() call fails for an empty repository location. // #pragma db map type(repository_location) as(_repository_location) \ to(brep::_repository_location {(?).url (), \ (?).empty () \ ? brep::repository_type::pkg \ : (?).type ()}) \ from(brep::repository_location (std::move ((?).url), (?).type)) // repository_id // #pragma db value struct repository_id { string tenant; string canonical_name; repository_id () = default; repository_id (string t, string n) : tenant (move (t)), canonical_name (move (n)) {} }; // public_key_id // #pragma db value struct public_key_id { string tenant; string fingerprint; public_key_id () = default; public_key_id (string t, string f) : tenant (move (t)), fingerprint (move (f)) {} }; // build_class_expr // using bpkg::build_class_expr; using build_class_exprs = small_vector; #pragma db value(build_class_expr) definition #pragma db member(build_class_expr::expr) transient #pragma db member(build_class_expr::underlying_classes) transient #pragma db member(build_class_expr::expression) virtual(string) before \ get(this.string ()) \ set(this = brep::build_class_expr ((?), "" /* comment */)) // build_constraints // using bpkg::build_constraint; using build_constraints = vector; #pragma db value(build_constraint) definition // build_auxiliaries // using bpkg::build_auxiliary; using build_auxiliaries = vector; #pragma db value(build_auxiliary) definition // build_toolchain // #pragma db value struct build_toolchain { string name; brep::version version; }; // email // using bpkg::email; #pragma db value(email) definition #pragma db member(email::value) virtual(string) before access(this) column("") // build_package_config_template // using bpkg::build_package_config_template; // 1 for the default configuration which is always present. // template using build_package_configs_template = small_vector, 1>; // Return the address of the configuration object with the specified name, // if present, and NULL otherwise. // template inline build_package_config_template* find (const string& name, build_package_configs_template& cs) { auto i (find_if (cs.begin (), cs.end (), [&name] (const build_package_config_template& c) {return c.name == name;})); return i != cs.end () ? &*i : nullptr; } // Note that build_package_configs_template is a container of the value // type build_package_config_template, which contains multiple // containers: builds, constraint, auxiliaries, bot_keys. We will // persist/load each of them via a separate virtual container. // build_package_config_template::builds // using build_class_expr_key = odb::nested_key; using build_class_exprs_map = std::map; #pragma db value(build_class_expr_key) #pragma db member(build_class_expr_key::outer) column("config_index") #pragma db member(build_class_expr_key::inner) column("index") // build_package_config_template::constraints // using build_constraint_key = odb::nested_key; using build_constraints_map = std::map; #pragma db value(build_constraint_key) #pragma db member(build_constraint_key::outer) column("config_index") #pragma db member(build_constraint_key::inner) column("index") // build_package_config_template::auxiliaries // using build_auxiliary_key = odb::nested_key; using build_auxiliaries_map = std::map; #pragma db value(build_auxiliary_key) #pragma db member(build_auxiliary_key::outer) column("config_index") #pragma db member(build_auxiliary_key::inner) column("index") // build_package_config_template::bot_keys // // Note that the nested container support types (*_key, *_map, etc) are // package object type-specific for this container and are defined in the // package.hxx and build-package.hxx headers, respectively. // The primary reason why a package is unbuildable by the build bot // controller service. // enum class unbuildable_reason: std::uint8_t { stub, // A stub, otherwise test, // A separate test (built as part of primary), otherwise external, // From an external repository, otherwise unbuildable // From an internal unbuildable repository. }; string to_string (unbuildable_reason); unbuildable_reason to_unbuildable_reason (const string&); // May throw invalid_argument. inline ostream& operator<< (ostream& os, unbuildable_reason r) {return os << to_string (r);} using optional_unbuildable_reason = optional; #pragma db map type(unbuildable_reason) as(string) \ to(to_string (?)) \ from(brep::to_unbuildable_reason (?)) #pragma db map type(optional_unbuildable_reason) as(brep::optional_string) \ to((?) ? to_string (*(?)) : brep::optional_string ()) \ from((?) \ ? brep::to_unbuildable_reason (*(?)) \ : brep::optional_unbuildable_reason ()) \ // version_constraint // using bpkg::version_constraint; #pragma db value(version_constraint) definition // test_dependency_type // using bpkg::test_dependency_type; using bpkg::to_test_dependency_type; #pragma db map type(test_dependency_type) as(string) \ to(to_string (?)) \ from(brep::to_test_dependency_type (?)) // requirements // // Note that this is a 2-level nested container (see package.hxx for // details). // using bpkg::requirement_alternative; using bpkg::requirement_alternatives; using requirements = vector; #pragma db value(requirement_alternative) definition #pragma db value(requirement_alternatives) definition using requirement_alternative_key = odb::nested_key; using requirement_alternatives_map = std::map; #pragma db value(requirement_alternative_key) #pragma db member(requirement_alternative_key::outer) column("requirement_index") #pragma db member(requirement_alternative_key::inner) column("index") using requirement_key = odb::nested2_key; using requirement_alternative_requirements_map = std::map; #pragma db value(requirement_key) #pragma db member(requirement_key::outer) column("requirement_index") #pragma db member(requirement_key::middle) column("alternative_index") #pragma db member(requirement_key::inner) column("index") // Third-party service state which may optionally be associated with a // tenant (see also mod/tenant-service.hxx for background). // // Note that the {id, type} pair must be unique. // #pragma db value struct tenant_service { string id; string type; optional data; tenant_service () = default; tenant_service (string i, string t, optional d = nullopt) : id (move (i)), type (move (t)), data (move (d)) {} }; // Version comparison operators. // // Compare objects that have epoch, canonical_upstream, canonical_release, // and revision data members. The idea is that this works for both query // members of types version and canonical_version. Note, though, that the // object revisions should be comparable (both optional, numeric, etc), so // to compare version to query member or canonical_version you may need to // explicitly convert the version object to canonical_version. // template inline auto compare_version_eq (const T1& x, const T2& y, bool revision) -> decltype (x.revision == y.revision) { // Since we don't quite know what T1 and T2 are (and where the resulting // expression will run), let's not push our luck with something like // (!revision || x.revision == y.revision). // auto r (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release == y.canonical_release); return revision ? r && x.revision == y.revision : r; } template inline auto compare_version_ne (const T1& x, const T2& y, bool revision) -> decltype (x.revision == y.revision) { auto r (x.epoch != y.epoch || x.canonical_upstream != y.canonical_upstream || x.canonical_release != y.canonical_release); return revision ? r || x.revision != y.revision : r; } template inline auto compare_version_lt (const T1& x, const T2& y, bool revision) -> decltype (x.revision == y.revision) { auto r ( x.epoch < y.epoch || (x.epoch == y.epoch && x.canonical_upstream < y.canonical_upstream) || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release < y.canonical_release)); return revision ? r || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release == y.canonical_release && x.revision < y.revision) : r; } template inline auto compare_version_le (const T1& x, const T2& y, bool revision) -> decltype (x.revision == y.revision) { auto r ( x.epoch < y.epoch || (x.epoch == y.epoch && x.canonical_upstream < y.canonical_upstream)); return revision ? r || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release < y.canonical_release) || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release == y.canonical_release && x.revision <= y.revision) : r || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release <= y.canonical_release); } template inline auto compare_version_gt (const T1& x, const T2& y, bool revision) -> decltype (x.revision == y.revision) { auto r ( x.epoch > y.epoch || (x.epoch == y.epoch && x.canonical_upstream > y.canonical_upstream) || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release > y.canonical_release)); return revision ? r || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release == y.canonical_release && x.revision > y.revision) : r; } template inline auto compare_version_ge (const T1& x, const T2& y, bool revision) -> decltype (x.revision == y.revision) { auto r ( x.epoch > y.epoch || (x.epoch == y.epoch && x.canonical_upstream > y.canonical_upstream)); return revision ? r || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release > y.canonical_release) || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release == y.canonical_release && x.revision >= y.revision) : r || (x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.canonical_release >= y.canonical_release); } template inline auto order_by_version_desc ( const T& x, bool first = true) -> //decltype ("ORDER BY" + x.epoch) decltype (x.epoch == 0) { return (first ? "ORDER BY" : ", ") + x.epoch + "DESC," + x.canonical_upstream + "DESC," + x.canonical_release + "DESC," + x.revision + "DESC"; } template inline auto order_by_version ( const T& x, bool first = true) -> //decltype ("ORDER BY" + x.epoch) decltype (x.epoch == 0) { return (first ? "ORDER BY" : ", ") + x.epoch + "," + x.canonical_upstream + "," + x.canonical_release + "," + x.revision; } // Package id comparison operators. // inline bool operator< (const package_id& x, const package_id& y) { if (int r = x.tenant.compare (y.tenant)) return r < 0; if (int r = x.name.compare (y.name)) return r < 0; return compare_version_lt (x.version, y.version, true); } // Compare objects that have tenant, name, and version data members. The // idea is that this works for both query members of package id types (in // particular in join conditions) as well as for values of package_id type. // template inline auto operator== (const T1& x, const T2& y) -> decltype (x.tenant == y.tenant && x.name == y.name && x.version.epoch == y.version.epoch) { return x.tenant == y.tenant && x.name == y.name && compare_version_eq (x.version, y.version, true); } template inline auto operator!= (const T1& x, const T2& y) -> decltype (x.tenant == y.tenant && x.name == y.name && x.version.epoch == y.version.epoch) { return x.tenant != y.tenant || x.name != y.name || compare_version_ne (x.version, y.version, true); } // Allow comparing the query members with the query parameters bound by // reference to variables of the canonical version type (in particular in // the prepared queries). // // Note that it is not operator==() since the query template parameter type // can not be deduced from the function parameter types and needs to be // specified explicitly. // template inline auto equal (const V& x, const canonical_version& y) -> decltype (x.epoch == odb::query::_ref (y.epoch)) { using query = odb::query; return x.epoch == query::_ref (y.epoch) && x.canonical_upstream == query::_ref (y.canonical_upstream) && x.canonical_release == query::_ref (y.canonical_release) && x.revision == query::_ref (y.revision); } // Allow comparing the query members with the query parameters bound by // reference to variables of the package id type (in particular in the // prepared queries). // // Note that it is not operator==() since the query template parameter type // can not be deduced from the function parameter types and needs to be // specified explicitly. // template inline auto equal (const ID& x, const package_id& y) -> decltype (x.tenant == odb::query::_ref (y.tenant) && x.name == odb::query::_ref (y.name) && x.version.epoch == odb::query::_ref (y.version.epoch)) { using query = odb::query; return x.tenant == query::_ref (y.tenant) && x.name == query::_ref (y.name) && equal (x.version, y.version); } // Repository id comparison operators. // inline bool operator< (const repository_id& x, const repository_id& y) { if (int r = x.tenant.compare (y.tenant)) return r < 0; return x.canonical_name.compare (y.canonical_name) < 0; } // Compare objects that have tenant and canonical_name data members. The // idea is that this works for both query members of repository id types (in // particular in join conditions) as well as for values of repository_id // type. // template inline auto operator== (const T1& x, const T2& y) -> decltype (x.tenant == y.tenant && x.canonical_name == y.canonical_name) { return x.tenant == y.tenant && x.canonical_name == y.canonical_name; } template inline auto operator!= (const T1& x, const T2& y) -> decltype (x.tenant == y.tenant && x.canonical_name == y.canonical_name) { return x.tenant != y.tenant || x.canonical_name != y.canonical_name; } // Public key id comparison operators. // inline bool operator< (const public_key_id& x, const public_key_id& y) { if (int r = x.tenant.compare (y.tenant)) return r < 0; return x.fingerprint.compare (y.fingerprint) < 0; } // Compare objects that have tenant and fingerprint data members. The idea // is that this works for both query members of public key id types (in // particular in join conditions) as well as for values of public_key_id // type. // template inline auto operator== (const T1& x, const T2& y) -> decltype (x.tenant == y.tenant && x.fingerprint == y.fingerprint) { return x.tenant == y.tenant && x.fingerprint == y.fingerprint; } template inline auto operator!= (const T1& x, const T2& y) -> decltype (x.tenant == y.tenant && x.fingerprint == y.fingerprint) { return x.tenant != y.tenant || x.fingerprint != y.fingerprint; } } #endif // LIBBREP_COMMON_HXX