diff options
Diffstat (limited to 'libbrep/common.hxx')
-rw-r--r-- | libbrep/common.hxx | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/libbrep/common.hxx b/libbrep/common.hxx new file mode 100644 index 0000000..bf60491 --- /dev/null +++ b/libbrep/common.hxx @@ -0,0 +1,341 @@ +// file : libbrep/common.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBREP_COMMON_HXX +#define LIBBREP_COMMON_HXX + +#include <ratio> +#include <chrono> +#include <type_traits> // static_assert + +#include <libbrep/types.hxx> +#include <libbrep/utility.hxx> + +// 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; + uint16_t revision; + string upstream; + optional<string> release; + }; +} + +#include <bpkg/manifest> + +namespace brep +{ + using optional_version = optional<bpkg::version>; + 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 <assert.h> 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)) + +#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) \ + : brep::optional_version ()) + +namespace brep +{ + // path + // + #pragma db map type(path) as(string) to((?).string ()) from(brep::path (?)) + + using optional_path = optional<path>; + using optional_string = optional<string>; + + #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 (?)) + + // Ensure that timestamp can be represented in nonoseconds without loss of + // accuracy, so the following ODB mapping is adequate. + // + static_assert( + std::ratio_greater_equal<timestamp::period, + std::chrono::nanoseconds::period>::value, + "The following timestamp ODB mapping is invalid"); + + // As it pointed out in butl/timestamp 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<std::chrono::nanoseconds> ( \ + (?).time_since_epoch ()).count ()) \ + from(brep::timestamp ( \ + std::chrono::duration_cast<brep::timestamp::duration> ( \ + std::chrono::nanoseconds (?)))) + + // version + // + using bpkg::version; + + #pragma db value + struct canonical_version + { + uint16_t epoch; + string canonical_upstream; + string canonical_release; + uint16_t 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 (), 0)) + + #pragma db member(release_) virtual(optional_string) \ + get(this.release) \ + set(this = brep::version ( \ + 0, std::move (this.upstream), std::move (?), 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) + { + *this = version (cv.epoch, uv.upstream, uv.release, cv.revision); + 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; + + #pragma db value + struct package_id + { + string name; + canonical_version version; + + package_id () = default; + package_id (string n, const brep::version& v) + : name (move (n)), + version { + v.epoch, v.canonical_upstream, v.canonical_release, v.revision} + { + } + }; + + // Version comparison operators. + // + // They allow comparing 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 + // as well as for comparing canonical_version to version. + // + template <typename T1, typename T2> + inline auto + compare_version_eq (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + // 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 <typename T1, typename T2> + inline auto + compare_version_ne (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + 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 <typename T1, typename T2> + inline auto + compare_version_lt (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + 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 <typename T1, typename T2> + inline auto + compare_version_le (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + 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 <typename T1, typename T2> + inline auto + compare_version_gt (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + 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 <typename T1, typename T2> + inline auto + compare_version_ge (const T1& x, const T2& y, bool revision) + -> decltype (x.epoch == y.epoch) + { + 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 <typename T> + inline auto + order_by_version_desc (const T& x) -> //decltype ("ORDER BY" + x.epoch) + decltype (x.epoch == 0) + { + return "ORDER BY" + + x.epoch + "DESC," + + x.canonical_upstream + "DESC," + + x.canonical_release + "DESC," + + x.revision + "DESC"; + } + + inline bool + operator< (const package_id& x, const package_id& y) + { + if (int r = x.name.compare (y.name)) + return r < 0; + + return compare_version_lt (x.version, y.version, true); + } +} + +#endif // LIBBREP_COMMON_HXX |