// file : brep/package -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BREP_PACKAGE #define BREP_PACKAGE #include #include #include #include #include // shared_ptr #include // size_t #include // move() #include // uint16 #include #include // database #include #include #include #include #include #include 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 { std::uint16_t epoch; std::string canonical_upstream; std::uint16_t revision; std::string upstream; }; } #include // 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 and comparison). // #pragma db map type(bpkg::version) as(brep::_version) \ to(brep::_version{(?).epoch, \ (?).canonical_upstream, \ (?).revision, \ (?).upstream}) \ from(bpkg::version ((?).epoch, std::move ((?).upstream), (?).revision)) #pragma db map type(bpkg::comparison) as(std::string) \ to(bpkg::to_string (?)) \ from(bpkg::to_comparison (?)) namespace brep { // @@ If namespace, then should probably call it 'repo'. // // @@ Might make sense to put some heavy members (e.g., description, // containers) into a separate section. // // @@ Not sure there is a benefit in making tags a full-blown container // (i.e., a separate table). Maybe provide a mapping of vector // to TEXT as a comma-separated list. // // Forward declarations. // class repository; class package; using strings = std::vector; template using optional = butl::optional; // path // using path = butl::path; #pragma db map type(path) as(std::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 ()) using dir_path = butl::dir_path; #pragma db map type(dir_path) as(std::string) \ to((?).string ()) from(brep::dir_path (?)) // timestamp // using timestamp = butl::timestamp; #pragma db map type(timestamp) as(std::uint64_t) \ to(std::chrono::system_clock::to_time_t (?)) \ from(std::chrono::system_clock::from_time_t (?)) // version // using version = bpkg::version; #pragma db value struct canonical_version { std::uint16_t epoch; std::string canonical_upstream; std::uint16_t revision; }; #pragma db value transient struct upstream_version: version { #pragma db member(upstream_) virtual(std::string) \ get(this.upstream) \ set(this = brep::version (0, std::move (?), 0)) upstream_version () = default; upstream_version (version v): version (std::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, cv.revision); assert (cv.canonical_upstream == canonical_upstream); } }; // priority // using priority = bpkg::priority; #pragma db value(priority) definition #pragma db member(priority::value) column("") // url // using url = bpkg::url; #pragma db value(url) definition #pragma db member(url::value) virtual(std::string) before access(this) \ column("") // email // using email = bpkg::email; #pragma db value(email) definition #pragma db member(email::value) virtual(std::string) before access(this) \ column("") // licenses // using licenses = bpkg::licenses; using license_alternatives = std::vector; #pragma db value(licenses) definition // dependencies // using comparison = bpkg::comparison; using dependency_constraint = bpkg::dependency_constraint; #pragma db value(dependency_constraint) definition // Notes: // // 1. Will the package be always resolvable? What if it is in // another repository (i.e., a "chained" third-party repo). // The question is then whether we will load such "third- // party packages" (i.e., packages that are not in our // repository). If the answer is yes, then we can have // a pointer here. If the answer is no, then we can't. // Also, if the answer is yes, we probably don't need to // load as much information as for "our own" packages. We // also shouldn't be showing them in search results, etc. // I think all we need is to know which repository this // package comes from so that we can tell the user. How are // we going to capture this? Poly hierarchy of packages? // // 2. I believe we don't need to use a weak pointer here since // there should be no package dependency cycles (and therefore // ownership cycles). // // 3. Actually there can be dependency cycle as dependency referes not to // just a package but a specific version, so for the same pair of // packages dependency for different versions can have an opposite // directions. The possible solution is instead of a package we point // to the earliest version that satisfies the constraint. But this // approach requires to ensure no cycles exist before instantiating // package objects which in presense of "foreign" packages can be // tricky. Can stick to just a package name until get some clarity on // "foreign" package resolution. // // 4. As we left just the the package class the dependency resolution come to // finding the best version matching package object. The question is // if to resolve dependencies on the loading phase or in the WEB interface // when required. The arguments in favour of doing that during loading // phase are: // * WEB interface get offloaded from a possibly expensive queries // which otherwise have to be executed multiple times for the same // dependency no matter the result would be the same. // * No need to complicate persisted object model with repository // relations otherwise required just for dependency resolution. // using dependency = bpkg::dependency; using dependency_alternatives = bpkg::dependency_alternatives; using dependencies = std::vector; #pragma db value(dependency) definition #pragma db member(dependency::constraint) column("") #pragma db value(dependency_alternatives) definition // requirements // using requirement_alternatives = bpkg::requirement_alternatives; using requirements = std::vector; #pragma db value(requirement_alternatives) definition // repository_location // using repository_location = bpkg::repository_location; #pragma db map type(repository_location) as(std::string) \ to((?).string ()) from(brep::repository_location (?)) #pragma db object pointer(std::shared_ptr) session class repository { public: // Create internal repository. // repository (repository_location, std::string display_name, dir_path local_path); // Create external repository. // explicit repository (repository_location); std::string name; // Object id (canonical name). repository_location location; std::string display_name; // Non empty for internal repositories and external ones with a filesystem // path location. // dir_path local_path; // Initialized with timestamp_nonexistent by default. // timestamp packages_timestamp; // Initialized with timestamp_nonexistent by default. // For external repositories stays timestamp_nonexistent. // timestamp repositories_timestamp; bool internal; // Database mapping. // #pragma db member(name) id #pragma db member(location) \ set(this.location = std::move (?); \ assert (this.name == this.location.canonical_name ())) private: friend class odb::access; repository () = default; }; #pragma db value struct package_id { std::string name; canonical_version version; package_id () = default; package_id (std::string n, const brep::version& v) : name (std::move (n)), version {v.epoch, v.canonical_upstream, v.revision} { } }; // The 'to' expression calls the PostgreSQL to_tsvector(weighted_text) // function overload (package-extra.sql). Since we are only interested // in "write-only" members of this type, make the 'from' expression // always return empty string (we still have to work the placeholder // in to keep overprotective ODB happy). // #pragma db map type("tsvector") as("TEXT") \ to("to_tsvector((?)::weighted_text)") from("COALESCE('',(?))") // C++ type for weighted PostgreSQL tsvector. // #pragma db value type("tsvector") struct weighted_text { std::string a; std::string b; std::string c; std::string d; }; #pragma db object pointer(std::shared_ptr) session class package { public: using repository_type = brep::repository; using version_type = brep::version; using priority_type = brep::priority; using license_alternatives_type = brep::license_alternatives; using url_type = brep::url; using email_type = brep::email; using dependencies_type = brep::dependencies; using requirements_type = brep::requirements; // Create internal package object. // package (std::string name, version_type, priority_type, std::string summary, license_alternatives_type, strings tags, optional description, std::string changes, url_type, optional package_url, email_type, optional package_email, dependencies_type, requirements_type, optional location, std::shared_ptr); // Create external package object. // // External repository packages can appear on the WEB interface only on // the package version details page in dependency list in the form of a // link to the corresponding WEB page. The only package information // required to compose such a link is the package name, version, and // repository location. // package (std::string name, version_type, std::shared_ptr); // Manifest data. // package_id id; upstream_version version; priority_type priority; std::string summary; license_alternatives_type license_alternatives; strings tags; optional description; std::string changes; url_type url; optional package_url; email_type email; optional package_email; dependencies_type dependencies; requirements_type requirements; odb::lazy_shared_ptr internal_repository; // Path to the package file. Set only for packages present in internal // repository. // optional location; std::vector> external_repositories; // Database mapping. // #pragma db member(id) id column("") #pragma db member(version) set(this.version.init (this.id.version, (?))) // license // using _license_key = odb::nested_key; using _licenses_type = std::map<_license_key, std::string>; #pragma db value(_license_key) #pragma db member(_license_key::outer) column("alternative_index") #pragma db member(_license_key::inner) column("index") #pragma db member(license_alternatives) id_column("") value_column("") #pragma db member(licenses) \ virtual(_licenses_type) \ after(license_alternatives) \ get(odb::nested_get (this.license_alternatives)) \ set(odb::nested_set (this.license_alternatives, std::move (?))) \ id_column("") key_column("") value_column("license") // tags // #pragma db member(tags) id_column("") value_column("tag") // dependencies // using _dependency_key = odb::nested_key; using _dependency_alternatives_type = std::map<_dependency_key, dependency>; #pragma db value(_dependency_key) #pragma db member(_dependency_key::outer) column("dependency_index") #pragma db member(_dependency_key::inner) column("index") #pragma db member(dependencies) id_column("") value_column("") #pragma db member(dependency_alternatives) \ virtual(_dependency_alternatives_type) \ after(dependencies) \ get(odb::nested_get (this.dependencies)) \ set(odb::nested_set (this.dependencies, std::move (?))) \ id_column("") key_column("") value_column("dep_") // requirements // using _requirement_key = odb::nested_key; using _requirement_alternatives_type = std::map<_requirement_key, std::string>; #pragma db value(_requirement_key) #pragma db member(_requirement_key::outer) column("requirement_index") #pragma db member(_requirement_key::inner) column("index") #pragma db member(requirements) id_column("") value_column("") #pragma db member(requirement_alternatives) \ virtual(_requirement_alternatives_type) \ after(requirements) \ get(odb::nested_get (this.requirements)) \ set(odb::nested_set (this.requirements, move (?))) \ id_column("") key_column("") value_column("id") // external_repositories // #pragma db member(external_repositories) \ id_column("") value_column("repository") value_not_null // search_index // #pragma db member(search_index) virtual(weighted_text) null \ access(search_text) #pragma db index method("GIN") member(search_index) private: friend class odb::access; package () = default; // Save keywords, summary, description, and changes to weighted_text // a, b, c, d members, respectively. So a word found in keywords will // have a higher weight than if it's found in the summary. // weighted_text search_text () const; // Noop as search_index is a write-only member. // void search_text (const weighted_text&) {} }; // Package search query matching rank. // #pragma db view query("/*CALL*/ SELECT * FROM search_latest_packages(?)") struct latest_package_search_rank { package_id id; double rank; }; #pragma db view \ query("/*CALL*/ SELECT count(*) FROM search_latest_packages(?)") struct latest_package_count { std::size_t result; operator std::size_t () const {return result;} }; #pragma db view query("/*CALL*/ SELECT * FROM search_packages(?)") struct package_search_rank { package_id id; double rank; }; #pragma db view query("/*CALL*/ SELECT count(*) FROM search_packages(?)") struct package_count { std::size_t result; operator std::size_t () const {return result;} }; #pragma db view query("/*CALL*/ SELECT * FROM latest_package(?)") struct latest_package { package_id id; }; // Version comparison operators. // // They allow comparing objects that have epoch, canonical_upstream // and revision data members. // template inline auto operator== (const T1& x, const T2& y) -> decltype (x.epoch == y.epoch) { return x.epoch == y.epoch && x.canonical_upstream == y.canonical_upstream && x.revision == y.revision; } template inline auto operator!= (const T1& x, const T2& y) -> decltype (x.epoch != y.epoch) { return x.epoch != y.epoch || x.canonical_upstream != y.canonical_upstream || x.revision != y.revision; } template inline auto operator< (const T1& x, const T2& y) -> decltype (x.epoch < y.epoch) { return 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.revision < y.revision); } template inline auto operator<= (const T1& x, const T2& y) -> decltype (x.epoch <= y.epoch) { return 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.revision <= y.revision); } template inline auto operator> (const T1& x, const T2& y) -> decltype (x.epoch > y.epoch) { return 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.revision > y.revision); } template inline auto operator>= (const T1& x, const T2& y) -> decltype (x.epoch >= y.epoch) { return 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.revision >= y.revision); } template 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.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 x.version < y.version; } } namespace odb { using ::brep::operator==; using ::brep::operator!=; using ::brep::operator<; using ::brep::operator<=; using ::brep::operator>; using ::brep::operator>=; using ::brep::order_by_version_desc; } #endif // BREP_PACKAGE