// 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 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 upstream; std::uint16_t revision; std::string canonical_upstream; }; } #include // We have to keep this mapping at the global scope instead of inside // the brep namespace because it needs 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 (), \ (?).upstream (), \ (?).revision (), \ (?).canonical_upstream ()}) \ from(bpkg::version ((?).epoch, std::move ((?).upstream), (?).revision)) 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; class package_version; using strings = std::vector; template using optional = butl::optional; 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 (?)) 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 (?)) using version = bpkg::version; using repository_location = bpkg::repository_location; #pragma db value struct package_version_id { std::string repository; std::string package; std::uint16_t epoch; std::string canonical_upstream; // Database mapping. // #pragma db member(repository) points_to(repository) //on_delete(cascade) #pragma db member(package) points_to(package) //on_delete(cascade) }; inline bool operator< (const package_version_id& x, const package_version_id& y) { int r (x.repository.compare (y.repository)); if (r != 0) return r < 0; r = x.package.compare (y.package); if (r != 0) return r < 0; return x.epoch < y.epoch || (x.epoch == y.epoch && x.canonical_upstream < y.canonical_upstream); } using priority = bpkg::priority; #pragma db value(priority) definition #pragma db member(priority::value) column("") using url = bpkg::url; #pragma db value(url) definition #pragma db member(url::value) virtual(std::string) before access(this) \ column("") 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; #pragma db value(licenses) definition using license_alternatives = std::vector; // dependencies // using comparison = bpkg::comparison; using version_comparison = bpkg::version_comparison; #pragma db value(version_comparison) definition #pragma db member(version_comparison::value) column("") // 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 condition. 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. // using dependency = bpkg::dependency; #pragma db value(dependency) definition using dependency_alternatives = bpkg::dependency_alternatives; #pragma db value(dependency_alternatives) definition using dependencies = std::vector; // requirements // using requirement_alternatives = bpkg::requirement_alternatives; #pragma db value(requirement_alternatives) definition using requirements = std::vector; // Intended for instantiating key classes for maps used for simulation of // 2-dimensional value type containers. Parameter type T not being used in // template implementation is required to instantiate unrelated key // classes to achieve proper table column naming with ODB pragmas. // template struct index_pair { std::size_t first; std::size_t second; index_pair () = default; index_pair (std::size_t f, std::size_t s): first (f), second (s) {} bool operator< (const index_pair& v) const { return first < v.first || (first == v.first && second < v.second); } }; #pragma db object pointer(std::shared_ptr) session class repository { public: using path_type = brep::path; using timestamp_type = brep::timestamp; using package_versions_type = std::vector>; using prerequisite_repositories_type = std::vector>; // Create internal repository. // repository (repository_location, std::string display_name, dir_path local_path); // Create external repository. // explicit repository (repository_location l) : location (std::move (l)), internal (false) {} 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_type packages_timestamp; // Initialized with timestamp_nonexistent by default. // For external repositories stays timestamp_nonexistent. // timestamp_type repositories_timestamp; bool internal; package_versions_type package_versions; prerequisite_repositories_type prerequisite_repositories; // Database mapping. // #pragma db value struct _id_type { std::string canonical_name; std::string location; }; _id_type _id () const; void _id (_id_type&&); #pragma db member(location) transient #pragma db member(id) virtual(_id_type) before id(canonical_name) \ get(_id) set(_id (std::move (?))) column("") #pragma db member(package_versions) inverse(id.data.repository) #pragma db member(prerequisite_repositories) id_column("repository") \ value_column("prerequisite_repository") value_not_null private: friend class odb::access; repository () = default; }; #pragma db object pointer(std::shared_ptr) session class package { public: using url_type = brep::url; using email_type = brep::email; package (std::string name, std::string summary, strings tags, optional description, url_type, optional package_url, email_type, optional package_email); // Manifest data. // std::string name; std::string summary; strings tags; optional description; url_type url; optional package_url; email_type email; optional package_email; std::vector> versions; // Additional data. // // Database mapping. // #pragma db member(name) id #pragma db member(tags) id_column("package") value_column("tag") #pragma db member(versions) inverse(id.data.package) private: friend class odb::access; package () = default; }; #pragma db object pointer(std::shared_ptr) session class package_version { public: using repository_type = brep::repository; using package_type = brep::package; using version_type = brep::version; using priority_type = brep::priority; using license_alternatives_type = brep::license_alternatives; using dependencies_type = brep::dependencies; using requirements_type = brep::requirements; package_version (odb::lazy_shared_ptr, odb::lazy_shared_ptr, version_type, priority_type, license_alternatives_type, std::string changes, dependencies_type, requirements_type, optional location); // Manifest data. // odb::lazy_shared_ptr repository; odb::lazy_shared_ptr package; version_type version; priority_type priority; license_alternatives_type license_alternatives; std::string changes; dependencies_type dependencies; requirements_type requirements; optional location; // Database mapping. // // id // #pragma db value struct _id_type { #pragma db column("") package_version_id data; std::string upstream; std::uint16_t revision; }; _id_type _id () const; void _id (_id_type&&, odb::database&); #pragma db member(version) transient #pragma db member(package) transient #pragma db member(repository) transient #pragma db member(id) virtual(_id_type) before id(data) \ get(_id) set(_id (std::move (?), (!))) column("") // license // using _license_key = index_pair; using _licenses_type = std::map<_license_key, std::string>; #pragma db value(_license_key) #pragma db member(_license_key::first) column("alternative") #pragma db member(_license_key::second) column("index") #pragma db member(license_alternatives) id_column("") value_column("") #pragma db member(licenses) \ virtual(_licenses_type) \ after(license_alternatives) \ get(_get (this.license_alternatives)) \ set(_set (this.license_alternatives, (?))) \ id_column("") key_column("") value_column("license") // dependencies // using _dependency_key = index_pair; using _dependency_alternatives_type = std::map<_dependency_key, dependency>; #pragma db value(_dependency_key) #pragma db member(_dependency_key::first) column("dependency") #pragma db member(_dependency_key::second) column("index") #pragma db member(dependencies) id_column("") value_column("") #pragma db member(dependency_alternatives) \ virtual(_dependency_alternatives_type) \ after(dependencies) \ get(_get (this.dependencies)) \ set(_set (this.dependencies, (?))) \ id_column("") key_column("") value_column("dep_") // requirements // using _requirement_key = index_pair; using _requirement_alternatives_type = std::map<_requirement_key, std::string>; #pragma db value(_requirement_key) #pragma db member(_requirement_key::first) column("requirement") #pragma db member(_requirement_key::second) column("index") #pragma db member(requirements) id_column("") value_column("") #pragma db member(requirement_alternatives) \ virtual(_requirement_alternatives_type) \ after(requirements) \ get(_get (this.requirements)) \ set(_set (this.requirements, (?))) \ id_column("") key_column("") value_column("id") private: friend class odb::access; package_version () = default; }; #pragma db view object(package_version) \ query((?) + "ORDER BY" + package_version::id.data.epoch + "DESC," + \ package_version::id.data.canonical_upstream + "DESC," + \ package_version::id.revision + "DESC LIMIT 1") struct max_package_version { using version_type = brep::version; version_type version; void _id (package_version::_id_type&&); // Database mapping. // #pragma db member(version) transient // Can't specify column expression using aggregate max function for a // data member of a composite value type. // #pragma db member(id) virtual(package_version::_id_type) \ get() set(_id (std::move (?))) }; #pragma db view object(package) struct package_count { #pragma db column("count(*)") std::size_t count; }; #pragma db view object(package_version) struct package_version_count { #pragma db column("count(*)") std::size_t count; }; } // Nested container emulation support for ODB. // // Note that the outer index in the inner container should strictly // speaking be a foreign key pointing to the index of the outer // container. The only way to achieve this currently is to manually // add the constraint via ALTER TABLE ADD CONSTRAINT. Note, however, // that as long as we only modify these tables via the ODB container // interface, not having the foreign key (and not having ON DELETE // CASCADE) should be harmless (since we have a foreign key pointing // to the object id). // #include #include #include // size_t #include // declval() #include #include // remove_reference namespace odb { template struct _inner: std::remove_reference ()[0])> {}; template std::map, typename _inner::type> _get (const std::vector& v) { using namespace std; using I = typename _inner::type; using key = brep::index_pair; map r; for (size_t n (0); n != v.size (); ++n) { const O& o (v[n]); for (size_t m (0); m != o.size (); ++m) r.emplace (key (n, m), o[m]); } return r; } //@@ Second argument should be && once ODB uses move(). // template void _set (std::vector& v, std::map& r) { using namespace std; for (auto& p: r) { size_t n (p.first.first); size_t m (p.first.second); I& i (p.second); assert (n < v.size ()); assert (m == v[n].size ()); v[n].push_back (std::move (i)); } } } #endif // BREP_PACKAGE