Browse Source

[trac1061] (Co|De)structor of SQLite3Connection

Most of the code is slightly modified copy-paste from the
Sqlite3DataSource. No documentation or log messages and the getZone
method is dummy. But it compiles and provides some kind of frame for the
rest.
Michal 'vorner' Vaner 14 years ago
parent
commit
e76dc86b0a

+ 7 - 0
src/lib/datasrc/datasrc_messages.mes

@@ -497,3 +497,10 @@ data source.
 This indicates a programming error. An internal task of unknown type was
 generated.
 
+% DATASRC_SQLITE_NEWCONN TODO
+
+% DATASRC_SQLITE_DROPCONN TODO
+
+% DATASRC_SQLITE_CONNOPEN TODO
+
+% DATASRC_SQLITE_CONNCLOSE TODO

+ 280 - 0
src/lib/datasrc/sqlite3_connection.cc

@@ -12,3 +12,283 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <sqlite3.h>
+
+#include <datasrc/sqlite3_connection.h>
+#include <datasrc/logger.h>
+#include <datasrc/data_source.h>
+
+namespace isc {
+namespace datasrc {
+
+struct SQLite3Parameters {
+    SQLite3Parameters() :
+        db_(NULL), version_(-1),
+        q_zone_(NULL) /*, q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
+        q_any_(NULL), q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
+        q_prevnsec3_(NULL) */
+    {}
+    sqlite3* db_;
+    int version_;
+    sqlite3_stmt* q_zone_;
+    /*
+    TODO: Yet unneeded statements
+    sqlite3_stmt* q_record_;
+    sqlite3_stmt* q_addrs_;
+    sqlite3_stmt* q_referral_;
+    sqlite3_stmt* q_any_;
+    sqlite3_stmt* q_count_;
+    sqlite3_stmt* q_previous_;
+    sqlite3_stmt* q_nsec3_;
+    sqlite3_stmt* q_prevnsec3_;
+    */
+};
+
+SQLite3Connection::SQLite3Connection(const isc::data::ConstElementPtr&
+                                     config) :
+    dbparameters_(new SQLite3Parameters)
+{
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
+
+    if (config && config->contains("database_file")) {
+        open(config->get("database_file")->stringValue());
+    } else {
+        isc_throw(DataSourceError, "No SQLite database file specified");
+    }
+}
+
+namespace {
+
+// This is a helper class to initialize a Sqlite3 DB safely.  An object of
+// this class encapsulates all temporary resources that are necessary for
+// the initialization, and release them in the destructor.  Once everything
+// is properly initialized, the move() method moves the allocated resources
+// to the main object in an exception free manner.  This way, the main code
+// for the initialization can be exception safe, and can provide the strong
+// exception guarantee.
+class Initializer {
+public:
+    ~Initializer() {
+        if (params_.q_zone_ != NULL) {
+            sqlite3_finalize(params_.q_zone_);
+        }
+        /*
+        if (params_.q_record_ != NULL) {
+            sqlite3_finalize(params_.q_record_);
+        }
+        if (params_.q_addrs_ != NULL) {
+            sqlite3_finalize(params_.q_addrs_);
+        }
+        if (params_.q_referral_ != NULL) {
+            sqlite3_finalize(params_.q_referral_);
+        }
+        if (params_.q_any_ != NULL) {
+            sqlite3_finalize(params_.q_any_);
+        }
+        if (params_.q_count_ != NULL) {
+            sqlite3_finalize(params_.q_count_);
+        }
+        if (params_.q_previous_ != NULL) {
+            sqlite3_finalize(params_.q_previous_);
+        }
+        if (params_.q_nsec3_ != NULL) {
+            sqlite3_finalize(params_.q_nsec3_);
+        }
+        if (params_.q_prevnsec3_ != NULL) {
+            sqlite3_finalize(params_.q_prevnsec3_);
+        }
+        */
+        if (params_.db_ != NULL) {
+            sqlite3_close(params_.db_);
+        }
+    }
+    void move(SQLite3Parameters* dst) {
+        *dst = params_;
+        params_ = SQLite3Parameters(); // clear everything
+    }
+    SQLite3Parameters params_;
+};
+
+const char* const SCHEMA_LIST[] = {
+    "CREATE TABLE schema_version (version INTEGER NOT NULL)",
+    "INSERT INTO schema_version VALUES (1)",
+    "CREATE TABLE zones (id INTEGER PRIMARY KEY, "
+    "name STRING NOT NULL COLLATE NOCASE, "
+    "rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN', "
+    "dnssec BOOLEAN NOT NULL DEFAULT 0)",
+    "CREATE INDEX zones_byname ON zones (name)",
+    "CREATE TABLE records (id INTEGER PRIMARY KEY, "
+    "zone_id INTEGER NOT NULL, name STRING NOT NULL COLLATE NOCASE, "
+    "rname STRING NOT NULL COLLATE NOCASE, ttl INTEGER NOT NULL, "
+    "rdtype STRING NOT NULL COLLATE NOCASE, sigtype STRING COLLATE NOCASE, "
+    "rdata STRING NOT NULL)",
+    "CREATE INDEX records_byname ON records (name)",
+    "CREATE INDEX records_byrname ON records (rname)",
+    "CREATE TABLE nsec3 (id INTEGER PRIMARY KEY, zone_id INTEGER NOT NULL, "
+    "hash STRING NOT NULL COLLATE NOCASE, "
+    "owner STRING NOT NULL COLLATE NOCASE, "
+    "ttl INTEGER NOT NULL, rdtype STRING NOT NULL COLLATE NOCASE, "
+    "rdata STRING NOT NULL)",
+    "CREATE INDEX nsec3_byhash ON nsec3 (hash)",
+    NULL
+};
+
+const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1";
+
+/* TODO: Prune the statements, not everything will be needed maybe?
+const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
+    "FROM records WHERE zone_id=?1 AND name=?2 AND "
+    "((rdtype=?3 OR sigtype=?3) OR "
+    "(rdtype='CNAME' OR sigtype='CNAME') OR "
+    "(rdtype='NS' OR sigtype='NS'))";
+
+const char* const q_addrs_str = "SELECT rdtype, ttl, sigtype, rdata "
+    "FROM records WHERE zone_id=?1 AND name=?2 AND "
+    "(rdtype='A' OR sigtype='A' OR rdtype='AAAA' OR sigtype='AAAA')";
+
+const char* const q_referral_str = "SELECT rdtype, ttl, sigtype, rdata FROM "
+    "records WHERE zone_id=?1 AND name=?2 AND"
+    "(rdtype='NS' OR sigtype='NS' OR rdtype='DS' OR sigtype='DS' OR "
+    "rdtype='DNAME' OR sigtype='DNAME')";
+
+const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
+    "FROM records WHERE zone_id=?1 AND name=?2";
+
+const char* const q_count_str = "SELECT COUNT(*) FROM records "
+    "WHERE zone_id=?1 AND rname LIKE (?2 || '%');";
+
+const char* const q_previous_str = "SELECT name FROM records "
+    "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
+    "rname < $2 ORDER BY rname DESC LIMIT 1";
+
+const char* const q_nsec3_str = "SELECT rdtype, ttl, rdata FROM nsec3 "
+    "WHERE zone_id = ?1 AND hash = $2";
+
+const char* const q_prevnsec3_str = "SELECT hash FROM nsec3 "
+    "WHERE zone_id = ?1 AND hash <= $2 ORDER BY hash DESC LIMIT 1";
+    */
+
+sqlite3_stmt*
+prepare(sqlite3* const db, const char* const statement) {
+    sqlite3_stmt* prepared = NULL;
+    if (sqlite3_prepare_v2(db, statement, -1, &prepared, NULL) != SQLITE_OK) {
+        isc_throw(SQLite3Error, "Could not prepare SQLite statement: " <<
+                  statement);
+    }
+    return (prepared);
+}
+
+void
+checkAndSetupSchema(Initializer* initializer) {
+    sqlite3* const db = initializer->params_.db_;
+
+    sqlite3_stmt* prepared = NULL;
+    if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
+                           &prepared, NULL) == SQLITE_OK &&
+        sqlite3_step(prepared) == SQLITE_ROW) {
+        initializer->params_.version_ = sqlite3_column_int(prepared, 0);
+        sqlite3_finalize(prepared);
+    } else {
+        logger.info(DATASRC_SQLITE_SETUP);
+        if (prepared != NULL) {
+            sqlite3_finalize(prepared);
+        }
+        for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
+            if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
+                SQLITE_OK) {
+                isc_throw(SQLite3Error,
+                          "Failed to set up schema " << SCHEMA_LIST[i]);
+            }
+        }
+    }
+
+    initializer->params_.q_zone_ = prepare(db, q_zone_str);
+    /* TODO: Yet unneeded statements
+    initializer->params_.q_record_ = prepare(db, q_record_str);
+    initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
+    initializer->params_.q_referral_ = prepare(db, q_referral_str);
+    initializer->params_.q_any_ = prepare(db, q_any_str);
+    initializer->params_.q_count_ = prepare(db, q_count_str);
+    initializer->params_.q_previous_ = prepare(db, q_previous_str);
+    initializer->params_.q_nsec3_ = prepare(db, q_nsec3_str);
+    initializer->params_.q_prevnsec3_ = prepare(db, q_prevnsec3_str);
+    */
+}
+
+}
+
+void
+SQLite3Connection::open(const std::string& name) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CONNOPEN).arg(name);
+    if (dbparameters_->db_ != NULL) {
+        // There shouldn't be a way to trigger this anyway
+        isc_throw(DataSourceError, "Duplicate SQLite open with " << name);
+    }
+
+    Initializer initializer;
+
+    if (sqlite3_open(name.c_str(), &initializer.params_.db_) != 0) {
+        isc_throw(SQLite3Error, "Cannot open SQLite database file: " << name);
+    }
+
+    checkAndSetupSchema(&initializer);
+    initializer.move(dbparameters_);
+}
+
+SQLite3Connection::~ SQLite3Connection() {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_DROPCONN);
+    if (dbparameters_->db_ != NULL) {
+        close();
+    }
+    delete dbparameters_;
+}
+
+void
+SQLite3Connection::close(void) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CONNCLOSE);
+    if (dbparameters_->db_ == NULL) {
+        isc_throw(DataSourceError,
+                  "SQLite data source is being closed before open");
+    }
+
+    // XXX: sqlite3_finalize() could fail.  What should we do in that case?
+    sqlite3_finalize(dbparameters_->q_zone_);
+    dbparameters_->q_zone_ = NULL;
+
+    /* TODO: Once they are needed or not, uncomment or drop
+    sqlite3_finalize(dbparameters->q_record_);
+    dbparameters->q_record_ = NULL;
+
+    sqlite3_finalize(dbparameters->q_addrs_);
+    dbparameters->q_addrs_ = NULL;
+
+    sqlite3_finalize(dbparameters->q_referral_);
+    dbparameters->q_referral_ = NULL;
+
+    sqlite3_finalize(dbparameters->q_any_);
+    dbparameters->q_any_ = NULL;
+
+    sqlite3_finalize(dbparameters->q_count_);
+    dbparameters->q_count_ = NULL;
+
+    sqlite3_finalize(dbparameters->q_previous_);
+    dbparameters->q_previous_ = NULL;
+
+    sqlite3_finalize(dbparameters->q_prevnsec3_);
+    dbparameters->q_prevnsec3_ = NULL;
+
+    sqlite3_finalize(dbparameters->q_nsec3_);
+    dbparameters->q_nsec3_ = NULL;
+    */
+
+    sqlite3_close(dbparameters_->db_);
+    dbparameters_->db_ = NULL;
+}
+
+std::pair<bool, int>
+SQLite3Connection::getZone(const isc::dns::Name&) const {
+    return std::pair<bool, int>(false, 0);
+}
+
+}
+}

+ 18 - 10
src/lib/datasrc/sqlite3_connection.h

@@ -18,23 +18,31 @@
 
 #include <datasrc/database.h>
 
-// TODO Once the whole SQLite3 thing is ported here, move the Sqlite3Error
-// here and remove the header file.
-#include <datasrc/sqlite3_datasrc.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
+#include <string>
 
 namespace isc {
 namespace datasrc {
 
-class SQLite3Connection : public DatabaseConnection {
+class SQLite3Error : public Exception {
 public:
-    // TODO Should we simplify this as well and just pass config to the
-    // constructor and be done? (whenever the config would change, we would
-    // recreate new connections)
-    Result init() { return (init(isc::data::ElementPtr())); }
-    Result init(const isc::data::ConstElementPtr& config);
-    Result close();
+    SQLite3Error(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
 
+struct SQLite3Parameters;
+
+class SQLite3Connection : public DatabaseConnection {
+public:
+    SQLite3Connection(const isc::data::ConstElementPtr& config);
+    ~ SQLite3Connection();
     virtual std::pair<bool, int> getZone(const isc::dns::Name& name) const;
+private:
+    SQLite3Parameters* dbparameters_;
+    void open(const std::string& filename);
+    void close();
 };
 
 }

+ 1 - 0
src/lib/datasrc/tests/Makefile.am

@@ -29,6 +29,7 @@ run_unittests_SOURCES += zonetable_unittest.cc
 run_unittests_SOURCES += memory_datasrc_unittest.cc
 run_unittests_SOURCES += logger_unittest.cc
 run_unittests_SOURCES += database_unittest.cc
+run_unittests_SOURCES += sqlite3_connection_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)

+ 70 - 0
src/lib/datasrc/tests/sqlite3_connection_unittest.cc

@@ -0,0 +1,70 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/sqlite3_connection.h>
+#include <datasrc/data_source.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::datasrc;
+using isc::data::ConstElementPtr;
+using isc::data::Element;
+
+namespace {
+// Some test data
+ConstElementPtr SQLITE_DBFILE_EXAMPLE = Element::fromJSON(
+    "{ \"database_file\": \"" TEST_DATA_DIR "/test.sqlite3\"}");
+ConstElementPtr SQLITE_DBFILE_EXAMPLE2 = Element::fromJSON(
+    "{ \"database_file\": \"" TEST_DATA_DIR "/example2.com.sqlite3\"}");
+ConstElementPtr SQLITE_DBFILE_EXAMPLE_ROOT = Element::fromJSON(
+    "{ \"database_file\": \"" TEST_DATA_DIR "/test-root.sqlite3\"}");
+ConstElementPtr SQLITE_DBFILE_BROKENDB = Element::fromJSON(
+    "{ \"database_file\": \"" TEST_DATA_DIR "/brokendb.sqlite3\"}");
+ConstElementPtr SQLITE_DBFILE_MEMORY = Element::fromJSON(
+    "{ \"database_file\": \":memory:\"}");
+
+// The following file must be non existent and must be non"creatable";
+// the sqlite3 library will try to create a new DB file if it doesn't exist,
+// so to test a failure case the create operation should also fail.
+// The "nodir", a non existent directory, is inserted for this purpose.
+ConstElementPtr SQLITE_DBFILE_NOTEXIST = Element::fromJSON(
+    "{ \"database_file\": \"" TEST_DATA_DIR "/nodir/notexist\"}");
+
+// Opening works (the content is tested in different tests)
+TEST(SQLite3Open, common) {
+    EXPECT_NO_THROW(SQLite3Connection conn(SQLITE_DBFILE_EXAMPLE));
+}
+
+// Missing config
+TEST(SQLite3Open, noConfig) {
+    EXPECT_THROW(SQLite3Connection conn(Element::fromJSON("{}")),
+                 DataSourceError);
+}
+
+// The file can't be opened
+TEST(SQLite3Open, notExist) {
+    EXPECT_THROW(SQLite3Connection conn(SQLITE_DBFILE_NOTEXIST), SQLite3Error);
+}
+
+// It rejects broken DB
+TEST(SQLite3Open, brokenDB) {
+    EXPECT_THROW(SQLite3Connection conn(SQLITE_DBFILE_BROKENDB), SQLite3Error);
+}
+
+// Test we can create the schema on the fly
+TEST(SQLite3Open, memoryDB) {
+    EXPECT_NO_THROW(SQLite3Connection conn(SQLITE_DBFILE_MEMORY));
+}
+
+}