Commit 38d1a8aa authored by Jelte Jansen's avatar Jelte Jansen
Browse files

[326] tests and implementation in refactored code

parent 4579a2a9
......@@ -21,6 +21,8 @@
#include <boost/lexical_cast.hpp>
#define SQLITE_SCHEMA_VERSION 1
namespace isc {
namespace datasrc {
......@@ -136,6 +138,8 @@ const char* const SCHEMA_LIST[] = {
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_any_str = "SELECT rdtype, ttl, sigtype, rdata, name "
......@@ -185,29 +189,90 @@ prepare(sqlite3* const db, const char* const statement) {
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;
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) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
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_any_ = prepare(db, q_any_str);
......
......@@ -679,7 +679,7 @@ int check_schema_version(sqlite3* db) {
if (rc == SQLITE_ERROR) {
// this is the error that is returned when the table does not
// exist
return -1;
return (-1);
} else if (rc == SQLITE_OK) {
break;
} else if (rc != SQLITE_BUSY || i == 50) {
......@@ -694,7 +694,7 @@ int check_schema_version(sqlite3* db) {
}
int version = sqlite3_column_int(prepared, 0);
sqlite3_finalize(prepared);
return version;
return (version);
}
// return db version
......@@ -726,9 +726,9 @@ int create_database(sqlite3* db) {
}
}
sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
return SQLITE_SCHEMA_VERSION;
return (SQLITE_SCHEMA_VERSION);
} else {
return schema_version;
return (schema_version);
}
}
......
......@@ -3,6 +3,7 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILD_DIR=\"$(builddir)/testdata\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
......
......@@ -19,6 +19,8 @@
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
#include <fstream>
#include <sqlite3.h>
using namespace isc::datasrc;
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.
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)
TEST(SQLite3Open, common) {
EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_EXAMPLE,
......@@ -291,4 +297,63 @@ TEST_F(SQLite3Access, getRecords) {
"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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment