Parcourir la source

Merge branch #1065 into master

Conflicts:
	src/lib/datasrc/tests/database_unittest.cc
Michal 'vorner' Vaner il y a 13 ans
Parent
commit
616fb3be8c

+ 12 - 0
src/lib/datasrc/database.cc

@@ -349,6 +349,18 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
                 result_status = CNAME;
             }
         }
+        if (!result_rrset && !records_found) {
+            // Nothing lives here. But check if something lives below this
+            // domain and if so, pretend something is here as well.
+            database_->searchForRecords(zone_id_, name.toText(), true);
+            std::string columns[DatabaseAccessor::COLUMN_COUNT];
+            if (database_->getNextRecord(columns,
+                                         DatabaseAccessor::COLUMN_COUNT)) {
+                records_found = true;
+                // We don't consume everything, so get rid of the rest
+                database_->resetSearch();
+            }
+        }
     } catch (const DataSourceError& dse) {
         logger.error(DATASRC_DATABASE_FIND_ERROR)
             .arg(database_->getDBName()).arg(dse.what());

+ 7 - 1
src/lib/datasrc/database.h

@@ -86,8 +86,14 @@ public:
      *
      * \param zone_id The zone to search in, as returned by getZone()
      * \param name The name of the records to find
+     * \param subdomains If set to true, match subdomains of name instead
+     *     of name itself. It is used to find empty domains and match
+     *     wildcards.
+     * \todo Should we return the name as well? If we search for subdomains
+     *     it might be useful (and needed in case of wildcard).
      */
-    virtual void searchForRecords(int zone_id, const std::string& name) = 0;
+    virtual void searchForRecords(int zone_id, const std::string& name,
+                                  bool subdomains = false) = 0;
 
     /**
      * \brief Retrieves the next record from the search started with searchForRecords()

+ 26 - 8
src/lib/datasrc/sqlite3_accessor.cc

@@ -25,7 +25,8 @@ namespace datasrc {
 struct SQLite3Parameters {
     SQLite3Parameters() :
         db_(NULL), version_(-1),
-        q_zone_(NULL), q_any_(NULL)
+        q_zone_(NULL), q_any_(NULL),
+        q_any_sub_(NULL), q_current_(NULL)
         /*q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
         q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
         q_prevnsec3_(NULL) */
@@ -34,6 +35,8 @@ struct SQLite3Parameters {
     int version_;
     sqlite3_stmt* q_zone_;
     sqlite3_stmt* q_any_;
+    sqlite3_stmt* q_any_sub_;
+    sqlite3_stmt* q_current_;
     /*
     TODO: Yet unneeded statements
     sqlite3_stmt* q_record_;
@@ -76,6 +79,11 @@ public:
         if (params_.q_any_ != NULL) {
             sqlite3_finalize(params_.q_any_);
         }
+        if (params_.q_any_sub_ != NULL) {
+            sqlite3_finalize(params_.q_any_sub_);
+        }
+        // we do NOT finalize q_current_ - that is just a pointer to one of
+        // the other statements, not a separate one.
         /*
         if (params_.q_record_ != NULL) {
             sqlite3_finalize(params_.q_record_);
@@ -139,6 +147,9 @@ const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass =
 const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
     "FROM records WHERE zone_id=?1 AND name=?2";
 
+const char* const q_any_sub_str = "SELECT rdtype, ttl, sigtype, rdata "
+    "FROM records WHERE zone_id=?1 AND name LIKE (\"%.\" || ?2)";
+
 /* TODO: Prune the statements, not everything will be needed maybe?
 const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
     "FROM records WHERE zone_id=?1 AND name=?2 AND "
@@ -204,7 +215,10 @@ checkAndSetupSchema(Initializer* initializer) {
     }
 
     initializer->params_.q_zone_ = prepare(db, q_zone_str);
-    initializer->params_.q_any_ = prepare(db, q_any_str);
+    // Make sure the current is initialized to one of the statements, not NULL
+    initializer->params_.q_current_ = initializer->params_.q_any_ =
+        prepare(db, q_any_str);
+    initializer->params_.q_any_sub_ = prepare(db, q_any_sub_str);
     /* TODO: Yet unneeded statements
     initializer->params_.q_record_ = prepare(db, q_record_str);
     initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
@@ -323,15 +337,19 @@ SQLite3Database::getZone(const isc::dns::Name& name) const {
 }
 
 void
-SQLite3Database::searchForRecords(int zone_id, const std::string& name) {
+SQLite3Database::searchForRecords(int zone_id, const std::string& name,
+                                  bool subdomains)
+{
+    dbparameters_->q_current_ = subdomains ? dbparameters_->q_any_sub_ :
+        dbparameters_->q_any_;
     resetSearch();
-    if (sqlite3_bind_int(dbparameters_->q_any_, 1, zone_id) != SQLITE_OK) {
+    if (sqlite3_bind_int(dbparameters_->q_current_, 1, zone_id) != SQLITE_OK) {
         isc_throw(DataSourceError,
                   "Error in sqlite3_bind_int() for zone_id " <<
                   zone_id << ": " << sqlite3_errmsg(dbparameters_->db_));
     }
     // use transient since name is a ref and may disappear
-    if (sqlite3_bind_text(dbparameters_->q_any_, 2, name.c_str(), -1,
+    if (sqlite3_bind_text(dbparameters_->q_current_, 2, name.c_str(), -1,
                                SQLITE_TRANSIENT) != SQLITE_OK) {
         isc_throw(DataSourceError,
                   "Error in sqlite3_bind_text() for name " <<
@@ -376,7 +394,7 @@ SQLite3Database::getNextRecord(std::string columns[], size_t column_count) {
                     "of size " << COLUMN_COUNT << " to getNextRecord()");
     }
 
-    sqlite3_stmt* current_stmt = dbparameters_->q_any_;
+    sqlite3_stmt* current_stmt = dbparameters_->q_current_;
     const int rc = sqlite3_step(current_stmt);
 
     if (rc == SQLITE_ROW) {
@@ -404,8 +422,8 @@ SQLite3Database::getNextRecord(std::string columns[], size_t column_count) {
 
 void
 SQLite3Database::resetSearch() {
-    sqlite3_reset(dbparameters_->q_any_);
-    sqlite3_clear_bindings(dbparameters_->q_any_);
+    sqlite3_reset(dbparameters_->q_current_);
+    sqlite3_clear_bindings(dbparameters_->q_current_);
 }
 
 }

+ 3 - 1
src/lib/datasrc/sqlite3_accessor.h

@@ -100,8 +100,10 @@ public:
      *
      * \param zone_id The zone to seach in, as returned by getZone()
      * \param name The name to find records for
+     * \param subdomains Match subdomains instead of the name.
      */
-    virtual void searchForRecords(int zone_id, const std::string& name);
+    virtual void searchForRecords(int zone_id, const std::string& name,
+                                  bool subdomains = false);
 
     /**
      * \brief Retrieve the next record from the search started with

+ 40 - 7
src/lib/datasrc/tests/database_unittest.cc

@@ -54,7 +54,9 @@ public:
         }
     }
 
-    virtual void searchForRecords(int zone_id, const std::string& name) {
+    virtual void searchForRecords(int zone_id, const std::string& name,
+                                  bool subdomains)
+    {
         search_running_ = true;
 
         // 'hardcoded' name to trigger exceptions (for testing
@@ -69,14 +71,29 @@ public:
         }
         searched_name_ = name;
 
-        // we're not aiming for efficiency in this test, simply
-        // copy the relevant vector from records
         cur_record = 0;
         if (zone_id == 42) {
-            if (records.count(name) > 0) {
-                cur_name = records.find(name)->second;
-            } else {
+            if (subdomains) {
                 cur_name.clear();
+                // Just walk everything and check if it is a subdomain.
+                // If it is, just copy all data from there.
+                for (Domains::iterator i(records.begin()); i != records.end();
+                     ++ i) {
+                    Name local(i->first);
+                    if (local.compare(isc::dns::Name(name)).getRelation() ==
+                        isc::dns::NameComparisonResult::SUBDOMAIN) {
+                        cur_name.insert(cur_name.end(), i->second.begin(),
+                                        i->second.end());
+                    }
+                }
+            } else {
+                if (records.count(name) > 0) {
+                    // we're not aiming for efficiency in this test, simply
+                    // copy the relevant vector from records
+                    cur_name = records.find(name)->second;
+                } else {
+                    cur_name.clear();
+                }
             }
         } else {
             cur_name.clear();
@@ -119,7 +136,9 @@ public:
         return (database_name_);
     }
 private:
-    std::map<std::string, std::vector< std::vector<std::string> > > records;
+    typedef std::map<std::string, std::vector< std::vector<std::string> > >
+        Domains;
+    Domains records;
     // used as internal index for getNextRecord()
     size_t cur_record;
     // used as temporary storage after searchForRecord() and during
@@ -303,6 +322,10 @@ private:
         addRecord("RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
                   "20000201000000 12345 example.org. FAKEFAKEFAKE");
         addCurName("example.org.");
+
+        // This is because of empty domain test
+        addRecord("A", "3600", "", "192.0.2.1");
+        addCurName("a.b.example.org.");
     }
 };
 
@@ -870,6 +893,16 @@ TEST_F(DatabaseClientTest, findDelegation) {
     EXPECT_FALSE(current_database_->searchRunning());
 }
 
+TEST_F(DatabaseClientTest, emptyDomain) {
+    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+
+    // This domain doesn't exist, but a subdomain of it does.
+    // Therefore we should pretend the domain is there, but contains no RRsets
+    doFindTest(finder, isc::dns::Name("b.example.org."), isc::dns::RRType::A(),
+               isc::dns::RRType::A(), isc::dns::RRTTL(3600),
+               ZoneFinder::NXRRSET, expected_rdatas_, expected_sig_rdatas_);
+}
+
 // Glue-OK mode. Just go trough NS delegations down (but not trough
 // DNAME) and pretend it is not there.
 TEST_F(DatabaseClientTest, glueOK) {

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

@@ -240,6 +240,16 @@ TEST_F(SQLite3Access, getRecords) {
     checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
                    "DNSKEY 5 2 3600 20100322084538 20100220084538 "
                    "33495 example.com. FAKEFAKEFAKEFAKE");
+
+    // Try searching for subdomain
+    // There's foo.bar.example.com in the data
+    db->searchForRecords(zone_id, "bar.example.com.", true);
+    EXPECT_TRUE(db->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "A", "3600", "", "192.0.2.1");
+    EXPECT_FALSE(db->getNextRecord(columns, column_count));
+    // But we shouldn't match mix.example.com here
+    db->searchForRecords(zone_id, "ix.example.com.", true);
+    EXPECT_FALSE(db->getNextRecord(columns, column_count));
 }
 
 } // end anonymous namespace