Browse Source

Merge branch #1065

Conflicts:
	src/lib/datasrc/database.cc
	src/lib/datasrc/database.h
	src/lib/datasrc/sqlite3_accessor.cc
	src/lib/datasrc/sqlite3_accessor.h
	src/lib/datasrc/tests/database_unittest.cc
	src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
Michal 'vorner' Vaner 13 years ago
parent
commit
5baa7aa73a

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

@@ -358,6 +358,22 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
                    result_rrset->getType() == isc::dns::RRType::CNAME()) {
             result_status = CNAME;
         }
+
+        if (!result_rrset && !records_found) {
+            // Request the context
+            DatabaseAccessor::IteratorContextPtr
+                context(database_->getRecords(name.toText(), zone_id_, true));
+            // It must not return NULL, that's a bug of the implementation
+            if (!context) {
+                isc_throw(isc::Unexpected, "Iterator context null at " +
+                          name.toText());
+            }
+
+            std::string columns[DatabaseAccessor::COLUMN_COUNT];
+            if (context->getNext(columns)) {
+                records_found = true;
+            }
+        }
     }
 
     if (!result_rrset) {

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

@@ -172,10 +172,14 @@ public:
      *
      * \param name The name to search for. This should be a FQDN.
      * \param id The ID of the zone, returned from getZone().
+     * \param subdomains If set to true, match subdomains of name instead
+     *     of name itself. It is used to find empty domains and match
+     *     wildcards.
      * \return Newly created iterator context. Must not be NULL.
      */
     virtual IteratorContextPtr getRecords(const std::string& name,
-                                          int id) const = 0;
+                                          int id,
+                                          bool subdomains = false) const = 0;
 
     /**
      * \brief Creates an iterator context for the whole zone.

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

@@ -28,23 +28,10 @@ struct SQLite3Parameters {
     SQLite3Parameters() :
         db_(NULL), version_(-1),
         q_zone_(NULL)
-        /*q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
-        q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
-        q_prevnsec3_(NULL) */
     {}
     sqlite3* db_;
     int version_;
     sqlite3_stmt* q_zone_;
-    /*
-    TODO: Yet unneeded statements
-    sqlite3_stmt* q_record_;
-    sqlite3_stmt* q_addrs_;
-    sqlite3_stmt* q_referral_;
-    sqlite3_stmt* q_count_;
-    sqlite3_stmt* q_previous_;
-    sqlite3_stmt* q_nsec3_;
-    sqlite3_stmt* q_prevnsec3_;
-    */
 };
 
 SQLite3Database::SQLite3Database(const std::string& filename,
@@ -74,6 +61,8 @@ public:
         if (params_.q_zone_ != NULL) {
             sqlite3_finalize(params_.q_zone_);
         }
+        // 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 +128,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)";
+
 // note that the order of the SELECT values is specifically chosen to match
 // the enum values in RecordColumns
 const char* const q_iterate_str = "SELECT rdtype, ttl, sigtype, rdata, name FROM records "
@@ -328,7 +320,6 @@ SQLite3Database::getZone(const isc::dns::Name& name) const {
     return (std::pair<bool, int>(false, 0));
 }
 
-
 class SQLite3Database::Context : public DatabaseAccessor::IteratorContext {
 public:
     // Construct an iterator for all records. When constructed this
@@ -347,14 +338,15 @@ public:
     // Construct an iterator for records with a specific name. When constructed
     // this way, the getNext() call will copy all fields except name
     Context(const boost::shared_ptr<const SQLite3Database>& database, int id,
-            const std::string& name) :
+            const std::string& name, bool subdomains) :
         iterator_type_(ITT_NAME),
         database_(database),
         statement_(NULL),
         name_(name)
     {
         // We create the statement now and then just keep getting data from it
-        statement_ = prepare(database->dbparameters_->db_, q_any_str);
+        statement_ = prepare(database->dbparameters_->db_,
+                             subdomains ? q_any_sub_str : q_any_str);
         bindZoneId(id);
         bindName(name_);
     }
@@ -459,8 +451,11 @@ private:
 };
 
 DatabaseAccessor::IteratorContextPtr
-SQLite3Database::getRecords(const std::string& name, int id) const {
-    return (IteratorContextPtr(new Context(shared_from_this(), id, name)));
+SQLite3Database::getRecords(const std::string& name, int id,
+                            bool subdomains) const
+{
+    return (IteratorContextPtr(new Context(shared_from_this(), id, name,
+                                           subdomains)));
 }
 
 DatabaseAccessor::IteratorContextPtr

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

@@ -102,10 +102,12 @@ public:
      *
      * \param name the name to look up
      * \param id the zone id, as returned by getZone()
+     * \param subdomains Match subdomains instead of the name.
      * \return Iterator that contains all records with the given name
      */
     virtual IteratorContextPtr getRecords(const std::string& name,
-                                          int id) const;
+                                          int id,
+                                          bool subdomains = false) const;
 
     /** \brief Look up all resource records for a zone
      *

+ 56 - 13
src/lib/datasrc/tests/database_unittest.cc

@@ -62,7 +62,9 @@ public:
         return (database_name_);
     }
 
-    virtual IteratorContextPtr getRecords(const std::string&, int) const {
+    virtual IteratorContextPtr getRecords(const std::string&, int, bool)
+        const
+        {
         isc_throw(isc::NotImplemented,
                   "This database datasource can't be iterated");
     };
@@ -93,7 +95,7 @@ private:
     class MockNameIteratorContext : public IteratorContext {
     public:
         MockNameIteratorContext(const MockAccessor& mock_accessor, int zone_id,
-                                const std::string& name) :
+                                const std::string& name, bool subdomains) :
             searched_name_(name), cur_record_(0)
         {
             // 'hardcoded' names to trigger exceptions
@@ -107,16 +109,32 @@ private:
                 throw std::exception();
             }
 
-            // we're not aiming for efficiency in this test, simply
-            // copy the relevant vector from records
             if (zone_id == 42) {
-                if (mock_accessor.records.count(searched_name_) > 0) {
-                    cur_name = mock_accessor.records.find(searched_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::const_iterator
+                         i(mock_accessor.records.begin());
+                         i != mock_accessor.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 {
+                    // we're not aiming for efficiency in this test, simply
+                    // copy the relevant vector from records
+                    if (mock_accessor.records.count(searched_name_) > 0) {
+                        cur_name = mock_accessor.records.find(searched_name_)->
+                            second;
+                    } else {
+                        cur_name.clear();
+                    }
                 }
-            } else {
-                cur_name.clear();
             }
         }
 
@@ -244,17 +262,27 @@ public:
         }
     }
 
-    virtual IteratorContextPtr getRecords(const std::string& name, int id) const {
+    virtual IteratorContextPtr getRecords(const std::string& name, int id,
+                                          bool subdomains) const
+    {
         if (id == 42) {
-            return (IteratorContextPtr(new MockNameIteratorContext(*this, id, name)));
+            return (IteratorContextPtr(new MockNameIteratorContext(*this, id,
+                name, subdomains)));
         } else {
             isc_throw(isc::Unexpected, "Unknown zone ID");
         }
     }
 
 private:
-    std::map<std::string, std::vector< std::vector<std::string> > > records;
+    typedef std::map<std::string, std::vector< std::vector<std::string> > >
+        Domains;
+    // used as internal index for getNextRecord()
+    size_t cur_record;
     // used as temporary storage during the building of the fake data
+    Domains records;
+    // used as temporary storage after searchForRecord() and during
+    // getNextRecord() calls, as well as during the building of the
+    // fake data
     std::vector< std::vector<std::string> > cur_name;
 
     // Adds one record to the current name in the database
@@ -428,12 +456,16 @@ 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.");
     }
 };
 
 // This tests the default getRecords behaviour, throwing NotImplemented
 TEST(DatabaseConnectionTest, getRecords) {
-    EXPECT_THROW(NopAccessor().getRecords(".", 1),
+    EXPECT_THROW(NopAccessor().getRecords(".", 1, false),
                  isc::NotImplemented);
 }
 
@@ -1103,6 +1135,17 @@ TEST_F(DatabaseClientTest, glueOK) {
                ZoneFinder::FIND_GLUE_OK);
 }
 
+TEST_F(DatabaseClientTest, empty) {
+    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+
+    // Check empty domain
+    // 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_);
+}
+
 TEST_F(DatabaseClientTest, getOrigin) {
     DataSourceClient::FindResult zone(client_->findZone(Name("example.org")));
     ASSERT_EQ(result::SUCCESS, zone.code);

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

@@ -327,6 +327,16 @@ TEST_F(SQLite3Access, getRecords) {
 
     // check that another getNext does not cause problems
     EXPECT_FALSE(context->getNext(columns));
+
+    // Try searching for subdomain
+    // There's foo.bar.example.com in the data
+    context = db->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);
+    EXPECT_FALSE(context->getNext(columns));
 }
 
 } // end anonymous namespace