// file : libbrep/package.hxx -*- C++ -*- // copyright : Copyright (c) 2014-2019 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef LIBBREP_PACKAGE_HXX #define LIBBREP_PACKAGE_HXX #include #include #include #include #include #include #include #include // Must be included last (see assert). // Used by the data migration entries. // #define LIBBREP_PACKAGE_SCHEMA_VERSION_BASE 7 #pragma db model version(LIBBREP_PACKAGE_SCHEMA_VERSION_BASE, 11, closed) namespace brep { // @@ 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; // priority // using bpkg::priority; #pragma db value(priority) definition #pragma db member(priority::value) column("") // url // using bpkg::url; #pragma db value(url) definition #pragma db member(url::value) virtual(string) before access(this) column("") // email // using bpkg::email; #pragma db value(email) definition #pragma db member(email::value) virtual(string) before access(this) column("") // licenses // using bpkg::licenses; using license_alternatives = vector; #pragma db value(licenses) definition // dependencies // using 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 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. // #pragma db value struct dependency { using package_type = brep::package; package_name name; optional constraint; // Resolved dependency package. NULL if the repository load was shallow // and so the package dependencies are not resolved. // lazy_shared_ptr package; // Database mapping. // #pragma db member(constraint) column("") }; ostream& operator<< (ostream&, const dependency&); bool operator== (const dependency&, const dependency&); bool operator!= (const dependency&, const dependency&); #pragma db value class dependency_alternatives: public vector { public: bool conditional; bool buildtime; string comment; dependency_alternatives () = default; dependency_alternatives (bool d, bool b, string c) : conditional (d), buildtime (b), comment (move (c)) {} }; using dependencies = vector; // requirements // using bpkg::requirement_alternatives; using requirements = vector; #pragma db value(requirement_alternatives) definition // certificate // #pragma db value class certificate { public: string fingerprint; // SHA256 fingerprint. Note: foreign-mapped in build. string name; // CN component of Subject. string organization; // O component of Subject. string email; // email: in Subject Alternative Name. string pem; // PEM representation. }; #pragma db object pointer(shared_ptr) session class tenant { public: // Create the tenant object with the timestamp set to now and the archived // flag set to false. // explicit tenant (string id); string id; timestamp creation_timestamp; bool archived = false; // Note: foreign-mapped in build. // Database mapping. // #pragma db member(id) id private: friend class odb::access; tenant () = default; }; #pragma db view object(tenant) struct tenant_id { #pragma db column("id") string value; }; // Tweak repository_id mapping to include a constraint (this only affects // the database schema). // #pragma db member(repository_id::tenant) points_to(tenant) #pragma db object pointer(shared_ptr) session class repository { public: using email_type = brep::email; using certificate_type = brep::certificate; // Create internal repository. // repository (string tenant, repository_location, string display_name, repository_location cache_location, optional, uint16_t priority); // Create external repository. // explicit repository (string tenant, repository_location); repository_id id; const string& tenant; // Tracks id.tenant. const string& canonical_name; // Tracks id.canonical_name. repository_location location; // Note: foreign-mapped in build. string display_name; // The order in the internal repositories configuration file, starting from // 1. 0 for external repositories. // uint16_t priority; optional interface_url; // Present only for internal repositories. // optional email; optional summary; optional description; // Location of the repository local cache. Non empty for internal // repositories and external ones with a filesystem path location. // repository_location cache_location; // Present only for internal signed repositories. Note that it is // foreign-mapped in build. // optional certificate; // Initialized with timestamp_nonexistent by default. // timestamp packages_timestamp; // Initialized with timestamp_nonexistent by default. // timestamp repositories_timestamp; bool internal; vector> complements; vector> prerequisites; // Database mapping. // #pragma db member(id) id column("") #pragma db member(tenant) transient #pragma db member(canonical_name) transient #pragma db member(location) \ set(this.location = std::move (?); \ assert (this.canonical_name == this.location.canonical_name ())) #pragma db member(complements) id_column("repository_") \ value_column("complement_") value_not_null #pragma db member(prerequisites) id_column("repository_") \ value_column("prerequisite_") value_not_null private: friend class odb::access; repository (): tenant (id.tenant), canonical_name (id.canonical_name) {} }; // Used for data migration (see migrate/migrate.cxx for details). // #pragma db view object(repository) query(distinct) struct repository_tenant { #pragma db column("tenant") string id; }; // 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 { string a; string b; string c; string d; }; // Tweak package_id mapping to include a constraint (this only affects the // database schema). // #pragma db member(package_id::tenant) points_to(tenant) #pragma db object pointer(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; using build_constraints_type = brep::build_constraints; // Create internal package object. Note that for stubs the build // constraints are meaningless, and so not saved. // package (package_name, version_type, package_name project, priority_type, string summary, license_alternatives_type, strings tags, optional description, string changes, optional, optional doc_url, optional src_url, optional package_url, optional, optional package_email, optional build_email, optional build_warning_email, optional build_error_email, dependencies_type, requirements_type, build_class_exprs, build_constraints_type, optional location, optional fragment, optional sha256sum, shared_ptr); // Create external package object. // // External repository packages can appear on the WEB interface only 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 (package_name name, version_type, shared_ptr); bool internal () const noexcept {return internal_repository != nullptr;} // Manifest data. // package_id id; const string& tenant; // Tracks id.tenant. const package_name& name; // Tracks id.name. upstream_version version; // Matches the package name if the project name is not specified in // the manifest. // package_name project; priority_type priority; string summary; license_alternatives_type license_alternatives; strings tags; optional description; string changes; optional url; optional doc_url; optional src_url; optional package_url; optional email; optional package_email; optional build_email; optional build_warning_email; optional build_error_email; dependencies_type dependencies; requirements_type requirements; build_class_exprs builds; // Note: foreign-mapped in build. build_constraints_type build_constraints; // Note: foreign-mapped in build. odb::section build_section; // Note that it is foreign-mapped in build. // lazy_shared_ptr internal_repository; // Path to the package file. Present only for internal packages. // optional location; // Present only for packages that come from the supporting fragmentation // internal repository (normally version control-based). // optional fragment; // Present only for internal packages. // optional sha256sum; vector> other_repositories; // Database mapping. // #pragma db member(id) id column("") #pragma db member(tenant) transient #pragma db member(name) transient #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, 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, 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, std::move (?))) \ id_column("") key_column("") value_column("id") // builds // #pragma db member(builds) id_column("") value_column("") \ section(build_section) // build_constraints // #pragma db member(build_constraints) id_column("") value_column("") \ section(build_section) #pragma db member(build_section) load(lazy) update(always) // other_repositories // #pragma db member(other_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 (): tenant (id.tenant), name (id.name) {} // 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 { size_t result; operator 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 { size_t result; operator size_t () const {return result;} }; #pragma db view query("/*CALL*/ SELECT * FROM latest_package(?)") struct latest_package { package_id id; }; } // Workaround for GCC __is_invocable/non-constant condition bug (#86441). // #ifdef ODB_COMPILER namespace std { template class map; } #endif #endif // LIBBREP_PACKAGE_HXX