From 17d44ec2c41a5b485cecae51a07396f85a601248 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 30 Jun 2017 02:53:57 +0300 Subject: Fix builds page to correctly display unbuilt package count --- mod/database-module.cxx | 2 + mod/database.cxx | 44 ++++++++- mod/database.hxx | 1 + mod/mod-builds.cxx | 238 ++++++++++++++++++++++-------------------------- mod/options.cli | 24 ++++- 5 files changed, 175 insertions(+), 134 deletions(-) (limited to 'mod') diff --git a/mod/database-module.cxx b/mod/database-module.cxx index 67e4c9d..ab39a18 100644 --- a/mod/database-module.cxx +++ b/mod/database-module.cxx @@ -43,6 +43,7 @@ namespace brep init (const options::package_db& o, size_t retry) { package_db_ = shared_database (o.package_db_user (), + o.package_db_role (), o.package_db_password (), o.package_db_name (), o.package_db_host (), @@ -89,6 +90,7 @@ namespace brep build_conf_map_ = make_shared (move (conf_map)); build_db_ = shared_database (dbo.build_db_user (), + dbo.build_db_role (), dbo.build_db_password (), dbo.build_db_name (), dbo.build_db_host (), diff --git a/mod/database.cxx b/mod/database.cxx index 67a862b..813c8cc 100644 --- a/mod/database.cxx +++ b/mod/database.cxx @@ -14,6 +14,7 @@ namespace brep struct db_key { string user; + string role; string password; string name; string host; @@ -25,6 +26,7 @@ namespace brep { int r; if ((r = x.user.compare (y.user)) != 0 || + (r = x.role.compare (y.role)) != 0 || (r = x.password.compare (y.password)) != 0 || (r = x.name.compare (y.name)) != 0 || (r = x.host.compare (y.host))) @@ -35,8 +37,41 @@ namespace brep using namespace odb; + class connection_pool_factory: public pgsql::connection_pool_factory + { + public: + connection_pool_factory (string role, size_t max_connections) + : pgsql::connection_pool_factory (max_connections), + role_ (move (role)) + { + } + + virtual pooled_connection_ptr + create () override + { + pooled_connection_ptr conn (pgsql::connection_pool_factory::create ()); + + // Set the serializable isolation level for the subsequent connection + // transactions. Note that the SET TRANSACTION command affects only the + // current transaction. + // + conn->execute ("SET default_transaction_isolation=serializable"); + + // Change the connection current user to the execution user name. + // + if (!role_.empty ()) + conn->execute ("SET ROLE '" + role_ + "'"); + + return conn; + } + + private: + string role_; + }; + shared_ptr shared_database (string user, + string role, string password, string name, string host, @@ -45,7 +80,10 @@ namespace brep { static std::map> databases; - db_key k ({move (user), move (password), move (name), host, port}); + db_key k ({ + move (user), move (role), move (password), + move (name), + move (host), port}); auto i (databases.find (k)); if (i != databases.end ()) @@ -55,7 +93,7 @@ namespace brep } unique_ptr - f (new pgsql::connection_pool_factory (max_connections)); + f (new connection_pool_factory (k.role, max_connections)); shared_ptr d ( make_shared ( @@ -64,7 +102,7 @@ namespace brep k.name, k.host, k.port, - "options='-c default_transaction_isolation=serializable'", + "", move (f))); databases[move (k)] = d; diff --git a/mod/database.hxx b/mod/database.hxx index 623e65b..fdd9bac 100644 --- a/mod/database.hxx +++ b/mod/database.hxx @@ -17,6 +17,7 @@ namespace brep // shared_ptr shared_database (string user, + string role, string password, string name, string host, diff --git a/mod/mod-builds.cxx b/mod/mod-builds.cxx index 354e5f5..979121f 100644 --- a/mod/mod-builds.cxx +++ b/mod/mod-builds.cxx @@ -22,8 +22,8 @@ #include #include -#include -#include +#include +#include #include #include @@ -55,8 +55,6 @@ init (scanner& s) options_ = make_shared ( s, unknown_mode::fail, unknown_mode::fail); - database_module::init (*options_, options_->package_db_retry ()); - if (options_->build_config_specified ()) database_module::init (static_cast (*options_), static_cast (*options_), @@ -98,9 +96,9 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) { using namespace brep; using query = query; + using qb = typename query::build; - query q ( - query::id.configuration.in_range (configs.begin (), configs.end ())); + query q (qb::id.configuration.in_range (configs.begin (), configs.end ())); // Note that there is no error reported if the filter parameters parsing // fails. Instead, it is considered that no package builds match such a @@ -111,12 +109,12 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) // Package name. // if (!params.name ().empty ()) - q = q && query::id.package.name.like (transform (params.name ())); + q = q && qb::id.package.name.like (transform (params.name ())); // Package version. // if (!params.version ().empty () && params.version () != "*") - q = q && compare_version_eq (query::id.package.version, + q = q && compare_version_eq (qb::id.package.version, version (params.version ()), // May throw. true); @@ -133,20 +131,19 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) string tn (tc, 0, p); version tv (string (tc, p + 1)); // May throw invalid_argument. - q = q && query::toolchain_name == tn && - compare_version_eq (query::id.toolchain_version, tv, true); + q = q && qb::toolchain_name == tn && + compare_version_eq (qb::id.toolchain_version, tv, true); } // Build configuration name. // if (!params.configuration ().empty ()) - q = q && query::id.configuration.like ( - transform (params.configuration ())); + q = q && qb::id.configuration.like (transform (params.configuration ())); // Build machine name. // if (!params.machine ().empty ()) - q = q && query::machine.like (transform (params.machine ())); + q = q && qb::machine.like (transform (params.machine ())); // Build target. // @@ -154,8 +151,8 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) if (tg != "*") q = q && (tg.empty () - ? query::target.is_null () - : query::target.like (transform (tg))); + ? qb::target.is_null () + : qb::target.like (transform (tg))); // Build result. // @@ -164,12 +161,12 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) if (rs != "*") { if (rs == "pending") - q = q && query::force != "unforced"; + q = q && qb::force != "unforced"; else if (rs == "building") - q = q && query::state == "building"; + q = q && qb::state == "building"; else { - query sq (query::status == rs); + query sq (qb::status == rs); result_status st (to_result_status(rs)); // May throw invalid_argument. if (st != result_status::success) @@ -184,13 +181,13 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) }; while (next ()) - sq = sq || query::status == to_string (st); + sq = sq || qb::status == to_string (st); } // Note that the result status may present for the building state as // well (rebuild). // - q = q && query::state == "built" && sq; + q = q && qb::state == "built" && sq; } } } @@ -211,10 +208,8 @@ package_query (const brep::params::builds& params) // Skip external and stub packages. // - query q (query::package::internal_repository.is_not_null () && - compare_version_ne (query::package::id.version, - wildcard_version, - false)); + query q (query::internal_repository.is_not_null () && + compare_version_ne (query::id.version, wildcard_version, false)); // Note that there is no error reported if the filter parameters parsing // fails. Instead, it is considered that no packages match such a query. @@ -224,12 +219,12 @@ package_query (const brep::params::builds& params) // Package name. // if (!params.name ().empty ()) - q = q && query::package::id.name.like (transform (params.name ())); + q = q && query::id.name.like (transform (params.name ())); // Package version. // if (!params.version ().empty () && params.version () != "*") - q = q && compare_version_eq (query::package::id.version, + q = q && compare_version_eq (query::id.version, version (params.version ()), // May throw. true); } @@ -272,8 +267,7 @@ handle (request& rq, response& rs) try { name_value_scanner s (rq.parameters ()); - params = params::builds ( - s, unknown_mode::fail, unknown_mode::fail); + params = params::builds (s, unknown_mode::fail, unknown_mode::fail); } catch (const cli::exception& e) { @@ -303,7 +297,7 @@ handle (request& rq, response& rs) << DIV(ID="content"); // Return the list of distinct toolchain name/version pairs. The build db - // transaction must be started and be the current one. + // transaction must be started. // using toolchains = vector>; @@ -408,14 +402,8 @@ handle (request& rq, response& rs) { transaction t (build_db_->begin ()); - // Having packages and package configuration builds in different - // databases, we are unable to filter out builds for non-existent packages - // at the query level. Doing that in the C++ code would complicate it - // significantly. So we will print all the builds, relying on the sorting - // algorithm which will place expired ones at the end of the query result. - // - count = build_db_->query_value ( - build_query (*build_conf_names_, params)); + count = build_db_->query_value ( + build_query (*build_conf_names_, params)); // Print the filter form. // @@ -429,12 +417,14 @@ handle (request& rq, response& rs) // Enclose the subsequent tables to be able to use nth-child CSS selector. // s << DIV; - for (auto& b: build_db_->query ( - build_query (*build_conf_names_, params) + + for (auto& pb: build_db_->query ( + build_query (*build_conf_names_, params) + "ORDER BY" + query::timestamp + "DESC" + "OFFSET" + to_string (page * page_configs) + "LIMIT" + to_string (page_configs))) { + build& b (*pb.build); + string ts (butl::to_string (b.timestamp, "%Y-%m-%d %H:%M:%S %Z", true, @@ -474,25 +464,13 @@ handle (request& rq, response& rs) bld_params.machine ().clear (); bld_params.result () = "*"; - // Query toolchains, and the number of package configurations being present - // in the build database and satisfying the specified filter arguments. - // Later we will subtract it from the maximum number of unbuilt package - // configurations, to get the real number of them. + // Query toolchains, filter build configurations and toolchains, and + // create the set of configuration/toolchain combinations, that we will + // print for packages. Also calculate the number of unbuilt package + // configurations. // toolchains toolchains; - { - transaction t (build_db_->begin ()); - toolchains = query_toolchains (); - - count = build_db_->query_value ( - build_query (*build_conf_names_, bld_params)); - t.commit (); - } - - // Filter build configurations and toolchains, and create the set of - // configuration/toolchain combinations, that we will print for packages. - // struct config_toolchain { const string& configuration; @@ -514,30 +492,41 @@ handle (request& rq, response& rs) } }; + // Note that config_toolchains contains shallow references to the toolchain + // names and versions, and in particular to the selected ones (tc_name and + // tc_version). + // string tc_name; version tc_version; - const string& tc (params.toolchain ()); + set config_toolchains; - if (tc != "*") - try { - size_t p (tc.find ('-')); - if (p == string::npos) // Invalid format. - throw invalid_argument (""); + transaction t (build_db_->begin ()); + toolchains = query_toolchains (); - tc_name.assign (tc, 0, p); - tc_version = version (string (tc, p + 1)); // May throw invalid_argument. - } - catch (const invalid_argument&) - { - // This is unlikely to be the user fault, as he selects the toolchain - // from the list. - // - throw invalid_request (400, "invalid toolchain"); - } + const string& tc (params.toolchain ()); + + if (tc != "*") + try + { + size_t p (tc.find ('-')); + if (p == string::npos) // Invalid format. + throw invalid_argument (""); + + tc_name.assign (tc, 0, p); + + // May throw invalid_argument. + // + tc_version = version (string (tc, p + 1)); + } + catch (const invalid_argument&) + { + // This is unlikely to be the user fault, as he selects the toolchain + // from the list. + // + throw invalid_request (400, "invalid toolchain"); + } - set config_toolchains; - { const string& pc (params.configuration ()); const string& tg (params.target ()); @@ -545,7 +534,6 @@ handle (request& rq, response& rs) { if ((pc.empty () || path_match (pc, c.name)) && // Filter by name. - (tg.empty () // Filter by target. ? !c.target : tg == "*" || @@ -563,16 +551,18 @@ handle (request& rq, response& rs) // Calculate the number of unbuilt package configurations as a // difference between the maximum possible number of unbuilt - // configurations and the number of configurations being in the built or - // building state (see above). + // configurations and the number of existing package builds. // - transaction t (package_db_->begin ()); + size_t nmax (config_toolchains.size () * + build_db_->query_value ( + package_query (params))); - size_t n (config_toolchains.size () * - package_db_->query_value ( - package_query (params))); + size_t ncur = build_db_->query_value ( + build_query (*build_conf_names_, bld_params)); + + assert (nmax >= ncur); + count = nmax - ncur; - count = n > count ? n - count : 0; t.commit (); } @@ -588,24 +578,24 @@ handle (request& rq, response& rs) // 4: toolchain name // 5: toolchain version (descending) // - // Prepare the package version prepared query. + // Prepare the build package prepared query. // // Note that we can't skip the proper number of packages in the database // query for a page numbers greater than one. So we will query packages // from the very beginning and skip the appropriate number of them while // iterating through the query result. // - // Note that such an approach has a security implication. An HTTP request - // with a large page number will be quite expensive to process, as it - // effectively results in traversing all the package versions and all the + // Also note that such an approach has a security implication. An HTTP + // request with a large page number will be quite expensive to process, as + // it effectively results in traversing all the build package and all the // built configurations. To address this problem we may consider to reduce // the pager to just '' links, and pass the offset as a // URL query parameter. Alternatively, we can invent the page number cap. // - using pkg_query = query; - using prep_pkg_query = prepared_query; + using pkg_query = query; + using prep_pkg_query = prepared_query; - pkg_query pq (package_query (params)); + pkg_query pq (package_query (params)); // Specify the portion. Note that we will still be querying packages in // chunks, not to hold locks for too long. @@ -613,32 +603,35 @@ handle (request& rq, response& rs) size_t offset (0); pq += "ORDER BY" + - pkg_query::package::id.name + - order_by_version_desc (pkg_query::package::id.version, false) + + pkg_query::id.name + + order_by_version_desc (pkg_query::id.version, false) + "OFFSET" + pkg_query::_ref (offset) + "LIMIT 50"; - connection_ptr pkg_conn (package_db_->connection ()); + connection_ptr conn (build_db_->connection ()); prep_pkg_query pkg_prep_query ( - pkg_conn->prepare_query ( - "mod-builds-package-version-query", pq)); + conn->prepare_query ("mod-builds-package-query", pq)); // Prepare the build prepared query. // - using bld_query = query; - using prep_bld_query = prepared_query; - - package_id id; // See the build query. - - const auto& qv (bld_query::id.package.version); + // For each package we will generate a set of all possible builds. Then, + // iterating over the actual builds for the package we will exclude them + // from the set of possible ones. The resulted set represents unbuilt + // package configurations, and so will be printed. + // + using bld_query = query; + using prep_bld_query = prepared_query; // We will use specific package name and version for each database query. // bld_params.name ().clear (); bld_params.version ().clear (); + package_id id; + const auto& qv (bld_query::build::id.package.version); + bld_query bq ( - bld_query::id.package.name == bld_query::_ref (id.name) && + bld_query::build::id.package.name == bld_query::_ref (id.name) && qv.epoch == bld_query::_ref (id.version.epoch) && qv.canonical_upstream == @@ -646,13 +639,11 @@ handle (request& rq, response& rs) qv.canonical_release == bld_query::_ref (id.version.canonical_release) && qv.revision == bld_query::_ref (id.version.revision) && - build_query (*build_conf_names_, bld_params)); - connection_ptr bld_conn (build_db_->connection ()); + build_query (*build_conf_names_, bld_params)); prep_bld_query bld_prep_query ( - bld_conn->prepare_query ( - "mod-builds-package-build-query", bq)); + conn->prepare_query ("mod-builds-build-query", bq)); size_t skip (page * page_configs); size_t print (page_configs); @@ -662,39 +653,37 @@ handle (request& rq, response& rs) s << DIV; for (bool prn (true); prn; ) { - // Start the package database transaction. - // - transaction pt (pkg_conn->begin ()); + transaction t (conn->begin ()); - // Query package versions. + // Query (and cache) build packages. // - auto package_versions (pkg_prep_query.execute ()); + auto packages (pkg_prep_query.execute ()); - if ((prn = !package_versions.empty ())) + if ((prn = !packages.empty ())) { - offset += package_versions.size (); - - // Start the build database transaction. - // - transaction bt (bld_conn->begin (), false); - transaction::current (bt); + offset += packages.size (); // Iterate over packages and print unbuilt configurations. Skip the // appropriate number of them first (for page number greater than one). // - for (auto& pv: package_versions) + for (auto& pv: packages) { id = move (pv.id); - // Iterate through the package configuration builds and erase them - // from the unbuilt configurations set. - // // Make a copy for this package. // auto unbuilt_configs (config_toolchains); - for (const auto& b: bld_prep_query.execute ()) + + // Iterate through the package configuration builds and erase them + // from the unbuilt configurations set. + // + for (const auto& pb: bld_prep_query.execute ()) + { + build& b (*pb.build); + unbuilt_configs.erase ({ b.id.configuration, b.toolchain_name, b.toolchain_version}); + } // Print unbuilt package configurations. // @@ -733,14 +722,9 @@ handle (request& rq, response& rs) if (!prn) break; } - - // Commit the build database transaction and switch to the package - // database transaction. - bt.commit (); - transaction::current (pt); } - pt.commit (); + t.commit (); } s << ~DIV; } diff --git a/mod/options.cli b/mod/options.cli index 05578bf..051081f 100644 --- a/mod/options.cli +++ b/mod/options.cli @@ -93,8 +93,16 @@ namespace brep string package-db-user { "", - "Package database user name. If not specified, then operating system - (login) name is used." + "Package database login user name. If not specified, then operating + system (login) name is used. See also \c{package-db-role}." + } + + string package-db-role = "brep" + { + "", + "Package database execution user name. If not empty then the login + user will be switched (with \c{SET ROLE}) to this user prior to + executing any statements. If not specified, then \cb{brep} is used." } string package-db-password @@ -190,8 +198,16 @@ namespace brep string build-db-user { "", - "Build database user name. If not specified, then operating system - (login) name is used." + "Build database login user name. If not specified, then operating + system (login) name is used. See also \c{build-db-role}." + } + + string build-db-role = "brep" + { + "", + "Build database execution user name. If not empty then the login + user will be switched (with \c{SET ROLE}) to this user prior to + executing any statements. If not specified, then \cb{brep} is used." } string build-db-password -- cgit v1.1