diff options
Diffstat (limited to 'utils/sqlite')
-rw-r--r-- | utils/sqlite/Kyuafile | 9 | ||||
-rw-r--r-- | utils/sqlite/Makefile.am.inc | 82 | ||||
-rw-r--r-- | utils/sqlite/c_gate.cpp | 83 | ||||
-rw-r--r-- | utils/sqlite/c_gate.hpp | 74 | ||||
-rw-r--r-- | utils/sqlite/c_gate_fwd.hpp | 45 | ||||
-rw-r--r-- | utils/sqlite/c_gate_test.cpp | 96 | ||||
-rw-r--r-- | utils/sqlite/database.cpp | 328 | ||||
-rw-r--r-- | utils/sqlite/database.hpp | 111 | ||||
-rw-r--r-- | utils/sqlite/database_fwd.hpp | 45 | ||||
-rw-r--r-- | utils/sqlite/database_test.cpp | 287 | ||||
-rw-r--r-- | utils/sqlite/exceptions.cpp | 175 | ||||
-rw-r--r-- | utils/sqlite/exceptions.hpp | 94 | ||||
-rw-r--r-- | utils/sqlite/exceptions_test.cpp | 129 | ||||
-rw-r--r-- | utils/sqlite/statement.cpp | 621 | ||||
-rw-r--r-- | utils/sqlite/statement.hpp | 137 | ||||
-rw-r--r-- | utils/sqlite/statement.ipp | 52 | ||||
-rw-r--r-- | utils/sqlite/statement_fwd.hpp | 57 | ||||
-rw-r--r-- | utils/sqlite/statement_test.cpp | 784 | ||||
-rw-r--r-- | utils/sqlite/test_utils.hpp | 151 | ||||
-rw-r--r-- | utils/sqlite/transaction.cpp | 142 | ||||
-rw-r--r-- | utils/sqlite/transaction.hpp | 69 | ||||
-rw-r--r-- | utils/sqlite/transaction_fwd.hpp | 45 | ||||
-rw-r--r-- | utils/sqlite/transaction_test.cpp | 135 |
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); +} |