Browse Source

[326] tests and implementation in refactored code

Jelte Jansen 13 years ago
parent
commit
38d1a8aa94

+ 78 - 13
src/lib/datasrc/sqlite3_accessor.cc

@@ -21,6 +21,8 @@
 
 
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 
 
+#define SQLITE_SCHEMA_VERSION 1
+
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
 
 
@@ -136,6 +138,8 @@ const char* const SCHEMA_LIST[] = {
     NULL
     NULL
 };
 };
 
 
+const char* const q_version_str = "SELECT version FROM schema_version";
+
 const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
 const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
 
 
 const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata, name "
 const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata, name "
@@ -185,29 +189,90 @@ prepare(sqlite3* const db, const char* const statement) {
     return (prepared);
     return (prepared);
 }
 }
 
 
-void
-checkAndSetupSchema(Initializer* initializer) {
-    sqlite3* const db = initializer->params_.db_;
+// small function to sleep for 0.1 seconds, needed when waiting for
+// exclusive database locks (which should only occur on startup, and only
+// when the database has not been created yet)
+void do_sleep() {
+    struct timespec req;
+    req.tv_sec = 0;
+    req.tv_nsec = 100000000;
+    nanosleep(&req, NULL);
+}
 
 
+// returns the schema version if the schema version table exists
+// returns -1 if it does not
+int check_schema_version(sqlite3* db) {
     sqlite3_stmt* prepared = NULL;
     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);
+    // At this point in time, the database might be exclusively locked, in
+    // which case even prepare() will return BUSY, so we may need to try a
+    // few times
+    for (size_t i = 0; i < 50; ++i) {
+        int rc = sqlite3_prepare_v2(db, q_version_str, -1, &prepared, NULL);
+        if (rc == SQLITE_ERROR) {
+            // this is the error that is returned when the table does not
+            // exist
+            return (-1);
+        } else if (rc == SQLITE_OK) {
+            break;
+        } else if (rc != SQLITE_BUSY || i == 50) {
+            isc_throw(SQLite3Error, "Unable to prepare version query: "
+                        << rc << " " << sqlite3_errmsg(db));
+        }
+        do_sleep();
+    }
+    if (sqlite3_step(prepared) != SQLITE_ROW) {
+        isc_throw(SQLite3Error,
+                    "Unable to query version: " << sqlite3_errmsg(db));
+    }
+    int version = sqlite3_column_int(prepared, 0);
+    sqlite3_finalize(prepared);
+    return (version);
+}
+
+// return db version
+int create_database(sqlite3* db) {
+    // try to get an exclusive lock. Once that is obtained, do the version
+    // check *again*, just in case this process was racing another
+    //
+    // try for 5 secs (50*0.1)
+    int rc;
+    logger.info(DATASRC_SQLITE_SETUP);
+    for (size_t i = 0; i < 50; ++i) {
+        rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
+                            NULL);
+        if (rc == SQLITE_OK) {
+            break;
+        } else if (rc != SQLITE_BUSY || i == 50) {
+            isc_throw(SQLite3Error, "Unable to acquire exclusive lock "
+                        "for database creation: " << sqlite3_errmsg(db));
         }
         }
+        do_sleep();
+    }
+    int schema_version = check_schema_version(db);
+    if (schema_version == -1) {
         for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
         for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
             if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
             if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
                 SQLITE_OK) {
                 SQLITE_OK) {
                 isc_throw(SQLite3Error,
                 isc_throw(SQLite3Error,
-                          "Failed to set up schema " << SCHEMA_LIST[i]);
+                        "Failed to set up schema " << SCHEMA_LIST[i]);
             }
             }
         }
         }
+        sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
+        return (SQLITE_SCHEMA_VERSION);
+    } else {
+        return (schema_version);
+    }
+}
+
+void
+checkAndSetupSchema(Initializer* initializer) {
+    sqlite3* const db = initializer->params_.db_;
+
+    int schema_version = check_schema_version(db);
+    if (schema_version != SQLITE_SCHEMA_VERSION) {
+        schema_version = create_database(db);
     }
     }
+    initializer->params_.version_ = schema_version;
 
 
     initializer->params_.q_zone_ = prepare(db, q_zone_str);
     initializer->params_.q_zone_ = prepare(db, q_zone_str);
     initializer->params_.q_any_ = prepare(db, q_any_str);
     initializer->params_.q_any_ = prepare(db, q_any_str);

+ 4 - 4
src/lib/datasrc/sqlite3_datasrc.cc

@@ -679,7 +679,7 @@ int check_schema_version(sqlite3* db) {
         if (rc == SQLITE_ERROR) {
         if (rc == SQLITE_ERROR) {
             // this is the error that is returned when the table does not
             // this is the error that is returned when the table does not
             // exist
             // exist
-            return -1;
+            return (-1);
         } else if (rc == SQLITE_OK) {
         } else if (rc == SQLITE_OK) {
             break;
             break;
         } else if (rc != SQLITE_BUSY || i == 50) {
         } else if (rc != SQLITE_BUSY || i == 50) {
@@ -694,7 +694,7 @@ int check_schema_version(sqlite3* db) {
     }
     }
     int version = sqlite3_column_int(prepared, 0);
     int version = sqlite3_column_int(prepared, 0);
     sqlite3_finalize(prepared);
     sqlite3_finalize(prepared);
-    return version;
+    return (version);
 }
 }
 
 
 // return db version
 // return db version
@@ -726,9 +726,9 @@ int create_database(sqlite3* db) {
             }
             }
         }
         }
         sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
         sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
-        return SQLITE_SCHEMA_VERSION;
+        return (SQLITE_SCHEMA_VERSION);
     } else {
     } else {
-        return schema_version;
+        return (schema_version);
     }
     }
 }
 }
 
 

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

@@ -3,6 +3,7 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(SQLITE_CFLAGS)
 AM_CPPFLAGS += $(SQLITE_CFLAGS)
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILD_DIR=\"$(builddir)/testdata\"
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 

+ 65 - 0
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc

@@ -19,6 +19,8 @@
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
+#include <fstream>
+#include <sqlite3.h>
 
 
 using namespace isc::datasrc;
 using namespace isc::datasrc;
 using isc::data::ConstElementPtr;
 using isc::data::ConstElementPtr;
@@ -42,6 +44,10 @@ std::string SQLITE_DBFILE_MEMORY = ":memory:";
 // The "nodir", a non existent directory, is inserted for this purpose.
 // The "nodir", a non existent directory, is inserted for this purpose.
 std::string SQLITE_DBFILE_NOTEXIST = TEST_DATA_DIR "/nodir/notexist";
 std::string SQLITE_DBFILE_NOTEXIST = TEST_DATA_DIR "/nodir/notexist";
 
 
+// new db file, we don't need this to be a std::string, and given the
+// raw calls we use it in a const char* is more convenient
+const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILD_DIR "/newdb.sqlite3";
+
 // Opening works (the content is tested in different tests)
 // Opening works (the content is tested in different tests)
 TEST(SQLite3Open, common) {
 TEST(SQLite3Open, common) {
     EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_EXAMPLE,
     EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_EXAMPLE,
@@ -291,4 +297,63 @@ TEST_F(SQLite3Access, getRecords) {
                    "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
                    "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
 }
 }
 
 
+// Test fixture for creating a db that automatically deletes it before start,
+// and when done
+class SQLite3Create : public ::testing::Test {
+public:
+    SQLite3Create() {
+        remove(SQLITE_NEW_DBFILE);
+    }
+
+    ~SQLite3Create() {
+        remove(SQLITE_NEW_DBFILE);
+    }
+};
+
+bool exists(const char* filename) {
+    std::ifstream f(filename);
+    return (f != NULL);
+}
+
+TEST_F(SQLite3Create, creationtest) {
+    ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
+    // Should simply be created
+    SQLite3Database db(SQLITE_NEW_DBFILE, RRClass::IN());
+    ASSERT_TRUE(exists(SQLITE_NEW_DBFILE));
+}
+
+TEST_F(SQLite3Create, emptytest) {
+    ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
+
+    // open one manualle
+    sqlite3* db;
+    ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
+
+    // empty, but not locked, so creating it now should work
+    SQLite3Database db2(SQLITE_NEW_DBFILE, RRClass::IN());
+
+    sqlite3_close(db);
+
+    // should work now that we closed it
+    SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
+}
+
+TEST_F(SQLite3Create, lockedtest) {
+    ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
+
+    // open one manually
+    sqlite3* db;
+    ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
+    sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
+
+    // should not be able to open it
+    EXPECT_THROW(SQLite3Database db2(SQLITE_NEW_DBFILE, RRClass::IN()),
+                 SQLite3Error);
+
+    sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+
+    // should work now that we closed it
+    SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
+}
+
 } // end anonymous namespace
 } // end anonymous namespace