Browse Source

provided the strong exception guarantee in DB initialization.

git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@1388 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 15 years ago
parent
commit
c600aab418
3 changed files with 270 additions and 335 deletions
  1. 266 309
      src/lib/auth/sqlite3_datasrc.cc
  2. 4 24
      src/lib/auth/sqlite3_datasrc.h
  3. 0 2
      src/lib/auth/tests/sqlite3_unittest.cc

+ 266 - 309
src/lib/auth/sqlite3_datasrc.cc

@@ -17,6 +17,8 @@
 #include <string>
 #include <sstream>
 
+#include <sqlite3.h>
+
 #include "sqlite3_datasrc.h"
 
 #include <dns/rrttl.h>
@@ -25,8 +27,6 @@
 #include <dns/rrset.h>
 #include <dns/rrsetlist.h>
 
-#include <iostream>
-
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
@@ -34,25 +34,78 @@ using namespace isc::dns::rdata;
 namespace isc {
 namespace auth {
 
+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_;
+    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_;
+};
+
 namespace {
 // Note: this cannot be std::string to avoid
 // "static initialization order fiasco".
 const char* DEFAULT_DB_FILE = "/tmp/zone.sqlite3";
-}
 
-//
-//  Prepare a statement.  Can call release() or sqlite3_finalize()
-//  directly.
-//
-sqlite3_stmt*
-Sqlite3DataSrc::prepare(const char* statement) {
-    sqlite3_stmt* prepared = NULL;
+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";
+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";
 
-    if (sqlite3_prepare_v2(db, statement, -1, &prepared, NULL) != SQLITE_OK) { 
-        isc_throw(Sqlite3Error, "could not prepare sqlite3 statement: " <<
-                  statement);
-    }
-    return (prepared);
 }
 
 //
@@ -64,17 +117,6 @@ Sqlite3DataSrc::release(sqlite3_stmt* prepared) {
 }
 
 //
-//  Get the database schema version.
-//
-int
-Sqlite3DataSrc::getVersion(void) {
-    if (database_version == -1) {
-        loadVersion();
-    }
-    return (database_version);
-}
-
-//
 //  Find the exact zone match.  Return -1 if not found, or the zone's
 //  ID if found.  This will always be >= 0 if found.
 //
@@ -82,16 +124,17 @@ int
 Sqlite3DataSrc::hasExactZone(const char* name) const {
     int rc;
 
-    sqlite3_reset(q_zone);
-    rc = sqlite3_bind_text(q_zone, 1, name, -1, SQLITE_STATIC);
+    sqlite3_reset(dbparameters->q_zone_);
+    rc = sqlite3_bind_text(dbparameters->q_zone_, 1, name, -1, SQLITE_STATIC);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind " << name <<
                   " to SQL statement (zone)");
     }
 
-    rc = sqlite3_step(q_zone);
-    const int i = (rc == SQLITE_ROW) ? sqlite3_column_int(q_zone, 0) : -1; 
-    sqlite3_reset(q_zone);
+    rc = sqlite3_step(dbparameters->q_zone_);
+    const int i = (rc == SQLITE_ROW) ?
+        sqlite3_column_int(dbparameters->q_zone_, 0) : -1; 
+    sqlite3_reset(dbparameters->q_zone_);
     return (i);
 }
 
@@ -200,16 +243,16 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
     sqlite3_stmt* query;
     switch (mode) {
     case ADDRESS:
-        query = q_addrs;
+        query = dbparameters->q_addrs_;
         break;
     case DELEGATION:
-        query = q_referral;
+        query = dbparameters->q_referral_;
         break;
     default:
         if (rdtype == RRType::ANY()) {
-            query = q_any;
+            query = dbparameters->q_any_;
         } else {
-            query = q_record;
+            query = dbparameters->q_record_;
         }
         break;
     }
@@ -230,7 +273,7 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
                   " to SQL statement (query)");
     }
 
-    if (query == q_record) {
+    if (query == dbparameters->q_record_) {
         rc = sqlite3_bind_text(query, 3, rdtype.toText().c_str(), -1,
                                SQLITE_STATIC);
         if (rc != SQLITE_OK) {
@@ -251,33 +294,33 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
     // any RRs with that name to determine whether this is NXDOMAIN or
     // NXRRSET
     //
-    sqlite3_reset(q_count);
-    sqlite3_clear_bindings(q_count);
-
-    rc = sqlite3_bind_int(q_count, 1, zone_id);
+    sqlite3_reset(dbparameters->q_count_);
+    sqlite3_clear_bindings(dbparameters->q_count_);
+    
+    rc = sqlite3_bind_int(dbparameters->q_count_, 1, zone_id);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                   " to SQL statement (qcount)");
     }
 
-    rc = sqlite3_bind_text(q_count, 2, name.reverse().toText().c_str(), -1,
-                           SQLITE_STATIC);
+    rc = sqlite3_bind_text(dbparameters->q_count_, 2,
+                           name.reverse().toText().c_str(), -1, SQLITE_STATIC);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind name " << name.reverse() <<
                   " to SQL statement (qcount)");
     }
 
-    rc = sqlite3_step(q_count);
+    rc = sqlite3_step(dbparameters->q_count_);
     if (rc == SQLITE_ROW) {
-        if (sqlite3_column_int(q_count, 0) != 0) {
+        if (sqlite3_column_int(dbparameters->q_count_, 0) != 0) {
             flags |= TYPE_NOT_FOUND;
-            sqlite3_reset(q_count);
+            sqlite3_reset(dbparameters->q_count_);
             return (0);
         }
     }
 
     flags |= NAME_NOT_FOUND;
-    sqlite3_reset(q_count);
+    sqlite3_reset(dbparameters->q_count_);
     return (0);
 }
 
@@ -303,200 +346,6 @@ Sqlite3DataSrc::findClosest(const Name& name, unsigned int* position) const {
     return (-1);
 }
 
-
-void
-Sqlite3DataSrc::loadVersion(void) {
-    sqlite3_stmt* prepared = prepare("SELECT version FROM schema_version");
-    if (sqlite3_step(prepared) != SQLITE_ROW) {
-        isc_throw(Sqlite3Error, "Failed to find a row in schema_version table");
-    }
-    database_version = sqlite3_column_int(prepared, 0);
-    release(prepared);
-}
-
-void
-Sqlite3DataSrc::setupPreparedStatements(void) {
-
-    const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1";
-    try {
-        q_zone = prepare(q_zone_str);
-    } catch (const char* e) {
-        cout << e << endl << q_zone_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-
-    const char* 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'))";
-    try {
-        q_record = prepare(q_record_str);
-    } catch (const char* e) {
-        cout << e << endl << q_record_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-
-    const char* 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')";
-    try {
-        q_addrs = prepare(q_addrs_str);
-    } catch (const char* e) {
-        cout << e << endl << q_addrs_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-    const char* 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')";
-    try {
-        q_referral = prepare(q_referral_str);
-    } catch (const char* e) {
-        cout << e << endl << q_referral_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-    const char* q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
-                             "FROM records WHERE zone_id=?1 AND name=?2";
-    try {
-        q_any = prepare(q_any_str);
-    } catch (const char* e) {
-        cout << e << endl << q_any_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-
-    const char* q_count_str = "SELECT COUNT(*) FROM records "
-                              "WHERE zone_id=?1 AND "
-                              "rname LIKE (?2 || '%');";
-    try {
-        q_count = prepare(q_count_str);
-    } catch (const char* e) {
-        cout << e << endl << q_count_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-
-    const char* q_previous_str = "SELECT name FROM records "
-                                 "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
-                                 "rname < $2 ORDER BY rname DESC LIMIT 1";
-    try {
-        q_previous = prepare(q_previous_str);
-    } catch (const char* e) {
-        cout << e << endl << q_previous_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-
-    const char* q_nsec3_str = "SELECT rdtype, ttl, rdata FROM nsec3 "
-                              "WHERE zone_id = ?1 AND hash = $2";
-    try {
-        q_nsec3 = prepare(q_nsec3_str);
-    } catch (const char* e) {
-        cout << e << endl << q_nsec3_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-
-    const char* q_prevnsec3_str = "SELECT hash FROM nsec3 "
-                                  "WHERE zone_id = ?1 AND hash <= $2 "
-                                  "ORDER BY hash DESC LIMIT 1";
-    try {
-        q_prevnsec3 = prepare(q_prevnsec3_str);
-    } catch (const char* e) {
-        cout << e << endl << q_prevnsec3_str << endl;
-        cout << sqlite3_errmsg(db) << endl;
-        throw(e);
-    }
-}
-
-void
-Sqlite3DataSrc::execSetupQuery(const char* const query) {
-    if (sqlite3_exec(db, query, NULL, NULL, NULL) != SQLITE_OK) {
-        isc_throw(Sqlite3Error, "Failed to find a row in schema_version table: "
-                  << query);
-    }
-}
-
-void
-Sqlite3DataSrc::checkAndSetupSchema(void) {
-    try {
-        loadVersion();
-        setupPreparedStatements();
-        // cout << "Loaded existing schema" << endl;
-    } catch(...) {
-        execSetupQuery("CREATE TABLE schema_version ("
-                          "version INTEGER NOT NULL)");
-        execSetupQuery("INSERT INTO schema_version VALUES (1)");
-        execSetupQuery("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)");
-        execSetupQuery("CREATE INDEX zones_byname ON zones (name)");
-        execSetupQuery("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)");
-        execSetupQuery("CREATE INDEX records_byname ON records (name)");
-        execSetupQuery("CREATE INDEX records_byrname ON records (rname)");
-        execSetupQuery("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)");
-        execSetupQuery("CREATE INDEX nsec3_byhash ON nsec3 (hash)");
-
-        setupPreparedStatements();
-        // cout << "Created new file and schema" << endl;
-    }
-}
-
-Sqlite3DataSrc::Sqlite3DataSrc() :
-    db(NULL)
-{
-    database_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;
-}
-
-Sqlite3DataSrc::~Sqlite3DataSrc() {
-    if (db != NULL) {
-        close();
-    }
-}
-
-DataSrc::Result
-Sqlite3DataSrc::init(const isc::data::ElementPtr config) {
-    if (config && config->contains("database_file")) {
-        open(config->get("database_file")->stringValue());
-    } else {
-        open(DEFAULT_DB_FILE);
-    }
-    return (SUCCESS);
-}
-
 void
 Sqlite3DataSrc::findClosestEnclosure(NameMatch& match,
                                      const RRClass& qclass) const
@@ -526,30 +375,31 @@ Sqlite3DataSrc::findPreviousName(const Name& qname,
         return (ERROR);
     }
     
-    sqlite3_reset(q_previous);
-    sqlite3_clear_bindings(q_previous);
+    sqlite3_reset(dbparameters->q_previous_);
+    sqlite3_clear_bindings(dbparameters->q_previous_);
 
-    int rc = sqlite3_bind_int(q_previous, 1, zone_id);
+    int rc = sqlite3_bind_int(dbparameters->q_previous_, 1, zone_id);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                   " to SQL statement (qprevious)");        
     }
-    rc = sqlite3_bind_text(q_previous, 2, qname.reverse().toText().c_str(),
-                           -1, SQLITE_STATIC);
+    rc = sqlite3_bind_text(dbparameters->q_previous_, 2,
+                           qname.reverse().toText().c_str(), -1, SQLITE_STATIC);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind name " << qname <<
                   " to SQL statement (qprevious)");
     }
   
-    rc = sqlite3_step(q_previous);
+    rc = sqlite3_step(dbparameters->q_previous_);
     if (rc != SQLITE_ROW) {
-        sqlite3_reset(q_previous);
+        sqlite3_reset(dbparameters->q_previous_);
         return (ERROR);
     }
 
     // XXX: bad cast.  we should revisit this.
-    target = Name((const char*)sqlite3_column_text(q_previous, 0));
-    sqlite3_reset(q_previous);
+    target = Name((const char*)sqlite3_column_text(dbparameters->q_previous_,
+                                                   0));
+    sqlite3_reset(dbparameters->q_previous_);
     return (SUCCESS);
 }
 
@@ -563,55 +413,57 @@ Sqlite3DataSrc::findCoveringNSEC3(const Name& zonename,
         return (ERROR);
     }
 
-    sqlite3_reset(q_prevnsec3);
-    sqlite3_clear_bindings(q_prevnsec3);
+    sqlite3_reset(dbparameters->q_prevnsec3_);
+    sqlite3_clear_bindings(dbparameters->q_prevnsec3_);
 
-    int rc = sqlite3_bind_int(q_prevnsec3, 1, zone_id);
+    int rc = sqlite3_bind_int(dbparameters->q_prevnsec3_, 1, zone_id);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                   " to SQL statement (previous NSEC3)");        
     }
 
-    rc = sqlite3_bind_text(q_prevnsec3, 2, hashstr.c_str(), -1, SQLITE_STATIC);
+    rc = sqlite3_bind_text(dbparameters->q_prevnsec3_, 2, hashstr.c_str(),
+                           -1, SQLITE_STATIC);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind hash " << hashstr <<
                   " to SQL statement (previous NSEC3)");
     }
 
-    rc = sqlite3_step(q_prevnsec3);
+    rc = sqlite3_step(dbparameters->q_prevnsec3_);
     const char* hash;
     if (rc == SQLITE_ROW) {
-        hash = (const char*) sqlite3_column_text(q_prevnsec3, 0);
+        hash = (const char*) sqlite3_column_text(dbparameters->q_prevnsec3_, 0);
     } else {
         // We need to find the final NSEC3 in the chain.
         // A valid NSEC3 hash is in base32, which contains no
         // letters higher than V, so a search for the previous 
         // NSEC3 from "w" will always find it.
-        sqlite3_reset(q_prevnsec3);
-        rc = sqlite3_bind_text(q_prevnsec3, 2, "w", -1, SQLITE_STATIC);
+        sqlite3_reset(dbparameters->q_prevnsec3_);
+        rc = sqlite3_bind_text(dbparameters->q_prevnsec3_, 2, "w", -1,
+                               SQLITE_STATIC);
         if (rc != SQLITE_OK) {
             isc_throw(Sqlite3Error, "Could not bind \"w\""
                       " to SQL statement (previous NSEC3)");
         }
 
-        rc = sqlite3_step(q_prevnsec3);
+        rc = sqlite3_step(dbparameters->q_prevnsec3_);
         if (rc != SQLITE_ROW) {
             return (ERROR);
         }
 
-        hash = (const char*) sqlite3_column_text(q_prevnsec3, 0);
+        hash = (const char*) sqlite3_column_text(dbparameters->q_prevnsec3_, 0);
     }
 
-    sqlite3_reset(q_nsec3);
-    sqlite3_clear_bindings(q_nsec3);
+    sqlite3_reset(dbparameters->q_nsec3_);
+    sqlite3_clear_bindings(dbparameters->q_nsec3_);
 
-    rc = sqlite3_bind_int(q_nsec3, 1, zone_id);
+    rc = sqlite3_bind_int(dbparameters->q_nsec3_, 1, zone_id);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
                   " to SQL statement (NSEC3)");        
     }
 
-    rc = sqlite3_bind_text(q_nsec3, 2, hash, -1, SQLITE_STATIC);
+    rc = sqlite3_bind_text(dbparameters->q_nsec3_, 2, hash, -1, SQLITE_STATIC);
     if (rc != SQLITE_OK) {
         isc_throw(Sqlite3Error, "Could not bind hash " << hash <<
                   " to SQL statement (NSEC3)");
@@ -619,13 +471,14 @@ Sqlite3DataSrc::findCoveringNSEC3(const Name& zonename,
 
     DataSrc::Result result = SUCCESS;
     uint32_t flags = 0;
-    if (importSqlite3Rows(q_nsec3, Name(hash).concatenate(zonename),
+    if (importSqlite3Rows(dbparameters->q_nsec3_,
+                          Name(hash).concatenate(zonename),
                           getClass(), RRType::NSEC3(), true, target,
                           flags) == 0 || flags != 0) {
         result = ERROR;
     }
     hashstr = string(hash);
-    sqlite3_reset(q_nsec3);
+    sqlite3_reset(dbparameters->q_nsec3_);
     return (result);
 }
 
@@ -684,78 +537,182 @@ Sqlite3DataSrc::findReferral(const Name& qname,
     findRecords(qname, RRType::ANY(), target, zonename, DELEGATION, flags);
     return (SUCCESS);
 }
+
+Sqlite3DataSrc::Sqlite3DataSrc() :
+    dbparameters(new Sqlite3Parameters)
+{}
+
+Sqlite3DataSrc::~Sqlite3DataSrc() {
+    if (dbparameters->db_ != NULL) {
+        close();
+    }
+    delete dbparameters;
+}
+
+DataSrc::Result
+Sqlite3DataSrc::init(const isc::data::ElementPtr config) {
+    if (config && config->contains("database_file")) {
+        open(config->get("database_file")->stringValue());
+    } else {
+        open(DEFAULT_DB_FILE);
+    }
+    return (SUCCESS);
+}
+
+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 Sqlite3Initializer {
+public:
+    ~Sqlite3Initializer() {
+        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_;
+};
+
+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 sqlite3 statement: " <<
+                  statement);
+    }
+    return (prepared);
+}
+
+void
+checkAndSetupSchema(Sqlite3Initializer* 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 {
+        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);
+    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);
+}
+}
+
 //
 //  Open the database.
 //
 void
 Sqlite3DataSrc::open(const string& name) {
-    if (db != NULL) {
+    if (dbparameters->db_ != NULL) {
         isc_throw(DataSourceError, "Duplicate Sqlite3 open with " << name);
     }
-    if (sqlite3_open(name.c_str(), &db) != 0) {
-        // sqlite3_close() must be called even when open fails.
-        sqlite3_close(db);
+
+    Sqlite3Initializer initializer;
+
+    if (sqlite3_open(name.c_str(), &initializer.params_.db_) != 0) {
         isc_throw(Sqlite3Error, "Cannot open Sqlite3 database file: " << name);
     }
 
-    checkAndSetupSchema();
+    checkAndSetupSchema(&initializer);
+    initializer.move(dbparameters);
 }
 
+//
+//  Close the database.
+//
 DataSrc::Result
 Sqlite3DataSrc::close(void) {
-    if (db == NULL) {
+    if (dbparameters->db_ == NULL) {
         isc_throw(DataSourceError,
                   "Sqlite3 data source is being closed before open");
     }
 
-    if (q_zone != NULL) {
-        release(q_zone);
-        q_zone = NULL;
-    }
+    // XXX: sqlite3_finalize() could fail.  What should we do in that case?
+    sqlite3_finalize(dbparameters->q_zone_);
+    dbparameters->q_zone_ = NULL;
 
-    if (q_record) {
-        release(q_record);
-        q_record = NULL;
-    }
+    sqlite3_finalize(dbparameters->q_record_);
+    dbparameters->q_record_ = NULL;
 
-    if (q_addrs) {
-        release(q_addrs);
-        q_addrs = NULL;
-    }
+    sqlite3_finalize(dbparameters->q_addrs_);
+    dbparameters->q_addrs_ = NULL;
 
-    if (q_referral) {
-        release(q_referral);
-        q_referral = NULL;
-    }
+    sqlite3_finalize(dbparameters->q_referral_);
+    dbparameters->q_referral_ = NULL;
 
-    if (q_any) {
-        release(q_any);
-        q_any = NULL;
-    }
+    sqlite3_finalize(dbparameters->q_any_);
+    dbparameters->q_any_ = NULL;
 
-    if (q_count) {
-        release(q_count);
-        q_count = NULL;
-    }
+    sqlite3_finalize(dbparameters->q_count_);
+    dbparameters->q_count_ = NULL;
 
-    if (q_previous) {
-        release(q_previous);
-        q_previous = NULL;
-    }
+    sqlite3_finalize(dbparameters->q_previous_);
+    dbparameters->q_previous_ = NULL;
 
-    if (q_prevnsec3) {
-        release(q_prevnsec3);
-        q_prevnsec3 = NULL;
-    }
+    sqlite3_finalize(dbparameters->q_prevnsec3_);
+    dbparameters->q_prevnsec3_ = NULL;
 
-    if (q_nsec3) {
-        release(q_nsec3);
-        q_nsec3 = NULL;
-    }
+    sqlite3_finalize(dbparameters->q_nsec3_);
+    dbparameters->q_nsec3_ = NULL;
 
-    sqlite3_close(db);
+    sqlite3_close(dbparameters->db_);
+    dbparameters->db_ = NULL;
 
-    db = NULL;
     return (SUCCESS);
 }
 

+ 4 - 24
src/lib/auth/sqlite3_datasrc.h

@@ -21,8 +21,6 @@
 
 #include <exceptions/exceptions.h>
 
-#include <sqlite3.h>
-
 #include "data_source.h"
 
 namespace isc {
@@ -37,6 +35,7 @@ class RRsetList;
 namespace auth {
 
 class Query;
+struct Sqlite3Parameters;
 
 class Sqlite3Error : public Exception {
 public:
@@ -108,34 +107,15 @@ private:
     };
 
     void open(const std::string& name);
-    sqlite3_stmt* prepare(const char *statement);
     void release(sqlite3_stmt* prepared);
-    int getVersion(void);
     int hasExactZone(const char *name) const;
     int findRecords(const isc::dns::Name& name, const isc::dns::RRType& rdtype,
                     isc::dns::RRsetList& target, const isc::dns::Name* zonename,
                     const Mode mode, uint32_t& flags) const;
     int findClosest(const isc::dns::Name& name, unsigned int* position) const;
-    void loadVersion(void);
-    void setupPreparedStatements(void);
-    void execSetupQuery(const char *query);
-    void checkAndSetupSchema(void);
-
-    sqlite3 *db;
-    int database_version;
-    
-    //
-    // Prepared statements
-    //
-    sqlite3_stmt *q_zone;
-    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;
+
+private:
+    Sqlite3Parameters* dbparameters;
 };
 
 }

+ 0 - 2
src/lib/auth/tests/sqlite3_unittest.cc

@@ -382,7 +382,6 @@ TEST_F(Sqlite3DataSourceTest, doubleClose) {
     EXPECT_THROW(data_source.close(), DataSourceError);
 }
 
-#if 0                           // currently fails
 TEST_F(Sqlite3DataSourceTest, openBrokenDB) {
     EXPECT_EQ(DataSrc::SUCCESS, data_source.close());
     // The database exists but is broken.  An exception will be thrown 
@@ -392,7 +391,6 @@ TEST_F(Sqlite3DataSourceTest, openBrokenDB) {
     // in the closed state.
     EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE));
 }
-#endif
 
 // This test only confirms that on-the-fly schema creation works.
 TEST_F(Sqlite3DataSourceTest, memoryDB) {