summaryrefslogtreecommitdiff
path: root/utils/sqlite
diff options
context:
space:
mode:
Diffstat (limited to 'utils/sqlite')
-rw-r--r--utils/sqlite/Kyuafile9
-rw-r--r--utils/sqlite/Makefile.am.inc82
-rw-r--r--utils/sqlite/c_gate.cpp83
-rw-r--r--utils/sqlite/c_gate.hpp74
-rw-r--r--utils/sqlite/c_gate_fwd.hpp45
-rw-r--r--utils/sqlite/c_gate_test.cpp96
-rw-r--r--utils/sqlite/database.cpp328
-rw-r--r--utils/sqlite/database.hpp111
-rw-r--r--utils/sqlite/database_fwd.hpp45
-rw-r--r--utils/sqlite/database_test.cpp287
-rw-r--r--utils/sqlite/exceptions.cpp175
-rw-r--r--utils/sqlite/exceptions.hpp94
-rw-r--r--utils/sqlite/exceptions_test.cpp129
-rw-r--r--utils/sqlite/statement.cpp621
-rw-r--r--utils/sqlite/statement.hpp137
-rw-r--r--utils/sqlite/statement.ipp52
-rw-r--r--utils/sqlite/statement_fwd.hpp57
-rw-r--r--utils/sqlite/statement_test.cpp784
-rw-r--r--utils/sqlite/test_utils.hpp151
-rw-r--r--utils/sqlite/transaction.cpp142
-rw-r--r--utils/sqlite/transaction.hpp69
-rw-r--r--utils/sqlite/transaction_fwd.hpp45
-rw-r--r--utils/sqlite/transaction_test.cpp135
23 files changed, 3751 insertions, 0 deletions
diff --git a/utils/sqlite/Kyuafile b/utils/sqlite/Kyuafile
new file mode 100644
index 0000000000000..47a8b95dac923
--- /dev/null
+++ b/utils/sqlite/Kyuafile
@@ -0,0 +1,9 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="c_gate_test"}
+atf_test_program{name="database_test"}
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="statement_test"}
+atf_test_program{name="transaction_test"}
diff --git a/utils/sqlite/Makefile.am.inc b/utils/sqlite/Makefile.am.inc
new file mode 100644
index 0000000000000..6064a641c14ff
--- /dev/null
+++ b/utils/sqlite/Makefile.am.inc
@@ -0,0 +1,82 @@
+# Copyright 2011 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+UTILS_CFLAGS += $(SQLITE3_CFLAGS)
+UTILS_LIBS += $(SQLITE3_LIBS)
+
+libutils_a_CPPFLAGS += $(SQLITE3_CFLAGS)
+libutils_a_SOURCES += utils/sqlite/c_gate.cpp
+libutils_a_SOURCES += utils/sqlite/c_gate.hpp
+libutils_a_SOURCES += utils/sqlite/c_gate_fwd.hpp
+libutils_a_SOURCES += utils/sqlite/database.cpp
+libutils_a_SOURCES += utils/sqlite/database.hpp
+libutils_a_SOURCES += utils/sqlite/database_fwd.hpp
+libutils_a_SOURCES += utils/sqlite/exceptions.cpp
+libutils_a_SOURCES += utils/sqlite/exceptions.hpp
+libutils_a_SOURCES += utils/sqlite/statement.cpp
+libutils_a_SOURCES += utils/sqlite/statement.hpp
+libutils_a_SOURCES += utils/sqlite/statement_fwd.hpp
+libutils_a_SOURCES += utils/sqlite/statement.ipp
+libutils_a_SOURCES += utils/sqlite/transaction.cpp
+libutils_a_SOURCES += utils/sqlite/transaction.hpp
+libutils_a_SOURCES += utils/sqlite/transaction_fwd.hpp
+
+if WITH_ATF
+tests_utils_sqlitedir = $(pkgtestsdir)/utils/sqlite
+
+tests_utils_sqlite_DATA = utils/sqlite/Kyuafile
+EXTRA_DIST += $(tests_utils_sqlite_DATA)
+
+tests_utils_sqlite_PROGRAMS = utils/sqlite/c_gate_test
+utils_sqlite_c_gate_test_SOURCES = utils/sqlite/c_gate_test.cpp \
+ utils/sqlite/test_utils.hpp
+utils_sqlite_c_gate_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_c_gate_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_sqlite_PROGRAMS += utils/sqlite/database_test
+utils_sqlite_database_test_SOURCES = utils/sqlite/database_test.cpp \
+ utils/sqlite/test_utils.hpp
+utils_sqlite_database_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_database_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_sqlite_PROGRAMS += utils/sqlite/exceptions_test
+utils_sqlite_exceptions_test_SOURCES = utils/sqlite/exceptions_test.cpp
+utils_sqlite_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_sqlite_PROGRAMS += utils/sqlite/statement_test
+utils_sqlite_statement_test_SOURCES = utils/sqlite/statement_test.cpp \
+ utils/sqlite/test_utils.hpp
+utils_sqlite_statement_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_statement_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_sqlite_PROGRAMS += utils/sqlite/transaction_test
+utils_sqlite_transaction_test_SOURCES = utils/sqlite/transaction_test.cpp
+utils_sqlite_transaction_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_transaction_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/sqlite/c_gate.cpp b/utils/sqlite/c_gate.cpp
new file mode 100644
index 0000000000000..e89ac5332ea0f
--- /dev/null
+++ b/utils/sqlite/c_gate.cpp
@@ -0,0 +1,83 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/c_gate.hpp"
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/database.hpp"
+
+namespace sqlite = utils::sqlite;
+
+using utils::none;
+
+
+/// Creates a new gateway to an existing C++ SQLite database.
+///
+/// \param database_ The database to connect to. This object must remain alive
+/// while the newly-constructed database_c_gate is alive.
+sqlite::database_c_gate::database_c_gate(database& database_) :
+ _database(database_)
+{
+}
+
+
+/// Destructor.
+///
+/// Destroying this object has no implications on the life cycle of the SQLite
+/// database. Only the corresponding database object controls when the SQLite 3
+/// database is closed.
+sqlite::database_c_gate::~database_c_gate(void)
+{
+}
+
+
+/// Creates a C++ database for a C SQLite 3 database.
+///
+/// \warning The created database object does NOT own the C database. You must
+/// take care to properly destroy the input sqlite3 when you are done with it to
+/// not leak resources.
+///
+/// \param raw_database The raw database to wrap temporarily.
+///
+/// \return The wrapped database without strong ownership on the input database.
+sqlite::database
+sqlite::database_c_gate::connect(::sqlite3* raw_database)
+{
+ return database(none, static_cast< void* >(raw_database), false);
+}
+
+
+/// Returns the C native SQLite 3 database.
+///
+/// \return A native sqlite3 object holding the SQLite 3 C API database.
+::sqlite3*
+sqlite::database_c_gate::c_database(void)
+{
+ return static_cast< ::sqlite3* >(_database.raw_database());
+}
diff --git a/utils/sqlite/c_gate.hpp b/utils/sqlite/c_gate.hpp
new file mode 100644
index 0000000000000..0ca9d79c4815a
--- /dev/null
+++ b/utils/sqlite/c_gate.hpp
@@ -0,0 +1,74 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file c_gate.hpp
+/// Provides direct access to the C state of the SQLite wrappers.
+
+#if !defined(UTILS_SQLITE_C_GATE_HPP)
+#define UTILS_SQLITE_C_GATE_HPP
+
+#include "utils/sqlite/c_gate_fwd.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include "utils/sqlite/database_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// Gateway to the raw C database of SQLite 3.
+///
+/// This class provides a mechanism to muck with the internals of the database
+/// wrapper class.
+///
+/// \warning The use of this class is discouraged. By using this class, you are
+/// entering the world of unsafety. Anything you do through the objects exposed
+/// through this class will not be controlled by RAII patterns not validated in
+/// any other way, so you can end up corrupting the SQLite 3 state and later get
+/// crashes on otherwise perfectly-valid C++ code.
+class database_c_gate {
+ /// The C++ database that this class wraps.
+ database& _database;
+
+public:
+ database_c_gate(database&);
+ ~database_c_gate(void);
+
+ static database connect(::sqlite3*);
+
+ ::sqlite3* c_database(void);
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_C_GATE_HPP)
diff --git a/utils/sqlite/c_gate_fwd.hpp b/utils/sqlite/c_gate_fwd.hpp
new file mode 100644
index 0000000000000..771efeeff463b
--- /dev/null
+++ b/utils/sqlite/c_gate_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/c_gate_fwd.hpp
+/// Forward declarations for utils/sqlite/c_gate.hpp
+
+#if !defined(UTILS_SQLITE_C_GATE_FWD_HPP)
+#define UTILS_SQLITE_C_GATE_FWD_HPP
+
+namespace utils {
+namespace sqlite {
+
+
+class database_c_gate;
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_C_GATE_FWD_HPP)
diff --git a/utils/sqlite/c_gate_test.cpp b/utils/sqlite/c_gate_test.cpp
new file mode 100644
index 0000000000000..edf46f76c902c
--- /dev/null
+++ b/utils/sqlite/c_gate_test.cpp
@@ -0,0 +1,96 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/c_gate.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/test_utils.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(connect);
+ATF_TEST_CASE_BODY(connect)
+{
+ ::sqlite3* raw_db;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2(":memory:", &raw_db,
+ SQLITE_OPEN_READWRITE, NULL));
+ {
+ sqlite::database database = sqlite::database_c_gate::connect(raw_db);
+ create_test_table(raw(database));
+ }
+ // If the wrapper object has closed the SQLite 3 database, we will misbehave
+ // here either by crashing or not finding our test table.
+ verify_test_table(raw_db);
+ ::sqlite3_close(raw_db);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(c_database);
+ATF_TEST_CASE_BODY(c_database)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ create_test_table(raw(db));
+ {
+ sqlite::database_c_gate gate(db);
+ ::sqlite3* raw_db = gate.c_database();
+ verify_test_table(raw_db);
+ }
+}
+
+
+ATF_TEST_CASE(database__db_filename);
+ATF_TEST_CASE_HEAD(database__db_filename)
+{
+ set_md_var("descr", "The current implementation of db_filename() has no "
+ "means to access the filename of a database connected to a raw "
+ "sqlite3 object");
+}
+ATF_TEST_CASE_BODY(database__db_filename)
+{
+ ::sqlite3* raw_db;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2(
+ "test.db", &raw_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL));
+
+ sqlite::database database = sqlite::database_c_gate::connect(raw_db);
+ ATF_REQUIRE(!database.db_filename());
+ ::sqlite3_close(raw_db);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, c_database);
+ ATF_ADD_TEST_CASE(tcs, connect);
+ ATF_ADD_TEST_CASE(tcs, database__db_filename);
+}
diff --git a/utils/sqlite/database.cpp b/utils/sqlite/database.cpp
new file mode 100644
index 0000000000000..41935c3b017d7
--- /dev/null
+++ b/utils/sqlite/database.cpp
@@ -0,0 +1,328 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/database.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include <cstring>
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/optional.ipp"
+#include "utils/sanity.hpp"
+#include "utils/sqlite/exceptions.hpp"
+#include "utils/sqlite/statement.ipp"
+#include "utils/sqlite/transaction.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+using utils::none;
+using utils::optional;
+
+
+/// Internal implementation for sqlite::database.
+struct utils::sqlite::database::impl : utils::noncopyable {
+ /// Path to the database as seen at construction time.
+ optional< fs::path > db_filename;
+
+ /// The SQLite 3 internal database.
+ ::sqlite3* db;
+
+ /// Whether we own the database or not (to decide if we close it).
+ bool owned;
+
+ /// Constructor.
+ ///
+ /// \param db_filename_ The path to the database as seen at construction
+ /// time, if any, or none for in-memory databases. We should use
+ /// sqlite3_db_filename instead, but this function appeared in 3.7.10
+ /// and Ubuntu 12.04 LTS (which we support for Travis CI builds as of
+ /// 2015-07-07) ships with 3.7.9.
+ /// \param db_ The SQLite internal database.
+ /// \param owned_ Whether this object owns the db_ object or not. If it
+ /// does, the internal db_ will be released during destruction.
+ impl(optional< fs::path > db_filename_, ::sqlite3* db_, const bool owned_) :
+ db_filename(db_filename_), db(db_), owned(owned_)
+ {
+ }
+
+ /// Destructor.
+ ///
+ /// It is important to keep this as part of the 'impl' class instead of the
+ /// container class. The 'impl' class is destroyed exactly once (because it
+ /// is managed by a shared_ptr) and thus releasing the resources here is
+ /// OK. However, the container class is potentially released many times,
+ /// which means that we would be double-freeing the internal object and
+ /// reusing invalid data.
+ ~impl(void)
+ {
+ if (owned && db != NULL)
+ close();
+ }
+
+ /// Exception-safe version of sqlite3_open_v2.
+ ///
+ /// \param file The path to the database file to be opened.
+ /// \param flags The flags to be passed to the open routine.
+ ///
+ /// \return The opened database.
+ ///
+ /// \throw std::bad_alloc If there is not enough memory to open the
+ /// database.
+ /// \throw api_error If there is any problem opening the database.
+ static ::sqlite3*
+ safe_open(const char* file, const int flags)
+ {
+ ::sqlite3* db;
+ const int error = ::sqlite3_open_v2(file, &db, flags, NULL);
+ if (error != SQLITE_OK) {
+ if (db == NULL)
+ throw std::bad_alloc();
+ else {
+ sqlite::database error_db(utils::make_optional(fs::path(file)),
+ db, true);
+ throw sqlite::api_error::from_database(error_db,
+ "sqlite3_open_v2");
+ }
+ }
+ INV(db != NULL);
+ return db;
+ }
+
+ /// Shared code for the public close() method.
+ void
+ close(void)
+ {
+ PRE(db != NULL);
+ int error = ::sqlite3_close(db);
+ // For now, let's consider a return of SQLITE_BUSY an error. We should
+ // not be trying to close a busy database in our code. Maybe revisit
+ // this later to raise busy errors as exceptions.
+ PRE(error == SQLITE_OK);
+ db = NULL;
+ }
+};
+
+
+/// Initializes the SQLite database.
+///
+/// You must share the same database object alongside the lifetime of your
+/// SQLite session. As soon as the object is destroyed, the session is
+/// terminated.
+///
+/// \param db_filename_ The path to the database as seen at construction
+/// time, if any, or none for in-memory databases.
+/// \param db_ Raw pointer to the C SQLite 3 object.
+/// \param owned_ Whether this instance will own the pointer or not.
+sqlite::database::database(
+ const utils::optional< utils::fs::path >& db_filename_, void* db_,
+ const bool owned_) :
+ _pimpl(new impl(db_filename_, static_cast< ::sqlite3* >(db_), owned_))
+{
+}
+
+
+/// Destructor for the SQLite 3 database.
+///
+/// Closes the session unless it has already been closed by calling the
+/// close() method. It is recommended to explicitly close the session in the
+/// code.
+sqlite::database::~database(void)
+{
+}
+
+
+/// Opens a memory-based temporary SQLite database.
+///
+/// \return An in-memory database instance.
+///
+/// \throw std::bad_alloc If there is not enough memory to open the database.
+/// \throw api_error If there is any problem opening the database.
+sqlite::database
+sqlite::database::in_memory(void)
+{
+ return database(none, impl::safe_open(":memory:", SQLITE_OPEN_READWRITE),
+ true);
+}
+
+
+/// Opens a named on-disk SQLite database.
+///
+/// \param file The path to the database file to be opened. This does not
+/// accept the values "" and ":memory:"; use temporary() and in_memory()
+/// instead.
+/// \param open_flags The flags to be passed to the open routine.
+///
+/// \return A file-backed database instance.
+///
+/// \throw std::bad_alloc If there is not enough memory to open the database.
+/// \throw api_error If there is any problem opening the database.
+sqlite::database
+sqlite::database::open(const fs::path& file, int open_flags)
+{
+ PRE_MSG(!file.str().empty(), "Use database::temporary() instead");
+ PRE_MSG(file.str() != ":memory:", "Use database::in_memory() instead");
+
+ int flags = 0;
+ if (open_flags & open_readonly) {
+ flags |= SQLITE_OPEN_READONLY;
+ open_flags &= ~open_readonly;
+ }
+ if (open_flags & open_readwrite) {
+ flags |= SQLITE_OPEN_READWRITE;
+ open_flags &= ~open_readwrite;
+ }
+ if (open_flags & open_create) {
+ flags |= SQLITE_OPEN_CREATE;
+ open_flags &= ~open_create;
+ }
+ PRE(open_flags == 0);
+
+ return database(utils::make_optional(file),
+ impl::safe_open(file.c_str(), flags), true);
+}
+
+
+/// Opens an unnamed on-disk SQLite database.
+///
+/// \return A file-backed database instance.
+///
+/// \throw std::bad_alloc If there is not enough memory to open the database.
+/// \throw api_error If there is any problem opening the database.
+sqlite::database
+sqlite::database::temporary(void)
+{
+ return database(none, impl::safe_open("", SQLITE_OPEN_READWRITE), true);
+}
+
+
+/// Gets the internal sqlite3 object.
+///
+/// \return The raw SQLite 3 database. This is returned as a void pointer to
+/// prevent including the sqlite3.h header file from our public interface. The
+/// only way to call this method is by using the c_gate module, and c_gate takes
+/// care of casting this object to the appropriate type.
+void*
+sqlite::database::raw_database(void)
+{
+ return _pimpl->db;
+}
+
+
+/// Terminates the connection to the database.
+///
+/// It is recommended to call this instead of relying on the destructor to do
+/// the cleanup, but it is not a requirement to use close().
+///
+/// \pre close() has not yet been called.
+void
+sqlite::database::close(void)
+{
+ _pimpl->close();
+}
+
+
+/// Returns the path to the connected database.
+///
+/// It is OK to call this function on a live database object, even after close()
+/// has been called. The returned value is consistent at all times.
+///
+/// \return The path to the file that matches the connected database or none if
+/// the connection points to a transient database.
+const optional< fs::path >&
+sqlite::database::db_filename(void) const
+{
+ return _pimpl->db_filename;
+}
+
+
+/// Executes an arbitrary SQL string.
+///
+/// As the documentation explains, this is unsafe. The code should really be
+/// preparing statements and executing them step by step. However, it is
+/// perfectly fine to use this function for, e.g. the initial creation of
+/// tables in a database and in tests.
+///
+/// \param sql The SQL commands to be executed.
+///
+/// \throw api_error If there is any problem while processing the SQL.
+void
+sqlite::database::exec(const std::string& sql)
+{
+ const int error = ::sqlite3_exec(_pimpl->db, sql.c_str(), NULL, NULL, NULL);
+ if (error != SQLITE_OK)
+ throw api_error::from_database(*this, "sqlite3_exec");
+}
+
+
+/// Opens a new transaction.
+///
+/// \return An object representing the state of the transaction.
+///
+/// \throw api_error If there is any problem while opening the transaction.
+sqlite::transaction
+sqlite::database::begin_transaction(void)
+{
+ exec("BEGIN TRANSACTION");
+ return transaction(*this);
+}
+
+
+/// Prepares a new statement.
+///
+/// \param sql The SQL statement to prepare.
+///
+/// \return The prepared statement.
+sqlite::statement
+sqlite::database::create_statement(const std::string& sql)
+{
+ LD(F("Creating statement: %s") % sql);
+ sqlite3_stmt* stmt;
+ const int error = ::sqlite3_prepare_v2(_pimpl->db, sql.c_str(),
+ sql.length() + 1, &stmt, NULL);
+ if (error != SQLITE_OK)
+ throw api_error::from_database(*this, "sqlite3_prepare_v2");
+ return statement(*this, static_cast< void* >(stmt));
+}
+
+
+/// Returns the row identifier of the last insert.
+///
+/// \return A row identifier.
+int64_t
+sqlite::database::last_insert_rowid(void)
+{
+ return ::sqlite3_last_insert_rowid(_pimpl->db);
+}
diff --git a/utils/sqlite/database.hpp b/utils/sqlite/database.hpp
new file mode 100644
index 0000000000000..ca91a6a360c69
--- /dev/null
+++ b/utils/sqlite/database.hpp
@@ -0,0 +1,111 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/database.hpp
+/// Wrapper classes and utilities for the SQLite database state.
+///
+/// This module contains thin RAII wrappers around the SQLite 3 structures
+/// representing the database, and lightweight.
+
+#if !defined(UTILS_SQLITE_DATABASE_HPP)
+#define UTILS_SQLITE_DATABASE_HPP
+
+#include "utils/sqlite/database_fwd.hpp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <cstddef>
+#include <memory>
+
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional_fwd.hpp"
+#include "utils/sqlite/c_gate_fwd.hpp"
+#include "utils/sqlite/statement_fwd.hpp"
+#include "utils/sqlite/transaction_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// Constant for the database::open flags: open in read-only mode.
+static const int open_readonly = 1 << 0;
+/// Constant for the database::open flags: open in read-write mode.
+static const int open_readwrite = 1 << 1;
+/// Constant for the database::open flags: create on open.
+static const int open_create = 1 << 2;
+
+
+/// A RAII model for the SQLite 3 database.
+///
+/// This class holds the database of the SQLite 3 interface during its existence
+/// and provides wrappers around several SQLite 3 library functions that operate
+/// on such database.
+///
+/// These wrapper functions differ from the C versions in that they use the
+/// implicit database hold by the class, they use C++ types where appropriate
+/// and they use exceptions to report errors.
+///
+/// The wrappers intend to be as lightweight as possible but, in some
+/// situations, they are pretty complex because of the workarounds needed to
+/// make the SQLite 3 more C++ friendly. We prefer a clean C++ interface over
+/// optimal efficiency, so this is OK.
+class database {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ friend class database_c_gate;
+ database(const utils::optional< utils::fs::path >&, void*, const bool);
+ void* raw_database(void);
+
+public:
+ ~database(void);
+
+ static database in_memory(void);
+ static database open(const fs::path&, int);
+ static database temporary(void);
+ void close(void);
+
+ const utils::optional< utils::fs::path >& db_filename(void) const;
+
+ void exec(const std::string&);
+
+ transaction begin_transaction(void);
+ statement create_statement(const std::string&);
+
+ int64_t last_insert_rowid(void);
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_DATABASE_HPP)
diff --git a/utils/sqlite/database_fwd.hpp b/utils/sqlite/database_fwd.hpp
new file mode 100644
index 0000000000000..209342f159d6b
--- /dev/null
+++ b/utils/sqlite/database_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/database_fwd.hpp
+/// Forward declarations for utils/sqlite/database.hpp
+
+#if !defined(UTILS_SQLITE_DATABASE_FWD_HPP)
+#define UTILS_SQLITE_DATABASE_FWD_HPP
+
+namespace utils {
+namespace sqlite {
+
+
+class database;
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_DATABASE_FWD_HPP)
diff --git a/utils/sqlite/database_test.cpp b/utils/sqlite/database_test.cpp
new file mode 100644
index 0000000000000..70f057b9b793d
--- /dev/null
+++ b/utils/sqlite/database_test.cpp
@@ -0,0 +1,287 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/database.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/statement.ipp"
+#include "utils/sqlite/test_utils.hpp"
+#include "utils/sqlite/transaction.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+using utils::optional;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(in_memory);
+ATF_TEST_CASE_BODY(in_memory)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ create_test_table(raw(db));
+ verify_test_table(raw(db));
+
+ ATF_REQUIRE(!fs::exists(fs::path(":memory:")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open__readonly__ok);
+ATF_TEST_CASE_BODY(open__readonly__ok)
+{
+ {
+ ::sqlite3* db;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2("test.db", &db,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL));
+ create_test_table(db);
+ ::sqlite3_close(db);
+ }
+ {
+ sqlite::database db = sqlite::database::open(fs::path("test.db"),
+ sqlite::open_readonly);
+ verify_test_table(raw(db));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open__readonly__fail);
+ATF_TEST_CASE_BODY(open__readonly__fail)
+{
+ REQUIRE_API_ERROR("sqlite3_open_v2",
+ sqlite::database::open(fs::path("missing.db"), sqlite::open_readonly));
+ ATF_REQUIRE(!fs::exists(fs::path("missing.db")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open__create__ok);
+ATF_TEST_CASE_BODY(open__create__ok)
+{
+ {
+ sqlite::database db = sqlite::database::open(fs::path("test.db"),
+ sqlite::open_readwrite | sqlite::open_create);
+ ATF_REQUIRE(fs::exists(fs::path("test.db")));
+ create_test_table(raw(db));
+ }
+ {
+ ::sqlite3* db;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2("test.db", &db,
+ SQLITE_OPEN_READONLY, NULL));
+ verify_test_table(db);
+ ::sqlite3_close(db);
+ }
+}
+
+
+ATF_TEST_CASE(open__create__fail);
+ATF_TEST_CASE_HEAD(open__create__fail)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(open__create__fail)
+{
+ fs::mkdir(fs::path("protected"), 0555);
+ REQUIRE_API_ERROR("sqlite3_open_v2",
+ sqlite::database::open(fs::path("protected/test.db"),
+ sqlite::open_readwrite | sqlite::open_create));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(temporary);
+ATF_TEST_CASE_BODY(temporary)
+{
+ // We could validate if files go to disk by setting the temp_store_directory
+ // PRAGMA to a subdirectory of pwd, and then ensuring the subdirectory is
+ // not empty. However, there does not seem to be a way to force SQLite to
+ // unconditionally write the temporary database to disk (even with
+ // temp_store = FILE), so this scenary is hard to reproduce.
+ sqlite::database db = sqlite::database::temporary();
+ create_test_table(raw(db));
+ verify_test_table(raw(db));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(close);
+ATF_TEST_CASE_BODY(close)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.close();
+ // The destructor for the database will run now. If it does a second close,
+ // we may crash, so let's see if we don't.
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(copy);
+ATF_TEST_CASE_BODY(copy)
+{
+ sqlite::database db1 = sqlite::database::in_memory();
+ {
+ sqlite::database db2 = sqlite::database::in_memory();
+ create_test_table(raw(db2));
+ db1 = db2;
+ verify_test_table(raw(db1));
+ }
+ // db2 went out of scope. If the destruction is not properly managed, the
+ // memory of db1 may have been invalidated and this would not work.
+ verify_test_table(raw(db1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(db_filename__in_memory);
+ATF_TEST_CASE_BODY(db_filename__in_memory)
+{
+ const sqlite::database db = sqlite::database::in_memory();
+ ATF_REQUIRE(!db.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(db_filename__file);
+ATF_TEST_CASE_BODY(db_filename__file)
+{
+ const sqlite::database db = sqlite::database::open(fs::path("test.db"),
+ sqlite::open_readwrite | sqlite::open_create);
+ ATF_REQUIRE(db.db_filename());
+ ATF_REQUIRE_EQ(fs::path("test.db"), db.db_filename().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(db_filename__temporary);
+ATF_TEST_CASE_BODY(db_filename__temporary)
+{
+ const sqlite::database db = sqlite::database::temporary();
+ ATF_REQUIRE(!db.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(db_filename__ok_after_close);
+ATF_TEST_CASE_BODY(db_filename__ok_after_close)
+{
+ sqlite::database db = sqlite::database::open(fs::path("test.db"),
+ sqlite::open_readwrite | sqlite::open_create);
+ const optional< fs::path > db_filename = db.db_filename();
+ ATF_REQUIRE(db_filename);
+ db.close();
+ ATF_REQUIRE_EQ(db_filename, db.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec__ok);
+ATF_TEST_CASE_BODY(exec__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec(create_test_table_sql);
+ verify_test_table(raw(db));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec__fail);
+ATF_TEST_CASE_BODY(exec__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ REQUIRE_API_ERROR("sqlite3_exec",
+ db.exec("SELECT * FROM test"));
+ REQUIRE_API_ERROR("sqlite3_exec",
+ db.exec("CREATE TABLE test (col INTEGER PRIMARY KEY);"
+ "FOO BAR"));
+ db.exec("SELECT * FROM test");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(create_statement__ok);
+ATF_TEST_CASE_BODY(create_statement__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3");
+ // Statement testing happens in statement_test. We are only interested here
+ // in ensuring that the API call exists and runs.
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(begin_transaction);
+ATF_TEST_CASE_BODY(begin_transaction)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::transaction stmt = db.begin_transaction();
+ // Transaction testing happens in transaction_test. We are only interested
+ // here in ensuring that the API call exists and runs.
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(create_statement__fail);
+ATF_TEST_CASE_BODY(create_statement__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ REQUIRE_API_ERROR("sqlite3_prepare_v2",
+ db.create_statement("SELECT * FROM missing"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(last_insert_rowid);
+ATF_TEST_CASE_BODY(last_insert_rowid)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE test (a INTEGER PRIMARY KEY, b INTEGER)");
+ db.exec("INSERT INTO test VALUES (723, 5)");
+ ATF_REQUIRE_EQ(723, db.last_insert_rowid());
+ db.exec("INSERT INTO test VALUES (145, 20)");
+ ATF_REQUIRE_EQ(145, db.last_insert_rowid());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, in_memory);
+
+ ATF_ADD_TEST_CASE(tcs, open__readonly__ok);
+ ATF_ADD_TEST_CASE(tcs, open__readonly__fail);
+ ATF_ADD_TEST_CASE(tcs, open__create__ok);
+ ATF_ADD_TEST_CASE(tcs, open__create__fail);
+
+ ATF_ADD_TEST_CASE(tcs, temporary);
+
+ ATF_ADD_TEST_CASE(tcs, close);
+
+ ATF_ADD_TEST_CASE(tcs, copy);
+
+ ATF_ADD_TEST_CASE(tcs, db_filename__in_memory);
+ ATF_ADD_TEST_CASE(tcs, db_filename__file);
+ ATF_ADD_TEST_CASE(tcs, db_filename__temporary);
+ ATF_ADD_TEST_CASE(tcs, db_filename__ok_after_close);
+
+ ATF_ADD_TEST_CASE(tcs, exec__ok);
+ ATF_ADD_TEST_CASE(tcs, exec__fail);
+
+ ATF_ADD_TEST_CASE(tcs, begin_transaction);
+
+ ATF_ADD_TEST_CASE(tcs, create_statement__ok);
+ ATF_ADD_TEST_CASE(tcs, create_statement__fail);
+
+ ATF_ADD_TEST_CASE(tcs, last_insert_rowid);
+}
diff --git a/utils/sqlite/exceptions.cpp b/utils/sqlite/exceptions.cpp
new file mode 100644
index 0000000000000..cc2d42cab16c9
--- /dev/null
+++ b/utils/sqlite/exceptions.cpp
@@ -0,0 +1,175 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/exceptions.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include <string>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/c_gate.hpp"
+#include "utils/sqlite/database.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+using utils::optional;
+
+
+namespace {
+
+
+/// Formats the database filename returned by sqlite for user consumption.
+///
+/// \param db_filename An optional database filename.
+///
+/// \return A string describing the filename.
+static std::string
+format_db_filename(const optional< fs::path >& db_filename)
+{
+ if (db_filename)
+ return db_filename.get().str();
+ else
+ return "in-memory or temporary";
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param db_filename_ Database filename as returned by database::db_filename()
+/// for error reporting purposes.
+/// \param message The plain-text error message.
+sqlite::error::error(const optional< fs::path >& db_filename_,
+ const std::string& message) :
+ std::runtime_error(F("%s (sqlite db: %s)") % message %
+ format_db_filename(db_filename_)),
+ _db_filename(db_filename_)
+{
+}
+
+
+/// Destructor for the error.
+sqlite::error::~error(void) throw()
+{
+}
+
+
+/// Returns the path to the database that raised this error.
+///
+/// \return A database filename as returned by database::db_filename().
+const optional< fs::path >&
+sqlite::error::db_filename(void) const
+{
+ return _db_filename;
+}
+
+
+/// Constructs a new error.
+///
+/// \param db_filename_ Database filename as returned by database::db_filename()
+/// for error reporting purposes.
+/// \param api_function_ The name of the API function that caused the error.
+/// \param message_ The plain-text error message provided by SQLite.
+sqlite::api_error::api_error(const optional< fs::path >& db_filename_,
+ const std::string& api_function_,
+ const std::string& message_) :
+ error(db_filename_, F("%s (sqlite op: %s)") % message_ % api_function_),
+ _api_function(api_function_)
+{
+}
+
+
+/// Destructor for the error.
+sqlite::api_error::~api_error(void) throw()
+{
+}
+
+
+/// Constructs a new api_error with the message in the SQLite database.
+///
+/// \param database_ The SQLite database.
+/// \param api_function_ The name of the SQLite C API function that caused the
+/// error.
+///
+/// \return A new api_error with the retrieved message.
+sqlite::api_error
+sqlite::api_error::from_database(database& database_,
+ const std::string& api_function_)
+{
+ ::sqlite3* c_db = database_c_gate(database_).c_database();
+ return api_error(database_.db_filename(), api_function_,
+ ::sqlite3_errmsg(c_db));
+}
+
+
+/// Gets the name of the SQlite C API function that caused this error.
+///
+/// \return The name of the function.
+const std::string&
+sqlite::api_error::api_function(void) const
+{
+ return _api_function;
+}
+
+
+/// Constructs a new error.
+///
+/// \param db_filename_ Database filename as returned by database::db_filename()
+/// for error reporting purposes.
+/// \param name_ The name of the unknown column.
+sqlite::invalid_column_error::invalid_column_error(
+ const optional< fs::path >& db_filename_,
+ const std::string& name_) :
+ error(db_filename_, F("Unknown column '%s'") % name_),
+ _column_name(name_)
+{
+}
+
+
+/// Destructor for the error.
+sqlite::invalid_column_error::~invalid_column_error(void) throw()
+{
+}
+
+
+/// Gets the name of the column that could not be found.
+///
+/// \return The name of the column requested by the user.
+const std::string&
+sqlite::invalid_column_error::column_name(void) const
+{
+ return _column_name;
+}
diff --git a/utils/sqlite/exceptions.hpp b/utils/sqlite/exceptions.hpp
new file mode 100644
index 0000000000000..a9450fce5c338
--- /dev/null
+++ b/utils/sqlite/exceptions.hpp
@@ -0,0 +1,94 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/exceptions.hpp
+/// Exception types raised by the sqlite module.
+
+#if !defined(UTILS_SQLITE_EXCEPTIONS_HPP)
+#define UTILS_SQLITE_EXCEPTIONS_HPP
+
+#include <stdexcept>
+#include <string>
+
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional.hpp"
+#include "utils/sqlite/database_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// Base exception for sqlite errors.
+class error : public std::runtime_error {
+ /// Path to the database that raised this error.
+ utils::optional< utils::fs::path > _db_filename;
+
+public:
+ explicit error(const utils::optional< utils::fs::path >&,
+ const std::string&);
+ virtual ~error(void) throw();
+
+ const utils::optional< utils::fs::path >& db_filename(void) const;
+};
+
+
+/// Exception for errors raised by the SQLite 3 API library.
+class api_error : public error {
+ /// The name of the SQLite 3 C API function that caused this error.
+ std::string _api_function;
+
+public:
+ explicit api_error(const utils::optional< utils::fs::path >&,
+ const std::string&, const std::string&);
+ virtual ~api_error(void) throw();
+
+ static api_error from_database(database&, const std::string&);
+
+ const std::string& api_function(void) const;
+};
+
+
+/// The caller requested a non-existent column name.
+class invalid_column_error : public error {
+ /// The name of the invalid column.
+ std::string _column_name;
+
+public:
+ explicit invalid_column_error(const utils::optional< utils::fs::path >&,
+ const std::string&);
+ virtual ~invalid_column_error(void) throw();
+
+ const std::string& column_name(void) const;
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+
+#endif // !defined(UTILS_SQLITE_EXCEPTIONS_HPP)
diff --git a/utils/sqlite/exceptions_test.cpp b/utils/sqlite/exceptions_test.cpp
new file mode 100644
index 0000000000000..d9e81038cc2fd
--- /dev/null
+++ b/utils/sqlite/exceptions_test.cpp
@@ -0,0 +1,129 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/exceptions.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include <cstring>
+#include <string>
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/c_gate.hpp"
+#include "utils/sqlite/database.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+using utils::none;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error__no_filename);
+ATF_TEST_CASE_BODY(error__no_filename)
+{
+ const sqlite::database db = sqlite::database::in_memory();
+ const sqlite::error e(db.db_filename(), "Some text");
+ ATF_REQUIRE_EQ("Some text (sqlite db: in-memory or temporary)",
+ std::string(e.what()));
+ ATF_REQUIRE_EQ(db.db_filename(), e.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error__with_filename);
+ATF_TEST_CASE_BODY(error__with_filename)
+{
+ const sqlite::database db = sqlite::database::open(
+ fs::path("test.db"), sqlite::open_readwrite | sqlite::open_create);
+ const sqlite::error e(db.db_filename(), "Some text");
+ ATF_REQUIRE_EQ("Some text (sqlite db: test.db)", std::string(e.what()));
+ ATF_REQUIRE_EQ(db.db_filename(), e.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(api_error__explicit);
+ATF_TEST_CASE_BODY(api_error__explicit)
+{
+ const sqlite::api_error e(none, "some_function", "Some text");
+ ATF_REQUIRE_EQ(
+ "Some text (sqlite op: some_function) "
+ "(sqlite db: in-memory or temporary)",
+ std::string(e.what()));
+ ATF_REQUIRE_EQ("some_function", e.api_function());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(api_error__from_database);
+ATF_TEST_CASE_BODY(api_error__from_database)
+{
+ sqlite::database db = sqlite::database::open(
+ fs::path("test.db"), sqlite::open_readwrite | sqlite::open_create);
+
+ // Use the raw sqlite3 API to cause an error. Our C++ wrappers catch all
+ // errors and reraise them as exceptions, but here we want to handle the raw
+ // error directly for testing purposes.
+ sqlite::database_c_gate gate(db);
+ ::sqlite3_stmt* dummy_stmt;
+ const char* query = "ABCDE INVALID QUERY";
+ (void)::sqlite3_prepare_v2(gate.c_database(), query, std::strlen(query),
+ &dummy_stmt, NULL);
+
+ const sqlite::api_error e = sqlite::api_error::from_database(
+ db, "real_function");
+ ATF_REQUIRE_MATCH(
+ ".*ABCDE.*\\(sqlite op: real_function\\) \\(sqlite db: test.db\\)",
+ std::string(e.what()));
+ ATF_REQUIRE_EQ("real_function", e.api_function());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_column_error);
+ATF_TEST_CASE_BODY(invalid_column_error)
+{
+ const sqlite::invalid_column_error e(none, "some_name");
+ ATF_REQUIRE_EQ("Unknown column 'some_name' "
+ "(sqlite db: in-memory or temporary)",
+ std::string(e.what()));
+ ATF_REQUIRE_EQ("some_name", e.column_name());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error__no_filename);
+ ATF_ADD_TEST_CASE(tcs, error__with_filename);
+
+ ATF_ADD_TEST_CASE(tcs, api_error__explicit);
+ ATF_ADD_TEST_CASE(tcs, api_error__from_database);
+
+ ATF_ADD_TEST_CASE(tcs, invalid_column_error);
+}
diff --git a/utils/sqlite/statement.cpp b/utils/sqlite/statement.cpp
new file mode 100644
index 0000000000000..0ae2af2d57cac
--- /dev/null
+++ b/utils/sqlite/statement.cpp
@@ -0,0 +1,621 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/statement.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include <map>
+
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/sqlite/c_gate.hpp"
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/exceptions.hpp"
+
+namespace sqlite = utils::sqlite;
+
+
+namespace {
+
+
+static sqlite::type c_type_to_cxx(const int) UTILS_PURE;
+
+
+/// Maps a SQLite 3 data type to our own representation.
+///
+/// \param original The native SQLite 3 data type.
+///
+/// \return Our internal representation for the native data type.
+static sqlite::type
+c_type_to_cxx(const int original)
+{
+ switch (original) {
+ case SQLITE_BLOB: return sqlite::type_blob;
+ case SQLITE_FLOAT: return sqlite::type_float;
+ case SQLITE_INTEGER: return sqlite::type_integer;
+ case SQLITE_NULL: return sqlite::type_null;
+ case SQLITE_TEXT: return sqlite::type_text;
+ default: UNREACHABLE_MSG("Unknown data type returned by SQLite 3");
+ }
+ UNREACHABLE;
+}
+
+
+/// Handles the return value of a sqlite3_bind_* call.
+///
+/// \param db The database the call was made on.
+/// \param api_function The name of the sqlite3_bind_* function called.
+/// \param error The error code returned by the function; can be SQLITE_OK.
+///
+/// \throw std::bad_alloc If there was no memory for the binding.
+/// \throw api_error If the binding fails for any other reason.
+static void
+handle_bind_error(sqlite::database& db, const char* api_function,
+ const int error)
+{
+ switch (error) {
+ case SQLITE_OK:
+ return;
+ case SQLITE_RANGE:
+ UNREACHABLE_MSG("Invalid index for bind argument");
+ case SQLITE_NOMEM:
+ throw std::bad_alloc();
+ default:
+ throw sqlite::api_error::from_database(db, api_function);
+ }
+}
+
+
+} // anonymous namespace
+
+
+/// Internal implementation for sqlite::statement.
+struct utils::sqlite::statement::impl : utils::noncopyable {
+ /// The database this statement belongs to.
+ sqlite::database& db;
+
+ /// The SQLite 3 internal statement.
+ ::sqlite3_stmt* stmt;
+
+ /// Cache for the column names in a statement; lazily initialized.
+ std::map< std::string, int > column_cache;
+
+ /// Constructor.
+ ///
+ /// \param db_ The database this statement belongs to. Be aware that we
+ /// keep a *reference* to the database; in other words, if the database
+ /// vanishes, this object will become invalid. (It'd be trivial to keep
+ /// a shallow copy here instead, but I feel that statements that outlive
+ /// their database represents sloppy programming.)
+ /// \param stmt_ The SQLite internal statement.
+ impl(database& db_, ::sqlite3_stmt* stmt_) :
+ db(db_),
+ stmt(stmt_)
+ {
+ }
+
+ /// Destructor.
+ ///
+ /// It is important to keep this as part of the 'impl' class instead of the
+ /// container class. The 'impl' class is destroyed exactly once (because it
+ /// is managed by a shared_ptr) and thus releasing the resources here is
+ /// OK. However, the container class is potentially released many times,
+ /// which means that we would be double-freeing the internal object and
+ /// reusing invalid data.
+ ~impl(void)
+ {
+ (void)::sqlite3_finalize(stmt);
+ }
+};
+
+
+/// Initializes a statement object.
+///
+/// This is an internal function. Use database::create_statement() to
+/// instantiate one of these objects.
+///
+/// \param db The database this statement belongs to.
+/// \param raw_stmt A void pointer representing a SQLite native statement of
+/// type sqlite3_stmt.
+sqlite::statement::statement(database& db, void* raw_stmt) :
+ _pimpl(new impl(db, static_cast< ::sqlite3_stmt* >(raw_stmt)))
+{
+}
+
+
+/// Destructor for the statement.
+///
+/// Remember that statements are reference-counted, so the statement will only
+/// cease to be valid once its last copy is destroyed.
+sqlite::statement::~statement(void)
+{
+}
+
+
+/// Executes a statement that is not supposed to return any data.
+///
+/// Use this function to execute DDL and INSERT statements; i.e. statements that
+/// only have one processing step and deliver no rows. This frees the caller
+/// from having to deal with the return value of the step() function.
+///
+/// \pre The statement to execute will not produce any rows.
+void
+sqlite::statement::step_without_results(void)
+{
+ const bool data = step();
+ INV_MSG(!data, "The statement should not have produced any rows, but it "
+ "did");
+}
+
+
+/// Performs a processing step on the statement.
+///
+/// \return True if the statement returned a row; false if the processing has
+/// finished.
+///
+/// \throw api_error If the processing of the step raises an error.
+bool
+sqlite::statement::step(void)
+{
+ const int error = ::sqlite3_step(_pimpl->stmt);
+ switch (error) {
+ case SQLITE_DONE:
+ LD("Step statement; no more rows");
+ return false;
+ case SQLITE_ROW:
+ LD("Step statement; row available for processing");
+ return true;
+ default:
+ throw api_error::from_database(_pimpl->db, "sqlite3_step");
+ }
+ UNREACHABLE;
+}
+
+
+/// Returns the number of columns in the step result.
+///
+/// \return The number of columns available for data retrieval.
+int
+sqlite::statement::column_count(void)
+{
+ return ::sqlite3_column_count(_pimpl->stmt);
+}
+
+
+/// Returns the name of a particular column in the result.
+///
+/// \param index The column to request the name of.
+///
+/// \return The name of the requested column.
+std::string
+sqlite::statement::column_name(const int index)
+{
+ const char* name = ::sqlite3_column_name(_pimpl->stmt, index);
+ if (name == NULL)
+ throw api_error::from_database(_pimpl->db, "sqlite3_column_name");
+ return name;
+}
+
+
+/// Returns the type of a particular column in the result.
+///
+/// \param index The column to request the type of.
+///
+/// \return The type of the requested column.
+sqlite::type
+sqlite::statement::column_type(const int index)
+{
+ return c_type_to_cxx(::sqlite3_column_type(_pimpl->stmt, index));
+}
+
+
+/// Finds a column by name.
+///
+/// \param name The name of the column to search for.
+///
+/// \return The column identifier.
+///
+/// \throw value_error If the name cannot be found.
+int
+sqlite::statement::column_id(const char* name)
+{
+ std::map< std::string, int >& cache = _pimpl->column_cache;
+
+ if (cache.empty()) {
+ for (int i = 0; i < column_count(); i++) {
+ const std::string aux_name = column_name(i);
+ INV(cache.find(aux_name) == cache.end());
+ cache[aux_name] = i;
+ }
+ }
+
+ const std::map< std::string, int >::const_iterator iter = cache.find(name);
+ if (iter == cache.end())
+ throw invalid_column_error(_pimpl->db.db_filename(), name);
+ else
+ return (*iter).second;
+}
+
+
+/// Returns a particular column in the result as a blob.
+///
+/// \param index The column to retrieve.
+///
+/// \return A block of memory with the blob contents. Note that the pointer
+/// returned by this call will be invalidated on the next call to any SQLite API
+/// function.
+sqlite::blob
+sqlite::statement::column_blob(const int index)
+{
+ PRE(column_type(index) == type_blob);
+ return blob(::sqlite3_column_blob(_pimpl->stmt, index),
+ ::sqlite3_column_bytes(_pimpl->stmt, index));
+}
+
+
+/// Returns a particular column in the result as a double.
+///
+/// \param index The column to retrieve.
+///
+/// \return The double value.
+double
+sqlite::statement::column_double(const int index)
+{
+ PRE(column_type(index) == type_float);
+ return ::sqlite3_column_double(_pimpl->stmt, index);
+}
+
+
+/// Returns a particular column in the result as an integer.
+///
+/// \param index The column to retrieve.
+///
+/// \return The integer value. Note that the value may not fit in an integer
+/// depending on the platform. Use column_int64 to retrieve the integer without
+/// truncation.
+int
+sqlite::statement::column_int(const int index)
+{
+ PRE(column_type(index) == type_integer);
+ return ::sqlite3_column_int(_pimpl->stmt, index);
+}
+
+
+/// Returns a particular column in the result as a 64-bit integer.
+///
+/// \param index The column to retrieve.
+///
+/// \return The integer value.
+int64_t
+sqlite::statement::column_int64(const int index)
+{
+ PRE(column_type(index) == type_integer);
+ return ::sqlite3_column_int64(_pimpl->stmt, index);
+}
+
+
+/// Returns a particular column in the result as a double.
+///
+/// \param index The column to retrieve.
+///
+/// \return A C string with the contents. Note that the pointer returned by
+/// this call will be invalidated on the next call to any SQLite API function.
+/// If you want to be extra safe, store the result in a std::string to not worry
+/// about this.
+std::string
+sqlite::statement::column_text(const int index)
+{
+ PRE(column_type(index) == type_text);
+ return reinterpret_cast< const char* >(::sqlite3_column_text(
+ _pimpl->stmt, index));
+}
+
+
+/// Returns the number of bytes stored in the column.
+///
+/// \pre This is only valid for columns of type blob and text.
+///
+/// \param index The column to retrieve the size of.
+///
+/// \return The number of bytes in the column. Remember that strings are stored
+/// in their UTF-8 representation; this call returns the number of *bytes*, not
+/// characters.
+int
+sqlite::statement::column_bytes(const int index)
+{
+ PRE(column_type(index) == type_blob || column_type(index) == type_text);
+ return ::sqlite3_column_bytes(_pimpl->stmt, index);
+}
+
+
+/// Type-checked version of column_blob.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_blob if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+sqlite::blob
+sqlite::statement::safe_column_blob(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_blob)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not a blob") % name);
+ return column_blob(column);
+}
+
+
+/// Type-checked version of column_double.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_double if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+double
+sqlite::statement::safe_column_double(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_float)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not a float") % name);
+ return column_double(column);
+}
+
+
+/// Type-checked version of column_int.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_int if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+int
+sqlite::statement::safe_column_int(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_integer)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not an integer") % name);
+ return column_int(column);
+}
+
+
+/// Type-checked version of column_int64.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_int64 if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+int64_t
+sqlite::statement::safe_column_int64(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_integer)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not an integer") % name);
+ return column_int64(column);
+}
+
+
+/// Type-checked version of column_text.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_text if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+std::string
+sqlite::statement::safe_column_text(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_text)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not a string") % name);
+ return column_text(column);
+}
+
+
+/// Type-checked version of column_bytes.
+///
+/// \param name The name of the column to retrieve the size of.
+///
+/// \return The same as column_bytes if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve the size of is invalid.
+/// \throw invalid_column_error If name is invalid.
+int
+sqlite::statement::safe_column_bytes(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_blob &&
+ column_type(column) != sqlite::type_text)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not a blob or a string") % name);
+ return column_bytes(column);
+}
+
+
+/// Resets a statement to allow further processing.
+void
+sqlite::statement::reset(void)
+{
+ (void)::sqlite3_reset(_pimpl->stmt);
+}
+
+
+/// Binds a blob to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param b Description of the blob, which must remain valid during the
+/// execution of the statement.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const blob& b)
+{
+ const int error = ::sqlite3_bind_blob(_pimpl->stmt, index, b.memory, b.size,
+ SQLITE_STATIC);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_blob", error);
+}
+
+
+/// Binds a double value to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param value The double value to bind.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const double value)
+{
+ const int error = ::sqlite3_bind_double(_pimpl->stmt, index, value);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_double", error);
+}
+
+
+/// Binds an integer value to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param value The integer value to bind.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const int value)
+{
+ const int error = ::sqlite3_bind_int(_pimpl->stmt, index, value);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_int", error);
+}
+
+
+/// Binds a 64-bit integer value to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param value The 64-bin integer value to bind.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const int64_t value)
+{
+ const int error = ::sqlite3_bind_int64(_pimpl->stmt, index, value);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_int64", error);
+}
+
+
+/// Binds a NULL value to a prepared statement.
+///
+/// \param index The index of the binding.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const null& /* null */)
+{
+ const int error = ::sqlite3_bind_null(_pimpl->stmt, index);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_null", error);
+}
+
+
+/// Binds a text string to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param text The string to bind. SQLite generates an internal copy of this
+/// string, so the original string object does not have to remain live. We
+/// do this because handling the lifetime of std::string objects is very
+/// hard (think about implicit conversions), so it is very easy to shoot
+/// ourselves in the foot if we don't do this.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const std::string& text)
+{
+ const int error = ::sqlite3_bind_text(_pimpl->stmt, index, text.c_str(),
+ text.length(), SQLITE_TRANSIENT);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_text", error);
+}
+
+
+/// Returns the index of the highest parameter.
+///
+/// \return A parameter index.
+int
+sqlite::statement::bind_parameter_count(void)
+{
+ return ::sqlite3_bind_parameter_count(_pimpl->stmt);
+}
+
+
+/// Returns the index of a named parameter.
+///
+/// \param name The name of the parameter to be queried; must exist.
+///
+/// \return A parameter index.
+int
+sqlite::statement::bind_parameter_index(const std::string& name)
+{
+ const int index = ::sqlite3_bind_parameter_index(_pimpl->stmt,
+ name.c_str());
+ PRE_MSG(index > 0, "Parameter name not in statement");
+ return index;
+}
+
+
+/// Returns the name of a parameter by index.
+///
+/// \param index The index to query; must be valid.
+///
+/// \return The name of the parameter.
+std::string
+sqlite::statement::bind_parameter_name(const int index)
+{
+ const char* name = ::sqlite3_bind_parameter_name(_pimpl->stmt, index);
+ PRE_MSG(name != NULL, "Index value out of range or nameless parameter");
+ return std::string(name);
+}
+
+
+/// Clears any bindings and releases their memory.
+void
+sqlite::statement::clear_bindings(void)
+{
+ const int error = ::sqlite3_clear_bindings(_pimpl->stmt);
+ PRE_MSG(error == SQLITE_OK, "SQLite3 contract has changed; it should "
+ "only return SQLITE_OK");
+}
diff --git a/utils/sqlite/statement.hpp b/utils/sqlite/statement.hpp
new file mode 100644
index 0000000000000..bcd1831e48417
--- /dev/null
+++ b/utils/sqlite/statement.hpp
@@ -0,0 +1,137 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/statement.hpp
+/// Wrapper classes and utilities for SQLite statement processing.
+///
+/// This module contains thin RAII wrappers around the SQLite 3 structures
+/// representing statements.
+
+#if !defined(UTILS_SQLITE_STATEMENT_HPP)
+#define UTILS_SQLITE_STATEMENT_HPP
+
+#include "utils/sqlite/statement_fwd.hpp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <memory>
+#include <string>
+
+#include "utils/sqlite/database_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// Representation of a BLOB.
+class blob {
+public:
+ /// Memory representing the contents of the blob, or NULL if empty.
+ ///
+ /// This memory must remain valid throughout the life of this object, as we
+ /// do not grab ownership of the memory.
+ const void* memory;
+
+ /// Number of bytes in memory.
+ int size;
+
+ /// Constructs a new blob.
+ ///
+ /// \param memory_ Pointer to the contents of the blob.
+ /// \param size_ The size of memory_.
+ blob(const void* memory_, const int size_) :
+ memory(memory_), size(size_)
+ {
+ }
+};
+
+
+/// Representation of a SQL NULL value.
+class null {
+};
+
+
+/// A RAII model for an SQLite 3 statement.
+class statement {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ statement(database&, void*);
+ friend class database;
+
+public:
+ ~statement(void);
+
+ bool step(void);
+ void step_without_results(void);
+
+ int column_count(void);
+ std::string column_name(const int);
+ type column_type(const int);
+ int column_id(const char*);
+
+ blob column_blob(const int);
+ double column_double(const int);
+ int column_int(const int);
+ int64_t column_int64(const int);
+ std::string column_text(const int);
+ int column_bytes(const int);
+
+ blob safe_column_blob(const char*);
+ double safe_column_double(const char*);
+ int safe_column_int(const char*);
+ int64_t safe_column_int64(const char*);
+ std::string safe_column_text(const char*);
+ int safe_column_bytes(const char*);
+
+ void reset(void);
+
+ void bind(const int, const blob&);
+ void bind(const int, const double);
+ void bind(const int, const int);
+ void bind(const int, const int64_t);
+ void bind(const int, const null&);
+ void bind(const int, const std::string&);
+ template< class T > void bind(const char*, const T&);
+
+ int bind_parameter_count(void);
+ int bind_parameter_index(const std::string&);
+ std::string bind_parameter_name(const int);
+
+ void clear_bindings(void);
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_STATEMENT_HPP)
diff --git a/utils/sqlite/statement.ipp b/utils/sqlite/statement.ipp
new file mode 100644
index 0000000000000..3f219016a2a9b
--- /dev/null
+++ b/utils/sqlite/statement.ipp
@@ -0,0 +1,52 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_SQLITE_STATEMENT_IPP)
+#define UTILS_SQLITE_STATEMENT_IPP
+
+#include "utils/sqlite/statement.hpp"
+
+
+/// Binds a value to a parameter of a prepared statement.
+///
+/// \param parameter The name of the parameter; must exist. This is a raw C
+/// string instead of a std::string because statement parameter names are
+/// known at compilation time and the program should really not be
+/// constructing them dynamically.
+/// \param value The value to bind to the parameter.
+///
+/// \throw api_error If the binding fails.
+template< class T >
+void
+utils::sqlite::statement::bind(const char* parameter, const T& value)
+{
+ bind(bind_parameter_index(parameter), value);
+}
+
+
+#endif // !defined(UTILS_SQLITE_STATEMENT_IPP)
diff --git a/utils/sqlite/statement_fwd.hpp b/utils/sqlite/statement_fwd.hpp
new file mode 100644
index 0000000000000..26634c965018d
--- /dev/null
+++ b/utils/sqlite/statement_fwd.hpp
@@ -0,0 +1,57 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/statement_fwd.hpp
+/// Forward declarations for utils/sqlite/statement.hpp
+
+#if !defined(UTILS_SQLITE_STATEMENT_FWD_HPP)
+#define UTILS_SQLITE_STATEMENT_FWD_HPP
+
+namespace utils {
+namespace sqlite {
+
+
+/// Representation of the SQLite data types.
+enum type {
+ type_blob,
+ type_float,
+ type_integer,
+ type_null,
+ type_text,
+};
+
+
+class blob;
+class null;
+class statement;
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_STATEMENT_FWD_HPP)
diff --git a/utils/sqlite/statement_test.cpp b/utils/sqlite/statement_test.cpp
new file mode 100644
index 0000000000000..40bc92cb5c0ef
--- /dev/null
+++ b/utils/sqlite/statement_test.cpp
@@ -0,0 +1,784 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/statement.ipp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <cstring>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/test_utils.hpp"
+
+namespace sqlite = utils::sqlite;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step__ok);
+ATF_TEST_CASE_BODY(step__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement(
+ "CREATE TABLE foo (a INTEGER PRIMARY KEY)");
+ ATF_REQUIRE_THROW(sqlite::error, db.exec("SELECT * FROM foo"));
+ ATF_REQUIRE(!stmt.step());
+ db.exec("SELECT * FROM foo");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step__many);
+ATF_TEST_CASE_BODY(step__many)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ create_test_table(raw(db));
+ sqlite::statement stmt = db.create_statement(
+ "SELECT prime FROM test ORDER BY prime");
+ for (int i = 0; i < 5; i++)
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step__fail);
+ATF_TEST_CASE_BODY(step__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement(
+ "CREATE TABLE foo (a INTEGER PRIMARY KEY)");
+ ATF_REQUIRE(!stmt.step());
+ REQUIRE_API_ERROR("sqlite3_step", stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step_without_results__ok);
+ATF_TEST_CASE_BODY(step_without_results__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement(
+ "CREATE TABLE foo (a INTEGER PRIMARY KEY)");
+ ATF_REQUIRE_THROW(sqlite::error, db.exec("SELECT * FROM foo"));
+ stmt.step_without_results();
+ db.exec("SELECT * FROM foo");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step_without_results__fail);
+ATF_TEST_CASE_BODY(step_without_results__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER PRIMARY KEY)");
+ db.exec("INSERT INTO foo VALUES (3)");
+ sqlite::statement stmt = db.create_statement(
+ "INSERT INTO foo VALUES (3)");
+ REQUIRE_API_ERROR("sqlite3_step", stmt.step_without_results());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_count);
+ATF_TEST_CASE_BODY(column_count)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER PRIMARY KEY, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (5, 3, 'foo');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(3, stmt.column_count());
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_name__ok);
+ATF_TEST_CASE_BODY(column_name__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (first INTEGER PRIMARY KEY, second TEXT);"
+ "INSERT INTO foo VALUES (5, 'foo');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ("first", stmt.column_name(0));
+ ATF_REQUIRE_EQ("second", stmt.column_name(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_name__fail);
+ATF_TEST_CASE_BODY(column_name__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (first INTEGER PRIMARY KEY);"
+ "INSERT INTO foo VALUES (5);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ("first", stmt.column_name(0));
+ REQUIRE_API_ERROR("sqlite3_column_name", stmt.column_name(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_type__ok);
+ATF_TEST_CASE_BODY(column_type__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a_blob BLOB,"
+ " a_float FLOAT,"
+ " an_integer INTEGER,"
+ " a_null BLOB,"
+ " a_text TEXT);"
+ "INSERT INTO foo VALUES (x'0102', 0.3, 5, NULL, 'foo bar');"
+ "INSERT INTO foo VALUES (NULL, NULL, NULL, NULL, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_blob == stmt.column_type(0));
+ ATF_REQUIRE(sqlite::type_float == stmt.column_type(1));
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(2));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(3));
+ ATF_REQUIRE(sqlite::type_text == stmt.column_type(4));
+ ATF_REQUIRE(stmt.step());
+ for (int i = 0; i < stmt.column_count(); i++)
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(i));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_type__out_of_range);
+ATF_TEST_CASE_BODY(column_type__out_of_range)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER PRIMARY KEY);"
+ "INSERT INTO foo VALUES (1);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(1));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(512));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_id__ok);
+ATF_TEST_CASE_BODY(column_id__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (bar INTEGER PRIMARY KEY, "
+ " baz INTEGER);"
+ "INSERT INTO foo VALUES (1, 2);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(0, stmt.column_id("bar"));
+ ATF_REQUIRE_EQ(1, stmt.column_id("baz"));
+ ATF_REQUIRE_EQ(0, stmt.column_id("bar"));
+ ATF_REQUIRE_EQ(1, stmt.column_id("baz"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_id__missing);
+ATF_TEST_CASE_BODY(column_id__missing)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (bar INTEGER PRIMARY KEY, "
+ " baz INTEGER);"
+ "INSERT INTO foo VALUES (1, 2);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(0, stmt.column_id("bar"));
+ try {
+ stmt.column_id("bazo");
+ fail("invalid_column_error not raised");
+ } catch (const sqlite::invalid_column_error& e) {
+ ATF_REQUIRE_EQ("bazo", e.column_name());
+ }
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_blob);
+ATF_TEST_CASE_BODY(column_blob)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b BLOB, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, x'cafe', NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ const sqlite::blob blob = stmt.column_blob(1);
+ ATF_REQUIRE_EQ(0xca, static_cast< const uint8_t* >(blob.memory)[0]);
+ ATF_REQUIRE_EQ(0xfe, static_cast< const uint8_t* >(blob.memory)[1]);
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_double);
+ATF_TEST_CASE_BODY(column_double)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b DOUBLE, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, 0.5, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(0.5, stmt.column_double(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_int__ok);
+ATF_TEST_CASE_BODY(column_int__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 987, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(987, stmt.column_int(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_int__overflow);
+ATF_TEST_CASE_BODY(column_int__overflow)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 4294967419, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(123, stmt.column_int(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_int64);
+ATF_TEST_CASE_BODY(column_int64)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 4294967419, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(4294967419LL, stmt.column_int64(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_text);
+ATF_TEST_CASE_BODY(column_text)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b TEXT, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, 'foo bar', NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ("foo bar", stmt.column_text(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_bytes__blob);
+ATF_TEST_CASE_BODY(column_bytes__blob)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a BLOB);"
+ "INSERT INTO foo VALUES (x'12345678');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(4, stmt.column_bytes(0));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_bytes__text);
+ATF_TEST_CASE_BODY(column_bytes__text)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('foo bar');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(7, stmt.column_bytes(0));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_blob__ok);
+ATF_TEST_CASE_BODY(safe_column_blob__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b BLOB, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, x'cafe', NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ const sqlite::blob blob = stmt.safe_column_blob("b");
+ ATF_REQUIRE_EQ(0xca, static_cast< const uint8_t* >(blob.memory)[0]);
+ ATF_REQUIRE_EQ(0xfe, static_cast< const uint8_t* >(blob.memory)[1]);
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_blob__fail);
+ATF_TEST_CASE_BODY(safe_column_blob__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER);"
+ "INSERT INTO foo VALUES (123);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_blob("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not a blob",
+ stmt.safe_column_blob("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_double__ok);
+ATF_TEST_CASE_BODY(safe_column_double__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b DOUBLE, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, 0.5, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(0.5, stmt.safe_column_double("b"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_double__fail);
+ATF_TEST_CASE_BODY(safe_column_double__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER);"
+ "INSERT INTO foo VALUES (NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_double("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not a float",
+ stmt.safe_column_double("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_int__ok);
+ATF_TEST_CASE_BODY(safe_column_int__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 987, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(987, stmt.safe_column_int("b"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_int__fail);
+ATF_TEST_CASE_BODY(safe_column_int__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('def');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_int("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not an integer",
+ stmt.safe_column_int("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_int64__ok);
+ATF_TEST_CASE_BODY(safe_column_int64__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 4294967419, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(4294967419LL, stmt.safe_column_int64("b"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_int64__fail);
+ATF_TEST_CASE_BODY(safe_column_int64__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('abc');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_int64("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not an integer",
+ stmt.safe_column_int64("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_text__ok);
+ATF_TEST_CASE_BODY(safe_column_text__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b TEXT, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, 'foo bar', NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ("foo bar", stmt.safe_column_text("b"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_text__fail);
+ATF_TEST_CASE_BODY(safe_column_text__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER);"
+ "INSERT INTO foo VALUES (NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_text("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not a string",
+ stmt.safe_column_text("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_bytes__ok__blob);
+ATF_TEST_CASE_BODY(safe_column_bytes__ok__blob)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a BLOB);"
+ "INSERT INTO foo VALUES (x'12345678');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(4, stmt.safe_column_bytes("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_bytes__ok__text);
+ATF_TEST_CASE_BODY(safe_column_bytes__ok__text)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('foo bar');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(7, stmt.safe_column_bytes("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_bytes__fail);
+ATF_TEST_CASE_BODY(safe_column_bytes__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES (NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_bytes("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not a blob or a string",
+ stmt.safe_column_bytes("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(reset);
+ATF_TEST_CASE_BODY(reset)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('foo bar');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(!stmt.step());
+ stmt.reset();
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__blob);
+ATF_TEST_CASE_BODY(bind__blob)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ const unsigned char blob[] = {0xca, 0xfe};
+ stmt.bind(1, sqlite::blob(static_cast< const void* >(blob), 2));
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_blob == stmt.column_type(1));
+ const unsigned char* ret_blob =
+ static_cast< const unsigned char* >(stmt.column_blob(1).memory);
+ ATF_REQUIRE(std::memcmp(blob, ret_blob, 2) == 0);
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__double);
+ATF_TEST_CASE_BODY(bind__double)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, 0.5);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_float == stmt.column_type(1));
+ ATF_REQUIRE_EQ(0.5, stmt.column_double(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__int);
+ATF_TEST_CASE_BODY(bind__int)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, 123);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(1));
+ ATF_REQUIRE_EQ(123, stmt.column_int(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__int64);
+ATF_TEST_CASE_BODY(bind__int64)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, static_cast< int64_t >(4294967419LL));
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(1));
+ ATF_REQUIRE_EQ(4294967419LL, stmt.column_int64(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__null);
+ATF_TEST_CASE_BODY(bind__null)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, sqlite::null());
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__text);
+ATF_TEST_CASE_BODY(bind__text)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ const std::string str = "Hello";
+ stmt.bind(1, str);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_text == stmt.column_type(1));
+ ATF_REQUIRE_EQ(str, stmt.column_text(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__text__transient);
+ATF_TEST_CASE_BODY(bind__text__transient)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, :foo");
+
+ {
+ const std::string str = "Hello";
+ stmt.bind(":foo", str);
+ }
+
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_text == stmt.column_type(1));
+ ATF_REQUIRE_EQ(std::string("Hello"), stmt.column_text(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__by_name);
+ATF_TEST_CASE_BODY(bind__by_name)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, :foo");
+
+ const std::string str = "Hello";
+ stmt.bind(":foo", str);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_text == stmt.column_type(1));
+ ATF_REQUIRE_EQ(str, stmt.column_text(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind_parameter_count);
+ATF_TEST_CASE_BODY(bind_parameter_count)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?, ?");
+ ATF_REQUIRE_EQ(2, stmt.bind_parameter_count());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind_parameter_index);
+ATF_TEST_CASE_BODY(bind_parameter_index)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, :foo, ?, :bar");
+ ATF_REQUIRE_EQ(1, stmt.bind_parameter_index(":foo"));
+ ATF_REQUIRE_EQ(3, stmt.bind_parameter_index(":bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind_parameter_name);
+ATF_TEST_CASE_BODY(bind_parameter_name)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, :foo, ?, :bar");
+ ATF_REQUIRE_EQ(":foo", stmt.bind_parameter_name(1));
+ ATF_REQUIRE_EQ(":bar", stmt.bind_parameter_name(3));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(clear_bindings);
+ATF_TEST_CASE_BODY(clear_bindings)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, 5);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(1));
+ ATF_REQUIRE_EQ(5, stmt.column_int(1));
+ stmt.clear_bindings();
+ stmt.reset();
+
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(1));
+
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, step__ok);
+ ATF_ADD_TEST_CASE(tcs, step__many);
+ ATF_ADD_TEST_CASE(tcs, step__fail);
+
+ ATF_ADD_TEST_CASE(tcs, step_without_results__ok);
+ ATF_ADD_TEST_CASE(tcs, step_without_results__fail);
+
+ ATF_ADD_TEST_CASE(tcs, column_count);
+
+ ATF_ADD_TEST_CASE(tcs, column_name__ok);
+ ATF_ADD_TEST_CASE(tcs, column_name__fail);
+
+ ATF_ADD_TEST_CASE(tcs, column_type__ok);
+ ATF_ADD_TEST_CASE(tcs, column_type__out_of_range);
+
+ ATF_ADD_TEST_CASE(tcs, column_id__ok);
+ ATF_ADD_TEST_CASE(tcs, column_id__missing);
+
+ ATF_ADD_TEST_CASE(tcs, column_blob);
+ ATF_ADD_TEST_CASE(tcs, column_double);
+ ATF_ADD_TEST_CASE(tcs, column_int__ok);
+ ATF_ADD_TEST_CASE(tcs, column_int__overflow);
+ ATF_ADD_TEST_CASE(tcs, column_int64);
+ ATF_ADD_TEST_CASE(tcs, column_text);
+
+ ATF_ADD_TEST_CASE(tcs, column_bytes__blob);
+ ATF_ADD_TEST_CASE(tcs, column_bytes__text);
+
+ ATF_ADD_TEST_CASE(tcs, safe_column_blob__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_blob__fail);
+ ATF_ADD_TEST_CASE(tcs, safe_column_double__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_double__fail);
+ ATF_ADD_TEST_CASE(tcs, safe_column_int__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_int__fail);
+ ATF_ADD_TEST_CASE(tcs, safe_column_int64__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_int64__fail);
+ ATF_ADD_TEST_CASE(tcs, safe_column_text__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_text__fail);
+
+ ATF_ADD_TEST_CASE(tcs, safe_column_bytes__ok__blob);
+ ATF_ADD_TEST_CASE(tcs, safe_column_bytes__ok__text);
+ ATF_ADD_TEST_CASE(tcs, safe_column_bytes__fail);
+
+ ATF_ADD_TEST_CASE(tcs, reset);
+
+ ATF_ADD_TEST_CASE(tcs, bind__blob);
+ ATF_ADD_TEST_CASE(tcs, bind__double);
+ ATF_ADD_TEST_CASE(tcs, bind__int64);
+ ATF_ADD_TEST_CASE(tcs, bind__int);
+ ATF_ADD_TEST_CASE(tcs, bind__null);
+ ATF_ADD_TEST_CASE(tcs, bind__text);
+ ATF_ADD_TEST_CASE(tcs, bind__text__transient);
+ ATF_ADD_TEST_CASE(tcs, bind__by_name);
+
+ ATF_ADD_TEST_CASE(tcs, bind_parameter_count);
+ ATF_ADD_TEST_CASE(tcs, bind_parameter_index);
+ ATF_ADD_TEST_CASE(tcs, bind_parameter_name);
+
+ ATF_ADD_TEST_CASE(tcs, clear_bindings);
+}
diff --git a/utils/sqlite/test_utils.hpp b/utils/sqlite/test_utils.hpp
new file mode 100644
index 0000000000000..bf35d209a1644
--- /dev/null
+++ b/utils/sqlite/test_utils.hpp
@@ -0,0 +1,151 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/test_utils.hpp
+/// Utilities for tests of the sqlite modules.
+///
+/// This file is intended to be included once, and only once, for every test
+/// program that needs it. All the code is herein contained to simplify the
+/// dependency chain in the build rules.
+
+#if !defined(UTILS_SQLITE_TEST_UTILS_HPP)
+# define UTILS_SQLITE_TEST_UTILS_HPP
+#else
+# error "test_utils.hpp can only be included once"
+#endif
+
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/sqlite/c_gate.hpp"
+#include "utils/sqlite/exceptions.hpp"
+
+
+namespace {
+
+
+/// Checks that a given expression raises a particular sqlite::api_error.
+///
+/// We cannot make any assumptions regarding the error text provided by SQLite,
+/// so we resort to checking only which API function raised the error (because
+/// our code is the one hardcoding these strings).
+///
+/// \param exp_api_function The name of the SQLite 3 C API function that causes
+/// the error.
+/// \param statement The statement to execute.
+#define REQUIRE_API_ERROR(exp_api_function, statement) \
+ do { \
+ try { \
+ statement; \
+ ATF_FAIL("api_error not raised by " #statement); \
+ } catch (const utils::sqlite::api_error& api_error) { \
+ ATF_REQUIRE_EQ(exp_api_function, api_error.api_function()); \
+ } \
+ } while (0)
+
+
+/// Gets the pointer to the internal sqlite3 of a database object.
+///
+/// This is pure syntactic sugar to simplify typing in the test cases.
+///
+/// \param db The SQLite database.
+///
+/// \return The internal sqlite3 of the input database.
+static inline ::sqlite3*
+raw(utils::sqlite::database& db)
+{
+ return utils::sqlite::database_c_gate(db).c_database();
+}
+
+
+/// SQL commands to create a test table.
+///
+/// See create_test_table() for more details.
+static const char* create_test_table_sql =
+ "CREATE TABLE test (prime INTEGER PRIMARY KEY);"
+ "INSERT INTO test (prime) VALUES (1);\n"
+ "INSERT INTO test (prime) VALUES (2);\n"
+ "INSERT INTO test (prime) VALUES (7);\n"
+ "INSERT INTO test (prime) VALUES (5);\n"
+ "INSERT INTO test (prime) VALUES (3);\n";
+
+
+static void create_test_table(::sqlite3*) UTILS_UNUSED;
+
+
+/// Creates a 'test' table in a database.
+///
+/// The created 'test' table is populated with a few rows. If there are any
+/// problems creating the database, this fails the test case.
+///
+/// Use the verify_test_table() function on the same database to verify that
+/// the table is present and contains the expected data.
+///
+/// \param db The database in which to create the test table.
+static void
+create_test_table(::sqlite3* db)
+{
+ std::cout << "Creating 'test' table in the database\n";
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_exec(db, create_test_table_sql,
+ NULL, NULL, NULL));
+}
+
+
+static void verify_test_table(::sqlite3*) UTILS_UNUSED;
+
+
+/// Verifies that the specified database contains the 'test' table.
+///
+/// This function ensures that the provided database contains the 'test' table
+/// created by the create_test_table() function on the same database. If it
+/// doesn't, this fails the caller test case.
+///
+/// \param db The database to validate.
+static void
+verify_test_table(::sqlite3* db)
+{
+ std::cout << "Verifying that the 'test' table is in the database\n";
+ char **result;
+ int rows, columns;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_get_table(db,
+ "SELECT * FROM test ORDER BY prime", &result, &rows, &columns, NULL));
+ ATF_REQUIRE_EQ(5, rows);
+ ATF_REQUIRE_EQ(1, columns);
+ ATF_REQUIRE_EQ("prime", std::string(result[0]));
+ ATF_REQUIRE_EQ("1", std::string(result[1]));
+ ATF_REQUIRE_EQ("2", std::string(result[2]));
+ ATF_REQUIRE_EQ("3", std::string(result[3]));
+ ATF_REQUIRE_EQ("5", std::string(result[4]));
+ ATF_REQUIRE_EQ("7", std::string(result[5]));
+ ::sqlite3_free_table(result);
+}
+
+
+} // anonymous namespace
diff --git a/utils/sqlite/transaction.cpp b/utils/sqlite/transaction.cpp
new file mode 100644
index 0000000000000..e0235fef9c574
--- /dev/null
+++ b/utils/sqlite/transaction.cpp
@@ -0,0 +1,142 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/transaction.hpp"
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/exceptions.hpp"
+#include "utils/sqlite/statement.ipp"
+
+namespace sqlite = utils::sqlite;
+
+
+/// Internal implementation for the transaction.
+struct utils::sqlite::transaction::impl : utils::noncopyable {
+ /// The database this transaction belongs to.
+ database& db;
+
+ /// Possible statuses of a transaction.
+ enum statuses {
+ open_status,
+ committed_status,
+ rolled_back_status,
+ };
+
+ /// The current status of the transaction.
+ statuses status;
+
+ /// Constructs a new transaction.
+ ///
+ /// \param db_ The database this transaction belongs to.
+ /// \param status_ The status of the new transaction.
+ impl(database& db_, const statuses status_) :
+ db(db_),
+ status(status_)
+ {
+ }
+
+ /// Destroys the transaction.
+ ///
+ /// This rolls back the transaction if it is open.
+ ~impl(void)
+ {
+ if (status == impl::open_status) {
+ try {
+ rollback();
+ } catch (const sqlite::error& e) {
+ LW(F("Error while rolling back a transaction: %s") % e.what());
+ }
+ }
+ }
+
+ /// Commits the transaction.
+ ///
+ /// \throw api_error If there is any problem while committing the
+ /// transaction.
+ void
+ commit(void)
+ {
+ PRE(status == impl::open_status);
+ db.exec("COMMIT");
+ status = impl::committed_status;
+ }
+
+ /// Rolls the transaction back.
+ ///
+ /// \throw api_error If there is any problem while rolling the
+ /// transaction back.
+ void
+ rollback(void)
+ {
+ PRE(status == impl::open_status);
+ db.exec("ROLLBACK");
+ status = impl::rolled_back_status;
+ }
+};
+
+
+/// Initializes a transaction object.
+///
+/// This is an internal function. Use database::begin_transaction() to
+/// instantiate one of these objects.
+///
+/// \param db The database this transaction belongs to.
+sqlite::transaction::transaction(database& db) :
+ _pimpl(new impl(db, impl::open_status))
+{
+}
+
+
+/// Destructor for the transaction.
+sqlite::transaction::~transaction(void)
+{
+}
+
+
+/// Commits the transaction.
+///
+/// \throw api_error If there is any problem while committing the transaction.
+void
+sqlite::transaction::commit(void)
+{
+ _pimpl->commit();
+}
+
+
+/// Rolls the transaction back.
+///
+/// \throw api_error If there is any problem while rolling the transaction back.
+void
+sqlite::transaction::rollback(void)
+{
+ _pimpl->rollback();
+}
diff --git a/utils/sqlite/transaction.hpp b/utils/sqlite/transaction.hpp
new file mode 100644
index 0000000000000..71f3b0c93f4ae
--- /dev/null
+++ b/utils/sqlite/transaction.hpp
@@ -0,0 +1,69 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/transaction.hpp
+/// A RAII model for SQLite transactions.
+
+#if !defined(UTILS_SQLITE_TRANSACTION_HPP)
+#define UTILS_SQLITE_TRANSACTION_HPP
+
+#include "utils/sqlite/transaction_fwd.hpp"
+
+#include <memory>
+
+#include "utils/sqlite/database_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// A RAII model for an SQLite 3 statement.
+///
+/// A transaction is automatically rolled back when it goes out of scope unless
+/// it has been explicitly committed.
+class transaction {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ explicit transaction(database&);
+ friend class database;
+
+public:
+ ~transaction(void);
+
+ void commit(void);
+ void rollback(void);
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_TRANSACTION_HPP)
diff --git a/utils/sqlite/transaction_fwd.hpp b/utils/sqlite/transaction_fwd.hpp
new file mode 100644
index 0000000000000..7773d83804580
--- /dev/null
+++ b/utils/sqlite/transaction_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/transaction_fwd.hpp
+/// Forward declarations for utils/sqlite/transaction.hpp
+
+#if !defined(UTILS_SQLITE_TRANSACTION_FWD_HPP)
+#define UTILS_SQLITE_TRANSACTION_FWD_HPP
+
+namespace utils {
+namespace sqlite {
+
+
+class transaction;
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_TRANSACTION_FWD_HPP)
diff --git a/utils/sqlite/transaction_test.cpp b/utils/sqlite/transaction_test.cpp
new file mode 100644
index 0000000000000..d53e6fba4378b
--- /dev/null
+++ b/utils/sqlite/transaction_test.cpp
@@ -0,0 +1,135 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/transaction.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/exceptions.hpp"
+#include "utils/sqlite/statement.ipp"
+
+namespace sqlite = utils::sqlite;
+
+
+namespace {
+
+
+/// Ensures that a table has a single specific value in a column.
+///
+/// \param db The SQLite database.
+/// \param table_name The table to be checked.
+/// \param column_name The column to be checked.
+/// \param exp_value The value expected to be found in the column.
+///
+/// \return True if the column contains a single value and it matches exp_value;
+/// false if not. If the query fails, the calling test is marked as bad.
+static bool
+check_in_table(sqlite::database& db, const char* table_name,
+ const char* column_name, int exp_value)
+{
+ sqlite::statement stmt = db.create_statement(
+ F("SELECT * FROM %s WHERE %s == %s") % table_name % column_name %
+ exp_value);
+ if (!stmt.step())
+ return false;
+ if (stmt.step())
+ ATF_FAIL("More than one value found in table");
+ return true;
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(automatic_rollback);
+ATF_TEST_CASE_BODY(automatic_rollback)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE t (col INTEGER PRIMARY KEY)");
+ db.exec("INSERT INTO t VALUES (3)");
+ {
+ sqlite::transaction tx = db.begin_transaction();
+ db.exec("INSERT INTO t VALUES (5)");
+ }
+ ATF_REQUIRE( check_in_table(db, "t", "col", 3));
+ ATF_REQUIRE(!check_in_table(db, "t", "col", 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(explicit_commit);
+ATF_TEST_CASE_BODY(explicit_commit)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE t (col INTEGER PRIMARY KEY)");
+ db.exec("INSERT INTO t VALUES (3)");
+ {
+ sqlite::transaction tx = db.begin_transaction();
+ db.exec("INSERT INTO t VALUES (5)");
+ tx.commit();
+ }
+ ATF_REQUIRE(check_in_table(db, "t", "col", 3));
+ ATF_REQUIRE(check_in_table(db, "t", "col", 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(explicit_rollback);
+ATF_TEST_CASE_BODY(explicit_rollback)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE t (col INTEGER PRIMARY KEY)");
+ db.exec("INSERT INTO t VALUES (3)");
+ {
+ sqlite::transaction tx = db.begin_transaction();
+ db.exec("INSERT INTO t VALUES (5)");
+ tx.rollback();
+ }
+ ATF_REQUIRE( check_in_table(db, "t", "col", 3));
+ ATF_REQUIRE(!check_in_table(db, "t", "col", 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(nested_fail);
+ATF_TEST_CASE_BODY(nested_fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ {
+ sqlite::transaction tx = db.begin_transaction();
+ ATF_REQUIRE_THROW(sqlite::error, db.begin_transaction());
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, automatic_rollback);
+ ATF_ADD_TEST_CASE(tcs, explicit_commit);
+ ATF_ADD_TEST_CASE(tcs, explicit_rollback);
+ ATF_ADD_TEST_CASE(tcs, nested_fail);
+}