aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE2
-rw-r--r--build/root.build10
-rw-r--r--buildfile4
-rw-r--r--makefile4
-rw-r--r--manifest9
-rw-r--r--odb/sqlite/buildfile102
-rw-r--r--odb/sqlite/connection-factory.cxx152
-rw-r--r--odb/sqlite/connection-factory.hxx69
-rw-r--r--odb/sqlite/connection.cxx109
-rw-r--r--odb/sqlite/connection.hxx142
-rw-r--r--odb/sqlite/connection.ixx40
-rw-r--r--odb/sqlite/database.cxx35
-rw-r--r--odb/sqlite/database.hxx86
-rw-r--r--odb/sqlite/database.ixx18
-rw-r--r--odb/sqlite/details/.gitignore2
-rw-r--r--odb/sqlite/details/config.hxx12
-rw-r--r--odb/sqlite/details/export.hxx10
-rw-r--r--odb/sqlite/details/pregenerated/odb/sqlite/details/options.cxx1027
-rw-r--r--odb/sqlite/details/pregenerated/odb/sqlite/details/options.hxx530
-rw-r--r--odb/sqlite/details/pregenerated/odb/sqlite/details/options.ixx324
-rw-r--r--odb/sqlite/makefile3
-rw-r--r--odb/sqlite/polymorphic-object-statements.txx2
-rw-r--r--odb/sqlite/prepared-query.cxx12
-rw-r--r--odb/sqlite/prepared-query.hxx3
-rw-r--r--odb/sqlite/statement-cache.cxx39
-rw-r--r--odb/sqlite/statement-cache.hxx54
-rw-r--r--odb/sqlite/statement.cxx38
-rw-r--r--odb/sqlite/stream.hxx4
-rw-r--r--odb/sqlite/transaction-impl.cxx83
-rw-r--r--odb/sqlite/transaction-impl.hxx12
-rw-r--r--odb/sqlite/transaction-impl.ixx14
-rw-r--r--odb/sqlite/transaction.hxx3
-rw-r--r--odb/sqlite/transaction.ixx8
-rw-r--r--odb/sqlite/version.hxx6
-rw-r--r--repositories.manifest4
-rw-r--r--version1
-rw-r--r--version.txt1
37 files changed, 2748 insertions, 226 deletions
diff --git a/LICENSE b/LICENSE
index d106b92..9d92da1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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
diff --git a/buildfile b/buildfile
index 7bd5ae3..4a1b0a7 100644
--- a/buildfile
+++ b/buildfile
@@ -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.
#
diff --git a/makefile b/makefile
index 3a42f20..f28752a 100644
--- a/makefile
+++ b/makefile
@@ -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)
diff --git a/manifest b/manifest
index fadd9a6..fef85a0 100644
--- a/manifest
+++ b/manifest
@@ -1,6 +1,6 @@
: 1
name: libodb-sqlite
-version: 2.5.0-b.20.z
+version: 2.5.0-b.26.z
project: odb
summary: SQLite ODB runtime library
license: GPL-2.0-only
@@ -15,7 +15,8 @@ email: odb-users@codesynthesis.com
build-warning-email: odb-builds@codesynthesis.com
builds: all
requires: c++11
-depends: * build2 >= 0.13.0
-depends: * bpkg >= 0.13.0
+depends: * build2 >= 0.16.0-
+depends: * bpkg >= 0.16.0-
depends: libsqlite3 ^3.6.18
-depends: libodb [2.5.0-b.20.1 2.5.0-b.21)
+depends: libodb [2.5.0-b.26.1 2.5.0-b.27)
+depends: * cli ^1.2.0- ? ($config.libodb_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 b2ec3cd..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 != 20470
+#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 2049970
-#define LIBODB_SQLITE_VERSION_STR "2.5.0-b.20"
+#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 a60117f..0000000
--- a/version
+++ /dev/null
@@ -1 +0,0 @@
-2.5.0-b.20
diff --git a/version.txt b/version.txt
new file mode 100644
index 0000000..6bc2f39
--- /dev/null
+++ b/version.txt
@@ -0,0 +1 @@
+2.5.0-b.26