aboutsummaryrefslogtreecommitdiff
path: root/libbrep/package.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbrep/package.hxx')
-rw-r--r--libbrep/package.hxx500
1 files changed, 500 insertions, 0 deletions
diff --git a/libbrep/package.hxx b/libbrep/package.hxx
new file mode 100644
index 0000000..8164639
--- /dev/null
+++ b/libbrep/package.hxx
@@ -0,0 +1,500 @@
+// file : libbrep/package.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBREP_PACKAGE_HXX
+#define LIBBREP_PACKAGE_HXX
+
+#include <map>
+#include <chrono>
+
+#include <odb/core.hxx>
+#include <odb/nested-container.hxx>
+
+#include <libbrep/types.hxx>
+#include <libbrep/utility.hxx>
+
+#include <libbrep/common.hxx> // Must be included last (see assert).
+
+// Used by the data migration entries.
+//
+#define LIBBREP_PACKAGE_SCHEMA_VERSION_BASE 4
+
+#pragma db model version(LIBBREP_PACKAGE_SCHEMA_VERSION_BASE, 4, open)
+
+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<string>
+ // 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<licenses>;
+
+ #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;
+
+ lazy_shared_ptr<package_type> package;
+ optional<dependency_constraint> constraint;
+
+ // Prerequisite package name.
+ //
+ string
+ name () const;
+
+ // Database mapping.
+ //
+ #pragma db member(package) column("") not_null
+ #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<dependency>
+ {
+ 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<dependency_alternatives>;
+
+ // requirements
+ //
+ using bpkg::requirement_alternatives;
+ using requirements = vector<requirement_alternatives>;
+
+ #pragma db value(requirement_alternatives) definition
+
+ // repository_location
+ //
+ using bpkg::repository_location;
+
+ #pragma db map type(repository_location) as(string) \
+ to((?).string ()) from(brep::repository_location (?))
+
+ #pragma db value
+ class certificate
+ {
+ public:
+ string fingerprint; // SHA256 fingerprint.
+ 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 repository
+ {
+ public:
+ using email_type = brep::email;
+ using certificate_type = brep::certificate;
+
+ // Create internal repository.
+ //
+ repository (repository_location,
+ string display_name,
+ repository_location cache_location,
+ optional<certificate_type>,
+ uint16_t priority);
+
+ // Create external repository.
+ //
+ explicit
+ repository (repository_location);
+
+ string name; // Object id (canonical name).
+ repository_location location;
+ string display_name;
+
+ // The order in the internal repositories configuration file, starting from
+ // 1. 0 for external repositories.
+ //
+ uint16_t priority;
+
+ optional<string> url;
+
+ // Present only for internal repositories.
+ //
+ optional<email_type> email;
+ optional<string> summary;
+ optional<string> 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.
+ //
+ optional<certificate_type> certificate;
+
+ // Initialized with timestamp_nonexistent by default.
+ //
+ timestamp packages_timestamp;
+
+ // Initialized with timestamp_nonexistent by default.
+ //
+ timestamp repositories_timestamp;
+
+ bool internal;
+ vector<lazy_weak_ptr<repository>> complements;
+ vector<lazy_weak_ptr<repository>> prerequisites;
+
+ // Database mapping.
+ //
+ #pragma db member(name) id
+
+ #pragma db member(location) \
+ set(this.location = std::move (?); \
+ assert (this.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 () = default;
+ };
+
+ // 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;
+ };
+
+ #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;
+
+ // Create internal package object.
+ //
+ package (string name,
+ version_type,
+ priority_type,
+ string summary,
+ license_alternatives_type,
+ strings tags,
+ optional<string> description,
+ string changes,
+ url_type,
+ optional<url_type> package_url,
+ email_type,
+ optional<email_type> package_email,
+ optional<email_type> build_email,
+ dependencies_type,
+ requirements_type,
+ optional<path> location,
+ optional<string> sha256sum,
+ shared_ptr<repository_type>);
+
+ // 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 (string name, version_type, shared_ptr<repository_type>);
+
+ bool
+ internal () const noexcept {return internal_repository != nullptr;}
+
+ // Manifest data.
+ //
+ package_id id;
+ upstream_version version;
+ priority_type priority;
+ string summary;
+ license_alternatives_type license_alternatives;
+ strings tags;
+ optional<string> description;
+ string changes;
+ url_type url;
+ optional<url_type> package_url;
+ email_type email;
+ optional<email_type> package_email;
+ optional<email_type> build_email;
+ dependencies_type dependencies;
+ requirements_type requirements;
+ lazy_shared_ptr<repository_type> internal_repository;
+
+ // Path to the package file. Present only for internal packages.
+ //
+ optional<path> location;
+
+ // Present only for internal packages.
+ //
+ optional<string> sha256sum;
+
+ vector<lazy_shared_ptr<repository_type>> other_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<licenses>;
+ 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<dependency_alternatives>;
+ 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<requirement_alternatives>;
+ 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")
+
+ // 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 () = 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
+ {
+ 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;
+ };
+
+ #pragma db view object(package)
+ struct package_version
+ {
+ package_id id;
+ upstream_version version;
+
+ // Database mapping.
+ //
+ #pragma db member(id) column("")
+ #pragma db member(version) set(this.version.init (this.id.version, (?)))
+ };
+}
+
+#endif // LIBBREP_PACKAGE_HXX