|
@@ -11,6 +11,10 @@
|
|
|
// 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 <algorithm>
|
|
|
+#include <vector>
|
|
|
+
|
|
|
#include <datasrc/sqlite3_accessor.h>
|
|
|
|
|
|
#include <datasrc/data_source.h>
|
|
@@ -22,7 +26,9 @@
|
|
|
#include <fstream>
|
|
|
#include <sqlite3.h>
|
|
|
|
|
|
+using namespace std;
|
|
|
using namespace isc::datasrc;
|
|
|
+using boost::shared_ptr;
|
|
|
using isc::data::ConstElementPtr;
|
|
|
using isc::data::Element;
|
|
|
using isc::dns::RRClass;
|
|
@@ -47,82 +53,81 @@ 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";
|
|
|
+const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILDDIR "/newdb.sqlite3";
|
|
|
|
|
|
// Opening works (the content is tested in different tests)
|
|
|
TEST(SQLite3Open, common) {
|
|
|
- EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_EXAMPLE,
|
|
|
- RRClass::IN()));
|
|
|
+ EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE,
|
|
|
+ RRClass::IN()));
|
|
|
}
|
|
|
|
|
|
// The file can't be opened
|
|
|
TEST(SQLite3Open, notExist) {
|
|
|
- EXPECT_THROW(SQLite3Database db(SQLITE_DBFILE_NOTEXIST,
|
|
|
- RRClass::IN()), SQLite3Error);
|
|
|
+ EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST,
|
|
|
+ RRClass::IN()), SQLite3Error);
|
|
|
}
|
|
|
|
|
|
// It rejects broken DB
|
|
|
TEST(SQLite3Open, brokenDB) {
|
|
|
- EXPECT_THROW(SQLite3Database db(SQLITE_DBFILE_BROKENDB,
|
|
|
- RRClass::IN()), SQLite3Error);
|
|
|
+ EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB,
|
|
|
+ RRClass::IN()), SQLite3Error);
|
|
|
}
|
|
|
|
|
|
// Test we can create the schema on the fly
|
|
|
TEST(SQLite3Open, memoryDB) {
|
|
|
- EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_MEMORY,
|
|
|
- RRClass::IN()));
|
|
|
+ EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY,
|
|
|
+ RRClass::IN()));
|
|
|
}
|
|
|
|
|
|
// Test fixture for querying the db
|
|
|
-class SQLite3Access : public ::testing::Test {
|
|
|
+class SQLite3AccessorTest : public ::testing::Test {
|
|
|
public:
|
|
|
- SQLite3Access() {
|
|
|
+ SQLite3AccessorTest() {
|
|
|
initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::IN());
|
|
|
}
|
|
|
// So it can be re-created with different data
|
|
|
void initAccessor(const std::string& filename, const RRClass& rrclass) {
|
|
|
- db.reset(new SQLite3Database(filename, rrclass));
|
|
|
+ accessor.reset(new SQLite3Accessor(filename, rrclass));
|
|
|
}
|
|
|
- // The tested db
|
|
|
- boost::shared_ptr<SQLite3Database> db;
|
|
|
+ // The tested accessor
|
|
|
+ boost::shared_ptr<SQLite3Accessor> accessor;
|
|
|
};
|
|
|
|
|
|
// This zone exists in the data, so it should be found
|
|
|
-TEST_F(SQLite3Access, getZone) {
|
|
|
- std::pair<bool, int> result(db->getZone("example.com."));
|
|
|
+TEST_F(SQLite3AccessorTest, getZone) {
|
|
|
+ std::pair<bool, int> result(accessor->getZone("example.com."));
|
|
|
EXPECT_TRUE(result.first);
|
|
|
EXPECT_EQ(1, result.second);
|
|
|
}
|
|
|
|
|
|
// But it should find only the zone, nothing below it
|
|
|
-TEST_F(SQLite3Access, subZone) {
|
|
|
- EXPECT_FALSE(db->getZone("sub.example.com.").first);
|
|
|
+TEST_F(SQLite3AccessorTest, subZone) {
|
|
|
+ EXPECT_FALSE(accessor->getZone("sub.example.com.").first);
|
|
|
}
|
|
|
|
|
|
// This zone is not there at all
|
|
|
-TEST_F(SQLite3Access, noZone) {
|
|
|
- EXPECT_FALSE(db->getZone("example.org.").first);
|
|
|
+TEST_F(SQLite3AccessorTest, noZone) {
|
|
|
+ EXPECT_FALSE(accessor->getZone("example.org.").first);
|
|
|
}
|
|
|
|
|
|
// This zone is there, but in different class
|
|
|
-TEST_F(SQLite3Access, noClass) {
|
|
|
+TEST_F(SQLite3AccessorTest, noClass) {
|
|
|
initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::CH());
|
|
|
- EXPECT_FALSE(db->getZone("example.com.").first);
|
|
|
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
|
|
|
}
|
|
|
|
|
|
// This tests the iterator context
|
|
|
-TEST_F(SQLite3Access, iterator) {
|
|
|
+TEST_F(SQLite3AccessorTest, iterator) {
|
|
|
// Our test zone is conveniently small, but not empty
|
|
|
initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, RRClass::IN());
|
|
|
|
|
|
- const std::pair<bool, int> zone_info(db->getZone("example.org."));
|
|
|
+ const std::pair<bool, int> zone_info(accessor->getZone("example.org."));
|
|
|
ASSERT_TRUE(zone_info.first);
|
|
|
|
|
|
// Get the iterator context
|
|
|
DatabaseAccessor::IteratorContextPtr
|
|
|
- context(db->getAllRecords(zone_info.second));
|
|
|
- ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
|
|
|
- context);
|
|
|
+ context(accessor->getAllRecords(zone_info.second));
|
|
|
+ ASSERT_NE(DatabaseAccessor::IteratorContextPtr(), context);
|
|
|
|
|
|
std::string data[DatabaseAccessor::COLUMN_COUNT];
|
|
|
// Get and check the first and only record
|
|
@@ -202,13 +207,13 @@ TEST_F(SQLite3Access, iterator) {
|
|
|
}
|
|
|
|
|
|
TEST(SQLite3Open, getDBNameExample2) {
|
|
|
- SQLite3Database db(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
|
|
|
- EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, db.getDBName());
|
|
|
+ SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
|
|
|
+ EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, accessor.getDBName());
|
|
|
}
|
|
|
|
|
|
TEST(SQLite3Open, getDBNameExampleROOT) {
|
|
|
- SQLite3Database db(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
|
|
|
- EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, db.getDBName());
|
|
|
+ SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
|
|
|
+ EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, accessor.getDBName());
|
|
|
}
|
|
|
|
|
|
// Simple function to cound the number of records for
|
|
@@ -228,8 +233,8 @@ checkRecordRow(const std::string columns[],
|
|
|
EXPECT_EQ(field4, columns[DatabaseAccessor::NAME_COLUMN]);
|
|
|
}
|
|
|
|
|
|
-TEST_F(SQLite3Access, getRecords) {
|
|
|
- const std::pair<bool, int> zone_info(db->getZone("example.com."));
|
|
|
+TEST_F(SQLite3AccessorTest, getRecords) {
|
|
|
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
|
|
|
ASSERT_TRUE(zone_info.first);
|
|
|
|
|
|
const int zone_id = zone_info.second;
|
|
@@ -238,14 +243,14 @@ TEST_F(SQLite3Access, getRecords) {
|
|
|
std::string columns[DatabaseAccessor::COLUMN_COUNT];
|
|
|
|
|
|
DatabaseAccessor::IteratorContextPtr
|
|
|
- context(db->getRecords("foo.bar", 1));
|
|
|
+ context(accessor->getRecords("foo.bar", 1));
|
|
|
ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
|
|
|
context);
|
|
|
EXPECT_FALSE(context->getNext(columns));
|
|
|
checkRecordRow(columns, "", "", "", "", "");
|
|
|
|
|
|
// now try some real searches
|
|
|
- context = db->getRecords("foo.example.com.", zone_id);
|
|
|
+ context = accessor->getRecords("foo.example.com.", zone_id);
|
|
|
ASSERT_TRUE(context->getNext(columns));
|
|
|
checkRecordRow(columns, "CNAME", "3600", "",
|
|
|
"cnametest.example.org.", "");
|
|
@@ -261,12 +266,13 @@ TEST_F(SQLite3Access, getRecords) {
|
|
|
"NSEC 5 3 7200 20100322084538 20100220084538 33495 "
|
|
|
"example.com. FAKEFAKEFAKEFAKE", "");
|
|
|
EXPECT_FALSE(context->getNext(columns));
|
|
|
+
|
|
|
// with no more records, the array should not have been modified
|
|
|
checkRecordRow(columns, "RRSIG", "7200", "NSEC",
|
|
|
"NSEC 5 3 7200 20100322084538 20100220084538 33495 "
|
|
|
"example.com. FAKEFAKEFAKEFAKE", "");
|
|
|
|
|
|
- context = db->getRecords("example.com.", zone_id);
|
|
|
+ context = accessor->getRecords("example.com.", zone_id);
|
|
|
ASSERT_TRUE(context->getNext(columns));
|
|
|
checkRecordRow(columns, "SOA", "3600", "",
|
|
|
"master.example.com. admin.example.com. "
|
|
@@ -336,12 +342,12 @@ TEST_F(SQLite3Access, getRecords) {
|
|
|
|
|
|
// Try searching for subdomain
|
|
|
// There's foo.bar.example.com in the data
|
|
|
- context = db->getRecords("bar.example.com.", zone_id, true);
|
|
|
+ context = accessor->getRecords("bar.example.com.", zone_id, true);
|
|
|
ASSERT_TRUE(context->getNext(columns));
|
|
|
checkRecordRow(columns, "A", "3600", "", "192.0.2.1", "");
|
|
|
EXPECT_FALSE(context->getNext(columns));
|
|
|
// But we shouldn't match mix.example.com here
|
|
|
- context = db->getRecords("ix.example.com.", zone_id, true);
|
|
|
+ context = accessor->getRecords("ix.example.com.", zone_id, true);
|
|
|
EXPECT_FALSE(context->getNext(columns));
|
|
|
}
|
|
|
|
|
@@ -366,7 +372,7 @@ bool exists(const char* filename) {
|
|
|
TEST_F(SQLite3Create, creationtest) {
|
|
|
ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
|
|
|
// Should simply be created
|
|
|
- SQLite3Database db(SQLITE_NEW_DBFILE, RRClass::IN());
|
|
|
+ SQLite3Accessor accessor(SQLITE_NEW_DBFILE, RRClass::IN());
|
|
|
ASSERT_TRUE(exists(SQLITE_NEW_DBFILE));
|
|
|
}
|
|
|
|
|
@@ -378,12 +384,12 @@ TEST_F(SQLite3Create, emptytest) {
|
|
|
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());
|
|
|
+ SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN());
|
|
|
|
|
|
sqlite3_close(db);
|
|
|
|
|
|
// should work now that we closed it
|
|
|
- SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
|
|
|
+ SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
|
|
|
}
|
|
|
|
|
|
TEST_F(SQLite3Create, lockedtest) {
|
|
@@ -395,13 +401,300 @@ TEST_F(SQLite3Create, lockedtest) {
|
|
|
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()),
|
|
|
+ EXPECT_THROW(SQLite3Accessor accessor2(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());
|
|
|
+ SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
|
|
|
}
|
|
|
|
|
|
+//
|
|
|
+// Commonly used data for update tests
|
|
|
+//
|
|
|
+const char* const common_expected_data[] = {
|
|
|
+ // Test record already stored in the tested sqlite3 DB file.
|
|
|
+ "foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
|
|
|
+ "192.0.2.1"
|
|
|
+};
|
|
|
+const char* const new_data[] = {
|
|
|
+ // Newly added data commonly used by some of the tests below
|
|
|
+ "newdata.example.com.", "com.example.newdata.", "3600", "A", "",
|
|
|
+ "192.0.2.1"
|
|
|
+};
|
|
|
+const char* const deleted_data[] = {
|
|
|
+ // Existing data to be removed commonly used by some of the tests below
|
|
|
+ "foo.bar.example.com.", "A", "192.0.2.1"
|
|
|
+};
|
|
|
+
|
|
|
+class SQLite3Update : public SQLite3AccessorTest {
|
|
|
+protected:
|
|
|
+ SQLite3Update() {
|
|
|
+ // Note: if "installing" the test file fails some of the subsequent
|
|
|
+ // tests will fail and we should be able to notice that.
|
|
|
+ system(INSTALL_PROG " " TEST_DATA_DIR
|
|
|
+ "/test.sqlite3 " TEST_DATA_BUILDDIR "/test.sqlite3.copied");
|
|
|
+ initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", RRClass::IN());
|
|
|
+ zone_id = accessor->getZone("example.com.").second;
|
|
|
+ another_accessor.reset(new SQLite3Accessor(
|
|
|
+ TEST_DATA_BUILDDIR "/test.sqlite3.copied",
|
|
|
+ RRClass::IN()));
|
|
|
+ expected_stored.push_back(common_expected_data);
|
|
|
+ }
|
|
|
+
|
|
|
+ int zone_id;
|
|
|
+ std::string get_columns[DatabaseAccessor::COLUMN_COUNT];
|
|
|
+ std::string add_columns[DatabaseAccessor::ADD_COLUMN_COUNT];
|
|
|
+ std::string del_params[DatabaseAccessor::DEL_PARAM_COUNT];
|
|
|
+
|
|
|
+ vector<const char* const*> expected_stored; // placeholder for checkRecords
|
|
|
+ vector<const char* const*> empty_stored; // indicate no corresponding data
|
|
|
+
|
|
|
+ // Another accessor, emulating one running on a different process/thread
|
|
|
+ shared_ptr<SQLite3Accessor> another_accessor;
|
|
|
+ DatabaseAccessor::IteratorContextPtr iterator;
|
|
|
+};
|
|
|
+
|
|
|
+void
|
|
|
+checkRecords(SQLite3Accessor& accessor, int zone_id, const std::string& name,
|
|
|
+ vector<const char* const*> expected_rows)
|
|
|
+{
|
|
|
+ DatabaseAccessor::IteratorContextPtr iterator =
|
|
|
+ accessor.getRecords(name, zone_id);
|
|
|
+ std::string columns[DatabaseAccessor::COLUMN_COUNT];
|
|
|
+ vector<const char* const*>::const_iterator it = expected_rows.begin();
|
|
|
+ while (iterator->getNext(columns)) {
|
|
|
+ ASSERT_TRUE(it != expected_rows.end());
|
|
|
+ checkRecordRow(columns, (*it)[3], (*it)[2], (*it)[4], (*it)[5], "");
|
|
|
+ ++it;
|
|
|
+ }
|
|
|
+ EXPECT_TRUE(it == expected_rows.end());
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, emptyUpdate) {
|
|
|
+ // If we do nothing between start and commit, the zone content
|
|
|
+ // should be intact.
|
|
|
+
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+ accessor->commitUpdateZone();
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, flushZone) {
|
|
|
+ // With 'replace' being true startUpdateZone() will flush the existing
|
|
|
+ // zone content.
|
|
|
+
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
|
|
+ accessor->commitUpdateZone();
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, readWhileUpdate) {
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
|
|
+
|
|
|
+ // Until commit is done, the other accessor should see the old data
|
|
|
+ checkRecords(*another_accessor, zone_id, "foo.bar.example.com.",
|
|
|
+ expected_stored);
|
|
|
+
|
|
|
+ // Once the changes are committed, the other accessor will see the new
|
|
|
+ // data.
|
|
|
+ accessor->commitUpdateZone();
|
|
|
+ checkRecords(*another_accessor, zone_id, "foo.bar.example.com.",
|
|
|
+ empty_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, rollback) {
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
|
|
+
|
|
|
+ // Rollback will revert the change made by startUpdateZone(, true).
|
|
|
+ accessor->rollbackUpdateZone();
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, rollbackFailure) {
|
|
|
+ // This test emulates a rare scenario of making rollback attempt fail.
|
|
|
+ // The iterator is paused in the middle of getting records, which prevents
|
|
|
+ // the rollback operation at the end of the test.
|
|
|
+
|
|
|
+ string columns[DatabaseAccessor::COLUMN_COUNT];
|
|
|
+ iterator = accessor->getRecords("example.com.", zone_id);
|
|
|
+ EXPECT_TRUE(iterator->getNext(columns));
|
|
|
+
|
|
|
+ accessor->startUpdateZone("example.com.", true);
|
|
|
+ EXPECT_THROW(accessor->rollbackUpdateZone(), DataSourceError);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, commitConflict) {
|
|
|
+ // Start reading the DB by another accessor. We should stop at a single
|
|
|
+ // call to getNextRecord() to keep holding the lock.
|
|
|
+ iterator = another_accessor->getRecords("foo.example.com.", zone_id);
|
|
|
+ EXPECT_TRUE(iterator->getNext(get_columns));
|
|
|
+
|
|
|
+ // Due to getNextRecord() above, the other accessor holds a DB lock,
|
|
|
+ // which will prevent commit.
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
|
|
+ EXPECT_THROW(accessor->commitUpdateZone(), DataSourceError);
|
|
|
+ accessor->rollbackUpdateZone(); // rollback should still succeed
|
|
|
+
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, updateConflict) {
|
|
|
+ // Similar to the previous case, but this is a conflict with another
|
|
|
+ // update attempt. Note that these two accessors modify disjoint sets
|
|
|
+ // of data; sqlite3 only has a coarse-grained lock so we cannot allow
|
|
|
+ // these updates to run concurrently.
|
|
|
+ EXPECT_TRUE(another_accessor->startUpdateZone("sql1.example.com.",
|
|
|
+ true).first);
|
|
|
+ EXPECT_THROW(accessor->startUpdateZone("example.com.", true),
|
|
|
+ DataSourceError);
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+
|
|
|
+ // Once we rollback the other attempt of change, we should be able to
|
|
|
+ // start and commit the transaction using the main accessor.
|
|
|
+ another_accessor->rollbackUpdateZone();
|
|
|
+ accessor->startUpdateZone("example.com.", true);
|
|
|
+ accessor->commitUpdateZone();
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, duplicateUpdate) {
|
|
|
+ accessor->startUpdateZone("example.com.", false);
|
|
|
+ EXPECT_THROW(accessor->startUpdateZone("example.com.", false),
|
|
|
+ DataSourceError);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, commitWithoutTransaction) {
|
|
|
+ EXPECT_THROW(accessor->commitUpdateZone(), DataSourceError);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, rollbackWithoutTransaction) {
|
|
|
+ EXPECT_THROW(accessor->rollbackUpdateZone(), DataSourceError);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, addRecord) {
|
|
|
+ // Before update, there should be no record for this name
|
|
|
+ checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
|
|
|
+
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
|
|
|
+ copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
|
|
|
+ add_columns);
|
|
|
+ accessor->addRecordToZone(add_columns);
|
|
|
+
|
|
|
+ expected_stored.clear();
|
|
|
+ expected_stored.push_back(new_data);
|
|
|
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
|
|
|
+
|
|
|
+ // Commit the change, and confirm the new data is still there.
|
|
|
+ accessor->commitUpdateZone();
|
|
|
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, addThenRollback) {
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
|
|
|
+ copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
|
|
|
+ add_columns);
|
|
|
+ accessor->addRecordToZone(add_columns);
|
|
|
+
|
|
|
+ expected_stored.clear();
|
|
|
+ expected_stored.push_back(new_data);
|
|
|
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
|
|
|
+
|
|
|
+ accessor->rollbackUpdateZone();
|
|
|
+ checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, duplicateAdd) {
|
|
|
+ const char* const dup_data[] = {
|
|
|
+ "foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
|
|
|
+ "192.0.2.1"
|
|
|
+ };
|
|
|
+ expected_stored.clear();
|
|
|
+ expected_stored.push_back(dup_data);
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+
|
|
|
+ // Adding exactly the same data. As this backend is "dumb", another
|
|
|
+ // row of the same content will be inserted.
|
|
|
+ copy(dup_data, dup_data + DatabaseAccessor::ADD_COLUMN_COUNT,
|
|
|
+ add_columns);
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
|
|
|
+ accessor->addRecordToZone(add_columns);
|
|
|
+ expected_stored.push_back(dup_data);
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, invalidAdd) {
|
|
|
+ // An attempt of add before an explicit start of transaction
|
|
|
+ EXPECT_THROW(accessor->addRecordToZone(add_columns), DataSourceError);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, deleteRecord) {
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
|
|
|
+
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+
|
|
|
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
|
|
|
+ del_params);
|
|
|
+ accessor->deleteRecordInZone(del_params);
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
|
|
+
|
|
|
+ // Commit the change, and confirm the deleted data still isn't there.
|
|
|
+ accessor->commitUpdateZone();
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, deleteThenRollback) {
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
|
|
|
+
|
|
|
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
|
|
|
+ del_params);
|
|
|
+ accessor->deleteRecordInZone(del_params);
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
|
|
|
+
|
|
|
+ // Rollback the change, and confirm the data still exists.
|
|
|
+ accessor->rollbackUpdateZone();
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, deleteNonexistent) {
|
|
|
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
|
|
|
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
|
|
|
+ del_params);
|
|
|
+
|
|
|
+ // Replace the name with a non existent one, then try to delete it.
|
|
|
+ // nothing should happen.
|
|
|
+ del_params[DatabaseAccessor::DEL_NAME] = "no-such-name.example.com.";
|
|
|
+ checkRecords(*accessor, zone_id, "no-such-name.example.com.",
|
|
|
+ empty_stored);
|
|
|
+ accessor->deleteRecordInZone(del_params);
|
|
|
+ checkRecords(*accessor, zone_id, "no-such-name.example.com.",
|
|
|
+ empty_stored);
|
|
|
+
|
|
|
+ // Name exists but the RR type is different. Delete attempt shouldn't
|
|
|
+ // delete only by name.
|
|
|
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
|
|
|
+ del_params);
|
|
|
+ del_params[DatabaseAccessor::DEL_TYPE] = "AAAA";
|
|
|
+ accessor->deleteRecordInZone(del_params);
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+
|
|
|
+ // Similar to the previous case, but RDATA is different.
|
|
|
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
|
|
|
+ del_params);
|
|
|
+ del_params[DatabaseAccessor::DEL_RDATA] = "192.0.2.2";
|
|
|
+ accessor->deleteRecordInZone(del_params);
|
|
|
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(SQLite3Update, invalidDelete) {
|
|
|
+ // An attempt of delete before an explicit start of transaction
|
|
|
+ EXPECT_THROW(accessor->deleteRecordInZone(del_params), DataSourceError);
|
|
|
+}
|
|
|
} // end anonymous namespace
|