Browse Source

[1177] Implement previous name for SQLite3

Michal 'vorner' Vaner 13 years ago
parent
commit
c35f6b15bb

+ 96 - 19
src/lib/datasrc/sqlite3_accessor.cc

@@ -47,7 +47,9 @@ enum StatementID {
     ADD_RECORD = 7,
     DEL_RECORD = 8,
     ITERATE = 9,
-    NUM_STATEMENTS = 10
+    FIND_PREVIOUS = 10,
+    FIND_PREVIOUS_WRAP = 11,
+    NUM_STATEMENTS = 12
 };
 
 const char* const text_statements[NUM_STATEMENTS] = {
@@ -68,7 +70,13 @@ const char* const text_statements[NUM_STATEMENTS] = {
     "DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
     "AND rdtype=?3 AND rdata=?4",
     "SELECT rdtype, ttl, sigtype, rdata, name FROM records " // ITERATE
-    "WHERE zone_id = ?1 ORDER BY name, rdtype"
+    "WHERE zone_id = ?1 ORDER BY name, rdtype",
+    "SELECT name FROM records " // FIND_PREVIOUS
+    "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
+    "rname < $2 ORDER BY rname DESC LIMIT 1", // FIND_PREVIOUS_WRAP
+    "SELECT name FROM records "
+    "WHERE zone_id = ?1 AND rdtype = 'NSEC' "
+    "ORDER BY rname DESC LIMIT 1"
 };
 
 struct SQLite3Parameters {
@@ -391,6 +399,28 @@ SQLite3Accessor::getZone(const std::string& name) const {
     return (std::pair<bool, int>(false, 0));
 }
 
+namespace {
+
+// Conversion to plain char
+const char*
+convertToPlainCharInternal(const unsigned char* ucp, sqlite3 *db) {
+    if (ucp == NULL) {
+        // The field can really be NULL, in which case we return an
+        // empty string, or sqlite may have run out of memory, in
+        // which case we raise an error
+        if (sqlite3_errcode(db) == SQLITE_NOMEM) {
+            isc_throw(DataSourceError,
+                      "Sqlite3 backend encountered a memory allocation "
+                      "error in sqlite3_column_text()");
+        } else {
+            return ("");
+        }
+    }
+    const void* p = ucp;
+    return (static_cast<const char*>(p));
+}
+
+}
 class SQLite3Accessor::Context : public DatabaseAccessor::IteratorContext {
 public:
     // Construct an iterator for all records. When constructed this
@@ -501,21 +531,8 @@ private:
     // In case sqlite3_column_text() returns NULL, we just make it an
     // empty string, unless it was caused by a memory error
     const char* convertToPlainChar(const unsigned char* ucp) {
-        if (ucp == NULL) {
-            // The field can really be NULL, in which case we return an
-            // empty string, or sqlite may have run out of memory, in
-            // which case we raise an error
-            if (sqlite3_errcode(accessor_->dbparameters_->db_)
-                                == SQLITE_NOMEM) {
-                isc_throw(DataSourceError,
-                        "Sqlite3 backend encountered a memory allocation "
-                        "error in sqlite3_column_text()");
-            } else {
-                return ("");
-            }
-        }
-        const void* p = ucp;
-        return (static_cast<const char*>(p));
+        return (convertToPlainCharInternal(ucp,
+                                           accessor_->dbparameters_->db_));
     }
 
     const IteratorType iterator_type_;
@@ -659,8 +676,68 @@ SQLite3Accessor::deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
 }
 
 std::string
-SQLite3Accessor::findPreviousName(int , const std::string&) const {
-    return ("."); // TODO Test and implement
+SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
+    const
+{
+    sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS]);
+    sqlite3_clear_bindings(dbparameters_->statements_[FIND_PREVIOUS]);
+
+    int rc = sqlite3_bind_int(dbparameters_->statements_[FIND_PREVIOUS], 1,
+                              zone_id);
+    if (rc != SQLITE_OK) {
+        isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+                  " to SQL statement (find previous)");
+    }
+    rc = sqlite3_bind_text(dbparameters_->statements_[FIND_PREVIOUS], 2,
+                           rname.c_str(), -1, SQLITE_STATIC);
+    if (rc != SQLITE_OK) {
+        isc_throw(SQLite3Error, "Could not bind name " << rname <<
+                  " to SQL statement (find previous)");
+    }
+
+    std::string result;
+    rc = sqlite3_step(dbparameters_->statements_[FIND_PREVIOUS]);
+    if (rc == SQLITE_ROW) {
+        // We found it
+        result = convertToPlainCharInternal(sqlite3_column_text(dbparameters_->
+            statements_[FIND_PREVIOUS], 0), dbparameters_->db_);
+    }
+    sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS]);
+
+    if (rc == SQLITE_DONE) {
+        // Nothing previous, wrap around (is it needed for anything?
+        // Well, just for completeness)
+        sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS_WRAP]);
+        sqlite3_clear_bindings(dbparameters_->statements_[FIND_PREVIOUS_WRAP]);
+
+        int rc = sqlite3_bind_int(
+            dbparameters_->statements_[FIND_PREVIOUS_WRAP], 1, zone_id);
+        if (rc != SQLITE_OK) {
+            isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+                      " to SQL statement (find previous wrap)");
+        }
+
+        rc = sqlite3_step(dbparameters_->statements_[FIND_PREVIOUS_WRAP]);
+        if (rc == SQLITE_ROW) {
+            // We found it
+            result =
+                convertToPlainCharInternal(sqlite3_column_text(dbparameters_->
+                    statements_[FIND_PREVIOUS_WRAP], 0), dbparameters_->db_);
+        }
+        sqlite3_reset(dbparameters_->statements_[FIND_PREVIOUS_WRAP]);
+
+        if (rc == SQLITE_DONE) {
+            // No NSEC records, this DB doesn't support DNSSEC
+            isc_throw(isc::NotImplemented, "The zone doesn't support DNSSEC");
+        }
+    }
+
+    if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+        // Some kind of error
+        isc_throw(SQLite3Error, "Could get data for previous name");
+    }
+
+    return (result);
 }
 
 }

+ 15 - 8
src/lib/datasrc/tests/database_unittest.cc

@@ -549,7 +549,8 @@ public:
         } else if (id == 42) {
             if (rname == "org.example.") {
                 return ("zzz.example.org.");
-            } else if (rname == "org.example.www2.") {
+            } else if (rname == "org.example.www2." ||
+                       rname == "org.example.www1.") {
                 return ("www.example.org.");
             } else {
                 isc_throw(isc::Unexpected, "Unexpected name");
@@ -2228,13 +2229,19 @@ TYPED_TEST(DatabaseClientTest, previous) {
     // Check wrap around
     EXPECT_EQ(Name("zzz.example.org."),
               finder->findPreviousName(Name("example.org.")));
-    // Check it doesn't crash or anything if the underlying DB throws
-    DataSourceClient::FindResult
-        zone(this->client_->findZone(Name("bad.example.org")));
-    finder = dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder);
-
-    EXPECT_THROW(finder->findPreviousName(Name("bad.example.org")),
-                 isc::NotImplemented);
+    // Check a name that doesn't exist there
+    EXPECT_EQ(Name("www.example.org."),
+              finder->findPreviousName(Name("www1.example.org.")));
+    if (this->is_mock_) { // We can't really force the DB to throw
+        // Check it doesn't crash or anything if the underlying DB throws
+        DataSourceClient::FindResult
+            zone(this->client_->findZone(Name("bad.example.org")));
+        finder =
+            dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder);
+
+        EXPECT_THROW(finder->findPreviousName(Name("bad.example.org")),
+                     isc::NotImplemented);
+    }
 }
 
 }

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

@@ -354,6 +354,9 @@ TEST_F(SQLite3AccessorTest, getRecords) {
 TEST_F(SQLite3AccessorTest, findPrevious) {
     EXPECT_EQ("dns01.example.com.",
               accessor->findPreviousName(1, "com.example.dns02."));
+    // A name that doesn't exist
+    EXPECT_EQ("dns01.example.com.",
+              accessor->findPreviousName(1, "com.example.dns01x."));
     // Wrap around
     EXPECT_EQ("www.example.com.",
               accessor->findPreviousName(1, "com.example."));