From 64f60cfa94d730ea5d6a9e7bc22a8d706f73d53c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 29 Apr 2021 09:05:42 +0200 Subject: Add support for SQLite ATTACH DATABASE functionality --- odb/sqlite/connection-factory.cxx | 118 ++++++++++++++++++++++++++++++++++++++ odb/sqlite/connection-factory.hxx | 32 +++++++++++ odb/sqlite/connection.cxx | 45 ++++++++++++++- odb/sqlite/connection.hxx | 109 ++++++++++++++++++++++++++++++++--- odb/sqlite/connection.ixx | 30 ++++++++++ odb/sqlite/database.cxx | 31 +++++++++- odb/sqlite/database.hxx | 79 +++++++++++++++++++++++++ odb/sqlite/database.ixx | 16 ++++++ odb/sqlite/makefile | 1 - odb/sqlite/prepared-query.cxx | 12 ++++ odb/sqlite/prepared-query.hxx | 3 + odb/sqlite/statement.cxx | 38 +++++++----- odb/sqlite/transaction-impl.cxx | 58 ++++++++++++++++--- odb/sqlite/transaction-impl.hxx | 9 +++ 14 files changed, 547 insertions(+), 34 deletions(-) diff --git a/odb/sqlite/connection-factory.cxx b/odb/sqlite/connection-factory.cxx index 1a1f85a..0bbf617 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 + #include #include @@ -295,5 +297,121 @@ namespace odb pooled_connection* c (static_cast (arg)); return static_cast (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, this'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 141fff6..0d01b85 100644 --- a/odb/sqlite/connection-factory.hxx +++ b/odb/sqlite/connection-factory.hxx @@ -233,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 (); + + // Active object interface. + // + virtual void + clear (); + + virtual void + detach (); + + 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 57ce860..bb71274 100644 --- a/odb/sqlite/connection.cxx +++ b/odb/sqlite/connection.cxx @@ -30,8 +30,11 @@ namespace odb 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) { @@ -83,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) { @@ -119,6 +125,25 @@ namespace odb } 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)); + } + + connection:: ~connection () { // Destroy prepared query statements before freeing the connections. @@ -213,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) @@ -261,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 30bb467..f8df9d0 100644 --- a/odb/sqlite/connection.hxx +++ b/odb/sqlite/connection.hxx @@ -32,13 +32,14 @@ namespace odb class statement_cache; class generic_statement; class connection_factory; + class attached_connection_factory; class connection; typedef details::shared_ptr 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 { @@ -77,15 +78,42 @@ namespace odb typedef sqlite::statement_cache statement_cache_type; typedef sqlite::database database_type; + // Translate the database schema in 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 (); @@ -142,10 +170,7 @@ namespace odb public: sqlite3* - handle () - { - return handle_; - } + handle (); statement_cache_type& statement_cache () @@ -167,6 +192,8 @@ namespace odb clear (); public: + // Note: only available on main connection. + // generic_statement& begin_statement (); @@ -182,6 +209,12 @@ namespace odb generic_statement& rollback_statement (); + protected: + friend class attached_connection_factory; + + connection_factory& + factory (); + private: connection (const connection&); connection& operator= (const connection&); @@ -191,8 +224,13 @@ namespace odb init (); private: + // Note that we use NULL handle as an indication of an attached + // connection. + // auto_handle handle_; + statement_translator* statement_translator_; + // Keep statement_cache_ after handle_ so that it is destroyed before // the connection is closed. // @@ -218,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 @@ -248,12 +287,68 @@ 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 () + { + return main_connection_->factory (); + } + + // Note that this essentially establishes a "protocol" 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 69739b7..c0e49b9 100644 --- a/odb/sqlite/connection.ixx +++ b/odb/sqlite/connection.ixx @@ -37,6 +37,36 @@ namespace odb return static_cast (factory_).database (); } + inline connection& connection:: + main_connection () + { + return handle_ != 0 + ? *this + : *static_cast (factory_).main_connection_; + } + + inline connection_ptr connection:: + main_connection (const connection_ptr& c) + { + return c->handle_ != 0 + ? c + : static_cast (c->factory_).main_connection_; + } + + inline sqlite3* connection:: + handle () + { + return handle_ != 0 + ? handle_ + : static_cast (factory_).main_connection_->handle_; + } + + inline connection_factory& connection:: + factory () + { + return static_cast (factory_); + } + template inline prepared_query connection:: prepare_query (const char* n, const char* q) diff --git a/odb/sqlite/database.cxx b/odb/sqlite/database.cxx index c15c633..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 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\" = ?"; diff --git a/odb/sqlite/database.hxx b/odb/sqlite/database.hxx index e4c9e78..478fbdb 100644 --- a/odb/sqlite/database.hxx +++ b/odb/sqlite/database.hxx @@ -83,6 +83,69 @@ namespace odb details::transfer_ptr = details::transfer_ptr ()); + // Attach to the specified connection a database with the specified name + // as the specified schema. Good understanding of SQLite ATTACH/DETACH + // DATABASE semantics and ODB connection management is 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 main connection and attached to it databases and connections are + // all meant to be used withing 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 connecton. 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 this model by deriving an aplication-specific + // database class and/or providing custom connection factories. + // + // Note also 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. + // + // The automatic translation of the statements relies on all the + // statements 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 a table alias. + // + database (const connection_ptr&, + const std::string& name, + const std::string& schema, + details::transfer_ptr = + details::transfer_ptr ()); + + // 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. // #ifdef ODB_CXX11 @@ -99,6 +162,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 +535,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 factory_; }; } diff --git a/odb/sqlite/database.ixx b/odb/sqlite/database.ixx index d5302a1..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 (*factory_).detach (); + } + + inline database& database:: + main_database () + { + return schema_.empty () + ? *this + : static_cast (*factory_).main_connection_->database (); + } + inline connection_ptr database:: connection () { diff --git a/odb/sqlite/makefile b/odb/sqlite/makefile index 13a3c40..cead138 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 \ 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 +#include + 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 (t.connection ()).main_connection () == + &static_cast (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.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/transaction-impl.cxx b/odb/sqlite/transaction-impl.cxx index 721b3fe..106270d 100644 --- a/odb/sqlite/transaction-impl.cxx +++ b/odb/sqlite/transaction-impl.cxx @@ -42,21 +42,23 @@ namespace odb odb::transaction_impl::connection_ = connection_.get (); } + connection_type& mc (connection_->main_connection ()); + switch (lock_) { case deferred: { - connection_->begin_statement ().execute (); + mc.begin_statement ().execute (); break; } case immediate: { - connection_->begin_immediate_statement ().execute (); + mc.begin_immediate_statement ().execute (); break; } case exclusive: { - connection_->begin_exclusive_statement ().execute (); + mc.begin_exclusive_statement ().execute (); break; } } @@ -92,6 +94,8 @@ namespace odb void transaction_impl:: commit () { + connection_type& mc (connection_->main_connection ()); + // Invalidate query results and reset active statements. // // Active statements will prevent COMMIT from completing (write @@ -99,11 +103,13 @@ namespace odb // statement is automatically reset on completion, however, if an // exception is thrown, that may not happen. // - connection_->clear (); + // Note: must be done via the main connection. + // + mc.clear (); { - commit_guard cg (*connection_); - connection_->commit_statement ().execute (); + commit_guard cg (mc); + mc.commit_statement ().execute (); cg.release (); } @@ -115,16 +121,52 @@ namespace odb void transaction_impl:: rollback () { + connection_type& mc (connection_->main_connection ()); + // Invalidate query results and reset active statements (the same // reasoning as in commit()). // - connection_->clear (); + // Note: must be done via the main connection. + // + mc.clear (); - connection_->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 (*pdb)); + + assert (&db.main_database () == + &static_cast (database_).main_database ()); + + return db.schema ().empty () + ? connection_->main_connection () + : *static_cast (*db.factory_).attached_connection_; + } + + // Store transaction tracer in the main database. + // + 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 040bcd7..d1a310b 100644 --- a/odb/sqlite/transaction-impl.hxx +++ b/odb/sqlite/transaction-impl.hxx @@ -45,6 +45,15 @@ namespace odb virtual void rollback (); + virtual odb::connection& + connection (odb::database*); + + virtual void + tracer (odb::tracer*); + + virtual odb::tracer* + tracer () const; + private: connection_ptr connection_; lock lock_; -- cgit v1.1