diff options
37 files changed, 2751 insertions, 229 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..a1975db 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_sqlite.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,9 @@ # file : buildfile # license : GNU GPL v2; see accompanying LICENSE file -./: {*/ -build/ -m4/ -etc/} doc{GPLv2 INSTALL LICENSE NEWS README} manifest +./: {*/ -build/ -m4/ -etc/} \ + doc{INSTALL NEWS README} legal{GPLv2 LICENSE} \ + manifest # Don't install tests or the INSTALL file. # @@ -12,13 +12,13 @@ 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-sqlite-vc8.sln libodb-sqlite-vc9.sln \ libodb-sqlite-vc10.sln libodb-sqlite-vc11.sln libodb-sqlite-vc12.sln \ $(subst $(src_base)/,,$(shell find $(src_base)/etc -type f)) $(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-sqlite.pc.in) @@ -1,10 +1,10 @@ : 1 name: libodb-sqlite -version: 2.5.0-b.18.z +version: 2.5.0-b.26.z project: odb summary: SQLite ODB runtime library -license: GPLv2 -license: proprietary +license: GPL-2.0-only +license: other: proprietary ; Not free/open source. topics: C++, ORM, SQLite, SQL description-file: README changes-file: NEWS @@ -12,10 +12,11 @@ url: https://www.codesynthesis.com/products/odb/ doc-url: https://www.codesynthesis.com/products/odb/doc/manual.xhtml src-url: https://git.codesynthesis.com/cgit/odb/libodb-sqlite/ email: odb-users@codesynthesis.com -build-email: odb-builds@codesynthesis.com +build-warning-email: odb-builds@codesynthesis.com builds: all requires: c++11 -depends: * build2 >= 0.12.0 -depends: * bpkg >= 0.12.0 +depends: * build2 >= 0.16.0- +depends: * bpkg >= 0.16.0- depends: libsqlite3 ^3.6.18 -depends: libodb [2.5.0-b.18.1 2.5.0-b.19) +depends: libodb [2.5.0-b.26.1 2.5.0-b.27) +depends: * cli ^1.2.0- ? ($config.libodb_sqlite.develop) diff --git a/odb/sqlite/buildfile b/odb/sqlite/buildfile index 73455f8..d2a6656 100644 --- a/odb/sqlite/buildfile +++ b/odb/sqlite/buildfile @@ -1,12 +1,15 @@ # file : odb/sqlite/buildfile # license : GNU GPL v2; see accompanying LICENSE file +define cli: file +cli{*}: extension = cli + import int_libs = libodb%lib{odb} import int_libs += libsqlite3%lib{sqlite3} -lib{odb-sqlite}: {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-sqlite}: {hxx ixx txx cxx}{* -version-build2} {hxx}{version-build2} \ + details/{hxx ixx txx cxx}{* -options} \ + details/build2/{h}{*} \ $int_libs # Include the generated version header into the distribution (so that we don't @@ -47,35 +50,76 @@ if $version.pre_release else lib{odb-sqlite}: bin.lib.version = @"-$version.major.$version.minor" -# Generated options parser. +develop = $config.libodb_sqlite.develop + +## Consumption build ($develop == false). +# + +# Use pregenerated versions in the consumption build. +# +lib{odb-sqlite}: 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-sqlite}: 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/sqlite/details \ ---guard-prefix LIBODB_SQLITE_DETAILS --generate-file-scanner \ ---cli-namespace odb::sqlite::details::cli --long-usage --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/sqlite/details/ : false) + + # Symlink the generated code in src for convenience of development. + # + backlink = true } +% +if $develop +{{ + options = --include-with-brackets --include-prefix odb/sqlite/details \ + --guard-prefix LIBODB_SQLITE_DETAILS --generate-file-scanner \ + --cli-namespace odb::sqlite::details::cli --long-usage \ + --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/sqlite/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/sqlite/ subdirectory of, say, /usr/include/ # recreating subdirectories. diff --git a/odb/sqlite/connection-factory.cxx b/odb/sqlite/connection-factory.cxx index 1269db3..794c6dd 100644 --- a/odb/sqlite/connection-factory.cxx +++ b/odb/sqlite/connection-factory.cxx @@ -1,6 +1,8 @@ // file : odb/sqlite/connection-factory.cxx // license : GNU GPL v2; see accompanying LICENSE file +#include <cassert> + #include <odb/details/lock.hxx> #include <odb/sqlite/database.hxx> @@ -17,6 +19,40 @@ namespace odb namespace sqlite { // + // serial_connection_factory + // + + serial_connection_factory:: + ~serial_connection_factory () + { + // We should hold the last reference to the connection. + // + if (connection_ != 0) + assert (connection_.count () == 1); + } + + connection_ptr serial_connection_factory:: + create () + { + return connection_ptr (new (shared) connection (*this)); + } + + connection_ptr serial_connection_factory:: + connect () + { + return connection_; + } + + void serial_connection_factory:: + database (database_type& db) + { + connection_factory::database (db); + + if (!connection_) + connection_ = create (); + } + + // // single_connection_factory // @@ -261,5 +297,121 @@ namespace odb pooled_connection* c (static_cast<pooled_connection*> (arg)); return static_cast<connection_pool_factory&> (c->factory_).release (c); } + + // + // default_attached_connection_factory + // + + void default_attached_connection_factory:: + detach () + { + // Note that this function may be called several times, for example, in + // case of detach_database() failure. + // + if (attached_connection_ != 0) + { + // We should hold the last reference to the attached connection. + // + assert (attached_connection_.count () == 1); + + // While it may seem like a good idea to also invalidate query results + // and reset active statements, if any such result/statement is still + // alive, then there would be bigger problems since it would have a + // dangling reference to the connection. In a way, that's the same + // reason we don't do it in the connection destructor. + + // Remove ourselves from the active object list of the main + // connection. + // + if (next_ != this) // Might have already been done. + list_remove (); + + const string& s (database ().schema ()); + + if (s != "main" && s != "temp") + main_factory ().detach_database (main_connection_, s); + + // Explicitly free the attached connection so that we don't try to + // redo this. + // + attached_connection_.reset (); + } + } + + default_attached_connection_factory:: + ~default_attached_connection_factory () + { + if (attached_connection_ != 0) + { + // This can throw. Ignoring the failure to detach seems like the most + // sensible thing to do here. + // + try{ detach (); } catch (...) {} + } + } + + connection_ptr default_attached_connection_factory:: + connect () + { + return attached_connection_; + } + + void default_attached_connection_factory:: + database (database_type& db) + { + attached_connection_factory::database (db); + + if (!attached_connection_) + { + const string& s (db.schema ()); + + if (s != "main" && s != "temp") + main_factory ().attach_database (main_connection_, db.name (), s); + + attached_connection_.reset ( + new (shared) connection (*this, + s != "main" ? &translate_statement : 0)); + + // Add ourselves to the active object list of the main connection. + // + list_add (); + } + } + + void default_attached_connection_factory:: + clear () + { + attached_connection_->clear (); + } + + void default_attached_connection_factory:: + translate_statement (string& r, + const char* text, + size_t text_size, + connection& conn) + { + r.assign (text, text_size); + + // Things will fall apart if any of the statements we translate use + // "main" as a table alias. So we have this crude check even though it + // means we cannot use "main" for other aliases (e.g., column). + // + assert (r.find ("AS \"main\"") == string::npos); + + const string& s (conn.database ().schema ()); + for (size_t p (0); (p = r.find ("\"main\".", p, 7)) != string::npos; ) + { + // Verify the preceding character. + // + if (p != 0 && r[p - 1] == '.') + { + p += 7; + continue; + } + + r.replace (p + 1, 4, s); + p += s.size () + 3; + } + } } } diff --git a/odb/sqlite/connection-factory.hxx b/odb/sqlite/connection-factory.hxx index b665625..b410997 100644 --- a/odb/sqlite/connection-factory.hxx +++ b/odb/sqlite/connection-factory.hxx @@ -23,7 +23,42 @@ namespace odb { namespace sqlite { - // Share a single connection. + // Share a single connection in a guaranteed serial database access. + // + // For example, a single-threaded application that executes all the + // operations via the database instance without messing with multiple + // connections/transactions would qualify. + // + class LIBODB_SQLITE_EXPORT serial_connection_factory: + public connection_factory + { + public: + serial_connection_factory () {} + + virtual connection_ptr + connect (); + + virtual void + database (database_type&); + + virtual + ~serial_connection_factory (); + + private: + serial_connection_factory (const serial_connection_factory&); + serial_connection_factory& operator= (const serial_connection_factory&); + + protected: + // This function is called when the factory needs to create the + // connection. + // + virtual connection_ptr + create (); + + connection_ptr connection_; + }; + + // Share a single connection potentially between multiple threads. // class LIBODB_SQLITE_EXPORT single_connection_factory: public connection_factory @@ -198,6 +233,38 @@ namespace odb details::mutex mutex_; details::condition cond_; }; + + class LIBODB_SQLITE_EXPORT default_attached_connection_factory: + public attached_connection_factory + { + public: + explicit + default_attached_connection_factory (const connection_ptr& main) + : attached_connection_factory (main) {} + + using attached_connection_factory::database; // Accessor. + + virtual void + database (database_type&); + + virtual connection_ptr + connect (); + + virtual void + detach (); + + // Active object interface. + // + virtual void + clear (); + + virtual + ~default_attached_connection_factory (); + + protected: + static void + translate_statement (std::string&, const char*, std::size_t, connection&); + }; } } diff --git a/odb/sqlite/connection.cxx b/odb/sqlite/connection.cxx index a807949..0445163 100644 --- a/odb/sqlite/connection.cxx +++ b/odb/sqlite/connection.cxx @@ -25,11 +25,16 @@ odb_sqlite_connection_unlock_callback (void**, int); namespace odb { + using namespace details; + namespace sqlite { connection:: - connection (connection_factory& cf, int extra_flags) + connection (connection_factory& cf, + int extra_flags, + statement_translator* st) : odb::connection (cf), + statement_translator_ (st), unlock_cond_ (unlock_mutex_), active_objects_ (0) { @@ -81,9 +86,12 @@ namespace odb } connection:: - connection (connection_factory& cf, sqlite3* handle) + connection (connection_factory& cf, + sqlite3* handle, + statement_translator* st) : odb::connection (cf), handle_ (handle), + statement_translator_ (st), unlock_cond_ (unlock_mutex_), active_objects_ (0) { @@ -105,6 +113,31 @@ namespace odb db.foreign_keys () ? 22 : 23); st.execute (); + // String lengths include '\0', as per the SQLite manual suggestion. + // + begin_.reset (new (shared) generic_statement (*this, "BEGIN", 6)); + commit_.reset (new (shared) generic_statement (*this, "COMMIT", 7)); + rollback_.reset (new (shared) generic_statement (*this, "ROLLBACK", 9)); + + // Create statement cache. + // + statement_cache_.reset (new statement_cache_type (*this)); + } + + connection:: + connection (attached_connection_factory& cf, statement_translator* st) + : odb::connection (cf), + handle_ (0), + statement_translator_ (st), + unlock_cond_ (unlock_mutex_), + active_objects_ (0) + { + // Copy some things over from the main connection. + // + connection& main (*cf.main_connection_); + + tracer_ = main.tracer_; + // Create statement cache. // statement_cache_.reset (new statement_cache_type (*this)); @@ -119,6 +152,44 @@ namespace odb clear_prepared_map (); } + generic_statement& connection:: + begin_statement () + { + return static_cast<generic_statement&> (*begin_); + } + + generic_statement& connection:: + begin_immediate_statement () + { + if (!begin_immediate_) + begin_immediate_.reset ( + new (shared) generic_statement (*this, "BEGIN IMMEDIATE", 16)); + + return static_cast<generic_statement&> (*begin_immediate_); + } + + generic_statement& connection:: + begin_exclusive_statement () + { + if (!begin_exclusive_) + begin_exclusive_.reset ( + new (shared) generic_statement (*this, "BEGIN EXCLUSIVE", 16)); + + return static_cast<generic_statement&> (*begin_exclusive_); + } + + generic_statement& connection:: + commit_statement () + { + return static_cast<generic_statement&> (*commit_); + } + + generic_statement& connection:: + rollback_statement () + { + return static_cast<generic_statement&> (*rollback_); + } + transaction_impl* connection:: begin () { @@ -167,7 +238,7 @@ namespace odb // unlock_notify() returns SQLITE_OK or SQLITE_LOCKED (deadlock). // - int e (sqlite3_unlock_notify (handle_, + int e (sqlite3_unlock_notify (handle (), &odb_sqlite_connection_unlock_callback, this)); if (e == SQLITE_LOCKED) @@ -185,11 +256,21 @@ namespace odb void connection:: clear () { - // The current first active_object will remove itself from the list - // and make the second object (if any) the new first. + invalidate_results (); + + // The current first active_object may remove itself from the list and + // make the second object (if any) the new first. // - while (active_objects_ != 0) - active_objects_->clear (); + for (active_object** pp (&active_objects_); *pp != 0; ) + { + active_object* p (*pp); + p->clear (); + + // Move to the next object if this one decided to stay on the list. + // + if (*pp == p) + pp = &p->next_; + } } // connection_factory @@ -205,6 +286,20 @@ namespace odb odb::connection_factory::db_ = &db; db_ = &db; } + + void connection_factory:: + attach_database (const connection_ptr& conn, + const std::string& name, + const std::string& schema) + { + conn->execute ("ATTACH DATABASE '" + name + "' AS \"" + schema + '"'); + } + + void connection_factory:: + detach_database (const connection_ptr& conn, const std::string& schema) + { + conn->execute ("DETACH DATABASE \"" + schema + '"'); + } } } diff --git a/odb/sqlite/connection.hxx b/odb/sqlite/connection.hxx index 5bc95c6..dbe4494 100644 --- a/odb/sqlite/connection.hxx +++ b/odb/sqlite/connection.hxx @@ -8,6 +8,7 @@ #include <sqlite3.h> +#include <odb/statement.hxx> #include <odb/connection.hxx> #include <odb/details/mutex.hxx> @@ -29,20 +30,22 @@ namespace odb namespace sqlite { class statement_cache; + class generic_statement; class connection_factory; + class attached_connection_factory; class connection; typedef details::shared_ptr<connection> connection_ptr; // SQLite "active object", i.e., an object that needs to be // "cleared" before the transaction can be committed and the - // connection release. These form a doubly-linked list. + // connection released. These form a doubly-linked list. // class LIBODB_SQLITE_EXPORT active_object { public: - // This function should remove the object from the list, since - // it shall no longer be "active". + // This function may remove the object from the list since it may no + // longer be "active". // virtual void clear () = 0; @@ -57,6 +60,8 @@ namespace odb list_remove (); protected: + friend class connection; + // prev_ == 0 means we are the first element. // next_ == 0 means we are the last element. // next_ == this means we are not on the list (prev_ should be 0). @@ -73,15 +78,42 @@ namespace odb typedef sqlite::statement_cache statement_cache_type; typedef sqlite::database database_type; + // Translate the database schema in the statement text (used to + // implement attached databases). If the result is empty, then no + // translation is required and the original text should be used as is. + // + typedef void (statement_translator) (std::string& result, + const char* text, + std::size_t text_size, + connection&); virtual ~connection (); - connection (connection_factory&, int extra_flags = 0); - connection (connection_factory&, sqlite3* handle); + connection (connection_factory&, + int extra_flags = 0, + statement_translator* = 0); + + connection (connection_factory&, + sqlite3* handle, + statement_translator* = 0); + + // Create an attached connection (see the attached database constructor + // for details). + // + connection (attached_connection_factory&, statement_translator*); database_type& database (); + // Return the main connection of an attached connection. If this + // connection is main, return itself. + // + connection& + main_connection (); + + static connection_ptr + main_connection (const connection_ptr&); + public: virtual transaction_impl* begin (); @@ -138,10 +170,7 @@ namespace odb public: sqlite3* - handle () - { - return handle_; - } + handle (); statement_cache_type& statement_cache () @@ -156,11 +185,36 @@ namespace odb wait (); public: - // Reset active statements. + // Reset active statements. Also invalidates query results by first + // calling invalidate_results(). // void clear (); + public: + // Note: only available on main connection. + // + generic_statement& + begin_statement (); + + generic_statement& + begin_immediate_statement (); + + generic_statement& + begin_exclusive_statement (); + + generic_statement& + commit_statement (); + + generic_statement& + rollback_statement (); + + protected: + friend class attached_connection_factory; + + connection_factory& + factory (); + private: connection (const connection&); connection& operator= (const connection&); @@ -170,13 +224,27 @@ namespace odb init (); private: + // Note that we use NULL handle as an indication of an attached + // connection. + // auto_handle<sqlite3> handle_; + statement_translator* statement_translator_; + // Keep statement_cache_ after handle_ so that it is destroyed before // the connection is closed. // details::unique_ptr<statement_cache_type> statement_cache_; + // Note: using odb::statement in order to break the connection-statement + // dependency cycle. + // + details::shared_ptr<odb::statement> begin_; + details::shared_ptr<odb::statement> begin_immediate_; + details::shared_ptr<odb::statement> begin_exclusive_; + details::shared_ptr<odb::statement> commit_; + details::shared_ptr<odb::statement> rollback_; + // Unlock notification machinery. // private: @@ -188,6 +256,7 @@ namespace odb connection_unlock_callback (void**, int); private: + friend class statement; // statement_translator_ friend class transaction_impl; // invalidate_results() // Linked list of active objects currently associated @@ -218,12 +287,65 @@ namespace odb connection_factory (): db_ (0) {} + // Attach/detach additional databases. Connection is one of the main + // connections created by this factory. Note: not called for "main" and + // "temp" schemas. + // + // The default implementations simply execute the ATTACH DATABASE and + // DETACH DATABASE SQLite statements. + // + virtual void + attach_database (const connection_ptr&, + const std::string& name, + const std::string& schema); + + virtual void + detach_database (const connection_ptr&, const std::string& schema); + // Needed to break the circular connection_factory-database dependency // (odb::connection_factory has the odb::database member). // protected: database_type* db_; }; + + // The call to database() should cause ATTACH DATABASE (or otherwise make + // sure the database is attached). Destruction of the factory should cause + // DETACH DATABASE (or otherwise notice that this factory no longer needs + // the database attached). + // + // Note that attached_connection_factory is an active object that + // registers itself with the main connection in order to get notified on + // transaction finalization. + // + class LIBODB_SQLITE_EXPORT attached_connection_factory: + public connection_factory, + public active_object + { + public: + explicit + attached_connection_factory (const connection_ptr& main) + : active_object (*main), main_connection_ (main) {} + + virtual void + detach () = 0; + + protected: + friend class database; + friend class connection; + friend class transaction_impl; + + connection_factory& + main_factory (); + + // Note that this essentially establishes a "framework" for all the + // attached connection factory implementations: they hold a counted + // reference to the main connection and they maintain a single shared + // attached connection. + // + connection_ptr main_connection_; + connection_ptr attached_connection_; + }; } } diff --git a/odb/sqlite/connection.ixx b/odb/sqlite/connection.ixx index c1bf77a..094fd52 100644 --- a/odb/sqlite/connection.ixx +++ b/odb/sqlite/connection.ixx @@ -5,7 +5,7 @@ namespace odb { namespace sqlite { - // active_objects + // active_object // inline void active_object:: list_add () @@ -37,6 +37,36 @@ namespace odb return static_cast<connection_factory&> (factory_).database (); } + inline connection& connection:: + main_connection () + { + return handle_ != 0 + ? *this + : *static_cast<attached_connection_factory&> (factory_).main_connection_; + } + + inline connection_ptr connection:: + main_connection (const connection_ptr& c) + { + return c->handle_ != 0 + ? c + : static_cast<attached_connection_factory&> (c->factory_).main_connection_; + } + + inline sqlite3* connection:: + handle () + { + return handle_ != 0 + ? handle_ + : static_cast<attached_connection_factory&> (factory_).main_connection_->handle_; + } + + inline connection_factory& connection:: + factory () + { + return static_cast<connection_factory&> (factory_); + } + template <typename T> inline prepared_query<T> connection:: prepare_query (const char* n, const char* q) @@ -66,5 +96,13 @@ namespace odb // return prepare_query<T> (n, sqlite::query_base (q)); } + + // attached_connection_factory + // + inline connection_factory& attached_connection_factory:: + main_factory () + { + return main_connection_->factory (); + } } } diff --git a/odb/sqlite/database.cxx b/odb/sqlite/database.cxx index 96fab8d..a7cf098 100644 --- a/odb/sqlite/database.cxx +++ b/odb/sqlite/database.cxx @@ -151,8 +151,35 @@ namespace odb factory_->database (*this); } + database:: + database (const connection_ptr& conn, + const string& name, + const string& schema, + transfer_ptr<attached_connection_factory> factory) + : odb::database (id_sqlite), + name_ (name), + schema_ (schema), + flags_ (0), + factory_ (factory.transfer ()) + { + assert (!schema_.empty ()); + + // Copy some things over from the connection's database. + // + database& db (conn->database ()); + + tracer_ = db.tracer_; + foreign_keys_ = db.foreign_keys_; + + if (!factory_) + factory_.reset (new default_attached_connection_factory ( + connection::main_connection (conn))); + + factory_->database (*this); + } + void database:: - print_usage (std::ostream& os) + print_usage (ostream& os) { details::options::print_usage (os); } @@ -196,7 +223,7 @@ namespace odb else if (!schema_version_table_.empty ()) text += schema_version_table_; // Already quoted. else - text += "\"schema_version\""; + text += "\"main\".\"schema_version\""; text += " WHERE \"name\" = ?"; @@ -225,7 +252,9 @@ namespace odb cp = factory_->connect (); sqlite::connection& c ( - cp != 0 ? *cp : transaction::current ().connection ()); + cp != 0 + ? *cp + : transaction::current ().connection (const_cast<database&> (*this))); try { diff --git a/odb/sqlite/database.hxx b/odb/sqlite/database.hxx index e4c9e78..e1e62cc 100644 --- a/odb/sqlite/database.hxx +++ b/odb/sqlite/database.hxx @@ -83,8 +83,78 @@ namespace odb details::transfer_ptr<connection_factory> = details::transfer_ptr<connection_factory> ()); + // Attach to the specified connection a database with the specified name + // as the specified schema. Good understanding of SQLite ATTACH/DETACH + // DATABASE statements semantics and ODB connection management is + // strongly recommended when using this mechanism. + // + // The resulting database instance is referred to as an "attached + // database" and the connection it returns as an "attached connection" + // (which is just a proxy for the main connection). Database operations + // executed on the attached database or attached connection are + // automatically translated to refer to the specified schema rather than + // "main". For uniformity attached databases can also be created for the + // pre-attached "main" and "temp" schemas (in this case name can be + // anything). + // + // The automatic translation of the statements relies on their text + // having references to top-level database entities (tables, indexes, + // etc) qualified with the "main" schema. To achieve this, compile your + // headers with `--schema main` and, if using schema migration, with + // `--schema-version-table main.schema_version`. You must also not use + // "main" as an object/table alias in views of native statements. For + // optimal translation performance use 4-character schema names. + // + // The main connection and attached to it databases and connections are + // all meant to be used within the same thread. In particular, the + // attached database holds a counted reference to the main connection + // which means the connection will not be released until all the + // attached to this connection databases are destroyed. + // + // Note that in this model the attached databases are attached to the + // main connection, not to the (main) database, which mimics the + // underlying semantics of SQLite. An alternative model would have been + // to notionally attach the databases to the main database and under the + // hood automatically attach them to each returned connection. While + // this may seem like a more convenient model in some cases, it is also + // less flexible: the current model allows attaching a different set of + // databases to different connections, attaching them on demand as the + // transaction progresses, etc. Also, the more convenient model can be + // implemented on top of this model by deriving an application-specific + // database class and/or providing custom connection factories. + // + // Note that unless the name is a URI with appropriate mode, it is + // opened with the SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE flags. So if + // you want just SQLITE_OPEN_READWRITE, then you will need to verify its + // existence manually prior to calling this constructor. + // + // Note that attaching/detaching databases withing a transaction is only + // supported since SQLite 3.21.0. + // + database (const connection_ptr&, + const std::string& name, + const std::string& schema, + details::transfer_ptr<attached_connection_factory> = + details::transfer_ptr<attached_connection_factory> ()); + + // The database is automatically detached on destruction but a failure + // to detach is ignored. To detect such a failure perform explicit + // detach. For uniformity detaching a main database is a no-op. + // + void + detach (); + + // Return the main database of an attached database. If this database + // is main, return itself. + // + database& + main_database (); + // Move-constructible but not move-assignable. // + // Note: noexcept is not specified since odb::database(odb::database&&) + // can throw. + // #ifdef ODB_CXX11 database (database&&); #endif @@ -99,6 +169,15 @@ namespace odb return name_; } + // Schema name under which this database was attached or empty for the + // main database. + // + const std::string& + schema () const + { + return schema_; + } + int flags () const { @@ -463,12 +542,19 @@ namespace odb connection_ (); private: + friend class transaction_impl; // factory_ + // Note: remember to update move ctor if adding any new members. // std::string name_; + std::string schema_; int flags_; bool foreign_keys_; std::string vfs_; + + // Note: keep last so that all other database members are still valid + // during factory's destruction. + // details::unique_ptr<connection_factory> factory_; }; } diff --git a/odb/sqlite/database.ixx b/odb/sqlite/database.ixx index c72d2f9..e906a39 100644 --- a/odb/sqlite/database.ixx +++ b/odb/sqlite/database.ixx @@ -14,6 +14,7 @@ namespace odb database (database&& db) // Has to be inline. : odb::database (std::move (db)), name_ (std::move (db.name_)), + schema_ (std::move (db.schema_)), flags_ (db.flags_), foreign_keys_ (db.foreign_keys_), vfs_ (std::move (db.vfs_)), @@ -23,6 +24,21 @@ namespace odb } #endif + inline void database:: + detach () + { + if (!schema_.empty ()) + static_cast<attached_connection_factory&> (*factory_).detach (); + } + + inline database& database:: + main_database () + { + return schema_.empty () + ? *this + : static_cast<attached_connection_factory&> (*factory_).main_connection_->database (); + } + inline connection_ptr database:: connection () { @@ -590,7 +606,7 @@ namespace odb { // Throws if not in transaction. // - sqlite::connection& c (transaction::current ().connection ()); + sqlite::connection& c (transaction::current ().connection (*this)); return c.prepare_query<T> (n, q); } diff --git a/odb/sqlite/details/.gitignore b/odb/sqlite/details/.gitignore index c6e608b..b298f89 100644 --- a/odb/sqlite/details/.gitignore +++ b/odb/sqlite/details/.gitignore @@ -1 +1 @@ -options.?xx +/options.?xx diff --git a/odb/sqlite/details/config.hxx b/odb/sqlite/details/config.hxx index bbf32f6..4f1c5ce 100644 --- a/odb/sqlite/details/config.hxx +++ b/odb/sqlite/details/config.hxx @@ -16,6 +16,18 @@ # endif #endif +// LIBODB_SQLITE_BUILD2 macro can be defined either by the buildfile or by the +// included odb/sqlite/details/config*.h (see above). +// +#ifdef LIBODB_SQLITE_BUILD2 +# include <sqlite3.h> + +# if SQLITE_VERSION_NUMBER >= 3006012 +# define LIBODB_SQLITE_HAVE_UNLOCK_NOTIFY 1 +# endif +# define LIBODB_SQLITE_HAVE_COLUMN_METADATA 1 +#endif + // no post #endif // ODB_SQLITE_DETAILS_CONFIG_HXX diff --git a/odb/sqlite/details/export.hxx b/odb/sqlite/details/export.hxx index 515b891..c0903ae 100644 --- a/odb/sqlite/details/export.hxx +++ b/odb/sqlite/details/export.hxx @@ -6,7 +6,15 @@ #include <odb/pre.hxx> -#include <odb/sqlite/details/config.hxx> +#ifdef ODB_COMPILER +# error libodb-sqlite header included in odb-compiled header +#elif !defined(LIBODB_SQLITE_BUILD2) +# ifdef _MSC_VER +# include <odb/sqlite/details/config-vc.h> +# else +# include <odb/sqlite/details/config.h> +# endif +#endif // Normally we don't export class templates (but do complete specializations), // inline functions, and classes with only inline member functions. Exporting diff --git a/odb/sqlite/details/pregenerated/odb/sqlite/details/options.cxx b/odb/sqlite/details/pregenerated/odb/sqlite/details/options.cxx new file mode 100644 index 0000000..12f4193 --- /dev/null +++ b/odb/sqlite/details/pregenerated/odb/sqlite/details/options.cxx @@ -0,0 +1,1027 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include <odb/sqlite/details/options.hxx> + +#include <map> +#include <set> +#include <string> +#include <vector> +#include <utility> +#include <ostream> +#include <sstream> +#include <cstring> +#include <fstream> + +namespace odb +{ + namespace sqlite + { + 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, 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); + } + }; + + template <> + struct parser<bool> + { + static void + parse (bool& x, 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); + } + }; + + template <> + struct parser<std::string> + { + static void + parse (std::string& x, scanner& s) + { + const char* o (s.next ()); + + if (s.more ()) + x = s.next (); + else + throw missing_value (o); + } + }; + + template <typename X> + struct parser<std::pair<X, std::size_t> > + { + static void + parse (std::pair<X, std::size_t>& x, scanner& s) + { + x.second = s.position (); + parser<X>::parse (x.first, s); + } + }; + + template <typename X> + struct parser<std::vector<X> > + { + static void + parse (std::vector<X>& c, scanner& s) + { + X x; + parser<X>::parse (x, s); + c.push_back (x); + } + }; + + template <typename X, typename C> + struct parser<std::set<X, C> > + { + static void + parse (std::set<X, C>& c, scanner& s) + { + X x; + parser<X>::parse (x, s); + c.insert (x); + } + }; + + template <typename K, typename V, typename C> + struct parser<std::map<K, V, C> > + { + static void + parse (std::map<K, V, C>& m, 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 + }; + + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<K>::parse (k, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<V>::parse (v, s); + } + + m[k] = v; + } + else + throw missing_value (o); + } + }; + + template <typename K, typename V, typename C> + struct parser<std::multimap<K, V, C> > + { + static void + parse (std::multimap<K, V, C>& m, 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 + }; + + if (!kstr.empty ()) + { + av[1] = const_cast<char*> (kstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<K>::parse (k, s); + } + + if (!vstr.empty ()) + { + av[1] = const_cast<char*> (vstr.c_str ()); + argv_scanner s (0, ac, av, false, pos); + parser<V>::parse (v, s); + } + + m.insert (typename std::multimap<K, V, C>::value_type (k, v)); + } + else + throw missing_value (o); + } + }; + + 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; + } + } + } + } +} + +#include <map> + +namespace odb +{ + namespace sqlite + { + namespace details + { + // options + // + + options:: + options () + : database_ (), + create_ (), + read_only_ (), + options_file_ () + { + } + + options:: + options (int& argc, + char** argv, + bool erase, + ::odb::sqlite::details::cli::unknown_mode opt, + ::odb::sqlite::details::cli::unknown_mode arg) + : database_ (), + create_ (), + read_only_ (), + options_file_ () + { + ::odb::sqlite::details::cli::argv_scanner s (argc, argv, erase); + _parse (s, opt, arg); + } + + options:: + options (int start, + int& argc, + char** argv, + bool erase, + ::odb::sqlite::details::cli::unknown_mode opt, + ::odb::sqlite::details::cli::unknown_mode arg) + : database_ (), + create_ (), + read_only_ (), + options_file_ () + { + ::odb::sqlite::details::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + } + + options:: + options (int& argc, + char** argv, + int& end, + bool erase, + ::odb::sqlite::details::cli::unknown_mode opt, + ::odb::sqlite::details::cli::unknown_mode arg) + : database_ (), + create_ (), + read_only_ (), + options_file_ () + { + ::odb::sqlite::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::sqlite::details::cli::unknown_mode opt, + ::odb::sqlite::details::cli::unknown_mode arg) + : database_ (), + create_ (), + read_only_ (), + options_file_ () + { + ::odb::sqlite::details::cli::argv_scanner s (start, argc, argv, erase); + _parse (s, opt, arg); + end = s.end (); + } + + options:: + options (::odb::sqlite::details::cli::scanner& s, + ::odb::sqlite::details::cli::unknown_mode opt, + ::odb::sqlite::details::cli::unknown_mode arg) + : database_ (), + create_ (), + read_only_ (), + options_file_ () + { + _parse (s, opt, arg); + } + + ::odb::sqlite::details::cli::usage_para options:: + print_usage (::std::ostream& os, ::odb::sqlite::details::cli::usage_para p) + { + CLI_POTENTIALLY_UNUSED (os); + + if (p != ::odb::sqlite::details::cli::usage_para::none) + os << ::std::endl; + + os << "--database <filename> SQLite database file name. If the database file is not" << ::std::endl + << " specified then a private, temporary on-disk database will" << ::std::endl + << " be created. Use the :memory: special name to create a" << ::std::endl + << " private, temporary in-memory database." << ::std::endl; + + os << std::endl + << "--create Create the SQLite database if it does not already exist." << ::std::endl + << " By default opening the database fails if it does not" << ::std::endl + << " already exist." << ::std::endl; + + os << std::endl + << "--read-only Open the SQLite database in read-only mode. By default" << ::std::endl + << " the database is opened for reading and writing if" << ::std::endl + << " possible, or reading only if the file is write-protected" << ::std::endl + << " by the operating system." << ::std::endl; + + os << std::endl + << "--options-file <file> Read additional options from <file>. Each option should" << ::std::endl + << " appear on a separate line optionally followed by space or" << ::std::endl + << " equal sign (=) and an option value. Empty lines and lines" << ::std::endl + << " starting with # are ignored." << ::std::endl; + + p = ::odb::sqlite::details::cli::usage_para::option; + + return p; + } + + typedef + std::map<std::string, void (*) (options&, ::odb::sqlite::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_["--database"] = + &::odb::sqlite::details::cli::thunk< options, std::string, &options::database_ >; + _cli_options_map_["--create"] = + &::odb::sqlite::details::cli::thunk< options, &options::create_ >; + _cli_options_map_["--read-only"] = + &::odb::sqlite::details::cli::thunk< options, &options::read_only_ >; + _cli_options_map_["--options-file"] = + &::odb::sqlite::details::cli::thunk< options, std::string, &options::options_file_ >; + } + }; + + static _cli_options_map_init _cli_options_map_init_; + + bool options:: + _parse (const char* o, ::odb::sqlite::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::sqlite::details::cli::scanner& s, + ::odb::sqlite::details::cli::unknown_mode opt_mode, + ::odb::sqlite::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::sqlite::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::sqlite::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::sqlite::details::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::odb::sqlite::details::cli::unknown_mode::stop: + { + break; + } + case ::odb::sqlite::details::cli::unknown_mode::fail: + { + throw ::odb::sqlite::details::cli::unknown_option (o); + } + } + + break; + } + } + + switch (arg_mode) + { + case ::odb::sqlite::details::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::odb::sqlite::details::cli::unknown_mode::stop: + { + break; + } + case ::odb::sqlite::details::cli::unknown_mode::fail: + { + throw ::odb::sqlite::details::cli::unknown_argument (o); + } + } + + break; + } + + return r; + } + } + } +} + +// Begin epilogue. +// +// +// End epilogue. + diff --git a/odb/sqlite/details/pregenerated/odb/sqlite/details/options.hxx b/odb/sqlite/details/pregenerated/odb/sqlite/details/options.hxx new file mode 100644 index 0000000..abc4b3f --- /dev/null +++ b/odb/sqlite/details/pregenerated/odb/sqlite/details/options.hxx @@ -0,0 +1,530 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +#ifndef LIBODB_SQLITE_DETAILS_OPTIONS_HXX +#define LIBODB_SQLITE_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 sqlite + { + 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 sqlite + { + namespace details + { + class options + { + public: + options (); + + options (int& argc, + char** argv, + bool erase = false, + ::odb::sqlite::details::cli::unknown_mode option = ::odb::sqlite::details::cli::unknown_mode::fail, + ::odb::sqlite::details::cli::unknown_mode argument = ::odb::sqlite::details::cli::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + bool erase = false, + ::odb::sqlite::details::cli::unknown_mode option = ::odb::sqlite::details::cli::unknown_mode::fail, + ::odb::sqlite::details::cli::unknown_mode argument = ::odb::sqlite::details::cli::unknown_mode::stop); + + options (int& argc, + char** argv, + int& end, + bool erase = false, + ::odb::sqlite::details::cli::unknown_mode option = ::odb::sqlite::details::cli::unknown_mode::fail, + ::odb::sqlite::details::cli::unknown_mode argument = ::odb::sqlite::details::cli::unknown_mode::stop); + + options (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::odb::sqlite::details::cli::unknown_mode option = ::odb::sqlite::details::cli::unknown_mode::fail, + ::odb::sqlite::details::cli::unknown_mode argument = ::odb::sqlite::details::cli::unknown_mode::stop); + + options (::odb::sqlite::details::cli::scanner&, + ::odb::sqlite::details::cli::unknown_mode option = ::odb::sqlite::details::cli::unknown_mode::fail, + ::odb::sqlite::details::cli::unknown_mode argument = ::odb::sqlite::details::cli::unknown_mode::stop); + + // Option accessors. + // + const std::string& + database () const; + + const bool& + create () const; + + const bool& + read_only () const; + + const std::string& + options_file () const; + + // Print usage information. + // + static ::odb::sqlite::details::cli::usage_para + print_usage (::std::ostream&, + ::odb::sqlite::details::cli::usage_para = ::odb::sqlite::details::cli::usage_para::none); + + // Implementation details. + // + protected: + bool + _parse (const char*, ::odb::sqlite::details::cli::scanner&); + + private: + bool + _parse (::odb::sqlite::details::cli::scanner&, + ::odb::sqlite::details::cli::unknown_mode option, + ::odb::sqlite::details::cli::unknown_mode argument); + + public: + std::string database_; + bool create_; + bool read_only_; + std::string options_file_; + }; + } + } +} + +#include <odb/sqlite/details/options.ixx> + +// Begin epilogue. +// +// +// End epilogue. + +#endif // LIBODB_SQLITE_DETAILS_OPTIONS_HXX diff --git a/odb/sqlite/details/pregenerated/odb/sqlite/details/options.ixx b/odb/sqlite/details/pregenerated/odb/sqlite/details/options.ixx new file mode 100644 index 0000000..54092aa --- /dev/null +++ b/odb/sqlite/details/pregenerated/odb/sqlite/details/options.ixx @@ -0,0 +1,324 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +// Begin prologue. +// +// +// End prologue. + +#include <cassert> + +namespace odb +{ + namespace sqlite + { + 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 sqlite + { + namespace details + { + // options + // + + inline const std::string& options:: + database () const + { + return this->database_; + } + + inline const bool& options:: + create () const + { + return this->create_; + } + + inline const bool& options:: + read_only () const + { + return this->read_only_; + } + + inline const std::string& options:: + options_file () const + { + return this->options_file_; + } + } + } +} + +// Begin epilogue. +// +// +// End epilogue. diff --git a/odb/sqlite/makefile b/odb/sqlite/makefile index 13a3c40..485b9ec 100644 --- a/odb/sqlite/makefile +++ b/odb/sqlite/makefile @@ -16,7 +16,6 @@ query-const-expr.cxx \ simple-object-statements.cxx \ statement.cxx \ statements-base.cxx \ -statement-cache.cxx \ stream.cxx \ tracer.cxx \ traits.cxx \ @@ -112,7 +111,7 @@ libodb-sqlite-vc10.vcxproj libodb-sqlite-vc10.vcxproj.filters \ libodb-sqlite-vc11.vcxproj libodb-sqlite-vc11.vcxproj.filters \ libodb-sqlite-vc12.vcxproj libodb-sqlite-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/sqlite/polymorphic-object-statements.txx b/odb/sqlite/polymorphic-object-statements.txx index f2a1605..6a376d3 100644 --- a/odb/sqlite/polymorphic-object-statements.txx +++ b/odb/sqlite/polymorphic-object-statements.txx @@ -116,7 +116,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/sqlite/prepared-query.cxx b/odb/sqlite/prepared-query.cxx index ed63191..79df0f2 100644 --- a/odb/sqlite/prepared-query.cxx +++ b/odb/sqlite/prepared-query.cxx @@ -3,6 +3,8 @@ #include <odb/sqlite/prepared-query.hxx> +#include <odb/sqlite/connection.hxx> + namespace odb { namespace sqlite @@ -11,5 +13,15 @@ namespace odb ~prepared_query_impl () { } + + bool prepared_query_impl:: + verify_connection (odb::transaction& t) + { + // The transaction can be started using the main database of any of the + // attached databases. So we verify the main connections match. + // + return &static_cast<connection&> (t.connection ()).main_connection () == + &static_cast<connection&> (stmt->connection ()).main_connection (); + } } } diff --git a/odb/sqlite/prepared-query.hxx b/odb/sqlite/prepared-query.hxx index 4938ff3..a8873a5 100644 --- a/odb/sqlite/prepared-query.hxx +++ b/odb/sqlite/prepared-query.hxx @@ -24,6 +24,9 @@ namespace odb prepared_query_impl (odb::connection& c): odb::prepared_query_impl (c) {} + virtual bool + verify_connection (odb::transaction&); + sqlite::query_base query; }; } diff --git a/odb/sqlite/statement-cache.cxx b/odb/sqlite/statement-cache.cxx deleted file mode 100644 index 095dcad..0000000 --- a/odb/sqlite/statement-cache.cxx +++ /dev/null @@ -1,39 +0,0 @@ -// file : odb/sqlite/statement-cache.cxx -// license : GNU GPL v2; see accompanying LICENSE file - -#include <odb/sqlite/statement-cache.hxx> - -namespace odb -{ - using namespace details; - - namespace sqlite - { - statement_cache:: - statement_cache (connection& conn) - : conn_ (conn), - version_seq_ (conn.database ().schema_version_sequence ()), - // String lengths below include '\0', as per SQLite manual - // suggestions. - // - begin_ (new (shared) generic_statement (conn_, "BEGIN", 6)), - commit_ (new (shared) generic_statement (conn_, "COMMIT", 7)), - rollback_ (new (shared) generic_statement (conn_, "ROLLBACK", 9)) - { - } - - void statement_cache:: - begin_immediate_statement_ () const - { - begin_immediate_.reset ( - new (shared) generic_statement (conn_, "BEGIN IMMEDIATE", 16)); - } - - void statement_cache:: - begin_exclusive_statement_ () const - { - begin_exclusive_.reset ( - new (shared) generic_statement (conn_, "BEGIN EXCLUSIVE", 16)); - } - } -} diff --git a/odb/sqlite/statement-cache.hxx b/odb/sqlite/statement-cache.hxx index b42bda4..31ca685 100644 --- a/odb/sqlite/statement-cache.hxx +++ b/odb/sqlite/statement-cache.hxx @@ -29,43 +29,9 @@ namespace odb class LIBODB_SQLITE_EXPORT statement_cache { public: - statement_cache (connection&); - - generic_statement& - begin_statement () const - { - return *begin_; - } - - generic_statement& - begin_immediate_statement () const - { - if (!begin_immediate_) - begin_immediate_statement_ (); - - return *begin_immediate_; - } - - generic_statement& - begin_exclusive_statement () const - { - if (!begin_exclusive_) - begin_exclusive_statement_ (); - - return *begin_exclusive_; - } - - generic_statement& - commit_statement () const - { - return *commit_; - } - - generic_statement& - rollback_statement () const - { - return *rollback_; - } + statement_cache (connection& conn) + : conn_ (conn), + version_seq_ (conn_.database ().schema_version_sequence ()) {} template <typename T> typename object_traits_impl<T, id_sqlite>::statements_type& @@ -76,26 +42,12 @@ namespace odb find_view (); private: - void - begin_immediate_statement_ () const; - - void - begin_exclusive_statement_ () const; - - private: typedef std::map<const std::type_info*, details::shared_ptr<statements_base>, details::type_info_comparator> map; connection& conn_; unsigned int version_seq_; - - details::shared_ptr<generic_statement> begin_; - mutable details::shared_ptr<generic_statement> begin_immediate_; - mutable details::shared_ptr<generic_statement> begin_exclusive_; - details::shared_ptr<generic_statement> commit_; - details::shared_ptr<generic_statement> rollback_; - map map_; }; } diff --git a/odb/sqlite/statement.cxx b/odb/sqlite/statement.cxx index 8f93a9f..b1b0f58 100644 --- a/odb/sqlite/statement.cxx +++ b/odb/sqlite/statement.cxx @@ -27,7 +27,7 @@ namespace odb { { odb::tracer* t; - if ((t = conn_.transaction_tracer ()) || + if ((t = conn_.main_connection ().transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->deallocate (conn_, *this); @@ -55,27 +55,27 @@ namespace odb { active_ = false; - string tmp; + string tmp1; if (proc != 0) { switch (sk) { case statement_select: - process_select (tmp, + process_select (tmp1, text, &proc->bind->buffer, proc->count, sizeof (bind), '"', '"', optimize); break; case statement_insert: - process_insert (tmp, + process_insert (tmp1, text, &proc->bind->buffer, proc->count, sizeof (bind), '?', '$'); break; case statement_update: - process_update (tmp, + process_update (tmp1, text, &proc->bind->buffer, proc->count, sizeof (bind), '?', @@ -86,8 +86,20 @@ namespace odb assert (false); } - text = tmp.c_str (); - text_size = tmp.size (); + text = tmp1.c_str (); + text_size = tmp1.size (); + } + + string tmp2; + if (conn_.statement_translator_ != 0) + { + conn_.statement_translator_ (tmp2, text, text_size, conn_); + + if (!tmp2.empty ()) + { + text = tmp2.c_str (); + text_size = tmp2.size (); + } } #if SQLITE_VERSION_NUMBER < 3005003 @@ -101,7 +113,7 @@ namespace odb { odb::tracer* t; - if ((t = conn_.transaction_tracer ()) || + if ((t = conn_.main_connection ().transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) { @@ -477,7 +489,7 @@ namespace odb { odb::tracer* t; - if ((t = conn_.transaction_tracer ()) || + if ((t = conn_.main_connection ().transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); @@ -618,7 +630,7 @@ namespace odb { odb::tracer* t; - if ((t = conn_.transaction_tracer ()) || + if ((t = conn_.main_connection ().transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); @@ -738,7 +750,7 @@ namespace odb { { odb::tracer* t; - if ((t = conn_.transaction_tracer ()) || + if ((t = conn_.main_connection ().transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); @@ -845,7 +857,7 @@ namespace odb { { odb::tracer* t; - if ((t = conn_.transaction_tracer ()) || + if ((t = conn_.main_connection ().transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); @@ -931,7 +943,7 @@ namespace odb { { odb::tracer* t; - if ((t = conn_.transaction_tracer ()) || + if ((t = conn_.main_connection ().transaction_tracer ()) || (t = conn_.tracer ()) || (t = conn_.database ().tracer ())) t->execute (conn_, *this); diff --git a/odb/sqlite/stream.hxx b/odb/sqlite/stream.hxx index c2d45c0..6ee76cb 100644 --- a/odb/sqlite/stream.hxx +++ b/odb/sqlite/stream.hxx @@ -23,6 +23,10 @@ namespace odb class LIBODB_SQLITE_EXPORT stream: public active_object { public: + // @@ TODO: db is actually what we now (and SQLite in other places) + // call schema (see database::schema(), ATTACH DATABASE). So we + // should probably rename this at some point for consistency. + // stream (const char* db, const char* table, const char* column, diff --git a/odb/sqlite/transaction-impl.cxx b/odb/sqlite/transaction-impl.cxx index 315af70..6485f7e 100644 --- a/odb/sqlite/transaction-impl.cxx +++ b/odb/sqlite/transaction-impl.cxx @@ -6,7 +6,6 @@ #include <odb/sqlite/database.hxx> #include <odb/sqlite/connection.hxx> #include <odb/sqlite/statement.hxx> -#include <odb/sqlite/statement-cache.hxx> #include <odb/sqlite/transaction-impl.hxx> namespace odb @@ -43,23 +42,23 @@ namespace odb odb::transaction_impl::connection_ = connection_.get (); } - statement_cache& sc (connection_->statement_cache ()); + connection_type& mc (connection_->main_connection ()); switch (lock_) { case deferred: { - sc.begin_statement ().execute (); + mc.begin_statement ().execute (); break; } case immediate: { - sc.begin_immediate_statement ().execute (); + mc.begin_immediate_statement ().execute (); break; } case exclusive: { - sc.begin_exclusive_statement ().execute (); + mc.begin_exclusive_statement ().execute (); break; } } @@ -82,7 +81,7 @@ namespace odb // try { - c_->statement_cache ().rollback_statement ().execute (); + c_->rollback_statement ().execute (); } catch (...) {} } @@ -95,21 +94,22 @@ namespace odb void transaction_impl:: commit () { - // Invalidate query results. - // - connection_->invalidate_results (); + connection_type& mc (connection_->main_connection ()); - // Reset active statements. Active statements will prevent COMMIT - // from completing (write statements) or releasing the locks (read - // statements). Normally, a statement is automatically reset on - // completion, however, if an exception is thrown, that may not - // happen. + // Invalidate query results and reset active statements. + // + // Active statements will prevent COMMIT from completing (write + // statements) or releasing the locks (read statements). Normally, a + // statement is automatically reset on completion, however, if an + // exception is thrown, that may not happen. + // + // Note: must be done via the main connection. // - connection_->clear (); + mc.clear (); { - commit_guard cg (*connection_); - connection_->statement_cache ().commit_statement ().execute (); + commit_guard cg (mc); + mc.commit_statement ().execute (); cg.release (); } @@ -121,23 +121,52 @@ namespace odb void transaction_impl:: rollback () { - // Invalidate query results. - // - connection_->invalidate_results (); + connection_type& mc (connection_->main_connection ()); - // Reset active statements. Active statements will prevent ROLLBACK - // from completing (write statements) or releasing the locks (read - // statements). Normally, a statement is automatically reset on - // completion, however, if an exception is thrown, that may not - // happen. + // Invalidate query results and reset active statements (the same + // reasoning as in commit()). + // + // Note: must be done via the main connection. // - connection_->clear (); + mc.clear (); - connection_->statement_cache ().rollback_statement ().execute (); + mc.rollback_statement ().execute (); // Release the connection. // connection_.reset (); } + + odb::connection& transaction_impl:: + connection (odb::database* pdb) + { + if (pdb == 0) + return *connection_; + + // Pick the corresponding connection for main/attached database. + // + database_type& db (static_cast<database_type&> (*pdb)); + + assert (&db.main_database () == + &static_cast<database_type&> (database_).main_database ()); + + return db.schema ().empty () + ? connection_->main_connection () + : *static_cast<attached_connection_factory&> (*db.factory_).attached_connection_; + } + + // Store transaction tracer in the main connection. + // + void transaction_impl:: + tracer (odb::tracer* t) + { + connection_->main_connection ().transaction_tracer_ = t; + } + + odb::tracer* transaction_impl:: + tracer () const + { + return connection_->main_connection ().transaction_tracer_; + } } } diff --git a/odb/sqlite/transaction-impl.hxx b/odb/sqlite/transaction-impl.hxx index 818270a..d1a310b 100644 --- a/odb/sqlite/transaction-impl.hxx +++ b/odb/sqlite/transaction-impl.hxx @@ -45,8 +45,14 @@ namespace odb virtual void rollback (); - connection_type& - connection (); + virtual odb::connection& + connection (odb::database*); + + virtual void + tracer (odb::tracer*); + + virtual odb::tracer* + tracer () const; private: connection_ptr connection_; @@ -55,8 +61,6 @@ namespace odb } } -#include <odb/sqlite/transaction-impl.ixx> - #include <odb/post.hxx> #endif // ODB_SQLITE_TRANSACTION_IMPL_HXX diff --git a/odb/sqlite/transaction-impl.ixx b/odb/sqlite/transaction-impl.ixx deleted file mode 100644 index c49fedb..0000000 --- a/odb/sqlite/transaction-impl.ixx +++ /dev/null @@ -1,14 +0,0 @@ -// file : odb/sqlite/transaction-impl.ixx -// license : GNU GPL v2; see accompanying LICENSE file - -namespace odb -{ - namespace sqlite - { - inline transaction_impl::connection_type& transaction_impl:: - connection () - { - return *connection_; - } - } -} diff --git a/odb/sqlite/transaction.hxx b/odb/sqlite/transaction.hxx index 01df7d4..5e8c141 100644 --- a/odb/sqlite/transaction.hxx +++ b/odb/sqlite/transaction.hxx @@ -40,6 +40,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/sqlite/transaction.ixx b/odb/sqlite/transaction.ixx index 0528cec..de4bd3e 100644 --- a/odb/sqlite/transaction.ixx +++ b/odb/sqlite/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/sqlite/version.hxx b/odb/sqlite/version.hxx index b92f5c3..ad468de 100644 --- a/odb/sqlite/version.hxx +++ b/odb/sqlite/version.hxx @@ -31,15 +31,15 @@ // Check that we have compatible ODB version. // -#if ODB_VERSION != 20468 +#if ODB_VERSION != 20476 # error incompatible odb interface version detected #endif // libodb-sqlite version: odb interface version plus the bugfix // version. // -#define LIBODB_SQLITE_VERSION 2049968 -#define LIBODB_SQLITE_VERSION_STR "2.5.0-b.18" +#define LIBODB_SQLITE_VERSION 2049976 +#define LIBODB_SQLITE_VERSION_STR "2.5.0-b.26" #include <odb/post.hxx> diff --git a/repositories.manifest b/repositories.manifest index b45f391..fe2f8c0 100644 --- a/repositories.manifest +++ b/repositories.manifest @@ -8,3 +8,7 @@ location: https://git.build2.org/packaging/sqlite/sqlite.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 a9b5191..0000000 --- a/version +++ /dev/null @@ -1 +0,0 @@ -2.5.0-b.18 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 |