diff options
36 files changed, 3068 insertions, 224 deletions
@@ -1,4 +1,4 @@ -Copyright (c) 2009-2020 Code Synthesis Tools CC. +Copyright (c) 2009-2024 Code Synthesis Tools CC. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as diff --git a/build/root.build b/build/root.build index ba74c09..329cc45 100644 --- a/build/root.build +++ b/build/root.build @@ -1,6 +1,8 @@ # file : build/root.build # license : GNU GPL v2; see accompanying LICENSE file +config [bool] config.libodb_pgsql.develop ?= false + cxx.std = latest using cxx @@ -15,11 +17,3 @@ if ($cxx.target.system == 'win32-msvc') if ($cxx.class == 'msvc') cxx.coptions += /wd4251 /wd4275 /wd4800 - -# Load the cli module but only if it's available. This way a distribution -# that includes pre-generated files can be built without installing cli. -# This is also the reason why we need to explicitly spell out individual -# source file prerequisites instead of using the cli.cxx{} group (it won't -# be there unless the module is configured). -# -using? cli @@ -1,7 +1,7 @@ # file : buildfile # license : GNU GPL v2; see accompanying LICENSE file -./: {*/ -build/ -m4/} doc{GPLv2 INSTALL LICENSE NEWS README} manifest +./: {*/ -build/ -m4/} doc{INSTALL NEWS README} legal{GPLv2 LICENSE} manifest # Don't install tests or the INSTALL file. # @@ -12,12 +12,12 @@ clean := $(out_base)/.clean $(default): $(addprefix $(out_base)/,$(addsuffix /,$(dirs))) $(dist): export dirs := $(dirs) -$(dist): export docs := GPLv2 LICENSE README NEWS version +$(dist): export docs := GPLv2 LICENSE README NEWS version.txt $(dist): data_dist := INSTALL libodb-pgsql-vc8.sln libodb-pgsql-vc9.sln \ libodb-pgsql-vc10.sln libodb-pgsql-vc11.sln libodb-pgsql-vc12.sln $(dist): exec_dist := bootstrap $(dist): export extra_dist := $(data_dist) $(exec_dist) -$(dist): export version = $(shell cat $(src_root)/version) +$(dist): export version = $(shell cat $(src_root)/version.txt) $(dist): $(addprefix $(out_base)/,$(addsuffix /.dist,$(dirs))) $(call dist-data,$(docs) $(data_dist) libodb-pgsql.pc.in) @@ -1,6 +1,6 @@ : 1 name: libodb-pgsql -version: 2.5.0-b.20.z +version: 2.5.0-b.26.z project: odb summary: PostgreSQL ODB runtime library license: GPL-2.0-only @@ -14,8 +14,10 @@ src-url: https://git.codesynthesis.com/cgit/odb/libodb-pgsql/ email: odb-users@codesynthesis.com build-warning-email: odb-builds@codesynthesis.com builds: all +builds: -wasm requires: c++11 -depends: * build2 >= 0.13.0 -depends: * bpkg >= 0.13.0 +depends: * build2 >= 0.16.0- +depends: * bpkg >= 0.16.0- depends: libpq >=7.4.0 -depends: libodb [2.5.0-b.20.1 2.5.0-b.21) +depends: libodb [2.5.0-b.26.1 2.5.0-b.27) +depends: * cli ^1.2.0- ? ($config.libodb_pgsql.develop) diff --git a/odb/pgsql/binding.hxx b/odb/pgsql/binding.hxx index 63cf2eb..1adf144 100644 --- a/odb/pgsql/binding.hxx +++ b/odb/pgsql/binding.hxx @@ -41,17 +41,27 @@ namespace odb public: typedef pgsql::bind bind_type; - binding (): bind (0), count (0), version (0) {} + binding () + : bind (0), count (0), version (0), + batch (0), skip (0), status (0) {} binding (bind_type* b, std::size_t n) - : bind (b), count (n), version (0) - { - } + : bind (b), count (n), version (0), + batch (0), skip (0), status (0) {} + + binding (bind_type* b, std::size_t n, + std::size_t bt, std::size_t s, unsigned long long* st) + : bind (b), count (n), version (0), + batch (bt), skip (s), status (st) {} bind_type* bind; std::size_t count; std::size_t version; + std::size_t batch; + std::size_t skip; + unsigned long long* status; // Batch status array. + private: binding (const binding&); binding& operator= (const binding&); diff --git a/odb/pgsql/buildfile b/odb/pgsql/buildfile index 5a08147..f1d05b6 100644 --- a/odb/pgsql/buildfile +++ b/odb/pgsql/buildfile @@ -1,12 +1,15 @@ # file : odb/pgsql/buildfile # license : GNU GPL v2; see accompanying LICENSE file +define cli: file +cli{*}: extension = cli + import int_libs = libodb%lib{odb} import imp_libs = libpq%lib{pq} -lib{odb-pgsql}: {hxx ixx txx cxx}{* -version-build2} {hxx}{version-build2} \ - details/{hxx ixx txx cxx}{* -options} details/{hxx ixx cxx}{options} \ - details/build2/{h}{*} \ +lib{odb-pgsql}: {hxx ixx txx cxx}{* -version-build2} {hxx}{version-build2} \ + details/{hxx ixx txx cxx}{* -options} \ + details/build2/{h}{*} \ $imp_libs $int_libs # Include the generated version header into the distribution (so that we don't @@ -47,36 +50,76 @@ if $version.pre_release else lib{odb-pgsql}: bin.lib.version = @"-$version.major.$version.minor" -# Generated options parser. +develop = $config.libodb_pgsql.develop + +## Consumption build ($develop == false). +# + +# Use pregenerated versions in the consumption build. +# +lib{odb-pgsql}: details/pregenerated/{hxx ixx cxx}{**}: include = (!$develop) + +if! $develop + cxx.poptions =+ "-I($src_base/details/pregenerated)" # Note: must come first. + +# Don't install pregenerated headers since they are only used internally in +# the database implementation (also below). +# +details/pregenerated/{hxx ixx}{*}: install = false + +# Distribute pregenerated versions only in the consumption build. +# +details/pregenerated/{hxx ixx cxx}{*}: dist = (!$develop) + # -details/ +## + +## Development build ($develop == true). +# + +lib{odb-pgsql}: details/{hxx ixx cxx}{options}: include = $develop + +if $develop + import! [metadata] cli = cli%exe{cli} + +# In the development build distribute regenerated {hxx ixx cxx}{options}, +# remapping their locations to the paths of the pregenerated versions (which +# are only distributed in the consumption build; see above). This way we make +# sure that the distributed files are always up-to-date. +# +<details/{hxx ixx cxx}{options}>: details/cli{options} $cli { - if $cli.configured - { - cli.cxx{options}: cli{options} - - cli.options += --include-with-brackets --include-prefix odb/pgsql/details \ ---guard-prefix LIBODB_PGSQL_DETAILS --generate-file-scanner \ ---cli-namespace odb::pgsql::details::cli --long-usage --generate-specifier \ ---no-combined-flags - - # Include generated cli files into the distribution and don't remove them - # when cleaning in src (so that clean results in a state identical to - # distributed). But don't install their headers since they are only used - # internally in the database implementation. - # - cli.cxx{*}: - { - dist = true - clean = ($src_root != $out_root) - install = false - } - } - else - # No install for the pre-generated case. - # - hxx{options}@./ ixx{options}@./: install = false + install = false + dist = ($develop ? pregenerated/odb/pgsql/details/ : false) + + # Symlink the generated code in src for convenience of development. + # + backlink = true } +% +if $develop +{{ + options = --include-with-brackets --include-prefix odb/pgsql/details \ + --guard-prefix LIBODB_PGSQL_DETAILS --generate-file-scanner \ + --cli-namespace odb::pgsql::details::cli --long-usage \ + --generate-specifier --no-combined-flags + + $cli $options -o $out_base/details/ $path($<[0]) + + # If the result differs from the pregenerated version, copy it over. + # + d = [dir_path] $src_base/details/pregenerated/odb/pgsql/details/ + + if diff $d/options.hxx $path($>[0]) >- && \ + diff $d/options.ixx $path($>[1]) >- && \ + diff $d/options.cxx $path($>[2]) >- + exit + end + + cp $path($>[0]) $d/options.hxx + cp $path($>[1]) $d/options.ixx + cp $path($>[2]) $d/options.cxx +}} # Install into the odb/pgsql/ subdirectory of, say, /usr/include/ # recreating subdirectories. diff --git a/odb/pgsql/database.cxx b/odb/pgsql/database.cxx index 5c60957..09bf6f0 100644 --- a/odb/pgsql/database.cxx +++ b/odb/pgsql/database.cxx @@ -249,10 +249,11 @@ namespace odb // Bind parameters and results. // + char* pbuf[1] = {const_cast<char*> (name.c_str ())}; size_t psize[1] = {name.size ()}; bool pnull[1] = {false}; bind pbind[1] = {{bind::text, - const_cast<char*> (name.c_str ()), + &pbuf[0], &psize[0], psize[0], &pnull[0], @@ -282,7 +283,9 @@ namespace odb cp = factory_->connect (); pgsql::connection& c ( - cp != 0 ? *cp : transaction::current ().connection ()); + cp != 0 + ? *cp + : transaction::current ().connection (const_cast<database&> (*this))); // If we are in the user's transaction then things are complicated. When // we try to execute SELECT on a non-existent table, PG "poisons" the @@ -300,10 +303,11 @@ namespace odb bool exists (true); if (cp == 0 && c.server_version () >= 90400) { + char* pbuf[1] = {const_cast<char*> (table)}; size_t psize[1] = {strlen (table)}; bool pnull[1] = {false}; bind pbind[1] = {{bind::text, - const_cast<char*> (table), + &pbuf[0], &psize[0], psize[0], &pnull[0], @@ -319,7 +323,7 @@ namespace odb native_binding nparam (values, lengths, formats, 1); bool rnull[1]; - bind rbind[1] = {{bind::boolean_, &exists, 0, 0, &rnull[1], 0}}; + bind rbind[1] = {{bind::boolean_, &exists, 0, 0, &rnull[0], 0}}; binding result (rbind, 1); result.version++; diff --git a/odb/pgsql/database.hxx b/odb/pgsql/database.hxx index d3b805d..950cad1 100644 --- a/odb/pgsql/database.hxx +++ b/odb/pgsql/database.hxx @@ -79,6 +79,9 @@ namespace odb // Move-constructible but not move-assignable. // + // Note: noexcept is not specified since odb::database(odb::database&&) + // can throw. + // #ifdef ODB_CXX11 database (database&&); #endif @@ -124,6 +127,13 @@ namespace odb typename object_traits<T>::id_type persist (const typename object_traits<T>::pointer_type& obj_ptr); + // Bulk persist. Can be a range of references or pointers (including + // smart pointers) to objects. + // + template <typename I> + void + persist (I begin, I end, bool continue_failed = true); + // Load an object. Throw object_not_persistent if not found. // template <typename T> @@ -210,6 +220,13 @@ namespace odb void update (const typename object_traits<T>::pointer_type& obj_ptr); + // Bulk update. Can be a range of references or pointers (including + // smart pointers) to objects. + // + template <typename I> + void + update (I begin, I end, bool continue_failed = true); + // Update a section of an object. Throws the section_not_loaded // exception if the section is not loaded. Note also that this // function does not clear the changed flag if it is set. @@ -253,6 +270,19 @@ namespace odb void erase (const typename object_traits<T>::pointer_type& obj_ptr); + // Bulk erase. + // + template <typename T, typename I> + void + erase (I id_begin, I id_end, bool continue_failed = true); + + // Can be a range of references or pointers (including smart pointers) + // to objects. + // + template <typename I> + void + erase (I obj_begin, I obj_end, bool continue_failed = true); + // Erase multiple objects matching a query predicate. // template <typename T> diff --git a/odb/pgsql/database.ixx b/odb/pgsql/database.ixx index 3e3d63e..f04c3e6 100644 --- a/odb/pgsql/database.ixx +++ b/odb/pgsql/database.ixx @@ -119,6 +119,13 @@ namespace odb return persist_<T, id_pgsql> (pobj); } + template <typename I> + inline void database:: + persist (I b, I e, bool cont) + { + persist_<I, id_pgsql> (b, e, cont); + } + template <typename T> inline typename object_traits<T>::pointer_type database:: load (const typename object_traits<T>::id_type& id) @@ -280,6 +287,13 @@ namespace odb update_<T, id_pgsql> (pobj); } + template <typename I> + inline void database:: + update (I b, I e, bool cont) + { + update_<I, id_pgsql> (b, e, cont); + } + template <typename T> inline void database:: update (const T& obj, const section& s) @@ -369,6 +383,20 @@ namespace odb erase_<T, id_pgsql> (pobj); } + template <typename T, typename I> + inline void database:: + erase (I idb, I ide, bool cont) + { + erase_id_<I, T, id_pgsql> (idb, ide, cont); + } + + template <typename I> + inline void database:: + erase (I ob, I oe, bool cont) + { + erase_object_<I, id_pgsql> (ob, oe, cont); + } + template <typename T> inline unsigned long long database:: erase_query () @@ -594,7 +622,7 @@ namespace odb { // Throws if not in transaction. // - pgsql::connection& c (transaction::current ().connection ()); + pgsql::connection& c (transaction::current ().connection (*this)); return c.prepare_query<T> (n, q); } diff --git a/odb/pgsql/details/.gitignore b/odb/pgsql/details/.gitignore index c6e608b..b298f89 100644 --- a/odb/pgsql/details/.gitignore +++ b/odb/pgsql/details/.gitignore @@ -1 +1 @@ -options.?xx +/options.?xx diff --git a/odb/pgsql/details/pregenerated/odb/pgsql/details/options.cxx b/odb/pgsql/details/pregenerated/odb/pgsql/details/options.cxx new file mode 100644 index 0000000..a4a5da6 --- /dev/null +++ b/odb/pgsql/details/pregenerated/odb/pgsql/details/options.cxx @@ -0,0 +1,1114 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include <odb/pgsql/details/options.hxx> + +#include <map> +#include <set> +#include <string> +#include <vector> +#include <utility> +#include <ostream> +#include <sstream> +#include <cstring> +#include <fstream> + +namespace odb +{ + namespace pgsql + { + namespace details + { + namespace cli + { + // unknown_option + // + unknown_option:: + ~unknown_option () throw () + { + } + + void unknown_option:: + print (::std::ostream& os) const + { + os << "unknown option '" << option ().c_str () << "'"; + } + + const char* unknown_option:: + what () const throw () + { + return "unknown option"; + } + + // unknown_argument + // + unknown_argument:: + ~unknown_argument () throw () + { + } + + void unknown_argument:: + print (::std::ostream& os) const + { + os << "unknown argument '" << argument ().c_str () << "'"; + } + + const char* unknown_argument:: + what () const throw () + { + return "unknown argument"; + } + + // missing_value + // + missing_value:: + ~missing_value () throw () + { + } + + void missing_value:: + print (::std::ostream& os) const + { + os << "missing value for option '" << option ().c_str () << "'"; + } + + const char* missing_value:: + what () const throw () + { + return "missing option value"; + } + + // invalid_value + // + invalid_value:: + ~invalid_value () throw () + { + } + + void invalid_value:: + print (::std::ostream& os) const + { + os << "invalid value '" << value ().c_str () << "' for option '" + << option ().c_str () << "'"; + + if (!message ().empty ()) + os << ": " << message ().c_str (); + } + + const char* invalid_value:: + what () const throw () + { + return "invalid option value"; + } + + // eos_reached + // + void eos_reached:: + print (::std::ostream& os) const + { + os << what (); + } + + const char* eos_reached:: + what () const throw () + { + return "end of argument stream reached"; + } + + // file_io_failure + // + file_io_failure:: + ~file_io_failure () throw () + { + } + + void file_io_failure:: + print (::std::ostream& os) const + { + os << "unable to open file '" << file ().c_str () << "' or read failure"; + } + + const char* file_io_failure:: + what () const throw () + { + return "unable to open file or read failure"; + } + + // unmatched_quote + // + unmatched_quote:: + ~unmatched_quote () throw () + { + } + + void unmatched_quote:: + print (::std::ostream& os) const + { + os << "unmatched quote in argument '" << argument ().c_str () << "'"; + } + + const char* unmatched_quote:: + what () const throw () + { + return "unmatched quote"; + } + + // scanner + // + scanner:: + ~scanner () + { + } + + // argv_scanner + // + bool argv_scanner:: + more () + { + return i_ < argc_; + } + + const char* argv_scanner:: + peek () + { + if (i_ < argc_) + return argv_[i_]; + else + throw eos_reached (); + } + + const char* argv_scanner:: + next () + { + if (i_ < argc_) + { + const char* r (argv_[i_]); + + if (erase_) + { + for (int i (i_ + 1); i < argc_; ++i) + argv_[i - 1] = argv_[i]; + + --argc_; + argv_[argc_] = 0; + } + else + ++i_; + + ++start_position_; + return r; + } + else + throw eos_reached (); + } + + void argv_scanner:: + skip () + { + if (i_ < argc_) + { + ++i_; + ++start_position_; + } + else + throw eos_reached (); + } + + std::size_t argv_scanner:: + position () + { + return start_position_; + } + + // argv_file_scanner + // + int argv_file_scanner::zero_argc_ = 0; + std::string argv_file_scanner::empty_string_; + + bool argv_file_scanner:: + more () + { + if (!args_.empty ()) + return true; + + while (base::more ()) + { + // See if the next argument is the file option. + // + const char* a (base::peek ()); + const option_info* oi = 0; + const char* ov = 0; + + if (!skip_) + { + if ((oi = find (a)) != 0) + { + base::next (); + + if (!base::more ()) + throw missing_value (a); + + ov = base::next (); + } + else if (std::strncmp (a, "-", 1) == 0) + { + if ((ov = std::strchr (a, '=')) != 0) + { + std::string o (a, 0, ov - a); + if ((oi = find (o.c_str ())) != 0) + { + base::next (); + ++ov; + } + } + } + } + + if (oi != 0) + { + if (oi->search_func != 0) + { + std::string f (oi->search_func (ov, oi->arg)); + + if (!f.empty ()) + load (f); + } + else + load (ov); + + if (!args_.empty ()) + return true; + } + else + { + if (!skip_) + skip_ = (std::strcmp (a, "--") == 0); + + return true; + } + } + + return false; + } + + const char* argv_file_scanner:: + peek () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? base::peek () : args_.front ().value.c_str (); + } + + const std::string& argv_file_scanner:: + peek_file () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? empty_string_ : *args_.front ().file; + } + + std::size_t argv_file_scanner:: + peek_line () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? 0 : args_.front ().line; + } + + const char* argv_file_scanner:: + next () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::next (); + else + { + hold_[i_ == 0 ? ++i_ : --i_].swap (args_.front ().value); + args_.pop_front (); + ++start_position_; + return hold_[i_].c_str (); + } + } + + void argv_file_scanner:: + skip () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::skip (); + else + { + args_.pop_front (); + ++start_position_; + } + } + + const argv_file_scanner::option_info* argv_file_scanner:: + find (const char* a) const + { + for (std::size_t i (0); i < options_count_; ++i) + if (std::strcmp (a, options_[i].option) == 0) + return &options_[i]; + + return 0; + } + + std::size_t argv_file_scanner:: + position () + { + return start_position_; + } + + void argv_file_scanner:: + load (const std::string& file) + { + using namespace std; + + ifstream is (file.c_str ()); + + if (!is.is_open ()) + throw file_io_failure (file); + + files_.push_back (file); + + arg a; + a.file = &*files_.rbegin (); + + for (a.line = 1; !is.eof (); ++a.line) + { + string line; + getline (is, line); + + if (is.fail () && !is.eof ()) + throw file_io_failure (file); + + string::size_type n (line.size ()); + + // Trim the line from leading and trailing whitespaces. + // + if (n != 0) + { + const char* f (line.c_str ()); + const char* l (f + n); + + const char* of (f); + while (f < l && (*f == ' ' || *f == '\t' || *f == '\r')) + ++f; + + --l; + + const char* ol (l); + while (l > f && (*l == ' ' || *l == '\t' || *l == '\r')) + --l; + + if (f != of || l != ol) + line = f <= l ? string (f, l - f + 1) : string (); + } + + // Ignore empty lines, those that start with #. + // + if (line.empty () || line[0] == '#') + continue; + + string::size_type p (string::npos); + if (line.compare (0, 1, "-") == 0) + { + p = line.find (' '); + + string::size_type q (line.find ('=')); + if (q != string::npos && q < p) + p = q; + } + + string s1; + if (p != string::npos) + { + s1.assign (line, 0, p); + + // Skip leading whitespaces in the argument. + // + if (line[p] == '=') + ++p; + else + { + n = line.size (); + for (++p; p < n; ++p) + { + char c (line[p]); + if (c != ' ' && c != '\t' && c != '\r') + break; + } + } + } + else if (!skip_) + skip_ = (line == "--"); + + string s2 (line, p != string::npos ? p : 0); + + // If the string (which is an option value or argument) is + // wrapped in quotes, remove them. + // + n = s2.size (); + char cf (s2[0]), cl (s2[n - 1]); + + if (cf == '"' || cf == '\'' || cl == '"' || cl == '\'') + { + if (n == 1 || cf != cl) + throw unmatched_quote (s2); + + s2 = string (s2, 1, n - 2); + } + + if (!s1.empty ()) + { + // See if this is another file option. + // + const option_info* oi; + if (!skip_ && (oi = find (s1.c_str ()))) + { + if (s2.empty ()) + throw missing_value (oi->option); + + if (oi->search_func != 0) + { + string f (oi->search_func (s2.c_str (), oi->arg)); + if (!f.empty ()) + load (f); + } + else + { + // If the path of the file being parsed is not simple and the + // path of the file that needs to be loaded is relative, then + // complete the latter using the former as a base. + // +#ifndef _WIN32 + string::size_type p (file.find_last_of ('/')); + bool c (p != string::npos && s2[0] != '/'); +#else + string::size_type p (file.find_last_of ("/\\")); + bool c (p != string::npos && s2[1] != ':'); +#endif + if (c) + s2.insert (0, file, 0, p + 1); + + load (s2); + } + + continue; + } + + a.value = s1; + args_.push_back (a); + } + + a.value = s2; + args_.push_back (a); + } + } + + template <typename X> + struct parser + { + static void + parse (X& x, bool& xs, scanner& s) + { + using namespace std; + + const char* o (s.next ()); + if (s.more ()) + { + string v (s.next ()); + istringstream is (v); + if (!(is >> x && is.peek () == istringstream::traits_type::eof ())) + throw invalid_value (o, v); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <> + struct parser<bool> + { + static void + parse (bool& x, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + const char* v (s.next ()); + + if (std::strcmp (v, "1") == 0 || + std::strcmp (v, "true") == 0 || + std::strcmp (v, "TRUE") == 0 || + std::strcmp (v, "True") == 0) + x = true; + else if (std::strcmp (v, "0") == 0 || + std::strcmp (v, "false") == 0 || + std::strcmp (v, "FALSE") == 0 || + std::strcmp (v, "False") == 0) + x = false; + else + throw invalid_value (o, v); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <> + struct parser<std::string> + { + static void + parse (std::string& x, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + x = s.next (); + else + throw missing_value (o); + + xs = true; + } + }; + + template <typename X> + struct parser<std::pair<X, std::size_t> > + { + static void + parse (std::pair<X, std::size_t>& x, bool& xs, scanner& s) + { + x.second = s.position (); + parser<X>::parse (x.first, xs, s); + } + }; + + template <typename X> + struct parser<std::vector<X> > + { + static void + parse (std::vector<X>& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser<X>::parse (x, dummy, s); + c.push_back (x); + xs = true; + } + }; + + template <typename X, typename C> + struct parser<std::set<X, C> > + { + static void + parse (std::set<X, C>& c, bool& xs, scanner& s) + { + X x; + bool dummy; + parser<X>::parse (x, dummy, s); + c.insert (x); + xs = true; + } + }; + + template <typename K, typename V, typename C> + struct parser<std::map<K, V, C> > + { + static void + parse (std::map<K, V, C>& m, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + std::size_t pos (s.position ()); + std::string ov (s.next ()); + std::string::size_type p = ov.find ('='); + + K k = K (); + V v = V (); + std::string kstr (ov, 0, p); + std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ())); + + int ac (2); + char* av[] = + { + const_cast<char*> (o), + 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<K>::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<V>::parse (v, dummy, s); + } + + m[k] = v; + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <typename K, typename V, typename C> + struct parser<std::multimap<K, V, C> > + { + static void + parse (std::multimap<K, V, C>& m, bool& xs, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + { + std::size_t pos (s.position ()); + std::string ov (s.next ()); + std::string::size_type p = ov.find ('='); + + K k = K (); + V v = V (); + std::string kstr (ov, 0, p); + std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ())); + + int ac (2); + char* av[] = + { + const_cast<char*> (o), + 0 + }; + + bool dummy; + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<K>::parse (k, dummy, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<V>::parse (v, dummy, s); + } + + m.insert (typename std::multimap<K, V, C>::value_type (k, v)); + } + else + throw missing_value (o); + + xs = true; + } + }; + + template <typename X, typename T, T X::*M> + void + thunk (X& x, scanner& s) + { + parser<T>::parse (x.*M, s); + } + + template <typename X, bool X::*M> + void + thunk (X& x, scanner& s) + { + s.next (); + x.*M = true; + } + + template <typename X, typename T, T X::*M, bool X::*S> + void + thunk (X& x, scanner& s) + { + parser<T>::parse (x.*M, x.*S, s); + } + } + } + } +} + +#include <map> + +namespace odb +{ + namespace pgsql + { + namespace details + { + // options + // + + options:: + options () + : user_ (), + user_specified_ (false), + password_ (), + password_specified_ (false), + database_ (), + database_specified_ (false), + host_ (), + host_specified_ (false), + port_ (), + port_specified_ (false), + options_file_ (), + options_file_specified_ (false) + { + } + + options:: + options (int& argc, + char** argv, + bool erase, + ::odb::pgsql::details::cli::unknown_mode opt, + ::odb::pgsql::details::cli::unknown_mode arg) + : user_ (), + user_specified_ (false), + password_ (), + password_specified_ (false), + database_ (), + database_specified_ (false), + host_ (), + host_specified_ (false), + port_ (), + port_specified_ (false), + options_file_ (), + options_file_specified_ (false) + { + ::odb::pgsql::details::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); + } + + options:: + options (int start, + int& argc, + char** argv, + bool erase, + ::odb::pgsql::details::cli::unknown_mode opt, + ::odb::pgsql::details::cli::unknown_mode arg) + : user_ (), + user_specified_ (false), + password_ (), + password_specified_ (false), + database_ (), + database_specified_ (false), + host_ (), + host_specified_ (false), + port_ (), + port_specified_ (false), + options_file_ (), + options_file_specified_ (false) + { + ::odb::pgsql::details::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + } + + options:: + options (int& argc, + char** argv, + int& end, + bool erase, + ::odb::pgsql::details::cli::unknown_mode opt, + ::odb::pgsql::details::cli::unknown_mode arg) + : user_ (), + user_specified_ (false), + password_ (), + password_specified_ (false), + database_ (), + database_specified_ (false), + host_ (), + host_specified_ (false), + port_ (), + port_specified_ (false), + options_file_ (), + options_file_specified_ (false) + { + ::odb::pgsql::details::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); + } + + options:: + options (int start, + int& argc, + char** argv, + int& end, + bool erase, + ::odb::pgsql::details::cli::unknown_mode opt, + ::odb::pgsql::details::cli::unknown_mode arg) + : user_ (), + user_specified_ (false), + password_ (), + password_specified_ (false), + database_ (), + database_specified_ (false), + host_ (), + host_specified_ (false), + port_ (), + port_specified_ (false), + options_file_ (), + options_file_specified_ (false) + { + ::odb::pgsql::details::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); + } + + options:: + options (::odb::pgsql::details::cli::scanner& s, + ::odb::pgsql::details::cli::unknown_mode opt, + ::odb::pgsql::details::cli::unknown_mode arg) + : user_ (), + user_specified_ (false), + password_ (), + password_specified_ (false), + database_ (), + database_specified_ (false), + host_ (), + host_specified_ (false), + port_ (), + port_specified_ (false), + options_file_ (), + options_file_specified_ (false) + { + _parse (s, opt, arg); + } + + ::odb::pgsql::details::cli::usage_para options:: + print_usage (::std::ostream& os, ::odb::pgsql::details::cli::usage_para p) + { + CLI_POTENTIALLY_UNUSED (os); + + if (p != ::odb::pgsql::details::cli::usage_para::none) + os << ::std::endl; + + os << "--user|--username <name> PostgreSQL database user." << ::std::endl; + + os << std::endl + << "--password <str> PostgreSQL database password." << ::std::endl; + + os << std::endl + << "--database|--dbname <name> PostgreSQL database name." << ::std::endl; + + os << std::endl + << "--host <str> PostgreSQL database host name or address (localhost" << ::std::endl + << " by default)." << ::std::endl; + + os << std::endl + << "--port <str> PostgreSQL database port number or socket file name" << ::std::endl + << " extension for Unix-domain connections." << ::std::endl; + + os << std::endl + << "--options-file <file> Read additional options from <file>. Each option" << ::std::endl + << " should appear on a separate line optionally followed" << ::std::endl + << " by space or equal sign (=) and an option value." << ::std::endl + << " Empty lines and lines starting with # are ignored." << ::std::endl; + + p = ::odb::pgsql::details::cli::usage_para::option; + + return p; + } + + typedef + std::map<std::string, void (*) (options&, ::odb::pgsql::details::cli::scanner&)> + _cli_options_map; + + static _cli_options_map _cli_options_map_; + + struct _cli_options_map_init + { + _cli_options_map_init () + { + _cli_options_map_["--user"] = + &::odb::pgsql::details::cli::thunk< options, std::string, &options::user_, + &options::user_specified_ >; + _cli_options_map_["--username"] = + &::odb::pgsql::details::cli::thunk< options, std::string, &options::user_, + &options::user_specified_ >; + _cli_options_map_["--password"] = + &::odb::pgsql::details::cli::thunk< options, std::string, &options::password_, + &options::password_specified_ >; + _cli_options_map_["--database"] = + &::odb::pgsql::details::cli::thunk< options, std::string, &options::database_, + &options::database_specified_ >; + _cli_options_map_["--dbname"] = + &::odb::pgsql::details::cli::thunk< options, std::string, &options::database_, + &options::database_specified_ >; + _cli_options_map_["--host"] = + &::odb::pgsql::details::cli::thunk< options, std::string, &options::host_, + &options::host_specified_ >; + _cli_options_map_["--port"] = + &::odb::pgsql::details::cli::thunk< options, std::string, &options::port_, + &options::port_specified_ >; + _cli_options_map_["--options-file"] = + &::odb::pgsql::details::cli::thunk< options, std::string, &options::options_file_, + &options::options_file_specified_ >; + } + }; + + static _cli_options_map_init _cli_options_map_init_; + + bool options:: + _parse (const char* o, ::odb::pgsql::details::cli::scanner& s) + { + _cli_options_map::const_iterator i (_cli_options_map_.find (o)); + + if (i != _cli_options_map_.end ()) + { + (*(i->second)) (*this, s); + return true; + } + + return false; + } + + bool options:: + _parse (::odb::pgsql::details::cli::scanner& s, + ::odb::pgsql::details::cli::unknown_mode opt_mode, + ::odb::pgsql::details::cli::unknown_mode arg_mode) + { + bool r = false; + bool opt = true; + + while (s.more ()) + { + const char* o = s.peek (); + + if (std::strcmp (o, "--") == 0) + { + opt = false; + s.skip (); + r = true; + continue; + } + + if (opt) + { + if (_parse (o, s)) + { + r = true; + continue; + } + + if (std::strncmp (o, "-", 1) == 0 && o[1] != '\0') + { + // Handle combined option values. + // + std::string co; + if (const char* v = std::strchr (o, '=')) + { + co.assign (o, 0, v - o); + ++v; + + int ac (2); + char* av[] = + { + const_cast<char*> (co.c_str ()), + const_cast<char*> (v) + }; + + ::odb::pgsql::details::cli::argv_scanner ns (0, ac, av); + + if (_parse (co.c_str (), ns)) + { + // Parsed the option but not its value? + // + if (ns.end () != 2) + throw ::odb::pgsql::details::cli::invalid_value (co, v); + + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = co.c_str (); + } + } + + switch (opt_mode) + { + case ::odb::pgsql::details::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::odb::pgsql::details::cli::unknown_mode::stop: + { + break; + } + case ::odb::pgsql::details::cli::unknown_mode::fail: + { + throw ::odb::pgsql::details::cli::unknown_option (o); + } + } + + break; + } + } + + switch (arg_mode) + { + case ::odb::pgsql::details::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::odb::pgsql::details::cli::unknown_mode::stop: + { + break; + } + case ::odb::pgsql::details::cli::unknown_mode::fail: + { + throw ::odb::pgsql::details::cli::unknown_argument (o); + } + } + + break; + } + + return r; + } + } + } +} + +// Begin epilogue. +// +// +// End epilogue. + diff --git a/odb/pgsql/details/pregenerated/odb/pgsql/details/options.hxx b/odb/pgsql/details/pregenerated/odb/pgsql/details/options.hxx new file mode 100644 index 0000000..4d264d4 --- /dev/null +++ b/odb/pgsql/details/pregenerated/odb/pgsql/details/options.hxx @@ -0,0 +1,562 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +#ifndef LIBODB_PGSQL_DETAILS_OPTIONS_HXX +#define LIBODB_PGSQL_DETAILS_OPTIONS_HXX + +// Begin prologue. +// +// +// End prologue. + +#include <list> +#include <deque> +#include <iosfwd> +#include <string> +#include <cstddef> +#include <exception> + +#ifndef CLI_POTENTIALLY_UNUSED +# if defined(_MSC_VER) || defined(__xlC__) +# define CLI_POTENTIALLY_UNUSED(x) (void*)&x +# else +# define CLI_POTENTIALLY_UNUSED(x) (void)x +# endif +#endif + +namespace odb +{ + namespace pgsql + { + namespace details + { + namespace cli + { + class usage_para + { + public: + enum value + { + none, + text, + option + }; + + usage_para (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + class unknown_mode + { + public: + enum value + { + skip, + stop, + fail + }; + + unknown_mode (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + // Exceptions. + // + + class exception: public std::exception + { + public: + virtual void + print (::std::ostream&) const = 0; + }; + + ::std::ostream& + operator<< (::std::ostream&, const exception&); + + class unknown_option: public exception + { + public: + virtual + ~unknown_option () throw (); + + unknown_option (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class unknown_argument: public exception + { + public: + virtual + ~unknown_argument () throw (); + + unknown_argument (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + class missing_value: public exception + { + public: + virtual + ~missing_value () throw (); + + missing_value (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class invalid_value: public exception + { + public: + virtual + ~invalid_value () throw (); + + invalid_value (const std::string& option, + const std::string& value, + const std::string& message = std::string ()); + + const std::string& + option () const; + + const std::string& + value () const; + + const std::string& + message () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + std::string value_; + std::string message_; + }; + + class eos_reached: public exception + { + public: + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + }; + + class file_io_failure: public exception + { + public: + virtual + ~file_io_failure () throw (); + + file_io_failure (const std::string& file); + + const std::string& + file () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string file_; + }; + + class unmatched_quote: public exception + { + public: + virtual + ~unmatched_quote () throw (); + + unmatched_quote (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + // Command line argument scanner interface. + // + // The values returned by next() are guaranteed to be valid + // for the two previous arguments up until a call to a third + // peek() or next(). + // + // The position() function returns a monotonically-increasing + // number which, if stored, can later be used to determine the + // relative position of the argument returned by the following + // call to next(). Note that if multiple scanners are used to + // extract arguments from multiple sources, then the end + // position of the previous scanner should be used as the + // start position of the next. + // + class scanner + { + public: + virtual + ~scanner (); + + virtual bool + more () = 0; + + virtual const char* + peek () = 0; + + virtual const char* + next () = 0; + + virtual void + skip () = 0; + + virtual std::size_t + position () = 0; + }; + + class argv_scanner: public scanner + { + public: + argv_scanner (int& argc, + char** argv, + bool erase = false, + std::size_t start_position = 0); + + argv_scanner (int start, + int& argc, + char** argv, + bool erase = false, + std::size_t start_position = 0); + + int + end () const; + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + virtual std::size_t + position (); + + protected: + std::size_t start_position_; + int i_; + int& argc_; + char** argv_; + bool erase_; + }; + + class argv_file_scanner: public argv_scanner + { + public: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (const std::string& file, + const std::string& option, + std::size_t start_position = 0); + + struct option_info + { + // If search_func is not NULL, it is called, with the arg + // value as the second argument, to locate the options file. + // If it returns an empty string, then the file is ignored. + // + const char* option; + std::string (*search_func) (const char*, void* arg); + void* arg; + }; + + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false, + std::size_t start_position = 0); + + argv_file_scanner (const std::string& file, + const option_info* options = 0, + std::size_t options_count = 0, + std::size_t start_position = 0); + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + virtual std::size_t + position (); + + // Return the file path if the peeked at argument came from a file and + // the empty string otherwise. The reference is guaranteed to be valid + // till the end of the scanner lifetime. + // + const std::string& + peek_file (); + + // Return the 1-based line number if the peeked at argument came from + // a file and zero otherwise. + // + std::size_t + peek_line (); + + private: + const option_info* + find (const char*) const; + + void + load (const std::string& file); + + typedef argv_scanner base; + + const std::string option_; + option_info option_info_; + const option_info* options_; + std::size_t options_count_; + + struct arg + { + std::string value; + const std::string* file; + std::size_t line; + }; + + std::deque<arg> args_; + std::list<std::string> files_; + + // Circular buffer of two arguments. + // + std::string hold_[2]; + std::size_t i_; + + bool skip_; + + static int zero_argc_; + static std::string empty_string_; + }; + + template <typename X> + struct parser; + } + } + } +} + +#include <string> + +namespace odb +{ + namespace pgsql + { + namespace details + { + class options + { + public: + options (); + + options (int& argc, + char** argv, + bool erase = false, + ::odb::pgsql::details::cli::unknown_mode option = ::odb::pgsql::details::cli::unknown_mode::fail, + ::odb::pgsql::details::cli::unknown_mode argument = ::odb::pgsql::details::cli::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + bool erase = false, + ::odb::pgsql::details::cli::unknown_mode option = ::odb::pgsql::details::cli::unknown_mode::fail, + ::odb::pgsql::details::cli::unknown_mode argument = ::odb::pgsql::details::cli::unknown_mode::stop); + + options (int& argc, + char** argv, + int& end, + bool erase = false, + ::odb::pgsql::details::cli::unknown_mode option = ::odb::pgsql::details::cli::unknown_mode::fail, + ::odb::pgsql::details::cli::unknown_mode argument = ::odb::pgsql::details::cli::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::odb::pgsql::details::cli::unknown_mode option = ::odb::pgsql::details::cli::unknown_mode::fail, + ::odb::pgsql::details::cli::unknown_mode argument = ::odb::pgsql::details::cli::unknown_mode::stop); + + options (::odb::pgsql::details::cli::scanner&, + ::odb::pgsql::details::cli::unknown_mode option = ::odb::pgsql::details::cli::unknown_mode::fail, + ::odb::pgsql::details::cli::unknown_mode argument = ::odb::pgsql::details::cli::unknown_mode::stop); + + // Option accessors. + // + const std::string& + user () const; + + bool + user_specified () const; + + const std::string& + password () const; + + bool + password_specified () const; + + const std::string& + database () const; + + bool + database_specified () const; + + const std::string& + host () const; + + bool + host_specified () const; + + const std::string& + port () const; + + bool + port_specified () const; + + const std::string& + options_file () const; + + bool + options_file_specified () const; + + // Print usage information. + // + static ::odb::pgsql::details::cli::usage_para + print_usage (::std::ostream&, + ::odb::pgsql::details::cli::usage_para = ::odb::pgsql::details::cli::usage_para::none); + + // Implementation details. + // + protected: + bool + _parse (const char*, ::odb::pgsql::details::cli::scanner&); + + private: + bool + _parse (::odb::pgsql::details::cli::scanner&, + ::odb::pgsql::details::cli::unknown_mode option, + ::odb::pgsql::details::cli::unknown_mode argument); + + public: + std::string user_; + bool user_specified_; + std::string password_; + bool password_specified_; + std::string database_; + bool database_specified_; + std::string host_; + bool host_specified_; + std::string port_; + bool port_specified_; + std::string options_file_; + bool options_file_specified_; + }; + } + } +} + +#include <odb/pgsql/details/options.ixx> + +// Begin epilogue. +// +// +// End epilogue. + +#endif // LIBODB_PGSQL_DETAILS_OPTIONS_HXX diff --git a/odb/pgsql/details/pregenerated/odb/pgsql/details/options.ixx b/odb/pgsql/details/pregenerated/odb/pgsql/details/options.ixx new file mode 100644 index 0000000..340789e --- /dev/null +++ b/odb/pgsql/details/pregenerated/odb/pgsql/details/options.ixx @@ -0,0 +1,372 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include <cassert> + +namespace odb +{ + namespace pgsql + { + namespace details + { + namespace cli + { + // usage_para + // + inline usage_para:: + usage_para (value v) + : v_ (v) + { + } + + // unknown_mode + // + inline unknown_mode:: + unknown_mode (value v) + : v_ (v) + { + } + + // exception + // + inline ::std::ostream& + operator<< (::std::ostream& os, const exception& e) + { + e.print (os); + return os; + } + + // unknown_option + // + inline unknown_option:: + unknown_option (const std::string& option) + : option_ (option) + { + } + + inline const std::string& unknown_option:: + option () const + { + return option_; + } + + // unknown_argument + // + inline unknown_argument:: + unknown_argument (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unknown_argument:: + argument () const + { + return argument_; + } + + // missing_value + // + inline missing_value:: + missing_value (const std::string& option) + : option_ (option) + { + } + + inline const std::string& missing_value:: + option () const + { + return option_; + } + + // invalid_value + // + inline invalid_value:: + invalid_value (const std::string& option, + const std::string& value, + const std::string& message) + : option_ (option), + value_ (value), + message_ (message) + { + } + + inline const std::string& invalid_value:: + option () const + { + return option_; + } + + inline const std::string& invalid_value:: + value () const + { + return value_; + } + + inline const std::string& invalid_value:: + message () const + { + return message_; + } + + // file_io_failure + // + inline file_io_failure:: + file_io_failure (const std::string& file) + : file_ (file) + { + } + + inline const std::string& file_io_failure:: + file () const + { + return file_; + } + + // unmatched_quote + // + inline unmatched_quote:: + unmatched_quote (const std::string& argument) + : argument_ (argument) + { + } + + inline const std::string& unmatched_quote:: + argument () const + { + return argument_; + } + + // argv_scanner + // + inline argv_scanner:: + argv_scanner (int& argc, + char** argv, + bool erase, + std::size_t sp) + : start_position_ (sp + 1), + i_ (1), + argc_ (argc), + argv_ (argv), + erase_ (erase) + { + } + + inline argv_scanner:: + argv_scanner (int start, + int& argc, + char** argv, + bool erase, + std::size_t sp) + : start_position_ (sp + static_cast<std::size_t> (start)), + i_ (start), + argc_ (argc), + argv_ (argv), + erase_ (erase) + { + } + + inline int argv_scanner:: + end () const + { + return i_; + } + + // argv_file_scanner + // + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase, + std::size_t sp) + : argv_scanner (argc, argv, erase, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase, + std::size_t sp) + : argv_scanner (start, argc, argv, erase, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + } + + inline argv_file_scanner:: + argv_file_scanner (const std::string& file, + const std::string& option, + std::size_t sp) + : argv_scanner (0, zero_argc_, 0, sp), + option_ (option), + options_ (&option_info_), + options_count_ (1), + i_ (1), + skip_ (false) + { + option_info_.option = option_.c_str (); + option_info_.search_func = 0; + + load (file); + } + + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase, + std::size_t sp) + : argv_scanner (argc, argv, erase, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase, + std::size_t sp) + : argv_scanner (start, argc, argv, erase, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (const std::string& file, + const option_info* options, + std::size_t options_count, + std::size_t sp) + : argv_scanner (0, zero_argc_, 0, sp), + options_ (options), + options_count_ (options_count), + i_ (1), + skip_ (false) + { + load (file); + } + } + } + } +} + +namespace odb +{ + namespace pgsql + { + namespace details + { + // options + // + + inline const std::string& options:: + user () const + { + return this->user_; + } + + inline bool options:: + user_specified () const + { + return this->user_specified_; + } + + inline const std::string& options:: + password () const + { + return this->password_; + } + + inline bool options:: + password_specified () const + { + return this->password_specified_; + } + + inline const std::string& options:: + database () const + { + return this->database_; + } + + inline bool options:: + database_specified () const + { + return this->database_specified_; + } + + inline const std::string& options:: + host () const + { + return this->host_; + } + + inline bool options:: + host_specified () const + { + return this->host_specified_; + } + + inline const std::string& options:: + port () const + { + return this->port_; + } + + inline bool options:: + port_specified () const + { + return this->port_specified_; + } + + inline const std::string& options:: + options_file () const + { + return this->options_file_; + } + + inline bool options:: + options_file_specified () const + { + return this->options_file_specified_; + } + } + } +} + +// Begin epilogue. +// +// +// End epilogue. diff --git a/odb/pgsql/error.cxx b/odb/pgsql/error.cxx index 5d34fde..ba8451e 100644 --- a/odb/pgsql/error.cxx +++ b/odb/pgsql/error.cxx @@ -15,11 +15,12 @@ namespace odb namespace pgsql { void - translate_error (connection& c, PGresult* r) + translate_error (connection& c, PGresult* r, + size_t pos, multiple_exceptions* mex) { if (!r) { - if (CONNECTION_BAD == PQstatus (c.handle ())) + if (PQstatus (c.handle ()) == CONNECTION_BAD) { c.mark_failed (); throw connection_lost (); @@ -28,50 +29,58 @@ namespace odb throw bad_alloc (); } - string msg; - { - // Can be NULL in case of PGRES_BAD_RESPONSE. - // - const char* m (PQresultErrorMessage (r)); - msg = (m != 0 ? m : "bad server response"); - - // Get rid of a trailing newline if there is one. - // - string::size_type n (msg.size ()); - if (n != 0 && msg[n - 1] == '\n') - msg.resize (n - 1); - } - + // Note that we expect the caller to handle PGRES_PIPELINE_ABORTED since + // it's not really an error but rather an indication that no attempt was + // made to execute this statement. + // + string ss; switch (PQresultStatus (r)) { case PGRES_BAD_RESPONSE: { - throw database_exception (msg); + throw database_exception ("bad server response"); } case PGRES_FATAL_ERROR: { - string ss; - { - const char* s (PQresultErrorField (r, PG_DIAG_SQLSTATE)); - ss = (s != 0 ? s : "?????"); - } + const char* s (PQresultErrorField (r, PG_DIAG_SQLSTATE)); + ss = (s != 0 ? s : "?????"); // Deadlock detected. // if (ss == "40001" || ss == "40P01") throw deadlock (); - else if (CONNECTION_BAD == PQstatus (c.handle ())) + else if (PQstatus (c.handle ()) == CONNECTION_BAD) { c.mark_failed (); throw connection_lost (); } - else - throw database_exception (ss, msg); + break; } default: assert (false); break; } + + string msg; + { + // Can be NULL in case of PGRES_BAD_RESPONSE. + // + const char* m (PQresultErrorMessage (r)); + msg = (m != 0 ? m : "bad server response"); + + // Get rid of the trailing newline if there is one. + // + string::size_type n (msg.size ()); + if (n != 0 && msg[n - 1] == '\n') + msg.resize (n - 1); + } + + if (mex == 0) + throw database_exception (ss, msg); + else + // In PosgreSQL all errors are fatal. + // + mex->insert (pos, database_exception (ss, msg), true); } } } diff --git a/odb/pgsql/error.hxx b/odb/pgsql/error.hxx index 36ecc44..8d2793d 100644 --- a/odb/pgsql/error.hxx +++ b/odb/pgsql/error.hxx @@ -9,23 +9,25 @@ #include <libpq-fe.h> #include <odb/pgsql/version.hxx> -#include <odb/pgsql/forward.hxx> // connection +#include <odb/pgsql/forward.hxx> // connection, multiple_exceptions #include <odb/pgsql/details/export.hxx> namespace odb { namespace pgsql { - // Translate an error condition involving a PGresult*. If r is null, it is - // assumed that the error was caused due to a bad connection or a memory - // allocation error. + // Translate an error condition involving PGresult* and throw (or return, + // in case multiple_exceptions is not NULL) an appropriate exception. If + // result is NULL, it is assumed that the error was caused due to a bad + // connection or a memory allocation error. // LIBODB_PGSQL_EXPORT void - translate_error (connection& c, PGresult* r); + translate_error (connection& c, PGresult* r, + std::size_t pos = 0, multiple_exceptions* = 0); - // Return true if the PGresult is in an error state. If both s and r are - // non-null, the pointed to value will be populated with the result status. - // Otherwise, s is ignored. + // Return true if PGresult is not NULL and is not in an error state. If + // both s and r are non-NULL, the pointed to value will be populated with + // the result status. Otherwise, s is ignored. // LIBODB_PGSQL_EXPORT bool is_good_result (PGresult* r, ExecStatusType* s = 0); diff --git a/odb/pgsql/error.ixx b/odb/pgsql/error.ixx index 0cda31b..6a010aa 100644 --- a/odb/pgsql/error.ixx +++ b/odb/pgsql/error.ixx @@ -5,8 +5,8 @@ namespace odb { namespace pgsql { - bool - inline is_good_result (PGresult* r, ExecStatusType* s) + inline bool + is_good_result (PGresult* r, ExecStatusType* s) { if (r != 0) { @@ -16,9 +16,13 @@ namespace odb *s = status; return - status != PGRES_BAD_RESPONSE && - status != PGRES_NONFATAL_ERROR && - status != PGRES_FATAL_ERROR; + status != PGRES_BAD_RESPONSE + && status != PGRES_NONFATAL_ERROR + && status != PGRES_FATAL_ERROR +#ifdef LIBPQ_HAS_PIPELINING + && status != PGRES_PIPELINE_ABORTED +#endif + ; } return false; diff --git a/odb/pgsql/exceptions.cxx b/odb/pgsql/exceptions.cxx index 01d9ab7..28e7fc4 100644 --- a/odb/pgsql/exceptions.cxx +++ b/odb/pgsql/exceptions.cxx @@ -26,7 +26,10 @@ namespace odb const string& message) : sqlstate_ (sqlstate), message_ (message) { - what_ = sqlstate_ + ": " + message_; + if (!sqlstate_.empty ()) + what_ = sqlstate_ + ": " + message_; + else + what_ = message_; } database_exception:: diff --git a/odb/pgsql/makefile b/odb/pgsql/makefile index d53eff6..acd23f6 100644 --- a/odb/pgsql/makefile +++ b/odb/pgsql/makefile @@ -109,7 +109,7 @@ libodb-pgsql-vc10.vcxproj libodb-pgsql-vc10.vcxproj.filters \ libodb-pgsql-vc11.vcxproj libodb-pgsql-vc11.vcxproj.filters \ libodb-pgsql-vc12.vcxproj libodb-pgsql-vc12.vcxproj.filters $(dist): export interface_version = $(shell sed -e \ -'s/^\([0-9]*\.[0-9]*\).*/\1/' $(src_root)/version) +'s/^\([0-9]*\.[0-9]*\).*/\1/' $(src_root)/version.txt) $(dist): $(gen) $(call dist-data,$(sources_dist) $(headers_dist) $(data_dist)) diff --git a/odb/pgsql/no-id-object-statements.hxx b/odb/pgsql/no-id-object-statements.hxx index 6e6b53f..baa1b2a 100644 --- a/odb/pgsql/no-id-object-statements.hxx +++ b/odb/pgsql/no-id-object-statements.hxx @@ -48,7 +48,10 @@ namespace odb // Object image. // image_type& - image () {return image_;} + image (std::size_t i = 0) + { + return image_[i]; + } // Insert binding. // @@ -112,7 +115,8 @@ namespace odb no_id_object_statements& operator= (const no_id_object_statements&); private: - image_type image_; + image_type image_[object_traits::batch]; + unsigned long long status_[object_traits::batch]; // Select binding. // diff --git a/odb/pgsql/no-id-object-statements.txx b/odb/pgsql/no-id-object-statements.txx index ced26ee..0c340ab 100644 --- a/odb/pgsql/no-id-object-statements.txx +++ b/odb/pgsql/no-id-object-statements.txx @@ -24,13 +24,17 @@ namespace odb // select select_image_binding_ (select_image_bind_, select_column_count), // insert - insert_image_binding_ (insert_image_bind_, insert_column_count), + insert_image_binding_ (insert_image_bind_, + insert_column_count, + object_traits::batch, + sizeof (image_type), + status_), insert_image_native_binding_ (insert_image_values_, insert_image_lengths_, insert_image_formats_, insert_column_count) { - image_.version = 0; + image_[0].version = 0; // Only version in the first element used. select_image_version_ = 0; insert_image_version_ = 0; diff --git a/odb/pgsql/pgsql-types.hxx b/odb/pgsql/pgsql-types.hxx index 93e4870..117a41e 100644 --- a/odb/pgsql/pgsql-types.hxx +++ b/odb/pgsql/pgsql-types.hxx @@ -14,8 +14,13 @@ namespace odb { namespace pgsql { - // The libpq result binding. This data structures is - // modelled after MYSQL_BIND from MySQL. + // The libpq result binding. This data structures is roughly modeled + // after MYSQL_BIND from MySQL. + // + // Types that may need to grow are bound as pointers to pointers to char + // array (normally in details::buffer) in order to allow simple offsetting + // in bulk operation support. Note that if we were to do the same for + // capacity, we could get rid of the buffer growth tracking altogether. // struct bind { @@ -27,14 +32,14 @@ namespace odb bigint, // Buffer is long long; size, capacity, truncated are unused. real, // Buffer is float; size, capacity, truncated are unused. double_, // Buffer is double; size, capacity, truncated are unused. - numeric, // Buffer is a char array. + numeric, // Buffer is a pointer to pointer to char array. date, // Buffer is int; size, capacity, truncated are unused. time, // Buffer is long long; size, capacity, truncated are unused. timestamp,// Buffer is long long; size, capacity, truncated are unused. - text, // Buffer is a char array. - bytea, // Buffer is a char array. - bit, // Buffer is a char array. - varbit, // Buffer is a char array. + text, // Buffer is a pointer to pointer to char array. + bytea, // Buffer is a pointer to pointer to char array. + bit, // Buffer is a pointer to char array. + varbit, // Buffer is a pointer to pointer to char array. uuid // Buffer is a 16-byte char array; size capacity, truncated // are unused. Note: big-endian, in RFC 4122/4.1.2 order. }; diff --git a/odb/pgsql/polymorphic-object-statements.txx b/odb/pgsql/polymorphic-object-statements.txx index 7f2aadd..8472fca 100644 --- a/odb/pgsql/polymorphic-object-statements.txx +++ b/odb/pgsql/polymorphic-object-statements.txx @@ -129,7 +129,7 @@ namespace odb root_type& robj, const schema_version_migration* svm) { - connection_type& conn (transaction::current ().connection ()); + connection_type& conn (transaction::current ().connection (db)); polymorphic_derived_object_statements& sts ( conn.statement_cache ().find_object<object_type> ()); root_statements_type& rsts (sts.root_statements ()); diff --git a/odb/pgsql/query.hxx b/odb/pgsql/query.hxx index b940261..42182d6 100644 --- a/odb/pgsql/query.hxx +++ b/odb/pgsql/query.hxx @@ -1671,7 +1671,7 @@ namespace odb bind (bind_type* b) { b->type = bind::numeric; - b->buffer = buffer_.data (); + b->buffer = buffer_.data_ptr (); b->capacity = buffer_.capacity (); b->size = &size_; } @@ -1836,7 +1836,7 @@ namespace odb bind (bind_type* b) { b->type = bind::text; - b->buffer = buffer_.data (); + b->buffer = buffer_.data_ptr (); b->capacity = buffer_.capacity (); b->size = &size_; } @@ -1881,7 +1881,7 @@ namespace odb bind (bind_type* b) { b->type = bind::bytea; - b->buffer = buffer_.data (); + b->buffer = buffer_.data_ptr (); b->capacity = buffer_.capacity (); b->size = &size_; } @@ -1926,7 +1926,7 @@ namespace odb bind (bind_type* b) { b->type = bind::bit; - b->buffer = buffer_.data (); + b->buffer = buffer_.data_ptr (); b->capacity = buffer_.capacity (); b->size = &size_; } @@ -1970,7 +1970,7 @@ namespace odb bind (bind_type* b) { b->type = bind::varbit; - b->buffer = buffer_.data (); + b->buffer = buffer_.data_ptr (); b->capacity = buffer_.capacity (); b->size = &size_; } diff --git a/odb/pgsql/simple-object-statements.hxx b/odb/pgsql/simple-object-statements.hxx index 64acbe9..086ef5f 100644 --- a/odb/pgsql/simple-object-statements.hxx +++ b/odb/pgsql/simple-object-statements.hxx @@ -167,7 +167,8 @@ namespace odb typedef T object_type; typedef object_traits_impl<object_type, id_pgsql> object_traits; - optimistic_data (bind*, char** nv, int* nl, int* nf); + optimistic_data (bind*, char** nv, int* nl, int* nf, + std::size_t skip, unsigned long long* status); binding* id_image_binding () {return &id_image_binding_;} @@ -190,7 +191,8 @@ namespace odb template <typename T> struct optimistic_data<T, false> { - optimistic_data (bind*, char**, int*, int*) {} + optimistic_data (bind*, char**, int*, int*, + std::size_t, unsigned long long*) {} binding* id_image_binding () {return 0;} @@ -301,7 +303,7 @@ namespace odb // Object image. // image_type& - image () {return image_;} + image (std::size_t i = 0) {return images_[i].obj;} // Insert binding. // @@ -348,7 +350,7 @@ namespace odb // Object id image and binding. // id_image_type& - id_image () {return id_image_;} + id_image (std::size_t i = 0) {return images_[i].id;} std::size_t id_image_version () const {return id_image_version_;} @@ -471,8 +473,8 @@ namespace odb { return extra_statement_cache_.get ( conn_, - image_, - id_image_, + images_[0].obj, + images_[0].id, id_image_binding_, od_.id_image_binding (), id_image_native_binding_, @@ -527,7 +529,18 @@ namespace odb image_type, id_image_type> extra_statement_cache_; - image_type image_; + // The UPDATE statement uses both the object and id image. Keep them + // next to each other so that the same skip distance can be used in + // batch binding. + // + struct images + { + image_type obj; + id_image_type id; + }; + + images images_[object_traits::batch]; + unsigned long long status_[object_traits::batch]; // Select binding. // @@ -574,7 +587,6 @@ namespace odb // Id image binding (only used as a parameter). Uses the suffix in // the update bind. // - id_image_type id_image_; std::size_t id_image_version_; binding id_image_binding_; native_binding id_image_native_binding_; diff --git a/odb/pgsql/simple-object-statements.txx b/odb/pgsql/simple-object-statements.txx index ad87e73..bb47b43 100644 --- a/odb/pgsql/simple-object-statements.txx +++ b/odb/pgsql/simple-object-statements.txx @@ -19,11 +19,15 @@ namespace odb template <typename T> optimistic_data<T, true>:: - optimistic_data (bind* b, char** nv, int* nl, int* nf) + optimistic_data (bind* b, char** nv, int* nl, int* nf, + std::size_t skip, unsigned long long* status) : id_image_binding_ ( b, object_traits::id_column_count + - object_traits::managed_optimistic_column_count), + object_traits::managed_optimistic_column_count, + object_traits::batch, + skip, + status), id_image_native_binding_ ( nv, nl, nf, object_traits::id_column_count + @@ -48,7 +52,11 @@ namespace odb // select select_image_binding_ (select_image_bind_, select_column_count), // insert - insert_image_binding_ (insert_image_bind_, insert_column_count), + insert_image_binding_ (insert_image_bind_, + insert_column_count, + object_traits::batch, + sizeof (images), + status_), insert_image_native_binding_ (insert_image_values_, insert_image_lengths_, insert_image_formats_, @@ -56,7 +64,10 @@ namespace odb // update update_image_binding_ (update_image_bind_, update_column_count + id_column_count + - managed_optimistic_column_count), + managed_optimistic_column_count, + object_traits::batch, + sizeof (images), + status_), update_image_native_binding_ (update_image_values_, update_image_lengths_, update_image_formats_, @@ -64,7 +75,10 @@ namespace odb managed_optimistic_column_count), // id id_image_binding_ (update_image_bind_ + update_column_count, - id_column_count), + id_column_count, + object_traits::batch, + sizeof (images), + status_), id_image_native_binding_ ( update_image_values_ + update_column_count, update_image_lengths_ + update_column_count, @@ -74,15 +88,19 @@ namespace odb od_ (update_image_bind_ + update_column_count, update_image_values_ + update_column_count, update_image_lengths_ + update_column_count, - update_image_formats_ + update_column_count) + update_image_formats_ + update_column_count, + sizeof (images), + status_) { - image_.version = 0; + // Only versions in the first element used. + // + images_[0].obj.version = 0; + images_[0].id.version = 0; + select_image_version_ = 0; insert_image_version_ = 0; update_image_version_ = 0; update_id_image_version_ = 0; - - id_image_.version = 0; id_image_version_ = 0; std::memset (insert_image_bind_, 0, sizeof (insert_image_bind_)); diff --git a/odb/pgsql/statement.cxx b/odb/pgsql/statement.cxx index 977db72..8574c37 100644 --- a/odb/pgsql/statement.cxx +++ b/odb/pgsql/statement.cxx @@ -1,16 +1,32 @@ // file : odb/pgsql/statement.cxx // license : GNU GPL v2; see accompanying LICENSE file -#include <cstdlib> // std::atol -#include <cassert> -#include <sstream> // istringstream +#include <odb/details/config.hxx> // ODB_CXX11 #include <libpq-fe.h> +#ifdef LIBPQ_HAS_PIPELINING +# ifndef _WIN32 +# include <errno.h> +# include <sys/select.h> +# endif +#endif + +#include <cstring> // strcmp +#include <utility> // pair +#include <cassert> + +#ifdef ODB_CXX11 +# include <cstdlib> // strtoull +#else +# include <sstream> // istringstream +#endif + #include <odb/tracer.hxx> #include <odb/pgsql/pgsql-oid.hxx> #include <odb/pgsql/statement.hxx> +#include <odb/pgsql/exceptions.hxx> #include <odb/pgsql/connection.hxx> #include <odb/pgsql/transaction.hxx> #include <odb/pgsql/auto-handle.hxx> @@ -36,11 +52,12 @@ namespace odb count = static_cast<unsigned long long> (s[0] - '0'); else { - // @@ Using stringstream conversion for now. See if we can optimize - // this (atoll possibly, even though it is not standard). - // +#ifdef ODB_CXX11 + count = strtoull (s, 0, 10); +#else istringstream ss (s); ss >> count; +#endif } return count; @@ -256,30 +273,39 @@ namespace odb return text_; } + template <typename T> + static inline T* + offset (T* base, size_t count, size_t size) + { + return reinterpret_cast<T*> ( + reinterpret_cast<char*> (base) + count * size); + } + void statement:: - bind_param (native_binding& n, const binding& b) + bind_param (native_binding& ns, const binding& bs, size_t pos) { - assert (n.count == b.count); + assert (ns.count == bs.count); - for (size_t i (0); i < n.count; ++i) + for (size_t i (0); i < ns.count; ++i) { - const bind& current_bind (b.bind[i]); + const bind& b (bs.bind[i]); - n.formats[i] = 1; + ns.formats[i] = 1; - if (current_bind.buffer == 0 || // Skip NULL entries. - (current_bind.is_null != 0 && *current_bind.is_null)) + bool* n (b.is_null != 0 ? offset (b.is_null, pos, bs.skip) : 0); + + if ((n != 0 && *n) || b.buffer == 0) // Handle NULL entries. { - n.values[i] = 0; - n.lengths[i] = 0; + ns.values[i] = 0; + ns.lengths[i] = 0; continue; } - n.values[i] = static_cast<char*> (current_bind.buffer); + ns.values[i] = static_cast<char*> (offset (b.buffer, pos, bs.skip)); size_t l (0); - switch (current_bind.type) + switch (b.type) { case bind::boolean_: { @@ -325,10 +351,18 @@ namespace odb case bind::numeric: case bind::text: case bind::bytea: - case bind::bit: case bind::varbit: { - l = *current_bind.size; + // In this case b.buffer is a pointer to pointer to buffer so we + // need to chase one level. + // + ns.values[i] = static_cast<char*> ( + *reinterpret_cast<void**> (ns.values[i])); + } + // Fall through. + case bind::bit: + { + l = *offset (b.size, pos, bs.skip); break; } case bind::uuid: @@ -340,54 +374,61 @@ namespace odb } } - n.lengths[i] = static_cast<int> (l); + ns.lengths[i] = static_cast<int> (l); } } bool statement:: - bind_result (bind* p, - size_t count, + bind_result (const binding& bs, PGresult* result, size_t row, - bool truncated) + bool truncated, + size_t pos) { bool r (true); int col_count (PQnfields (result)); int col (0); - for (size_t i (0); i != count && col != col_count; ++i) + for (size_t i (0); i != bs.count && col != col_count; ++i) { - const bind& b (p[i]); + const bind& b (bs.bind[i]); if (b.buffer == 0) // Skip NULL entries. continue; int c (col++); - if (truncated && (b.truncated == 0 || !*b.truncated)) - continue; + { + bool* t (b.truncated != 0 ? offset (b.truncated, pos, bs.skip) : 0); - if (b.truncated != 0) - *b.truncated = false; + if (truncated && (t == 0 || !*t)) + continue; + + if (t != 0) + *t = false; + } // Check for NULL unless we are reloading a truncated result. // if (!truncated) { - *b.is_null = PQgetisnull (result, static_cast<int> (row), c) == 1; + bool* n (offset (b.is_null, pos, bs.skip)); + + *n = PQgetisnull (result, static_cast<int> (row), c) == 1; - if (*b.is_null) + if (*n) continue; } + void* buf (offset (b.buffer, pos, bs.skip)); + const char* v (PQgetvalue (result, static_cast<int> (row), c)); switch (b.type) { case bind::boolean_: { - *static_cast<bool*> (b.buffer) = - *reinterpret_cast<const bool*> (v); + *static_cast<bool*> (buf) = *reinterpret_cast<const bool*> (v); break; } case bind::smallint: @@ -430,19 +471,19 @@ namespace odb { case bind::smallint: { - *static_cast<short*> (b.buffer) = + *static_cast<short*> (buf) = endian_traits::hton (static_cast<short> (i)); break; } case bind::integer: { - *static_cast<int*> (b.buffer) = + *static_cast<int*> (buf) = endian_traits::hton (static_cast<int> (i)); break; } case bind::bigint: { - *static_cast<long long*> (b.buffer) = endian_traits::hton (i); + *static_cast<long long*> (buf) = endian_traits::hton (i); break; } default: @@ -453,25 +494,23 @@ namespace odb } case bind::real: { - *static_cast<float*> (b.buffer) = - *reinterpret_cast<const float*> (v); + *static_cast<float*> (buf) = *reinterpret_cast<const float*> (v); break; } case bind::double_: { - *static_cast<double*> (b.buffer) = - *reinterpret_cast<const double*> (v); + *static_cast<double*> (buf) = *reinterpret_cast<const double*> (v); break; } case bind::date: { - *static_cast<int*> (b.buffer) = *reinterpret_cast<const int*> (v); + *static_cast<int*> (buf) = *reinterpret_cast<const int*> (v); break; } case bind::time: case bind::timestamp: { - *static_cast<long long*> (b.buffer) = + *static_cast<long long*> (buf) = *reinterpret_cast<const long long*> (v); break; } @@ -481,24 +520,37 @@ namespace odb case bind::bit: case bind::varbit: { + // Currently this is neither supported (due to capacity) nor used + // in batches. + // +#ifdef LIBPGSQL_EXTRA_CHECKS + assert (pos == 0); +#endif + *b.size = static_cast<size_t> ( PQgetlength (result, static_cast<int> (row), c)); - if (b.capacity < *b.size) - { - if (b.truncated) - *b.truncated = true; + if (b.capacity < *b.size) + { + if (b.truncated) + *b.truncated = true; - r = false; - continue; - } + r = false; + continue; + } + + // In these cases b.buffer is a pointer to pointer to buffer so we + // need to chase one level. + // + if (b.type != bind::bit) + buf = *static_cast<void**> (buf); - memcpy (b.buffer, v, *b.size); - break; + memcpy (buf, v, *b.size); + break; } case bind::uuid: { - memcpy (b.buffer, v, 16); + memcpy (buf, v, 16); break; } } @@ -514,6 +566,388 @@ namespace odb return r; } +#if defined(LIBPQ_HAS_PIPELINING) && !defined(_WIN32) + + // Note that this function always marks the connection as failed. + // + static void + translate_connection_error (connection& conn) + { + const char* m (PQerrorMessage (conn.handle ())); + + if (PQstatus (conn.handle ()) == CONNECTION_BAD) + { + conn.mark_failed (); + throw connection_lost (); + } + else + { + conn.mark_failed (); + throw database_exception (m != 0 ? m : "bad connection state"); + } + } + + // A RAII object for PGconn's non-blocking pipeline mode. + // + struct pipeline + { + connection& conn; + int sock; + + explicit + pipeline (connection& c) + : conn (c) + { + PGconn* ch (conn.handle ()); + + if ((sock = PQsocket (ch)) == -1 || + PQsetnonblocking (ch, 1) == -1 || + PQenterPipelineMode (ch) == 0) + { + translate_connection_error (conn); + } + } + + void + close (bool throw_ = true) + { + if (!conn.failed ()) + { + PGconn* ch (conn.handle ()); + + if (PQexitPipelineMode (ch) == 0 || + PQsetnonblocking (ch, 0) == -1) + { + if (throw_) + translate_connection_error (conn); + else + conn.mark_failed (); + } + } + } + + ~pipeline () + { + close (false); + } + + pair<bool /* read */, bool /* write */> + wait (bool write, bool throw_ = true) + { + fd_set wds; + fd_set rds; + + for (;;) + { + if (write) + { + FD_ZERO (&wds); + FD_SET (sock, &wds); + } + + FD_ZERO (&rds); + FD_SET (sock, &rds); + + if (select (sock + 1, &rds, write ? &wds : 0, 0, 0) != -1) + break; + + if (errno != EINTR) + { + if (throw_) + translate_connection_error (conn); + else + { + conn.mark_failed (); + return pair<bool, bool> (false, false); + } + } + } + + return pair<bool, bool> (FD_ISSET (sock, &rds), + write && FD_ISSET (sock, &wds)); + } + }; + + // A RAII object for recovering from an error in a pipeline. + // + // Specifically, it reads and discards results until reaching + // PGRES_PIPELINE_SYNC. + // + struct pipeline_recovery + { + pipeline_recovery (pipeline& pl, bool wdone, bool sync) + : pl_ (&pl), wdone_ (wdone), sync_ (sync) + { + } + + ~pipeline_recovery () + { + if (pl_ != 0 && !pl_->conn.failed ()) + { + PGconn* ch (pl_->conn.handle ()); + + // This code runs as part of stack unwinding caused by an exception + // so if we encounter an error, we "upgrade" the existing exception + // by marking the connection as failed. + // + // The rest is essentially a special version of execute() below. + // + // Note that on the first iteration we may still have results from + // the previous call to PQconsumeInput() (and these results may + // be the entire outstanding sequence, in which case calling wait() + // will block indefinitely). + // + for (bool first (true);; first = false) + { + if (sync_) + { + assert (!wdone_); + + if (PQpipelineSync (ch) == 0) + break; + + sync_ = false; + } + + pair<bool, bool> r (false, false); + + if (!first) + { + r = pl_->wait (!wdone_); + if (!r.first && !r.second) + break; + } + + if (r.first /* read */ || first) + { + if (r.first && PQconsumeInput (ch) == 0) + break; + + while (PQisBusy (ch) == 0) + { + auto_handle<PGresult> res (PQgetResult (ch)); + + // We should only get NULLs as well as PGRES_PIPELINE_ABORTED + // finished with PGRES_PIPELINE_SYNC. + // + if (res != 0) + { + ExecStatusType stat (PQresultStatus (res)); + + if (stat == PGRES_PIPELINE_SYNC) + return; + + assert (stat == PGRES_PIPELINE_ABORTED); + } + } + } + + if (r.second /* write */) + { + int r (PQflush (ch)); + if (r == -1) + break; + + if (r == 0) + wdone_ = true; + } + } + + pl_->conn.mark_failed (); + } + } + + void + cancel () + { + pl_ = 0; + } + + private: + pipeline* pl_; + bool wdone_; + bool sync_; + }; + + size_t statement:: + execute (const binding& param, + native_binding& native_param, + size_t n, + multiple_exceptions& mex, + bool (*process) (size_t, PGresult*, bool, void*), + void* data) + { + size_t i (0); // Parameter set being attempted. + mex.current (i); + + PGconn* ch (conn_.handle ()); + + pipeline pl (conn_); + + // True if we've written and read everything, respectively. + // + bool wdone (false), rdone (false); + + for (size_t wn (0), rn (0); !rdone; ) + { + // Note that there is a special version of this code above in + // ~pipeline_recovery(). + // + pair<bool, bool> r (pl.wait (!wdone)); + + // Note that once we start the pipeline, any call that may throw + // without marking the connection as failed should be guarded by + // pipeline_recovery. + + // Try to minimize the chance of blocking the server by first + // processing the result and then sending more queries. + // + if (r.first /* read */) + { + if (PQconsumeInput (ch) == 0) + translate_connection_error (conn_); + + // Note that PQisBusy() will return 0 (and subsequent PQgetResult() + // -- NULL) if we have consumed all the results for the queries that + // we have sent so far. Thus the (wn > rn) condition. Note that for + // this to work correctly we have to count the PQpipelineSync() call + // below as one of the queries (since it has a separate result). + // + while (wn > rn && PQisBusy (ch) == 0) + { + auto_handle<PGresult> res (PQgetResult (ch)); + + ExecStatusType stat (PGRES_FATAL_ERROR); + bool gr (is_good_result (res, &stat)); + + if (stat == PGRES_PIPELINE_SYNC) + { + assert (wdone && rn == n); + rdone = true; + break; + } + + assert (rn != n); + ++rn; + + if (stat != PGRES_PIPELINE_ABORTED) + { + // translate_error() may throw an exception (e.g., deadlock) + // without marking the connection as failed. + // + { + pipeline_recovery plr (pl, wdone, wn < n); + + if (!process (i, res, gr, data)) + translate_error (conn_, res, i, &mex); + + plr.cancel (); + } + + mex.attempted (++i); + mex.current (i); + } + else + { + // Should we treat PGRES_PIPELINE_ABORTED entries as attempted + // or not? While we did issue PQsendQueryPrepared() for them, + // the server tells us that it did not attemp to execute them. + // So it feels like they should not be treated as attempted. + // + // Note that for this to fit into out multiple_exceptions model, + // such an incomplete batch should be fatal (otherwise we could + // end up with unattempted "holes"). This is currently the case + // for errors handled by translate_error() but not necessarily + // the case for those handled by the process function (e.g., + // duplicate id handled by process_insert_result() below). So in + // a somewhat hackish way we assume the error (e.g., duplicate + // id) will always be translated to an exception and pre-mark + // multiple_exceptions as fatal. + // + mex.fatal (true); + } + + // We get a NULL result after each query result. + // + { + PGresult* end (PQgetResult (ch)); + assert (end == 0); + } + } + } + + if (r.second /* write */) + { + // Send queries until we get blocked (write-biased). This feels like + // a better overall strategy to keep the server busy compared to + // sending one query at a time and then re-checking if there is + // anything to read because the results of INSERT/UPDATE/DELETE are + // presumably small and quite a few of them can get buffered before + // the server gets blocked. + // + for (;;) + { + if (wn < n) + { + bind_param (native_param, param, wn); + + if (PQsendQueryPrepared (ch, + name_, + static_cast<int> (native_param.count), + native_param.values, + native_param.lengths, + native_param.formats, + 1) == 0) + translate_connection_error (conn_); + + if (++wn == n) + { + if (PQpipelineSync (ch) == 0) + translate_connection_error (conn_); + + // Count as one of the queries since it has a separate result. + // + ++wn; + } + } + + // PQflush() result: + // + // 0 -- success (queue is now empty) + // 1 -- blocked + // -1 -- error + // + int r (PQflush (ch)); + if (r == -1) + translate_connection_error (conn_); + + if (r == 0) + { + if (wn < n) + { + // If we continue here, then we are write-biased. And if we + // break, then we are read-biased. + // +#ifdef LIBPGSQL_READ_BIASED + break; +#else + continue; +#endif + } + + wdone = true; + } + + break; // Blocked or done. + } + } + } + + pl.close (); + return i; + } +#endif + // // select_statement // @@ -689,10 +1123,7 @@ namespace odb return no_data; assert (current_row_ > 0); - return bind_result (result_.bind, - result_.count, - handle_, - current_row_ - 1) + return bind_result (result_, handle_, current_row_ - 1) ? success : truncated; } @@ -703,11 +1134,7 @@ namespace odb assert (current_row_ > 0); assert (current_row_ <= row_count_); - if (!bind_result (result_.bind, - result_.count, - handle_, - current_row_ - 1, - true)) + if (!bind_result (result_, handle_, current_row_ - 1, true)) assert (false); } @@ -738,6 +1165,8 @@ namespace odb native_param_ (native_param), returning_ (returning) { + if (returning_ != 0) + assert (returning_->count == 1); } insert_statement:: @@ -759,6 +1188,8 @@ namespace odb native_param_ (native_param), returning_ (returning) { + if (returning_ != 0) + assert (returning_->count == 1); } bool insert_statement:: @@ -792,9 +1223,9 @@ namespace odb // if (returning_ == 0 && stat == PGRES_FATAL_ERROR) { - string s (PQresultErrorField (h, PG_DIAG_SQLSTATE)); + const char* ss (PQresultErrorField (h, PG_DIAG_SQLSTATE)); - if (s == "23505") + if (ss != 0 && strcmp (ss, "23505") == 0) return false; } @@ -802,11 +1233,76 @@ namespace odb } if (returning_ != 0) - bind_result (returning_->bind, 1, h, 0, false); + bind_result (*returning_, h, 0); + + return true; + } + +#if defined(LIBPQ_HAS_PIPELINING) && !defined(_WIN32) + + struct insert_data + { + binding& param; + binding* returning; + }; + + static bool + process_insert_result (size_t i, PGresult* r, bool gr, void* data) + { + insert_data& d (*static_cast<insert_data*> (data)); + + unsigned long long& s (d.param.status[i]); + s = 1; + + if (gr) + { + // Note that the result can never be truncated. + // + if (d.returning != 0) + statement::bind_result (*d.returning, r, 0, false, i); + } + else + { + // An auto-assigned object id should never cause a duplicate + // primary key. + // + if (d.returning == 0 && + r != 0 && PQresultStatus (r) == PGRES_FATAL_ERROR) + { + // Note that statement::execute() assumes that this will eventually + // be translated to an entry in multiple_exceptions. + // + const char* ss (PQresultErrorField (r, PG_DIAG_SQLSTATE)); + + if (ss != 0 && strcmp (ss, "23505") == 0) + s = 0; + } + + if (s == 1) + return false; + } return true; } + size_t insert_statement:: + execute (size_t n, multiple_exceptions& mex) + { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + + insert_data d {param_, returning_}; + + return statement::execute ( + param_, native_param_, n, mex, &process_insert_result, &d); + } +#endif + // // update_statement // @@ -881,6 +1377,43 @@ namespace odb return affected_row_count (h); } +#if defined(LIBPQ_HAS_PIPELINING) && !defined(_WIN32) + + static bool + process_update_result (size_t i, PGresult* r, bool gr, void* data) + { + binding& param (*static_cast<binding*> (data)); + + unsigned long long& s (param.status[i]); + + if (gr) + { + s = affected_row_count (r); + return true; + } + else + { + s = update_statement::result_unknown; + return false; + } + } + + size_t update_statement:: + execute (size_t n, multiple_exceptions& mex) + { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + + return statement::execute ( + param_, native_param_, n, mex, &process_update_result, ¶m_); + } +#endif + // // delete_statement // @@ -969,5 +1502,44 @@ namespace odb return affected_row_count (h); } + +#if defined(LIBPQ_HAS_PIPELINING) && !defined(_WIN32) + + static bool + process_delete_result (size_t i, PGresult* r, bool gr, void* data) + { + binding& param (*static_cast<binding*> (data)); + + unsigned long long& s (param.status[i]); + + if (gr) + { + s = affected_row_count (r); + return true; + } + else + { + s = delete_statement::result_unknown; + return false; + } + } + + size_t delete_statement:: + execute (size_t n, multiple_exceptions& mex) + { + assert (param_ != 0); + + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + + return statement::execute ( + *param_, native_param_, n, mex, &process_delete_result, param_); + } +#endif } } diff --git a/odb/pgsql/statement.hxx b/odb/pgsql/statement.hxx index b417f1c..139d2d6 100644 --- a/odb/pgsql/statement.hxx +++ b/odb/pgsql/statement.hxx @@ -63,22 +63,24 @@ namespace odb void deallocate (); - // Adapt an ODB binding to a native PostgreSQL parameter binding. + // Adapt an ODB binding to a native PostgreSQL parameter binding. If pos + // is not 0, then bind the parameter set at this position in a batch. // static void - bind_param (native_binding&, const binding&); + bind_param (native_binding&, const binding&, std::size_t pos = 0); // Populate an ODB binding given a PostgreSQL result. If the truncated // argument is true, then only truncated columns are extracted. Return // true if all the data was extracted successfully and false if one or - // more columns were truncated. + // more columns were truncated. If pos is not 0, then populate the + // parameter set at this position in a batch. // static bool - bind_result (bind*, - std::size_t count, + bind_result (const binding&, PGresult*, std::size_t row, - bool truncated = false); + bool truncated = false, + std::size_t pos = 0); protected: // We keep two versions to take advantage of std::string COW. @@ -102,6 +104,16 @@ namespace odb const Oid* types, std::size_t types_count); + // Bulk execute implementation. + // + std::size_t + execute (const binding& param, + native_binding& native_param, + std::size_t n, + multiple_exceptions&, + bool (*process) (size_t i, PGresult*, bool good, void* data), + void* data); + private: void init (statement_kind, @@ -296,6 +308,20 @@ namespace odb bool execute (); + // Return the number of parameter sets (out of n) that were attempted. + // + std::size_t + execute (std::size_t n, multiple_exceptions&); + + // Return true if successful and false if this row is a duplicate. + // All other errors are reported via exceptions. + // + bool + result (std::size_t i) + { + return param_.status[i] != 0; + } + private: insert_statement (const insert_statement&); insert_statement& operator= (const insert_statement&); @@ -334,6 +360,22 @@ namespace odb unsigned long long execute (); + // Return the number of parameter sets (out of n) that were attempted. + // + std::size_t + execute (std::size_t n, multiple_exceptions&); + + // Return the number of rows affected (updated) by the parameter + // set. All errors are reported by throwing exceptions. + // + static const unsigned long long result_unknown = ~0ULL; + + unsigned long long + result (std::size_t i) + { + return param_.status[i]; + } + private: update_statement (const update_statement&); update_statement& operator= (const update_statement&); @@ -376,6 +418,22 @@ namespace odb unsigned long long execute (); + // Return the number of parameter sets (out of n) that were attempted. + // + std::size_t + execute (std::size_t n, multiple_exceptions&); + + // Return the number of rows affected (deleted) by the parameter + // set. All errors are reported by throwing exceptions. + // + static const unsigned long long result_unknown = ~0ULL; + + unsigned long long + result (std::size_t i) + { + return param_->status[i]; + } + private: delete_statement (const delete_statement&); delete_statement& operator= (const delete_statement&); diff --git a/odb/pgsql/transaction-impl.hxx b/odb/pgsql/transaction-impl.hxx index 59924a0..5c93b0e 100644 --- a/odb/pgsql/transaction-impl.hxx +++ b/odb/pgsql/transaction-impl.hxx @@ -38,17 +38,12 @@ namespace odb virtual void rollback (); - connection_type& - connection (); - private: connection_ptr connection_; }; } } -#include <odb/pgsql/transaction-impl.ixx> - #include <odb/post.hxx> #endif // ODB_PGSQL_TRANSACTION_IMPL_HXX diff --git a/odb/pgsql/transaction-impl.ixx b/odb/pgsql/transaction-impl.ixx deleted file mode 100644 index f812969..0000000 --- a/odb/pgsql/transaction-impl.ixx +++ /dev/null @@ -1,14 +0,0 @@ -// file : odb/pgsql/transaction-impl.ixx -// license : GNU GPL v2; see accompanying LICENSE file - -namespace odb -{ - namespace pgsql - { - inline transaction_impl::connection_type& transaction_impl:: - connection () - { - return *connection_; - } - } -} diff --git a/odb/pgsql/transaction.hxx b/odb/pgsql/transaction.hxx index f1240ec..e83c754 100644 --- a/odb/pgsql/transaction.hxx +++ b/odb/pgsql/transaction.hxx @@ -41,6 +41,9 @@ namespace odb connection_type& connection (); + connection_type& + connection (odb::database&); + // Return current transaction or throw if there is no transaction // in effect. // diff --git a/odb/pgsql/transaction.ixx b/odb/pgsql/transaction.ixx index 7a2c375..31aa603 100644 --- a/odb/pgsql/transaction.ixx +++ b/odb/pgsql/transaction.ixx @@ -39,7 +39,13 @@ namespace odb inline transaction::connection_type& transaction:: connection () { - return implementation ().connection (); + return static_cast<connection_type&> (odb::transaction::connection ()); + } + + inline transaction::connection_type& transaction:: + connection (odb::database& db) + { + return static_cast<connection_type&> (odb::transaction::connection (db)); } inline void transaction:: diff --git a/odb/pgsql/version.hxx b/odb/pgsql/version.hxx index 15cc71b..e050de4 100644 --- a/odb/pgsql/version.hxx +++ b/odb/pgsql/version.hxx @@ -32,15 +32,15 @@ // Check that we have compatible ODB version. // -#if ODB_VERSION != 20470 +#if ODB_VERSION != 20476 # error incompatible odb interface version detected #endif // libodb-pgsql version: odb interface version plus the bugfix // version. // -#define LIBODB_PGSQL_VERSION 2049970 -#define LIBODB_PGSQL_VERSION_STR "2.5.0-b.20" +#define LIBODB_PGSQL_VERSION 2049976 +#define LIBODB_PGSQL_VERSION_STR "2.5.0-b.26" #include <odb/post.hxx> diff --git a/repositories.manifest b/repositories.manifest index cdcc545..4451c88 100644 --- a/repositories.manifest +++ b/repositories.manifest @@ -8,3 +8,7 @@ location: https://git.build2.org/packaging/postgresql/postgresql.git##HEAD : role: prerequisite location: ../libodb.git##HEAD + +: +role: prerequisite +location: https://git.codesynthesis.com/cli/cli.git##HEAD diff --git a/version b/version deleted file mode 100644 index a60117f..0000000 --- a/version +++ /dev/null @@ -1 +0,0 @@ -2.5.0-b.20 diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..6bc2f39 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +2.5.0-b.26 |