Browse Source

70. [func] each
Added a hot-spot cache to libdatasrc to speed up access to
repeatedly-queried data and reduce the number of queries to
the underlying database; this should substantially improve
performance. Also added a "-n" ("no cache") option to
bind10 and b10-auth to disable the cache if needed.
(Trac #192, svn r2383)


git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@2383 e5f2f494-b856-4b98-b285-d166d9295462

Evan Hunt 15 years ago
parent
commit
34159d6962

+ 8 - 0
ChangeLog

@@ -1,3 +1,11 @@
+  70.  [func]		each
+  	Added a hot-spot cache to libdatasrc to speed up access to
+	repeatedly-queried data and reduce the number of queries to
+	the underlying database; this should substantially improve
+	performance.  Also added a "-n" ("no cache") option to
+	bind10 and b10-auth to disable the cache if needed.
+	(Trac #192, svn r2383)
+
 bind10-devel-20100701 released on July 1, 2010
 bind10-devel-20100701 released on July 1, 2010
 
 
   69.  [func]*		jelte
   69.  [func]*		jelte

+ 1 - 1
doc/Doxyfile

@@ -568,7 +568,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 # with spaces.
 
 
-INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions
+INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc
 
 
 # This tag can be used to specify the character encoding of the source files
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

+ 11 - 4
src/bin/auth/auth_srv.cc

@@ -60,7 +60,7 @@ private:
     AuthSrvImpl(const AuthSrvImpl& source);
     AuthSrvImpl(const AuthSrvImpl& source);
     AuthSrvImpl& operator=(const AuthSrvImpl& source);
     AuthSrvImpl& operator=(const AuthSrvImpl& source);
 public:
 public:
-    AuthSrvImpl();
+    AuthSrvImpl(const bool use_cache);
 
 
     isc::data::ElementPtr setDbFile(const isc::data::ElementPtr config);
     isc::data::ElementPtr setDbFile(const isc::data::ElementPtr config);
 
 
@@ -76,9 +76,13 @@ public:
 
 
     /// Currently non-configurable, but will be.
     /// Currently non-configurable, but will be.
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
+
+    /// Hot spot cache
+    isc::datasrc::HotCache cache_;
 };
 };
 
 
-AuthSrvImpl::AuthSrvImpl() : cs_(NULL), verbose_mode_(false)
+AuthSrvImpl::AuthSrvImpl(const bool use_cache) :
+    cs_(NULL), verbose_mode_(false)
 {
 {
     // cur_datasrc_ is automatically initialized by the default constructor,
     // cur_datasrc_ is automatically initialized by the default constructor,
     // effectively being an empty (sqlite) data source.  once ccsession is up
     // effectively being an empty (sqlite) data source.  once ccsession is up
@@ -86,9 +90,12 @@ AuthSrvImpl::AuthSrvImpl() : cs_(NULL), verbose_mode_(false)
 
 
     // add static data source
     // add static data source
     data_sources_.addDataSrc(ConstDataSrcPtr(new StaticDataSrc));
     data_sources_.addDataSrc(ConstDataSrcPtr(new StaticDataSrc));
+
+    // enable or disable the cache
+    cache_.setEnabled(use_cache);
 }
 }
 
 
-AuthSrv::AuthSrv() : impl_(new AuthSrvImpl) {
+AuthSrv::AuthSrv(const bool use_cache) : impl_(new AuthSrvImpl(use_cache)) {
 }
 }
 
 
 AuthSrv::~AuthSrv() {
 AuthSrv::~AuthSrv() {
@@ -239,7 +246,7 @@ AuthSrv::processMessage(InputBuffer& request_buffer, Message& message,
     message.setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
     message.setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
 
 
     try {
     try {
-        Query query(message, dnssec_ok);
+        Query query(message, impl_->cache_, dnssec_ok);
         impl_->data_sources_.doQuery(query);
         impl_->data_sources_.doQuery(query);
     } catch (const Exception& ex) {
     } catch (const Exception& ex) {
         if (impl_->verbose_mode_) {
         if (impl_->verbose_mode_) {

+ 1 - 1
src/bin/auth/auth_srv.h

@@ -43,7 +43,7 @@ private:
     AuthSrv(const AuthSrv& source);
     AuthSrv(const AuthSrv& source);
     AuthSrv& operator=(const AuthSrv& source);
     AuthSrv& operator=(const AuthSrv& source);
 public:
 public:
-    explicit AuthSrv();
+    explicit AuthSrv(const bool use_cache);
     ~AuthSrv();
     ~AuthSrv();
     //@}
     //@}
     /// \return \c true if the \message contains a response to be returned;
     /// \return \c true if the \message contains a response to be returned;

+ 7 - 4
src/bin/auth/main.cc

@@ -86,7 +86,7 @@ my_command_handler(const string& command, const ElementPtr args) {
 
 
 void
 void
 usage() {
 usage() {
-    cerr << "Usage: b10-auth [-p port] [-4|-6]" << endl;
+    cerr << "Usage: b10-auth [-p port] [-4|-6] [-nv]" << endl;
     exit(1);
     exit(1);
 }
 }
 } // end of anonymous namespace
 } // end of anonymous namespace
@@ -95,9 +95,9 @@ int
 main(int argc, char* argv[]) {
 main(int argc, char* argv[]) {
     int ch;
     int ch;
     const char* port = DNSPORT;
     const char* port = DNSPORT;
-    bool use_ipv4 = true, use_ipv6 = true;
+    bool use_ipv4 = true, use_ipv6 = true, cache = true;
 
 
-    while ((ch = getopt(argc, argv, "46p:v")) != -1) {
+    while ((ch = getopt(argc, argv, "46np:v")) != -1) {
         switch (ch) {
         switch (ch) {
         case '4':
         case '4':
             // Note that -4 means "ipv4 only", we need to set "use_ipv6" here,
             // Note that -4 means "ipv4 only", we need to set "use_ipv6" here,
@@ -110,6 +110,9 @@ main(int argc, char* argv[]) {
             // The same note as -4 applies.
             // The same note as -4 applies.
             use_ipv4 = false;
             use_ipv4 = false;
             break;
             break;
+        case 'n':
+            cache = false;
+            break;
         case 'p':
         case 'p':
             port = optarg;
             port = optarg;
             break;
             break;
@@ -142,7 +145,7 @@ main(int argc, char* argv[]) {
             specfile = string(AUTH_SPECFILE_LOCATION);
             specfile = string(AUTH_SPECFILE_LOCATION);
         }
         }
 
 
-        auth_server = new AuthSrv;
+        auth_server = new AuthSrv(cache);
         auth_server->setVerbose(verbose_mode);
         auth_server->setVerbose(verbose_mode);
 
 
         io_service = new asio_link::IOService(auth_server, port, use_ipv4,
         io_service = new asio_link::IOService(auth_server, port, use_ipv4,

+ 1 - 1
src/bin/auth/tests/auth_srv_unittest.cc

@@ -44,7 +44,7 @@ const char* BADCONFIG_TESTDB =
 
 
 class AuthSrvTest : public ::testing::Test {
 class AuthSrvTest : public ::testing::Test {
 protected:
 protected:
-    AuthSrvTest() : request_message(Message::RENDER),
+    AuthSrvTest() : server(true), request_message(Message::RENDER),
                     parse_message(Message::PARSE), default_qid(0x1035),
                     parse_message(Message::PARSE), default_qid(0x1035),
                     opcode(Opcode(Opcode::QUERY())), qname("www.example.com"),
                     opcode(Opcode(Opcode::QUERY())), qname("www.example.com"),
                     qclass(RRClass::IN()), qtype(RRType::A()), ibuffer(NULL),
                     qclass(RRClass::IN()), qtype(RRType::A()), ibuffer(NULL),

+ 8 - 3
src/bin/bind10/bind10.py.in

@@ -176,8 +176,8 @@ class ProcessInfo:
 class BoB:
 class BoB:
     """Boss of BIND class."""
     """Boss of BIND class."""
     
     
-    def __init__(self, msgq_socket_file=None, auth_port=5300, verbose=False,
-                 setuid=None, username=None):
+    def __init__(self, msgq_socket_file=None, auth_port=5300, nocache=False,
+                 verbose=False, setuid=None, username=None):
         """Initialize the Boss of BIND. This is a singleton (only one
         """Initialize the Boss of BIND. This is a singleton (only one
         can run).
         can run).
         
         
@@ -195,6 +195,7 @@ class BoB:
         self.runnable = False
         self.runnable = False
         self.uid = setuid
         self.uid = setuid
         self.username = username
         self.username = username
+        self.nocache = nocache
 
 
     def config_handler(self, new_config):
     def config_handler(self, new_config):
         if self.verbose:
         if self.verbose:
@@ -302,6 +303,8 @@ class BoB:
         # start b10-auth
         # start b10-auth
         # XXX: this must be read from the configuration manager in the future
         # XXX: this must be read from the configuration manager in the future
         authargs = ['b10-auth', '-p', str(self.auth_port)]
         authargs = ['b10-auth', '-p', str(self.auth_port)]
+        if self.nocache:
+            authargs += ['-n']
         if self.verbose:
         if self.verbose:
             sys.stdout.write("[bind10] Starting b10-auth using port %d\n" %
             sys.stdout.write("[bind10] Starting b10-auth using port %d\n" %
                              self.auth_port)
                              self.auth_port)
@@ -557,6 +560,8 @@ def main():
     parser = OptionParser(version=__version__)
     parser = OptionParser(version=__version__)
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
                       help="display more about what is going on")
                       help="display more about what is going on")
+    parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
+                      default=False, help="disable hot-spot cache in b10-auth")
     parser.add_option("-p", "--port", dest="auth_port", type="string",
     parser.add_option("-p", "--port", dest="auth_port", type="string",
                       action="callback", callback=check_port, default="5300",
                       action="callback", callback=check_port, default="5300",
                       help="port the b10-auth daemon will use (default 5300)")
                       help="port the b10-auth daemon will use (default 5300)")
@@ -621,7 +626,7 @@ def main():
 
 
     # Go bob!
     # Go bob!
     boss_of_bind = BoB(options.msgq_socket_file, int(options.auth_port),
     boss_of_bind = BoB(options.msgq_socket_file, int(options.auth_port),
-                       options.verbose, setuid, username)
+                       options.nocache, options.verbose, setuid, username)
     startup_result = boss_of_bind.startup()
     startup_result = boss_of_bind.startup()
     if startup_result:
     if startup_result:
         sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
         sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)

+ 1 - 0
src/lib/datasrc/Makefile.am

@@ -13,3 +13,4 @@ libdatasrc_la_SOURCES = data_source.h data_source.cc
 libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
 libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
 libdatasrc_la_SOURCES += query.h query.cc
 libdatasrc_la_SOURCES += query.h query.cc
+libdatasrc_la_SOURCES += cache.h cache.cc

+ 450 - 133
src/lib/datasrc/data_source.cc

@@ -24,6 +24,10 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 
 
+#include <datasrc/cache.h>
+#include <datasrc/data_source.h>
+#include <datasrc/query.h>
+
 #include <dns/base32.h>
 #include <dns/base32.h>
 #include <dns/buffer.h>
 #include <dns/buffer.h>
 #include <dns/message.h>
 #include <dns/message.h>
@@ -35,9 +39,6 @@
 
 
 #include <cc/data.h>
 #include <cc/data.h>
 
 
-#include "data_source.h"
-#include "query.h"
-
 #define RETERR(x) do { \
 #define RETERR(x) do { \
                       DataSrc::Result r = (x); \
                       DataSrc::Result r = (x); \
                       if (r != DataSrc::SUCCESS) \
                       if (r != DataSrc::SUCCESS) \
@@ -53,7 +54,37 @@ namespace datasrc {
 
 
 typedef boost::shared_ptr<const Nsec3Param> ConstNsec3ParamPtr;
 typedef boost::shared_ptr<const Nsec3Param> ConstNsec3ParamPtr;
 
 
-namespace {
+class ZoneInfo {
+public:
+    ZoneInfo(DataSrc* ts,
+             const isc::dns::Name& n,
+             const isc::dns::RRClass& c, 
+             const isc::dns::RRType& t = isc::dns::RRType::ANY()) :
+        top_source_(ts),
+        dsm_(((t == RRType::DS() && n.getLabelCount() != 1)
+              ? n.split(1, n.getLabelCount() - 1) : n),
+             c)
+    {}
+
+    const Name* getEnclosingZone() {
+        if (dsm_.getEnclosingZone() == NULL) {
+            top_source_->findClosestEnclosure(dsm_);
+        }
+        return (dsm_.getEnclosingZone());
+    }
+
+    const DataSrc* getDataSource() {
+        if (dsm_.getDataSource() == NULL) {
+            top_source_->findClosestEnclosure(dsm_);
+        }
+        return (dsm_.getDataSource());
+    }
+
+private:
+    const DataSrc* top_source_;
+    DataSrcMatch dsm_;
+};
+
 // Add a task to the query task queue to look up additional data
 // Add a task to the query task queue to look up additional data
 // (i.e., address records for the names included in NS or MX records)
 // (i.e., address records for the names included in NS or MX records)
 void
 void
@@ -68,14 +99,14 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
         if (rrset->getType() == RRType::NS()) {
         if (rrset->getType() == RRType::NS()) {
             const generic::NS& ns = dynamic_cast<const generic::NS&>(rd);
             const generic::NS& ns = dynamic_cast<const generic::NS&>(rd);
             q.tasks().push(QueryTaskPtr(
             q.tasks().push(QueryTaskPtr(
-                               new QueryTask(ns.getNSName(), q.qclass(),
+                               new QueryTask(q, ns.getNSName(),
                                              Section::ADDITIONAL(),
                                              Section::ADDITIONAL(),
                                              QueryTask::GLUE_QUERY,
                                              QueryTask::GLUE_QUERY,
                                              QueryTask::GETADDITIONAL))); 
                                              QueryTask::GETADDITIONAL))); 
         } else if (rrset->getType() == RRType::MX()) {
         } else if (rrset->getType() == RRType::MX()) {
             const generic::MX& mx = dynamic_cast<const generic::MX&>(rd);
             const generic::MX& mx = dynamic_cast<const generic::MX&>(rd);
             q.tasks().push(QueryTaskPtr(
             q.tasks().push(QueryTaskPtr(
-                               new QueryTask(mx.getMXName(), q.qclass(),
+                               new QueryTask(q, mx.getMXName(),
                                              Section::ADDITIONAL(),
                                              Section::ADDITIONAL(),
                                              QueryTask::NOGLUE_QUERY,
                                              QueryTask::NOGLUE_QUERY,
                                              QueryTask::GETADDITIONAL))); 
                                              QueryTask::GETADDITIONAL))); 
@@ -125,47 +156,313 @@ chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
         return;
         return;
     }
     }
 
 
+    // Stop chasing CNAMES after 16 lookups, to prevent loops
     if (q.tooMany()) {
     if (q.tooMany()) {
         return;
         return;
     }
     }
 
 
     q.tasks().push(QueryTaskPtr(
     q.tasks().push(QueryTaskPtr(
-                       new QueryTask(dynamic_cast<const generic::CNAME&>
+                       new QueryTask(q, dynamic_cast<const generic::CNAME&>
                                      (it->getCurrent()).getCname(),
                                      (it->getCurrent()).getCname(),
-                                     task->qclass,
-                                     task->qtype,
-                                     Section::ANSWER(),
+                                     task->qtype, Section::ANSWER(),
                                      QueryTask::FOLLOWCNAME)));
                                      QueryTask::FOLLOWCNAME)));
 }
 }
 
 
-// Perform the query specified in a QueryTask object
+// Check the cache for data which can answer the current query task.
+bool
+checkCache(QueryTask& task, RRsetList& target) {
+    HotCache& cache = task.q.getCache();
+    RRsetList rrsets;
+    RRsetPtr rrset;
+    int count = 0;
+    uint32_t flags = 0, cflags = 0;
+    bool hit = false, found = false;
+
+    switch (task.op) {
+    case QueryTask::SIMPLE_QUERY:       // Find exact RRset
+        // ANY queries must be handled by the low-level data source,
+        // or the results won't be guaranteed to be complete
+        if (task.qtype == RRType::ANY() || task.qclass == RRClass::ANY()) {
+            break;
+        }
+
+        hit = cache.retrieve(task.qname, task.qclass, task.qtype, rrset, flags);
+        if (hit) {
+            if (rrset) {
+                rrsets.addRRset(rrset);
+                target.append(rrsets);
+            }
+            task.flags = flags;
+            return (true);
+        }
+        break;
+
+    case QueryTask::AUTH_QUERY:         // Find exact RRset or CNAME
+        if (task.qtype == RRType::ANY() || task.qclass == RRClass::ANY()) {
+            break;
+        }
+
+        hit = cache.retrieve(task.qname, task.qclass, task.qtype, rrset, flags);
+        if (!hit || !rrset || (flags & DataSrc::CNAME_FOUND) != 0) {
+            hit = cache.retrieve(task.qname, task.qclass, RRType::CNAME(),
+                                 rrset, flags);
+        }
+
+        if (hit) {
+            if (rrset) {
+                rrsets.addRRset(rrset);
+                target.append(rrsets);
+            }
+            task.flags = flags;
+            return (true);
+        }
+        break;
+
+    case QueryTask::GLUE_QUERY:         // Find addresses
+    case QueryTask::NOGLUE_QUERY:
+        // (XXX: need to figure out how to deal with noglue case)
+        flags = 0;
+
+        hit = cache.retrieve(task.qname, task.qclass, RRType::A(),
+                             rrset, cflags);
+        if (hit) {
+            flags |= cflags;
+            ++count;
+            if (rrset) {
+                rrsets.addRRset(rrset);
+                found = true;
+            }
+        }
+
+        hit = cache.retrieve(task.qname, task.qclass, RRType::AAAA(),
+                             rrset, flags);
+        if (hit) {
+            flags |= cflags;
+            ++count;
+            if (rrset) {
+                rrsets.addRRset(rrset);
+                found = true;
+            }
+        }
+
+        if (count == 2) {
+            if (found) {
+                flags &= ~DataSrc::TYPE_NOT_FOUND;
+                target.append(rrsets);
+            }
+            task.flags = flags;
+            return (true);
+        } 
+        break;
+
+
+    case QueryTask::REF_QUERY:          // Find NS, DS and/or DNAME
+        flags = count = 0;
+
+        hit = cache.retrieve(task.qname, task.qclass, RRType::NS(),
+                             rrset, cflags);
+        if (hit) {
+            flags |= cflags;
+            ++count;
+            if (rrset) {
+                rrsets.addRRset(rrset);
+                found = true;
+            }
+        }
+
+        hit = cache.retrieve(task.qname, task.qclass, RRType::DS(),
+                             rrset, flags);
+        if (hit) {
+            flags |= cflags;
+            ++count;
+            if (rrset) {
+                rrsets.addRRset(rrset);
+                found = true;
+            }
+        }
+
+        hit = cache.retrieve(task.qname, task.qclass, RRType::DNAME(),
+                             rrset, flags);
+        if (hit) {
+            flags |= cflags;
+            ++count;
+            if (rrset) {
+                rrsets.addRRset(rrset);
+                found = true;
+            }
+        }
+
+        if (count == 3) {
+            if (found) {
+                flags &= ~DataSrc::TYPE_NOT_FOUND;
+                flags &= DataSrc::REFERRAL;
+                target.append(rrsets);
+            }
+            task.flags = flags;
+            return (true);
+        } 
+        break;
+    }
+
+    return (false);
+}
+
+// Carry out the query specified in a QueryTask object
 DataSrc::Result
 DataSrc::Result
-doQueryTask(const DataSrc* ds, const Name* zonename, QueryTask& task,
-            RRsetList& target)
-{
+doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
+    HotCache& cache = task.q.getCache();
+    RRsetPtr rrset;
+
+    // First, check the cache for matching data
+    if (checkCache(task, target)) {
+        return (DataSrc::SUCCESS);
+    }
+
+    // Requested data weren't in the cache (or were, but had expired),
+    // so now we proceed with the low-level data source lookup, and cache
+    // whatever we find.
+    const DataSrc* ds = zoneinfo.getDataSource();
+    const Name* const zonename = zoneinfo.getEnclosingZone();
+
+    if (ds == NULL) {
+        task.flags |= DataSrc::NO_SUCH_ZONE;
+        return (DataSrc::SUCCESS);
+    }
+
+    DataSrc::Result result;
     switch (task.op) {
     switch (task.op) {
+    case QueryTask::SIMPLE_QUERY:
+        result = ds->findExactRRset(task.qname, task.qclass, task.qtype,
+                                    target, task.flags, zonename);
+
+        if (result != DataSrc::SUCCESS) {
+            return (result);
+        }
+
+        if (task.qclass == RRClass::ANY()) {
+            // XXX: Currently, RRsetList::findRRset() doesn't handle
+            // ANY queries, and without that we can't cache the results,
+            // so we just return in that case.
+            return (result);
+        }
+
+        if (task.flags == 0) {
+            rrset = target.findRRset(task.qtype, task.qclass);
+            assert(rrset);
+            cache.addPositive(rrset, task.flags);
+        } else {
+            cache.addNegative(task.qname, task.qclass, task.qtype, task.flags);
+        }
+
+        return (result);
+
     case QueryTask::AUTH_QUERY:
     case QueryTask::AUTH_QUERY:
-        return (ds->findRRset(task.qname, task.qclass, task.qtype,
-                              target, task.flags, zonename));
+        result = ds->findRRset(task.qname, task.qclass, task.qtype,
+                               target, task.flags, zonename);
 
 
-    case QueryTask::SIMPLE_QUERY:
-        return (ds->findExactRRset(task.qname, task.qclass, task.qtype,
-                                   target, task.flags, zonename));
+        if (result != DataSrc::SUCCESS) {
+            return (result);
+        }
+
+        if (task.qclass == RRClass::ANY()) {
+            return (result);
+        }
+
+        if (task.qtype == RRType::ANY()) {
+            BOOST_FOREACH(RRsetPtr rr, target) {
+                cache.addPositive(rr, task.flags);
+            }
+        } else if ((task.flags & DataSrc::CNAME_FOUND) != 0) {
+            cache.addNegative(task.qname, task.qclass, task.qtype, task.flags);
+            rrset = target.findRRset(RRType::CNAME(), task.qclass);
+            assert(rrset);
+            cache.addPositive(rrset, task.flags);
+        } else if ((task.flags & DataSrc::DATA_NOT_FOUND) == 0) {
+            if (task.qtype != RRType::CNAME()) {
+                cache.addNegative(task.qname, task.qclass, RRType::CNAME(),
+                                  task.flags);
+            }
+            rrset = target.findRRset(task.qtype, task.qclass);
+            assert(rrset);
+            cache.addPositive(rrset, task.flags);
+        } else {
+            cache.addNegative(task.qname, task.qclass, task.qtype, task.flags);
+        }
+
+        return (result);
 
 
     case QueryTask::GLUE_QUERY:
     case QueryTask::GLUE_QUERY:
     case QueryTask::NOGLUE_QUERY:
     case QueryTask::NOGLUE_QUERY:
-        return (ds->findAddrs(task.qname, task.qclass, target,
-                              task.flags, zonename));
+        result = ds->findAddrs(task.qname, task.qclass, target,
+                               task.flags, zonename);
+
+        if (result != DataSrc::SUCCESS) {
+            return (result);
+        }
+
+        if (task.qclass == RRClass::ANY()) {
+            return (result);
+        }
+
+        rrset = target.findRRset(RRType::A(), task.qclass);
+        if (rrset) {
+            cache.addPositive(rrset, task.flags);
+        } else {
+            cache.addNegative(task.qname, task.qclass, RRType::A(), task.flags);
+        }
+
+        rrset = target.findRRset(RRType::AAAA(), task.qclass);
+        if (rrset) {
+            cache.addPositive(rrset, task.flags);
+        } else {
+            cache.addNegative(task.qname, task.qclass, RRType::AAAA(),
+                              task.flags);
+        }
+
+        return (result);
 
 
     case QueryTask::REF_QUERY:
     case QueryTask::REF_QUERY:
-        return (ds->findReferral(task.qname, task.qclass, target,
-                                 task.flags, zonename));
+        result = ds->findReferral(task.qname, task.qclass, target,
+                                 task.flags, zonename);
+
+        if (result != DataSrc::SUCCESS) {
+            return (result);
+        }
+
+        if (task.qclass == RRClass::ANY()) {
+            return (result);
+        }
+
+        rrset = target.findRRset(RRType::NS(), task.qclass);
+        if (rrset) {
+            cache.addPositive(rrset, task.flags);
+        } else {
+            cache.addNegative(task.qname, task.qclass, RRType::NS(),
+                              task.flags);
+        }
+        rrset = target.findRRset(RRType::DS(), task.qclass);
+        if (rrset) {
+            cache.addPositive(rrset, task.flags);
+        } else {
+            cache.addNegative(task.qname, task.qclass, RRType::DS(),
+                              task.flags);
+        }
+        rrset = target.findRRset(RRType::DNAME(), task.qclass);
+        if (rrset) {
+            cache.addPositive(rrset, task.flags);
+        } else {
+            cache.addNegative(task.qname, task.qclass, RRType::DNAME(),
+                              task.flags);
+        }
+
+        return (result);
     }
     }
 
 
     // Not reached
     // Not reached
     return (DataSrc::ERROR);
     return (DataSrc::ERROR);
 }
 }
 
 
+
 // Add an RRset (and its associated RRSIG) to a message section,
 // Add an RRset (and its associated RRSIG) to a message section,
 // checking first to ensure that there isn't already an RRset with
 // checking first to ensure that there isn't already an RRset with
 // the same name and type.
 // the same name and type.
@@ -202,12 +499,12 @@ copyAuth(Query& q, RRsetList& auth) {
 
 
 // Query for referrals (i.e., NS/DS or DNAME) at a given name
 // Query for referrals (i.e., NS/DS or DNAME) at a given name
 inline bool
 inline bool
-refQuery(const Name& name, const RRClass& qclass, const DataSrc* ds,
-         const Name* zonename, RRsetList& target)
+refQuery(const Query& q, const Name& name, ZoneInfo& zoneinfo,
+         RRsetList& target)
 {
 {
-    QueryTask newtask(name, qclass, QueryTask::REF_QUERY);
+    QueryTask newtask(q, name, QueryTask::REF_QUERY);
 
 
-    if (doQueryTask(ds, zonename, newtask, target) != DataSrc::SUCCESS) {
+    if (doQueryTask(newtask, zoneinfo, target) != DataSrc::SUCCESS) {
         // Lookup failed
         // Lookup failed
         return (false);
         return (false);
     }
     }
@@ -224,16 +521,22 @@ refQuery(const Name& name, const RRClass& qclass, const DataSrc* ds,
 // referrals.  Note that we exclude the apex name and query name themselves;
 // referrals.  Note that we exclude the apex name and query name themselves;
 // they'll be handled in a normal lookup in the zone.
 // they'll be handled in a normal lookup in the zone.
 inline bool
 inline bool
-hasDelegation(const DataSrc* ds, const Name* zonename, Query& q,
-              QueryTaskPtr task)
-{
+hasDelegation(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo) {
+    const Name* const zonename = zoneinfo.getEnclosingZone();
+    if (zonename == NULL) {
+        if (task->state == QueryTask::GETANSWER) {
+            q.message().setRcode(Rcode::REFUSED());
+        }
+        return (false);
+    }
+
     const int diff = task->qname.getLabelCount() - zonename->getLabelCount();
     const int diff = task->qname.getLabelCount() - zonename->getLabelCount();
     if (diff > 1) {
     if (diff > 1) {
         bool found = false;
         bool found = false;
         RRsetList ref;
         RRsetList ref;
         for (int i = diff - 1; i > 0; --i) {
         for (int i = diff - 1; i > 0; --i) {
             const Name sub(task->qname.split(i));
             const Name sub(task->qname.split(i));
-            if (refQuery(sub, q.qclass(), ds, zonename, ref)) {
+            if (refQuery(q, sub, zoneinfo, ref)) {
                 found = true;
                 found = true;
                 break;
                 break;
             }
             }
@@ -280,12 +583,12 @@ hasDelegation(const DataSrc* ds, const Name* zonename, Query& q,
 }
 }
 
 
 inline DataSrc::Result
 inline DataSrc::Result
-addSOA(Query& q, const Name* zonename, const DataSrc* ds) {
+addSOA(Query& q, ZoneInfo& zoneinfo) {
     RRsetList soa;
     RRsetList soa;
 
 
-    QueryTask newtask(*zonename, q.qclass(), RRType::SOA(),
-                      QueryTask::SIMPLE_QUERY);
-    RETERR(doQueryTask(ds, zonename, newtask, soa));
+    const Name* const zonename = zoneinfo.getEnclosingZone();
+    QueryTask newtask(q, *zonename, RRType::SOA(), QueryTask::SIMPLE_QUERY);
+    RETERR(doQueryTask(newtask, zoneinfo, soa));
     if (newtask.flags != 0) {
     if (newtask.flags != 0) {
         return (DataSrc::ERROR);
         return (DataSrc::ERROR);
     }
     }
@@ -296,14 +599,11 @@ addSOA(Query& q, const Name* zonename, const DataSrc* ds) {
 }
 }
 
 
 inline DataSrc::Result
 inline DataSrc::Result
-addNSEC(Query& q, const QueryTaskPtr task, const Name& name,
-        const Name& zonename, const DataSrc* ds)
-{
+addNSEC(Query& q, const Name& name, ZoneInfo& zoneinfo) {
     RRsetList nsec;
     RRsetList nsec;
 
 
-    QueryTask newtask(name, task->qclass, RRType::NSEC(),
-                      QueryTask::SIMPLE_QUERY); 
-    RETERR(doQueryTask(ds, &zonename, newtask, nsec));
+    QueryTask newtask(q, name, RRType::NSEC(), QueryTask::SIMPLE_QUERY); 
+    RETERR(doQueryTask(newtask, zoneinfo, nsec));
     if (newtask.flags == 0) {
     if (newtask.flags == 0) {
         addToMessage(q, Section::AUTHORITY(),
         addToMessage(q, Section::AUTHORITY(),
                      nsec.findRRset(RRType::NSEC(), q.qclass()));
                      nsec.findRRset(RRType::NSEC(), q.qclass()));
@@ -313,23 +613,31 @@ addNSEC(Query& q, const QueryTaskPtr task, const Name& name,
 }
 }
 
 
 inline DataSrc::Result
 inline DataSrc::Result
-getNsec3(const DataSrc* ds, const Name& zonename, const RRClass& qclass,
-         string& hash, RRsetPtr& target)
-{
+getNsec3(Query& q, ZoneInfo& zoneinfo, string& hash, RRsetPtr& target) {
+    const DataSrc* ds = zoneinfo.getDataSource();
+    const Name* const zonename = zoneinfo.getEnclosingZone();
+
+    if (ds == NULL) {
+        q.message().setRcode(Rcode::SERVFAIL());
+        return (DataSrc::ERROR);
+    }
+
     RRsetList rl;
     RRsetList rl;
-    RETERR(ds->findCoveringNSEC3(zonename, hash, rl));
-    target = rl.findRRset(RRType::NSEC3(), qclass);
+    RETERR(ds->findCoveringNSEC3(*zonename, hash, rl));
+    target = rl.findRRset(RRType::NSEC3(), q.qclass());
+
     return (DataSrc::SUCCESS);
     return (DataSrc::SUCCESS);
 }
 }
 
 
 ConstNsec3ParamPtr
 ConstNsec3ParamPtr
-getNsec3Param(Query& q, const DataSrc* ds, const Name& zonename) {
+getNsec3Param(Query& q, ZoneInfo& zoneinfo) {
     DataSrc::Result result;
     DataSrc::Result result;
     RRsetList nsec3param;
     RRsetList nsec3param;
 
 
-    QueryTask newtask(zonename, q.qclass(), RRType::NSEC3PARAM(),
+    const Name* const zonename = zoneinfo.getEnclosingZone();
+    QueryTask newtask(q, *zonename, RRType::NSEC3PARAM(),
                       QueryTask::SIMPLE_QUERY); 
                       QueryTask::SIMPLE_QUERY); 
-    result = doQueryTask(ds, &zonename, newtask, nsec3param);
+    result = doQueryTask(newtask, zoneinfo, nsec3param);
     newtask.flags &= ~DataSrc::REFERRAL;
     newtask.flags &= ~DataSrc::REFERRAL;
     if (result != DataSrc::SUCCESS || newtask.flags != 0) {
     if (result != DataSrc::SUCCESS || newtask.flags != 0) {
         return (ConstNsec3ParamPtr());
         return (ConstNsec3ParamPtr());
@@ -356,15 +664,16 @@ getNsec3Param(Query& q, const DataSrc* ds, const Name& zonename) {
 }
 }
 
 
 inline DataSrc::Result
 inline DataSrc::Result
-proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds,
-        const Name& zonename, const bool wildcard)
-{
-    ConstNsec3ParamPtr nsec3 = getNsec3Param(q, ds, zonename);
+proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) {
+    Message& m = q.message();
+    const Name* const zonename = zoneinfo.getEnclosingZone();
+    ConstNsec3ParamPtr nsec3 = getNsec3Param(q, zoneinfo);
+
     if (nsec3 != NULL) {
     if (nsec3 != NULL) {
         // Attach the NSEC3 record covering the QNAME
         // Attach the NSEC3 record covering the QNAME
         RRsetPtr rrset;
         RRsetPtr rrset;
         string hash1(nsec3->getHash(task->qname));
         string hash1(nsec3->getHash(task->qname));
-        RETERR(getNsec3(ds, zonename, q.qclass(), hash1, rrset));
+        RETERR(getNsec3(q, zoneinfo, hash1, rrset));
         addToMessage(q, Section::AUTHORITY(), rrset);
         addToMessage(q, Section::AUTHORITY(), rrset);
 
 
         // If this is an NXRRSET or NOERROR/NODATA, we're done
         // If this is an NXRRSET or NOERROR/NODATA, we're done
@@ -373,7 +682,7 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds,
         }
         }
 
 
         // Find the closest provable enclosing name for QNAME
         // Find the closest provable enclosing name for QNAME
-        Name enclosure(zonename);
+        Name enclosure(*zonename);
         const int diff = task->qname.getLabelCount() -
         const int diff = task->qname.getLabelCount() -
             enclosure.getLabelCount();
             enclosure.getLabelCount();
         string hash2;
         string hash2;
@@ -388,7 +697,7 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds,
 
 
             // hash2 will be overwritten with the actual hash found;
             // hash2 will be overwritten with the actual hash found;
             // we don't want to use one until we find an exact match
             // we don't want to use one until we find an exact match
-            RETERR(getNsec3(ds, zonename, q.qclass(), hash2, rrset));
+            RETERR(getNsec3(q, zoneinfo, hash2, rrset));
             if (hash2 == nodehash) {
             if (hash2 == nodehash) {
                 addToMessage(q, Section::AUTHORITY(), rrset);
                 addToMessage(q, Section::AUTHORITY(), rrset);
                 break;
                 break;
@@ -403,19 +712,24 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds,
         // Otherwise, there is no wildcard record, so we must add a
         // Otherwise, there is no wildcard record, so we must add a
         // covering NSEC3 to prove that it doesn't exist.
         // covering NSEC3 to prove that it doesn't exist.
         string hash3(nsec3->getHash(Name("*").concatenate(enclosure)));
         string hash3(nsec3->getHash(Name("*").concatenate(enclosure)));
-        RETERR(getNsec3(ds, zonename, q.qclass(), hash3, rrset));
+        RETERR(getNsec3(q, zoneinfo, hash3, rrset));
         if (hash3 != hash1 && hash3 != hash2) {
         if (hash3 != hash1 && hash3 != hash2) {
             addToMessage(q, Section::AUTHORITY(), rrset);
             addToMessage(q, Section::AUTHORITY(), rrset);
         }
         }
     } else {
     } else {
         Name nsecname(task->qname);
         Name nsecname(task->qname);
         if ((task->flags & DataSrc::NAME_NOT_FOUND) != 0 || wildcard) {
         if ((task->flags & DataSrc::NAME_NOT_FOUND) != 0 || wildcard) {
-            ds->findPreviousName(task->qname, nsecname, &zonename);
+            const DataSrc* ds = zoneinfo.getDataSource();
+            if (ds == NULL) {
+                m.setRcode(Rcode::SERVFAIL());
+                return (DataSrc::ERROR);
+            }
+            ds->findPreviousName(task->qname, nsecname, zonename);
         }
         }
 
 
-        RETERR(addNSEC(q, task, nsecname, zonename, ds));
+        RETERR(addNSEC(q, nsecname, zoneinfo));
         if ((task->flags & DataSrc::TYPE_NOT_FOUND) != 0 ||
         if ((task->flags & DataSrc::TYPE_NOT_FOUND) != 0 ||
-            nsecname == zonename)
+            nsecname == *zonename)
         {
         {
             return (DataSrc::SUCCESS);
             return (DataSrc::SUCCESS);
         }
         }
@@ -427,7 +741,7 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds,
 
 
         // Otherwise, there is no wildcard record, so we must add an
         // Otherwise, there is no wildcard record, so we must add an
         // NSEC for the zone to prove the wildcard doesn't exist.
         // NSEC for the zone to prove the wildcard doesn't exist.
-        RETERR(addNSEC(q, task, zonename, zonename, ds));
+        RETERR(addNSEC(q, *zonename, zoneinfo));
     }
     }
 
 
     return (DataSrc::SUCCESS);
     return (DataSrc::SUCCESS);
@@ -435,9 +749,7 @@ proveNX(Query& q, QueryTaskPtr task, const DataSrc* ds,
 
 
 // Attempt a wildcard lookup
 // Attempt a wildcard lookup
 inline DataSrc::Result
 inline DataSrc::Result
-tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds,
-            const Name* zonename, bool& found)
-{
+tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
     Message& m = q.message();
     Message& m = q.message();
     DataSrc::Result result;
     DataSrc::Result result;
     found = false;
     found = false;
@@ -448,6 +760,7 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds,
         return (DataSrc::SUCCESS);
         return (DataSrc::SUCCESS);
     }
     }
 
 
+    const Name* const zonename = zoneinfo.getEnclosingZone();
     const int diff = task->qname.getLabelCount() - zonename->getLabelCount();
     const int diff = task->qname.getLabelCount() - zonename->getLabelCount();
     if (diff < 1) {
     if (diff < 1) {
         return (DataSrc::SUCCESS);
         return (DataSrc::SUCCESS);
@@ -459,9 +772,9 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds,
 
 
     for (int i = 1; i <= diff; ++i) {
     for (int i = 1; i <= diff; ++i) {
         const Name& wname(star.concatenate(task->qname.split(i)));
         const Name& wname(star.concatenate(task->qname.split(i)));
-        QueryTask newtask(wname, task->qclass, task->qtype, Section::ANSWER(),
+        QueryTask newtask(q, wname, task->qtype, Section::ANSWER(),
                           QueryTask::AUTH_QUERY); 
                           QueryTask::AUTH_QUERY); 
-        result = doQueryTask(ds, zonename, newtask, wild);
+        result = doQueryTask(newtask, zoneinfo, wild);
         if (result == DataSrc::SUCCESS) {
         if (result == DataSrc::SUCCESS) {
             if (newtask.flags == 0) {
             if (newtask.flags == 0) {
                 task->flags &= ~DataSrc::NAME_NOT_FOUND;
                 task->flags &= ~DataSrc::NAME_NOT_FOUND;
@@ -487,7 +800,7 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds,
     if (found) {
     if (found) {
         // Prove the nonexistence of the name we were looking for
         // Prove the nonexistence of the name we were looking for
         if (q.wantDnssec()) {
         if (q.wantDnssec()) {
-            result = proveNX(q, task, ds, *zonename, true);
+            result = proveNX(q, task, zoneinfo, true);
             if (result != DataSrc::SUCCESS) {
             if (result != DataSrc::SUCCESS) {
                 m.setRcode(Rcode::SERVFAIL());
                 m.setRcode(Rcode::SERVFAIL());
                 return (DataSrc::ERROR);
                 return (DataSrc::ERROR);
@@ -511,7 +824,7 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds,
             }
             }
 
 
             RRsetList auth;
             RRsetList auth;
-            if (!refQuery(*zonename, q.qclass(), ds, zonename, auth)) {
+            if (!refQuery(q, *zonename, zoneinfo, auth)) {
                 return (DataSrc::ERROR);
                 return (DataSrc::ERROR);
             }
             }
 
 
@@ -521,7 +834,6 @@ tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds,
 
 
     return (DataSrc::SUCCESS);
     return (DataSrc::SUCCESS);
 }
 }
-} // end of anonymous namespace
 
 
 //
 //
 // doQuery: Processes a query.
 // doQuery: Processes a query.
@@ -531,6 +843,12 @@ DataSrc::doQuery(Query& q) {
     Message& m = q.message();
     Message& m = q.message();
     vector<RRsetPtr> additional;
     vector<RRsetPtr> additional;
 
 
+    // Record the fact that the query is being processed by the
+    // current data source.
+    q.setDatasrc(this);
+
+    // Process the query task queue.  (The queue is initialized
+    // and the first task placed on it by the Query constructor.)
     m.clearHeaderFlag(MessageFlag::AA());
     m.clearHeaderFlag(MessageFlag::AA());
     while (!q.tasks().empty()) {
     while (!q.tasks().empty()) {
         QueryTaskPtr task = q.tasks().front();
         QueryTaskPtr task = q.tasks().front();
@@ -549,55 +867,29 @@ DataSrc::doQuery(Query& q) {
             return;
             return;
         }
         }
 
 
-        // Find the closest enclosing zone for which we are authoritative,
-        // and the concrete data source which is authoritative for it.
-        // (Note that RRtype DS queries need to go to the parent.)
-        const int nlabels = task->qname.getLabelCount() - 1;
-        NameMatch match(nlabels != 0 && task->qtype == RRType::DS() ?
-                        task->qname.split(1) : task->qname);
-        findClosestEnclosure(match, task->qclass);
-        const DataSrc* datasource = match.bestDataSrc();
-        const Name* zonename = match.closestName();
-
-        assert((datasource == NULL && zonename == NULL) ||
-               (datasource != NULL && zonename != NULL));
-
+        ZoneInfo zoneinfo(this, task->qname, task->qclass, task->qtype);
         RRsetList data;
         RRsetList data;
         Result result = SUCCESS;
         Result result = SUCCESS;
 
 
-        if (datasource) {
-            // For these query task types, if there is more than
-            // one level between the zone name and qname, we need to
-            // check the intermediate nodes for referrals.
-            if ((task->op == QueryTask::AUTH_QUERY ||
-                 task->op == QueryTask::NOGLUE_QUERY) &&
-                hasDelegation(datasource, zonename, q, task)) {
-                continue;
-            }
-
-            result = doQueryTask(datasource, zonename, *task, data);
-            if (result != SUCCESS) {
-                m.setRcode(Rcode::SERVFAIL());
-                return;
-            }
+        // For these query task types, if there is more than
+        // one level between the zone name and qname, we need to
+        // check the intermediate nodes for referrals.
+        if ((task->op == QueryTask::AUTH_QUERY ||
+             task->op == QueryTask::NOGLUE_QUERY) &&
+            hasDelegation(q, task, zoneinfo)) {
+            continue;
+        }
 
 
-            // Query found a referral; let's find out if that was expected--
-            // i.e., if an NS was at the zone apex, or if we were querying
-            // specifically for, and found, a DS, NSEC, or DNAME record.
-            if ((task->flags & REFERRAL) != 0 &&
-                (zonename->getLabelCount() == task->qname.getLabelCount() ||
-                 ((task->qtype == RRType::NSEC() ||
-                   task->qtype == RRType::DS() ||
-                   task->qtype == RRType::DNAME()) &&
-                  data.findRRset(task->qtype, task->qclass)))) {
-                task->flags &= ~REFERRAL;
-            }
-        } else {
-            task->flags = NO_SUCH_ZONE;
+        result = doQueryTask(*task, zoneinfo, data);
+        if (result != SUCCESS) {
+            m.setRcode(Rcode::SERVFAIL());
+            return;
+        }
 
 
-            // No such zone.  If we're chasing cnames or adding additional
-            // data, that's okay, but if doing an original query, return
-            // REFUSED.
+        // No such zone.  If we're chasing cnames or adding additional
+        // data, that's okay, but if doing an original query, return
+        // REFUSED.
+        if (task->flags == NO_SUCH_ZONE) {
             if (task->state == QueryTask::GETANSWER) {
             if (task->state == QueryTask::GETANSWER) {
                 m.setRcode(Rcode::REFUSED());
                 m.setRcode(Rcode::REFUSED());
                 return;
                 return;
@@ -605,6 +897,19 @@ DataSrc::doQuery(Query& q) {
             continue;
             continue;
         }
         }
 
 
+        // Query found a referral; let's find out if that was expected--
+        // i.e., if an NS was at the zone apex, or if we were querying
+        // specifically for, and found, a DS, NSEC, or DNAME record.
+        const Name* const zonename = zoneinfo.getEnclosingZone();
+        if ((task->flags & REFERRAL) != 0 &&
+            (zonename->getLabelCount() == task->qname.getLabelCount() ||
+             ((task->qtype == RRType::NSEC() ||
+               task->qtype == RRType::DS() ||
+               task->qtype == RRType::DNAME()) &&
+              data.findRRset(task->qtype, task->qclass)))) {
+            task->flags &= ~REFERRAL;
+        }
+
         if (result == SUCCESS && task->flags == 0) {
         if (result == SUCCESS && task->flags == 0) {
             bool have_ns = false, need_auth = false;
             bool have_ns = false, need_auth = false;
             switch (task->state) {
             switch (task->state) {
@@ -626,13 +931,12 @@ DataSrc::doQuery(Query& q) {
                     // Add the NS records for the enclosing zone to
                     // Add the NS records for the enclosing zone to
                     // the authority section.
                     // the authority section.
                     RRsetList auth;
                     RRsetList auth;
-                    if (!refQuery(*zonename, q.qclass(), datasource, zonename,
-                                  auth) ||
-                        !auth.findRRset(RRType::NS(),
-                                        datasource->getClass())) {
+                    const DataSrc* ds = zoneinfo.getDataSource();
+                    if (!refQuery(q, Name(*zonename), zoneinfo, auth)  ||
+                        !auth.findRRset(RRType::NS(), ds->getClass())) {
                         isc_throw(DataSourceError,
                         isc_throw(DataSourceError,
                                   "NS RR not found in " << *zonename << "/" <<
                                   "NS RR not found in " << *zonename << "/" <<
-                                  datasource->getClass());
+                                  q.qclass());
                     }
                     }
 
 
                     copyAuth(q, auth);
                     copyAuth(q, auth);
@@ -673,8 +977,7 @@ DataSrc::doQuery(Query& q) {
             if (task->state == QueryTask::GETANSWER) {
             if (task->state == QueryTask::GETANSWER) {
                 RRsetList auth;
                 RRsetList auth;
                 m.clearHeaderFlag(MessageFlag::AA());
                 m.clearHeaderFlag(MessageFlag::AA());
-                if (!refQuery(task->qname, q.qclass(), datasource, zonename,
-                              auth)) {
+                if (!refQuery(q, task->qname, zoneinfo, auth)) {
                     m.setRcode(Rcode::SERVFAIL());
                     m.setRcode(Rcode::SERVFAIL());
                     return;
                     return;
                 }
                 }
@@ -697,7 +1000,7 @@ DataSrc::doQuery(Query& q) {
             // and the name was not found, we need to find out whether
             // and the name was not found, we need to find out whether
             // there are any relevant wildcards.
             // there are any relevant wildcards.
             bool wildcard_found = false;
             bool wildcard_found = false;
-            result = tryWildcard(q, task, datasource, zonename, wildcard_found);
+            result = tryWildcard(q, task, zoneinfo, wildcard_found);
             if (result != SUCCESS) {
             if (result != SUCCESS) {
                 m.setRcode(Rcode::SERVFAIL());
                 m.setRcode(Rcode::SERVFAIL());
                 return;
                 return;
@@ -719,21 +1022,22 @@ DataSrc::doQuery(Query& q) {
                     m.setRcode(Rcode::NXDOMAIN());
                     m.setRcode(Rcode::NXDOMAIN());
                 }
                 }
 
 
-                result = addSOA(q, zonename, datasource);
+                result = addSOA(q, zoneinfo);
                 if (result != SUCCESS) {
                 if (result != SUCCESS) {
                     isc_throw(DataSourceError,
                     isc_throw(DataSourceError,
                               "SOA RR not found in" << *zonename <<
                               "SOA RR not found in" << *zonename <<
-                              "/" << datasource->getClass());
+                              "/" << q.qclass());
                 }
                 }
             }
             }
 
 
             Name nsecname(task->qname);
             Name nsecname(task->qname);
             if ((task->flags & NAME_NOT_FOUND) != 0) {
             if ((task->flags & NAME_NOT_FOUND) != 0) {
-                datasource->findPreviousName(task->qname, nsecname, zonename);
+                const DataSrc* ds = zoneinfo.getDataSource();
+                ds->findPreviousName(task->qname, nsecname, zonename);
             }
             }
 
 
             if (q.wantDnssec()) {
             if (q.wantDnssec()) {
-                result = proveNX(q, task, datasource, *zonename, false);
+                result = proveNX(q, task, zoneinfo, false);
                 if (result != DataSrc::SUCCESS) {
                 if (result != DataSrc::SUCCESS) {
                     m.setRcode(Rcode::SERVFAIL());
                     m.setRcode(Rcode::SERVFAIL());
                     return;
                     return;
@@ -858,25 +1162,38 @@ MetaDataSrc::removeDataSrc(ConstDataSrcPtr data_src) {
 }
 }
 
 
 void
 void
-MetaDataSrc::findClosestEnclosure(NameMatch& match, const RRClass& qclass) const
-{
-    if (getClass() != qclass &&
-        getClass() != RRClass::ANY() && qclass != RRClass::ANY()) {
+MetaDataSrc::findClosestEnclosure(DataSrcMatch& match) const {
+    if (getClass() != match.getClass() &&
+        getClass() != RRClass::ANY() && match.getClass() != RRClass::ANY()) {
         return;
         return;
     }
     }
 
 
     BOOST_FOREACH (ConstDataSrcPtr data_src, data_sources) {
     BOOST_FOREACH (ConstDataSrcPtr data_src, data_sources) {
-        data_src->findClosestEnclosure(match, qclass);
+        data_src->findClosestEnclosure(match);
     }
     }
 }
 }
 
 
-NameMatch::~NameMatch() {
+DataSrcMatch::~DataSrcMatch() {
     delete closest_name_;
     delete closest_name_;
 }
 }
 
 
 void
 void
-NameMatch::update(const DataSrc& new_source, const Name& container) {
+DataSrcMatch::update(const DataSrc& new_source, const Name& container) {
+    if (getClass() != new_source.getClass() && getClass() != RRClass::ANY() &&
+        new_source.getClass() != RRClass::ANY())
+    {
+        return;
+    }
+
     if (closest_name_ == NULL) {
     if (closest_name_ == NULL) {
+        const NameComparisonResult::NameRelation cmp =
+            getName().compare(container).getRelation();
+        if (cmp != NameComparisonResult::EQUAL &&
+            cmp != NameComparisonResult::SUBDOMAIN)
+        {
+            return;
+        }
+
         closest_name_ = new Name(container);
         closest_name_ = new Name(container);
         best_source_ = &new_source;
         best_source_ = &new_source;
         return;
         return;
@@ -884,7 +1201,7 @@ NameMatch::update(const DataSrc& new_source, const Name& container) {
 
 
     if (container.compare(*closest_name_).getRelation() ==
     if (container.compare(*closest_name_).getRelation() ==
         NameComparisonResult::SUBDOMAIN) {
         NameComparisonResult::SUBDOMAIN) {
-        const Name* newname = new Name(container);
+        Name* newname = new Name(container);
         delete closest_name_;
         delete closest_name_;
         closest_name_ = newname;
         closest_name_ = newname;
         best_source_ = &new_source;
         best_source_ = &new_source;

+ 98 - 24
src/lib/datasrc/data_source.h

@@ -40,7 +40,7 @@ class RRsetList;
 
 
 namespace datasrc {
 namespace datasrc {
 
 
-class NameMatch;
+class DataSrcMatch;
 class Query;
 class Query;
 
 
 class DataSrc;
 class DataSrc;
@@ -89,12 +89,15 @@ public:
     // NAME_NOT_FOUND: The node does not exist in the data source.
     // NAME_NOT_FOUND: The node does not exist in the data source.
     // TYPE_NOT_FOUND: The node does not contain the requested RRType
     // TYPE_NOT_FOUND: The node does not contain the requested RRType
     // NO_SUCH_ZONE:   The zone does not exist in this data source.
     // NO_SUCH_ZONE:   The zone does not exist in this data source.
+    //
+    // DATA_NOT_FOUND: A combination of the last three, for coding convenience
     enum QueryResponseFlags {
     enum QueryResponseFlags {
         REFERRAL = 0x01,
         REFERRAL = 0x01,
         CNAME_FOUND = 0x02,
         CNAME_FOUND = 0x02,
         NAME_NOT_FOUND = 0x04,
         NAME_NOT_FOUND = 0x04,
         TYPE_NOT_FOUND = 0x08,
         TYPE_NOT_FOUND = 0x08,
-        NO_SUCH_ZONE = 0x10
+        NO_SUCH_ZONE = 0x10,
+        DATA_NOT_FOUND = (NAME_NOT_FOUND|TYPE_NOT_FOUND|NO_SUCH_ZONE)
     };
     };
 
 
     // 'High-level' methods.  These will be implemented by the
     // 'High-level' methods.  These will be implemented by the
@@ -107,9 +110,7 @@ public:
 
 
     // 'Medium-level' methods.  This will be implemented by the general
     // 'Medium-level' methods.  This will be implemented by the general
     // DataSrc class but MAY be overwritten by subclasses.
     // DataSrc class but MAY be overwritten by subclasses.
-    virtual void findClosestEnclosure(NameMatch& match,
-                                      const isc::dns::RRClass& qclasss)
-                                      const = 0;
+    virtual void findClosestEnclosure(DataSrcMatch& match) const = 0;
 
 
     // Optional 'low-level' methods.  These will have stub implementations
     // Optional 'low-level' methods.  These will have stub implementations
     // in the general DataSrc class but MAY be overwritten by subclasses
     // in the general DataSrc class but MAY be overwritten by subclasses
@@ -179,9 +180,7 @@ public:
 
 
     virtual void doQuery(Query& q);
     virtual void doQuery(Query& q);
 
 
-    virtual void findClosestEnclosure(NameMatch& match,
-                                      const isc::dns::RRClass& qclass)
-                                      const = 0;
+    virtual void findClosestEnclosure(DataSrcMatch& match) const = 0;
 
 
     const isc::dns::RRClass& getClass() const { return rrclass; }
     const isc::dns::RRClass& getClass() const { return rrclass; }
     void setClass(isc::dns::RRClass& c) { rrclass = c; }
     void setClass(isc::dns::RRClass& c) { rrclass = c; }
@@ -250,8 +249,7 @@ public:
     void removeDataSrc(ConstDataSrcPtr data_src);
     void removeDataSrc(ConstDataSrcPtr data_src);
     size_t dataSrcCount() { return data_sources.size(); };
     size_t dataSrcCount() { return data_sources.size(); };
     
     
-    void findClosestEnclosure(NameMatch& match,
-                              const isc::dns::RRClass& qclass) const;
+    void findClosestEnclosure(DataSrcMatch& match) const;
 
 
     // Actual queries for data should not be sent to a MetaDataSrc object,
     // Actual queries for data should not be sent to a MetaDataSrc object,
     // so we return NOT_IMPLEMENTED if we receive any.
     // so we return NOT_IMPLEMENTED if we receive any.
@@ -298,31 +296,107 @@ private:
     std::vector<ConstDataSrcPtr> data_sources;
     std::vector<ConstDataSrcPtr> data_sources;
 };
 };
 
 
-class NameMatch {
+/// \brief Information about the zone along with the %data source that best
+/// matches a give name and RR class.
+///
+/// A \c DataSrcMatch object is created with a domain name and RR class to
+/// hold the search state of looking for the zone and the %data source that
+/// stores the zone that best match the given name and RR class.
+/// The application of this class passes an object of \c DataSrcMatch to
+/// one or more ^data sources via their \c findClosestEnclosure() method.
+/// The %data source searches its content for the given key, and update
+/// the state if it finds a better zone than the currently recorded one.
+///
+/// The state of a \c DataSrcMatch object should be updated if and only if:
+///  - The specified RR class and the RR class of the %data source are the
+//     same, or the specified RR class is ANY; and
+///  - There is no matching %data source and name found (which is probably
+///    wrong, see below), or the given enclosing name gives a longer match
+///    than the currently stored enclosing name against the specified name.
+class DataSrcMatch {
     ///
     ///
     /// \name Constructors, Assignment Operator and Destructor.
     /// \name Constructors, Assignment Operator and Destructor.
     ///
     ///
-    /// Note: The copy constructor and the assignment operator are intentionally
-    /// defined as private.
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private.
+    //@{
 private:
 private:
-    NameMatch(const NameMatch& source);
-    NameMatch& operator=(const NameMatch& source);
+    DataSrcMatch(const DataSrcMatch& source);
+    DataSrcMatch& operator=(const DataSrcMatch& source);
 public:
 public:
-    NameMatch(const isc::dns::Name& qname) :
-        closest_name_(NULL), best_source_(NULL), qname_(qname) {}
-    ~NameMatch();
+    /// \brief The constructor.
+    ///
+    /// This constructor normally doesn't throw an exception.  However,
+    /// it creates a copy of the given name object, which may require memory
+    /// allocation, and if it fails the corresponding standard exception will
+    /// be thrown.
+    ///
+    /// \param name The domain name to be matched.
+    /// \param rrclass The RR class to be matched
+    DataSrcMatch(const isc::dns::Name& name,
+                 const isc::dns::RRClass& rrclass) :
+        closest_name_(NULL), best_source_(NULL),
+        name_(name), rrclass_(rrclass)
+    {}
+    ~DataSrcMatch();
     //@}
     //@}
 
 
-    void update(const DataSrc& new_source, const isc::dns::Name& container);
+    /// \name Getter and Setter Methods
+    //@{
+    /// \brief Returns the name to be matched.
+    const isc::dns::Name& getName() const { return (name_); }
 
 
-    const isc::dns::Name& qname() const { return (qname_); }
-    const isc::dns::Name* closestName() const { return (closest_name_); }
-    const DataSrc* bestDataSrc() const { return (best_source_); }
+    /// \brief Returns the RR class to be matched.
+    ///
+    /// This method never throws an exception.
+    const isc::dns::RRClass& getClass() const { return (rrclass_); }
+
+    /// \brief Returns the best enclosing zone name found for the given
+    // name and RR class so far.
+    /// 
+    /// \return A pointer to the zone apex \c Name, NULL if none found yet.
+    ///
+    /// This method never throws an exception.
+    const isc::dns::Name* getEnclosingZone() const { return (closest_name_); }
+
+    /// \brief Returns the best %data source found for the given name and
+    /// RR class so far.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A pointer to a concrete %data source, NULL if none found yet.
+    const DataSrc* getDataSource() const { return (best_source_); }
+    //@}
+
+    /// \brief Update the object state with better information if possible.
+    ///
+    /// This method is intended to be called by a concrete %data source's
+    /// \c findClosestEnclosure() method to store the best match for
+    /// the given name and class that has been found so far.
+    ///
+    /// It compares the best name (if found) and \c container, and if the
+    /// latter gives a longer match, it will install the given %data source
+    /// and the enclosing name as the best match;
+    /// if there is no known pair of %data source and enclosing name,
+    /// this method will install the given pair unconditionally.
+    /// (which is probably BAD);
+    /// otherwise this method does nothing.
+    ///
+    /// In any case, if a new pair of %data source and enclosing name are
+    /// installed, a new name object will be internally allocated.
+    /// And, if memory allocation fails the corresponding standard exception
+    /// will be thrown.
+    ///
+    /// \param new_source A candidate %data source that gives a better match.
+    /// \param container The enclosing name of the matching zone in
+    /// \c new_source.
+    void update(const DataSrc& new_source, const isc::dns::Name& container);
 
 
 private:
 private:
-    const isc::dns::Name* closest_name_;
+    isc::dns::Name* closest_name_;
     const DataSrc* best_source_;
     const DataSrc* best_source_;
-    const isc::dns::Name qname_;
+    const isc::dns::Name name_;
+    const isc::dns::RRClass& rrclass_;
 };
 };
 
 
 class Nsec3Param {
 class Nsec3Param {

+ 24 - 24
src/lib/datasrc/query.cc

@@ -28,36 +28,37 @@ using namespace isc::dns;
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
 
 
-QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
                      const isc::dns::RRType& t, const isc::dns::Section& sect) :
                      const isc::dns::RRType& t, const isc::dns::Section& sect) :
-    qname(n), qclass(c), qtype(t), section(sect), op(AUTH_QUERY),
-    state(GETANSWER), flags(0)
+    q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect),
+    op(AUTH_QUERY), state(GETANSWER), flags(0)
 {}
 {}
 
 
-QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, 
                      const isc::dns::RRType& t, const isc::dns::Section& sect,
                      const isc::dns::RRType& t, const isc::dns::Section& sect,
                      const Op o) :
                      const Op o) :
-    qname(n), qclass(c), qtype(t), section(sect), op(o), state(GETANSWER),
-    flags(0)
+    q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect), op(o),
+    state(GETANSWER), flags(0)
 {}
 {}
 
 
-QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
                      const isc::dns::RRType& t, const isc::dns::Section& sect,
                      const isc::dns::RRType& t, const isc::dns::Section& sect,
                      const State st) :
                      const State st) :
-    qname(n), qclass(c), qtype(t), section(sect), op(AUTH_QUERY), state(st),
-    flags(0)
+    q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect),
+    op(AUTH_QUERY), state(st), flags(0)
 {}
 {}
 
 
-QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
                      const isc::dns::RRType& t, const isc::dns::Section& sect,
                      const isc::dns::RRType& t, const isc::dns::Section& sect,
                      const Op o, const State st) :
                      const Op o, const State st) :
-    qname(n), qclass(c), qtype(t), section(sect), op(o), state(st), flags(0) 
+    q(qry), qname(n), qclass(qry.qclass()), qtype(t), section(sect), op(o),
+    state(st), flags(0) 
 {}
 {}
 
 
-QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, 
                      const isc::dns::RRType& t, const Op o) :
                      const isc::dns::RRType& t, const Op o) :
-    qname(n), qclass(c), qtype(t), section(Section::ANSWER()), op(o),
-    state(GETANSWER), flags(0)
+    q(qry), qname(n), qclass(qry.qclass()), qtype(t),
+    section(Section::ANSWER()), op(o), state(GETANSWER), flags(0)
 {
 {
     if (op != SIMPLE_QUERY) {
     if (op != SIMPLE_QUERY) {
         isc_throw(Unexpected, "invalid constructor for this task operation");
         isc_throw(Unexpected, "invalid constructor for this task operation");
@@ -65,21 +66,20 @@ QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
 }
 }
 
 
 // A referral query doesn't need to specify section, state, or type.
 // A referral query doesn't need to specify section, state, or type.
-QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
-                     const Op o) :
-    qname(n), qclass(c), qtype(RRType::ANY()), section(Section::ANSWER()),
-    op(o), state(GETANSWER), flags(0)
+QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n, const Op o) :
+    q(qry), qname(n), qclass(qry.qclass()), qtype(RRType::ANY()),
+    section(Section::ANSWER()), op(o), state(GETANSWER), flags(0)
 {
 {
     if (op != REF_QUERY) {
     if (op != REF_QUERY) {
         isc_throw(Unexpected, "invalid constructor for this task operation");
         isc_throw(Unexpected, "invalid constructor for this task operation");
     }
     }
 }
 }
 
 
-QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+QueryTask::QueryTask(const Query& qry, const isc::dns::Name& n,
                      const isc::dns::Section& sect, const Op o,
                      const isc::dns::Section& sect, const Op o,
                      const State st) :
                      const State st) :
-        qname(n), qclass(c), qtype(RRType::ANY()), section(sect), op(o),
-        state(st), flags(0)
+        q(qry), qname(n), qclass(qry.qclass()), qtype(RRType::ANY()),
+        section(sect), op(o), state(st), flags(0)
 {
 {
     if (op != GLUE_QUERY && op != NOGLUE_QUERY) {
     if (op != GLUE_QUERY && op != NOGLUE_QUERY) {
         isc_throw(Unexpected, "invalid constructor for this task operation");
         isc_throw(Unexpected, "invalid constructor for this task operation");
@@ -88,9 +88,9 @@ QueryTask::QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
 
 
 QueryTask::~QueryTask() {}
 QueryTask::~QueryTask() {}
 
 
-Query::Query(Message& m, bool dnssec) :
+Query::Query(Message& m, HotCache& c, bool dnssec) :
     status_(PENDING), qname_(NULL), qclass_(NULL), qtype_(NULL),
     status_(PENDING), qname_(NULL), qclass_(NULL), qtype_(NULL),
-    message_(&m), want_additional_(true), want_dnssec_(dnssec)
+    cache_(&c), message_(&m), want_additional_(true), want_dnssec_(dnssec)
 {
 {
     // Check message formatting
     // Check message formatting
     if (message_->getRRCount(Section::QUESTION()) != 1) {
     if (message_->getRRCount(Section::QUESTION()) != 1) {
@@ -104,7 +104,7 @@ Query::Query(Message& m, bool dnssec) :
     qtype_ = &question->getType();
     qtype_ = &question->getType();
     restarts_ = 0;
     restarts_ = 0;
 
 
-    querytasks_.push(QueryTaskPtr(new QueryTask(*qname_, *qclass_, *qtype_,
+    querytasks_.push(QueryTaskPtr(new QueryTask(*this, *qname_, *qtype_,
                                                 Section::ANSWER())));
                                                 Section::ANSWER())));
 }
 }
 
 

+ 36 - 22
src/lib/datasrc/query.h

@@ -19,6 +19,9 @@
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
+#include <datasrc/cache.h>
+#include <datasrc/data_source.h>
+
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/message.h>
 #include <dns/message.h>
 #include <dns/rrtype.h>
 #include <dns/rrtype.h>
@@ -27,9 +30,11 @@
 #include <queue>
 #include <queue>
 
 
 namespace isc {
 namespace isc {
-
 namespace datasrc {
 namespace datasrc {
 
 
+class Query;
+typedef boost::shared_ptr<Query> QueryPtr;
+
 // An individual task to be carried out by the query logic
 // An individual task to be carried out by the query logic
 class QueryTask {
 class QueryTask {
 private:
 private:
@@ -41,6 +46,9 @@ public:
     // XXX: Members are currently public, but should probably be
     // XXX: Members are currently public, but should probably be
     // moved to private and wrapped in get() functions later.
     // moved to private and wrapped in get() functions later.
 
 
+    // The \c Query that this \c QueryTask was created to service.
+    const Query& q;
+
     // The standard query tuple: qname/qclass/qtype.
     // The standard query tuple: qname/qclass/qtype.
     // Note that qtype is ignored in the GLUE_QUERY/NOGLUE_QUERY case.
     // Note that qtype is ignored in the GLUE_QUERY/NOGLUE_QUERY case.
     const isc::dns::Name qname;
     const isc::dns::Name qname;
@@ -118,15 +126,14 @@ public:
     uint32_t flags;
     uint32_t flags;
 
 
     // Constructors
     // Constructors
-    QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+    QueryTask(const Query& q, const isc::dns::Name& n,
               const isc::dns::RRType& t, const isc::dns::Section& sect);
               const isc::dns::RRType& t, const isc::dns::Section& sect);
-    QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
-              const isc::dns::RRType& t, const isc::dns::Section& sect,
-              Op o);
-    QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+    QueryTask(const Query& q, const isc::dns::Name& n,
+              const isc::dns::RRType& t, const isc::dns::Section& sect, Op o);
+    QueryTask(const Query& q, const isc::dns::Name& n,
               const isc::dns::RRType& t, const isc::dns::Section& sect,
               const isc::dns::RRType& t, const isc::dns::Section& sect,
               const State st);
               const State st);
-    QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+    QueryTask(const Query& q, const isc::dns::Name& n,
               const isc::dns::RRType& t, const isc::dns::Section& sect,
               const isc::dns::RRType& t, const isc::dns::Section& sect,
               Op o, State st);
               Op o, State st);
 
 
@@ -134,12 +141,12 @@ public:
     // to simplify the code.
     // to simplify the code.
     //
     //
     // A simple query doesn't need to specify section or state.
     // A simple query doesn't need to specify section or state.
-    QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+    QueryTask(const Query& q, const isc::dns::Name& n,
               const isc::dns::RRType& t, Op o);
               const isc::dns::RRType& t, Op o);
     // A referral query doesn't need to specify section, state, or type.
     // A referral query doesn't need to specify section, state, or type.
-    QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c, Op o);
+    QueryTask(const Query& q, const isc::dns::Name& n, Op o);
     // A glue (or noglue) query doesn't need to specify type.
     // A glue (or noglue) query doesn't need to specify type.
-    QueryTask(const isc::dns::Name& n, const isc::dns::RRClass& c,
+    QueryTask(const Query& q, const isc::dns::Name& n,
               const isc::dns::Section& sect, Op o, State st);
               const isc::dns::Section& sect, Op o, State st);
 
 
     ~QueryTask();
     ~QueryTask();
@@ -148,9 +155,6 @@ public:
 typedef boost::shared_ptr<QueryTask> QueryTaskPtr;
 typedef boost::shared_ptr<QueryTask> QueryTaskPtr;
 typedef std::queue<QueryTaskPtr> QueryTaskQueue;
 typedef std::queue<QueryTaskPtr> QueryTaskQueue;
 
 
-class Query;
-typedef boost::shared_ptr<Query> QueryPtr;
-
 // Data Source query
 // Data Source query
 class Query {
 class Query {
 public:
 public:
@@ -171,7 +175,7 @@ private:
     Query& operator=(const Query& source);
     Query& operator=(const Query& source);
 public:
 public:
     // Query constructor
     // Query constructor
-    Query(isc::dns::Message& m, bool dnssec);
+    Query(isc::dns::Message& m, HotCache& c, bool dnssec);
     /// \brief The destructor.
     /// \brief The destructor.
     virtual ~Query();
     virtual ~Query();
     //@}
     //@}
@@ -179,17 +183,17 @@ public:
     // wantAdditional() == true indicates that additional-section data
     // wantAdditional() == true indicates that additional-section data
     // should be looked up while processing this query.  false indicates
     // should be looked up while processing this query.  false indicates
     // that we're only interested in answer-section data
     // that we're only interested in answer-section data
-    bool wantAdditional() { return want_additional_; }
+    bool wantAdditional() { return (want_additional_); }
     void setWantAdditional(bool d) { want_additional_ = d; }
     void setWantAdditional(bool d) { want_additional_ = d; }
 
 
     // wantDnssec() == true indicates that DNSSEC data should be retrieved
     // wantDnssec() == true indicates that DNSSEC data should be retrieved
     // from the data source when this query is being processed
     // from the data source when this query is being processed
-    bool wantDnssec() const { return want_dnssec_; }
+    bool wantDnssec() const { return (want_dnssec_); }
     void setWantDnssec(bool d) { want_dnssec_ = d; }
     void setWantDnssec(bool d) { want_dnssec_ = d; }
 
 
-    const isc::dns::Name& qname() const { return *qname_; }
-    const isc::dns::RRClass& qclass() const { return *qclass_; }
-    const isc::dns::RRType& qtype() const { return *qtype_; }
+    const isc::dns::Name& qname() const { return (*qname_); }
+    const isc::dns::RRClass& qclass() const { return (*qclass_); }
+    const isc::dns::RRType& qtype() const { return (*qtype_); }
 
 
     // Note: these can't be constant member functions because they expose
     // Note: these can't be constant member functions because they expose
     // writable 'handles' of internal member variables.  It's questionable
     // writable 'handles' of internal member variables.  It's questionable
@@ -197,10 +201,10 @@ public:
     // corresponding members are public (which itself is not a good practice
     // corresponding members are public (which itself is not a good practice
     // but it's a different topic), but at the moment we keep them.
     // but it's a different topic), but at the moment we keep them.
     // We should definitely revisit the design later.
     // We should definitely revisit the design later.
-    isc::dns::Message& message() { return *message_; }
-    QueryTaskQueue& tasks() { return querytasks_; }
+    isc::dns::Message& message() { return (*message_); }
+    QueryTaskQueue& tasks() { return (querytasks_); }
 
 
-    Status status() const { return status_; }
+    Status status() const { return (status_); }
     void setStatus(Status s) { status_ = s; }
     void setStatus(Status s) { status_ = s; }
 
 
     // Limit CNAME chains to 16 per query, to avoid loops
     // Limit CNAME chains to 16 per query, to avoid loops
@@ -211,6 +215,13 @@ public:
         return (false);
         return (false);
     }
     }
 
 
+    void setDatasrc(DataSrc* ds) { datasrc_ = ds; }
+    DataSrc* datasrc() const { return (datasrc_); }
+
+    // \brief The query cache.  This is a static member of class \c Query;
+    // the same cache will be used by all instances.
+    HotCache& getCache() const { return (*cache_); }
+
 private:
 private:
     Status status_;
     Status status_;
 
 
@@ -218,6 +229,9 @@ private:
     const isc::dns::RRClass* qclass_;
     const isc::dns::RRClass* qclass_;
     const isc::dns::RRType* qtype_;
     const isc::dns::RRType* qtype_;
 
 
+    HotCache* cache_;
+    DataSrc* datasrc_;
+
     isc::dns::Message* message_;
     isc::dns::Message* message_;
     QueryTaskQueue querytasks_;
     QueryTaskQueue querytasks_;
 
 

+ 4 - 6
src/lib/datasrc/sqlite3_datasrc.cc

@@ -344,19 +344,17 @@ Sqlite3DataSrc::findClosest(const Name& name, unsigned int* position) const {
 }
 }
 
 
 void
 void
-Sqlite3DataSrc::findClosestEnclosure(NameMatch& match,
-                                     const RRClass& qclass) const
-{
-    if (qclass != getClass() && qclass != RRClass::ANY()) {
+Sqlite3DataSrc::findClosestEnclosure(DataSrcMatch& match) const {
+    if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) {
         return;
         return;
     }
     }
 
 
     unsigned int position;
     unsigned int position;
-    if (findClosest(match.qname(), &position) == -1) {
+    if (findClosest(match.getName(), &position) == -1) {
         return;
         return;
     }
     }
 
 
-    match.update(*this, match.qname().split(position));
+    match.update(*this, match.getName().split(position));
 }
 }
 
 
 DataSrc::Result
 DataSrc::Result

+ 1 - 2
src/lib/datasrc/sqlite3_datasrc.h

@@ -58,8 +58,7 @@ public:
     ~Sqlite3DataSrc();
     ~Sqlite3DataSrc();
     //@}
     //@}
 
 
-    void findClosestEnclosure(NameMatch& match,
-                              const isc::dns::RRClass& qclass) const;
+    void findClosestEnclosure(DataSrcMatch& match) const;
 
 
     Result findRRset(const isc::dns::Name& qname,
     Result findRRset(const isc::dns::Name& qname,
                      const isc::dns::RRClass& qclass,
                      const isc::dns::RRClass& qclass,

+ 3 - 4
src/lib/datasrc/static_datasrc.cc

@@ -129,11 +129,10 @@ isSubdomain(const Name& qname, const Name& zone) {
 }
 }
 
 
 void
 void
-StaticDataSrc::findClosestEnclosure(NameMatch& match,
-                                    const RRClass& qclass) const {
-    const Name& qname = match.qname();
+StaticDataSrc::findClosestEnclosure(DataSrcMatch& match) const {
+    const Name& qname = match.getName();
 
 
-    if (qclass != getClass() && qclass != RRClass::ANY()) {
+    if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) {
         return;
         return;
     }
     }
 
 

+ 1 - 4
src/lib/datasrc/static_datasrc.h

@@ -39,8 +39,6 @@ class RRsetList;
 
 
 namespace datasrc {
 namespace datasrc {
 
 
-class Query;
-class NameMatch;
 struct StaticDataSrcImpl;
 struct StaticDataSrcImpl;
 
 
 class StaticDataSrc : public DataSrc {
 class StaticDataSrc : public DataSrc {
@@ -58,8 +56,7 @@ public:
     ~StaticDataSrc();
     ~StaticDataSrc();
     //@}
     //@}
 
 
-    void findClosestEnclosure(NameMatch& match,
-                              const isc::dns::RRClass& qclass) const;
+    void findClosestEnclosure(DataSrcMatch& match) const;
 
 
     Result findRRset(const isc::dns::Name& qname,
     Result findRRset(const isc::dns::Name& qname,
                      const isc::dns::RRClass& qclass,
                      const isc::dns::RRClass& qclass,

+ 1 - 0
src/lib/datasrc/tests/Makefile.am

@@ -16,6 +16,7 @@ run_unittests_SOURCES += datasrc_unittest.cc
 run_unittests_SOURCES += sqlite3_unittest.cc
 run_unittests_SOURCES += sqlite3_unittest.cc
 run_unittests_SOURCES += static_unittest.cc
 run_unittests_SOURCES += static_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
+run_unittests_SOURCES += cache_unittest.cc
 run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
 run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 192 - 16
src/lib/datasrc/tests/datasrc_unittest.cc

@@ -69,6 +69,7 @@ protected:
     void createAndProcessQuery(const Name& qname, const RRClass& qclass,
     void createAndProcessQuery(const Name& qname, const RRClass& qclass,
                                const RRType& qtype);
                                const RRType& qtype);
 
 
+    HotCache cache;
     MetaDataSrc meta_source;
     MetaDataSrc meta_source;
     OutputBuffer obuffer;
     OutputBuffer obuffer;
     MessageRenderer renderer;
     MessageRenderer renderer;
@@ -76,10 +77,10 @@ protected:
 };
 };
 
 
 void
 void
-performQuery(DataSrc& data_source, Message& message) {
+performQuery(DataSrc& data_source, HotCache& cache, Message& message) {
     message.setHeaderFlag(MessageFlag::AA());
     message.setHeaderFlag(MessageFlag::AA());
     message.setRcode(Rcode::NOERROR());
     message.setRcode(Rcode::NOERROR());
-    Query q(message, true);
+    Query q(message, cache, true);
     data_source.doQuery(q);
     data_source.doQuery(q);
 }
 }
 
 
@@ -92,7 +93,7 @@ DataSrcTest::readAndProcessQuery(const char* datafile) {
     msg.fromWire(buffer);
     msg.fromWire(buffer);
 
 
     msg.makeResponse();
     msg.makeResponse();
-    performQuery(meta_source, msg);
+    performQuery(meta_source, cache, msg);
 }
 }
 
 
 void
 void
@@ -103,7 +104,7 @@ DataSrcTest::createAndProcessQuery(const Name& qname, const RRClass& qclass,
     msg.setOpcode(Opcode::QUERY());
     msg.setOpcode(Opcode::QUERY());
     msg.addQuestion(Question(qname, qclass, qtype));
     msg.addQuestion(Question(qname, qclass, qtype));
     msg.setHeaderFlag(MessageFlag::RD());
     msg.setHeaderFlag(MessageFlag::RD());
-    performQuery(meta_source, msg);
+    performQuery(meta_source, cache, msg);
 }
 }
 
 
 void
 void
@@ -213,6 +214,83 @@ TEST_F(DataSrcTest, NSQuery) {
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
 }
 }
 
 
+// Make sure two successive queries have the same result
+TEST_F(DataSrcTest, DuplicateQuery) {
+    readAndProcessQuery("q_example_ns");
+    headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6);
+
+    RRsetIterator rit = msg.beginSection(Section::ANSWER());
+    RRsetPtr rrset = *rit;
+    EXPECT_EQ(Name("example.com"), rrset->getName());
+    EXPECT_EQ(RRType::NS(), rrset->getType());
+    EXPECT_EQ(RRClass::IN(), rrset->getClass());
+
+    RdataIteratorPtr it = rrset->getRdataIterator();
+    it->first();
+    EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
+    it->next();
+    EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
+    it->next();
+    EXPECT_EQ("dns03.example.com.", it->getCurrent().toText());
+    it->next();
+    EXPECT_TRUE(it->isLast());
+
+    msg.clear(Message::PARSE);
+    readAndProcessQuery("q_example_ns");
+    headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6);
+
+    rit = msg.beginSection(Section::ANSWER());
+    rrset = *rit;
+    EXPECT_EQ(Name("example.com"), rrset->getName());
+    EXPECT_EQ(RRType::NS(), rrset->getType());
+    EXPECT_EQ(RRClass::IN(), rrset->getClass());
+
+    it = rrset->getRdataIterator();
+    it->first();
+    EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
+    it->next();
+    EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
+    it->next();
+    EXPECT_EQ("dns03.example.com.", it->getCurrent().toText());
+    it->next();
+    EXPECT_TRUE(it->isLast());
+}
+
+TEST_F(DataSrcTest, DNSKEYQuery) {
+    readAndProcessQuery("q_example_dnskey");
+    headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+
+    RRsetIterator rit = msg.beginSection(Section::ANSWER());
+    RRsetPtr rrset = *rit;
+    EXPECT_EQ(Name("example.com"), rrset->getName());
+    EXPECT_EQ(RRType::DNSKEY(), rrset->getType());
+    EXPECT_EQ(RRClass::IN(), rrset->getClass());
+}
+
+// Repeat the previous query to check that cache is working correctly.
+// We query for a record at a zone cut to ensure the REFERRAL flag doesn't
+// cause incorrect behavior.
+TEST_F(DataSrcTest, DNSKEYDuplicateQuery) {
+    readAndProcessQuery("q_example_dnskey");
+    headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+
+    RRsetIterator rit = msg.beginSection(Section::ANSWER());
+    RRsetPtr rrset = *rit;
+    EXPECT_EQ(Name("example.com"), rrset->getName());
+    EXPECT_EQ(RRType::DNSKEY(), rrset->getType());
+    EXPECT_EQ(RRClass::IN(), rrset->getClass());
+
+    msg.clear(Message::PARSE);
+    readAndProcessQuery("q_example_dnskey");
+    headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+
+    rit = msg.beginSection(Section::ANSWER());
+    rrset = *rit;
+    EXPECT_EQ(Name("example.com"), rrset->getName());
+    EXPECT_EQ(RRType::DNSKEY(), rrset->getType());
+    EXPECT_EQ(RRClass::IN(), rrset->getClass());
+}
+
 TEST_F(DataSrcTest, NxRRset) {
 TEST_F(DataSrcTest, NxRRset) {
     readAndProcessQuery("q_example_ptr");
     readAndProcessQuery("q_example_ptr");
 
 
@@ -858,18 +936,6 @@ TEST_F(DataSrcTest, AddRemoveDataSrc) {
     EXPECT_EQ(0, ds.dataSrcCount());
     EXPECT_EQ(0, ds.dataSrcCount());
 }
 }
 
 
-// currently fails
-TEST_F(DataSrcTest, DISABLED_synthesizedCnameTooLong) {
-    // qname has the possible max length (255 octets).  it matches a DNAME,
-    // and the synthesized CNAME would exceed the valid length.
-    createAndProcessQuery(
-        Name("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde."
-             "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde."
-             "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde."
-             "0123456789abcdef0123456789abcdef0123456789a.dname.example.org."),
-        RRClass::IN(), RRType::A());
-}
-
 TEST_F(DataSrcTest, noNSZone) {
 TEST_F(DataSrcTest, noNSZone) {
     EXPECT_THROW(createAndProcessQuery(Name("www.nons.example"),
     EXPECT_THROW(createAndProcessQuery(Name("www.nons.example"),
                                        RRClass::IN(), RRType::A()),
                                        RRClass::IN(), RRType::A()),
@@ -888,4 +954,114 @@ TEST_F(DataSrcTest, noSOAZone) {
                  DataSourceError);
                  DataSourceError);
 }
 }
 
 
+// currently fails
+TEST_F(DataSrcTest, DISABLED_synthesizedCnameTooLong) {
+    // qname has the possible max length (255 octets).  it matches a DNAME,
+    // and the synthesized CNAME would exceed the valid length.
+    createAndProcessQuery(
+        Name("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde."
+             "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde."
+             "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde."
+             "0123456789abcdef0123456789abcdef0123456789a.dname.example.org."),
+        RRClass::IN(), RRType::A());
+}
+
+// Tests of the DataSrcMatch class start here
+class DataSrcMatchTest : public ::testing::Test {
+protected:
+    DataSrcMatchTest() {
+        datasrc1.init();
+    }
+    // test data source serves example.com/IN.
+    TestDataSrc datasrc1;
+    // this data source is dummy.  Its content doesn't matter in the tests.
+    TestDataSrc datasrc2;
+};
+
+TEST_F(DataSrcMatchTest, match) {
+    DataSrcMatch match(Name("very.very.long.example.com"), RRClass::IN());
+    datasrc1.findClosestEnclosure(match);
+    EXPECT_EQ(Name("example.com"), *match.getEnclosingZone());
+    EXPECT_EQ(&datasrc1, match.getDataSource());
+}
+
+TEST_F(DataSrcMatchTest, matchWithWrongClass) {
+    DataSrcMatch match(Name("very.very.long.example.com"), RRClass::CH());
+    datasrc1.findClosestEnclosure(match);
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
+}
+
+TEST_F(DataSrcMatchTest, matchWithAnyClass) {
+    DataSrcMatch match(Name("very.very.long.example.com"), RRClass::ANY());
+    datasrc1.findClosestEnclosure(match);
+    EXPECT_EQ(Name("example.com"), *match.getEnclosingZone());
+    EXPECT_EQ(&datasrc1, match.getDataSource());
+}
+
+TEST_F(DataSrcMatchTest, updateWithWrongClass) {
+    DataSrcMatch match(Name("www.example.com"), RRClass::CH());
+
+    EXPECT_EQ(RRClass::IN(), datasrc2.getClass());
+    match.update(datasrc2, Name("com"));
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
+
+    EXPECT_EQ(RRClass::IN(), datasrc1.getClass());
+    match.update(datasrc1, Name("example.com"));
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
+}
+
+TEST_F(DataSrcMatchTest, updateAgainstAnyClass) {
+    DataSrcMatch match(Name("www.example.com"), RRClass::ANY());
+    match.update(datasrc2, Name("com"));
+    EXPECT_EQ(Name("com"), *match.getEnclosingZone());
+    EXPECT_EQ(&datasrc2, match.getDataSource());
+
+    // the given class for search is ANY, so update should be okay.
+    EXPECT_EQ(RRClass::IN(), datasrc1.getClass());
+    match.update(datasrc1, Name("example.com"));
+    EXPECT_EQ(Name("example.com"), *match.getEnclosingZone());
+    EXPECT_EQ(&datasrc1, match.getDataSource());
+}
+
+TEST_F(DataSrcMatchTest, updateWithNoMatch) {
+    DataSrcMatch match(Name("www.example.com"), RRClass::IN());
+    match.update(datasrc1, Name("com"));
+    EXPECT_EQ(Name("com"), *match.getEnclosingZone());
+    EXPECT_EQ(&datasrc1, match.getDataSource());
+
+    // An attempt of update with a name that doesn't match.  This attempt
+    // should be ignored.
+    match.update(datasrc2, Name("example.org"));
+    EXPECT_EQ(Name("com"), *match.getEnclosingZone());
+    EXPECT_EQ(&datasrc1, match.getDataSource());
+}
+
+// This test currently fails.
+TEST_F(DataSrcMatchTest, initialUpdateWithNoMatch) {
+    DataSrcMatch match(Name("www.example.com"), RRClass::IN());
+
+    // An initial attempt of update with a name that doesn't match.
+    // Should be ignored.
+    match.update(datasrc1, Name("example.org"));
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
+}
+
+TEST_F(DataSrcMatchTest, updateWithShorterMatch) {
+    DataSrcMatch match(Name("www.example.com"), RRClass::IN());
+
+    match.update(datasrc1, Name("example.com"));
+    EXPECT_EQ(Name("example.com"), *match.getEnclosingZone());
+    EXPECT_EQ(&datasrc1, match.getDataSource());
+
+    // An attempt of update with a name that gives a shorter match.
+    // This attempt should be ignored.
+    match.update(datasrc2, Name("com"));
+    EXPECT_EQ(Name("example.com"), *match.getEnclosingZone());
+    EXPECT_EQ(&datasrc1, match.getDataSource());
+}
+
 }
 }

+ 32 - 14
src/lib/datasrc/tests/query_unittest.cc

@@ -16,43 +16,61 @@
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
+#include <dns/buffer.h>
+#include <dns/message.h>
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/rrtype.h>
 #include <dns/rrtype.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
 
 
 #include <datasrc/query.h>
 #include <datasrc/query.h>
 
 
-namespace {
+#include <dns/tests/unittest_util.h>
 
 
+using isc::UnitTestUtil;
 using namespace isc::dns;
 using namespace isc::dns;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
 
 
+namespace {
 
 
 class QueryTest : public ::testing::Test {
 class QueryTest : public ::testing::Test {
 protected:
 protected:
-    QueryTest() :
-        name(Name("www.example.com")),
-        rrtype(RRType::A()),
-        rrclass(RRClass::IN())
-    {}
-    const Name name;
-    const RRType rrtype;
-    const RRClass rrclass;
+    void readQuery(Message& m, const char* datafile);
+
+    HotCache cache;
 };
 };
 
 
+void
+QueryTest::readQuery(Message& m, const char* datafile) {
+    std::vector<unsigned char> data;
+    UnitTestUtil::readWireData(datafile, data);
+
+    InputBuffer buffer(&data[0], data.size());
+    m.fromWire(buffer);
+}
+
 QueryTaskPtr
 QueryTaskPtr
-createTask(const Name& name, const RRClass& rrclass0, const RRType& rrtype0) {
+createTask(Message& m, const Name& name, const RRType& rrtype0, HotCache& c) {
     RRType rrtype(rrtype0);
     RRType rrtype(rrtype0);
-    return (QueryTaskPtr(new QueryTask(name, rrclass0, rrtype,
+    Query q(m, c, true);
+    return (QueryTaskPtr(new QueryTask(q, name, rrtype,
                                        QueryTask::SIMPLE_QUERY)));
                                        QueryTask::SIMPLE_QUERY)));
 }
 }
 
 
 // Check the QueryTask created using a temporary RRType object will remain
 // Check the QueryTask created using a temporary RRType object will remain
 // valid.
 // valid.
 TEST_F(QueryTest, constructWithTemporary) {
 TEST_F(QueryTest, constructWithTemporary) {
-    QueryTaskPtr task_a = createTask(name, rrclass, RRType::A());
-    QueryTaskPtr task_aaaa = createTask(name, rrclass, RRType::AAAA());
-    EXPECT_EQ(rrtype, task_a->qtype);
+    Message m1(Message::PARSE);
+    readQuery(m1, "q_wild_a");
+    QueryTaskPtr task_a = createTask(m1, Name("www.wild.example.com"),
+                                        RRType::A(), cache);
+    EXPECT_EQ(RRType::A(), task_a->qtype);
+
+    Message m2(Message::PARSE);
+    readQuery(m2, "q_wild_aaaa");
+    QueryTaskPtr task_aaaa = createTask(m2, Name("www.wild.example.com"),
+                                        RRType::AAAA(), cache);
+    EXPECT_EQ(RRType::AAAA(), task_aaaa->qtype);
+
 }
 }
 
 
 }
 }

+ 28 - 30
src/lib/datasrc/tests/sqlite3_unittest.cc

@@ -374,12 +374,10 @@ TEST_F(Sqlite3DataSourceTest, reOpen) {
     EXPECT_EQ(DataSrc::SUCCESS, data_source.close());
     EXPECT_EQ(DataSrc::SUCCESS, data_source.close());
     EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE2));
     EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE2));
 
 
-    NameMatch name_match(www_name);
-    data_source.findClosestEnclosure(name_match, rrclass);
-    // XXX: some deviant compilers seem to fail to recognize a NULL as a
-    // pointer type.  This explicit cast works around such compilers.
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.closestName());
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.bestDataSrc());
+    DataSrcMatch match(www_name, rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
 }
 }
 
 
 TEST_F(Sqlite3DataSourceTest, openFail) {
 TEST_F(Sqlite3DataSourceTest, openFail) {
@@ -415,52 +413,52 @@ TEST_F(Sqlite3DataSourceTest, memoryDB) {
 }
 }
 
 
 TEST_F(Sqlite3DataSourceTest, findClosestEnclosure) {
 TEST_F(Sqlite3DataSourceTest, findClosestEnclosure) {
-    NameMatch name_match(www_name);
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(zone_name, *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(www_name, rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(zone_name, *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 TEST_F(Sqlite3DataSourceTest, findClosestEnclosureMatchRoot) {
 TEST_F(Sqlite3DataSourceTest, findClosestEnclosureMatchRoot) {
     EXPECT_EQ(DataSrc::SUCCESS, data_source.close());
     EXPECT_EQ(DataSrc::SUCCESS, data_source.close());
     EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE_ROOT));
     EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE_ROOT));
 
 
-    NameMatch name_match(Name("org."));
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(Name("."), *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(Name("org."), rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(Name("."), *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 TEST_F(Sqlite3DataSourceTest, findClosestEnclosureAtDelegation) {
 TEST_F(Sqlite3DataSourceTest, findClosestEnclosureAtDelegation) {
     // The search name exists both in the parent and child zones, but
     // The search name exists both in the parent and child zones, but
     // child has a better match.
     // child has a better match.
-    NameMatch name_match(child_name);
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(child_name, *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(child_name, rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(child_name, *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 TEST_F(Sqlite3DataSourceTest, findClosestEnclosureNoMatch) {
 TEST_F(Sqlite3DataSourceTest, findClosestEnclosureNoMatch) {
-    NameMatch name_match(nomatch_name);
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.closestName());
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.bestDataSrc());
+    DataSrcMatch match(nomatch_name, rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
 }
 }
 
 
 TEST_F(Sqlite3DataSourceTest, findClosestClassMismatch) {
 TEST_F(Sqlite3DataSourceTest, findClosestClassMismatch) {
-    NameMatch name_match(www_name);
-    data_source.findClosestEnclosure(name_match, rrclass_notmatch);
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.closestName());
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.bestDataSrc());
+    DataSrcMatch match(nomatch_name, rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
 }
 }
 
 
 // If the query class is ANY, the result should be the same as the case where
 // If the query class is ANY, the result should be the same as the case where
 // the class exactly matches.
 // the class exactly matches.
 TEST_F(Sqlite3DataSourceTest, findClosestClassAny) {
 TEST_F(Sqlite3DataSourceTest, findClosestClassAny) {
-    NameMatch name_match(www_name);
-    data_source.findClosestEnclosure(name_match, RRClass::ANY());
-    EXPECT_EQ(zone_name, *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(www_name, RRClass::ANY());
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(zone_name, *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 TEST_F(Sqlite3DataSourceTest, findRRsetNormal) {
 TEST_F(Sqlite3DataSourceTest, findRRsetNormal) {

+ 28 - 29
src/lib/datasrc/tests/static_unittest.cc

@@ -196,55 +196,54 @@ TEST_F(StaticDataSourceTest, close) {
 }
 }
 
 
 TEST_F(StaticDataSourceTest, findClosestEnclosureForVersion) {
 TEST_F(StaticDataSourceTest, findClosestEnclosureForVersion) {
-    NameMatch name_match(version_name);
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(version_name, *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(version_name, rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(version_name, *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 // Class Any query should result in the same answer.
 // Class Any query should result in the same answer.
 TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionClassAny) {
 TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionClassAny) {
-    NameMatch name_match(version_name);
-    data_source.findClosestEnclosure(name_match, RRClass::ANY());
-    EXPECT_EQ(version_name, *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(version_name, RRClass::ANY());
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(version_name, *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 // If class doesn't match the lookup should fail.
 // If class doesn't match the lookup should fail.
 TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionClassMismatch) {
 TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionClassMismatch) {
-    NameMatch name_match(version_name);
-    data_source.findClosestEnclosure(name_match, RRClass::IN());
-    // XXX: see sqlite3_unittest.cc about the cast.
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.closestName());
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.bestDataSrc());
+    DataSrcMatch match(version_name, RRClass::IN());
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
 }
 }
 
 
 TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionPartial) {
 TEST_F(StaticDataSourceTest, findClosestEnclosureForVersionPartial) {
-    NameMatch name_match(Name("foo").concatenate(version_name));
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(version_name, *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(Name("foo").concatenate(version_name), rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(version_name, *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 TEST_F(StaticDataSourceTest, findClosestEnclosureForAuthors) {
 TEST_F(StaticDataSourceTest, findClosestEnclosureForAuthors) {
-    NameMatch name_match(authors_name);
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(authors_name, *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(authors_name, rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(authors_name, *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 TEST_F(StaticDataSourceTest, findClosestEnclosureForAuthorsPartial) {
 TEST_F(StaticDataSourceTest, findClosestEnclosureForAuthorsPartial) {
-    NameMatch name_match(Name("foo").concatenate(authors_name));
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(authors_name, *name_match.closestName());
-    EXPECT_EQ(&data_source, name_match.bestDataSrc());
+    DataSrcMatch match(Name("foo").concatenate(authors_name), rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(authors_name, *match.getEnclosingZone());
+    EXPECT_EQ(&data_source, match.getDataSource());
 }
 }
 
 
 TEST_F(StaticDataSourceTest, findClosestEnclosureNoMatch) {
 TEST_F(StaticDataSourceTest, findClosestEnclosureNoMatch) {
-    NameMatch name_match(nomatch_name);
-    data_source.findClosestEnclosure(name_match, rrclass);
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.closestName());
-    EXPECT_EQ(static_cast<void*>(NULL), name_match.bestDataSrc());
+    DataSrcMatch match(nomatch_name, rrclass);
+    data_source.findClosestEnclosure(match);
+    EXPECT_EQ(NULL, match.getEnclosingZone());
+    EXPECT_EQ(NULL, match.getDataSource());
 }
 }
 
 
 TEST_F(StaticDataSourceTest, findRRsetVersionTXT) {
 TEST_F(StaticDataSourceTest, findRRsetVersionTXT) {

+ 12 - 8
src/lib/datasrc/tests/test_datasrc.cc

@@ -65,8 +65,9 @@ namespace {
 // {"example.com", "AAAA", "2001:db8::2"},
 // {"example.com", "AAAA", "2001:db8::2"},
 // ...
 // ...
 // If an RRset is associated with an RRSIG, the RRSIG must immediately follow
 // If an RRset is associated with an RRSIG, the RRSIG must immediately follow
-// the RRset to be signed.  Currently, only one (or zero) RRSIG can be
-// specified per RRset.
+// the RRset to be signed.  Multiple RRSIGs can follow the RRset.  RRSIG
+// records will always be attached to the most recent non-RRSIG RRset;
+// consequently, the first RR listed must not be an RRSIG record.
 //
 //
 // Names are sorted internally, and don't have to be sorted in the data.
 // Names are sorted internally, and don't have to be sorted in the data.
 //
 //
@@ -107,6 +108,10 @@ const struct RRData example_com_records[] = {
     {"example.com", "RRSIG", "SOA 5 2 3600 20100322084538 20100220084538 33495 example.com.  KUun66Qaw36osk2BJS6U1fAy3PPDkNo2QK4meGNbDBY8q8b+f2o+IXJ14YCvssGl1ORW0CcLnDRxssnk8V/Svmj5iFhO+8HC2hnVBdi2zewvdVtwRb+lWwKN7pkXXwuy6g1t9WCd/j5FCc/wgxqtZUTPb6XgZcnHrORDMOTqLs4="},
     {"example.com", "RRSIG", "SOA 5 2 3600 20100322084538 20100220084538 33495 example.com.  KUun66Qaw36osk2BJS6U1fAy3PPDkNo2QK4meGNbDBY8q8b+f2o+IXJ14YCvssGl1ORW0CcLnDRxssnk8V/Svmj5iFhO+8HC2hnVBdi2zewvdVtwRb+lWwKN7pkXXwuy6g1t9WCd/j5FCc/wgxqtZUTPb6XgZcnHrORDMOTqLs4="},
     {"example.com", "NSEC", "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY"},
     {"example.com", "NSEC", "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY"},
     {"example.com", "RRSIG", "NSEC 5 2 7200 20100322084538 20100220084538 33495 example.com. KxuVaPPKNPJzr/q+cJPiNlkHVTQK0LVsgTbSqruXQc25lAd0wn5oKUtxL1bEAchHkfA8eLzcYCj2ZqqAv9OJubw53mfskTad7UHs4Uj2RTrIsNGMCiZGgOpvNb9JcWpQtoyXVT1uNse+Qsbeir0eyeYIufUynFU041jtNrlJMio="},
     {"example.com", "RRSIG", "NSEC 5 2 7200 20100322084538 20100220084538 33495 example.com. KxuVaPPKNPJzr/q+cJPiNlkHVTQK0LVsgTbSqruXQc25lAd0wn5oKUtxL1bEAchHkfA8eLzcYCj2ZqqAv9OJubw53mfskTad7UHs4Uj2RTrIsNGMCiZGgOpvNb9JcWpQtoyXVT1uNse+Qsbeir0eyeYIufUynFU041jtNrlJMio="},
+    {"example.com", "DNSKEY", "257 3 5 AwEAAe5WFbxdCPq2jZrZhlMj7oJdff3W7syJtbvzg62tRx0gkoCDoBI9DPjlOQG0UAbj+xUV4HQZJStJaZ+fHU5AwVNT+bBZdtV+NujSikhdTHb4FYLg2b3Cx9NyJvAVukHp/91HnWuG4T36CzAFrfPwsHIrBz9BsaIQ21VRkcmj7DswfI/iDGd8j6bqiODyNZYQ+ZrLmF0KIJ2yPN3iO6Zq23TaOrVTjB7d1a/h31ODfiHAxFHrkY3t3D5JR9Nsl/7fdRmSznwtcSDgLXBoFEYmw6p86AcvRyoYNcL1SXjaKVLG5jyU3UR+LcGZT5t/0xGfoIK/aKwENrsjcKZZj660b1M="},
+    {"example.com", "DNSKEY", "256 3 5 AwEAAcOUBllYc1hf7ND9uDy+Yz1BF3sI0m4qNGV7WcTD0WEiuV7IjXgHE36fCmS9QsUxSSOVo1I/FMxI2PJVqTYHkXFBS7AzLGsQYMU7UjBZSotBJ6Imt5pXMu+lEDNy8TOUzG3xm7g0qcbWYF6qCEfvZoBtAqi5Rk7Mlrqs8agxYyMx"},
+    {"example.com", "RRSIG", "DNSKEY 5 2 3600 20100416210049 20100317210049 4456 example.com. 37FC0rcwOZVarTMjft0BMbvv8hbJU7OHNsvO7R1q6OgsLTj7QGMX3sC42JGbwUrYI/OwnZblNcv1eim0g0jX5k+sVr2OJsEubngRjVqLo54qV8rBC14tLk9PGKxxjQG0IBJU866uHxzXYBO2a1r2g93/qyTtrT7iPLu/2Ce1WRKMBPK0yf4nW2usFU/PXesXFWpZ7HLGZL73/NWv8wcezBDuU0B2PlHLjSu7k6poq6JWDC02o5SYnEBwsJ5Chi+3/NZmzKTiNP7g0H4t6QhunkEXxL3z0617mwwQt00ypXsNunnPy4Ub5Kllk1SKJl8ZkEDKkJtSvuXJhcAZsLyMQw=="},
+    {"example.com", "RRSIG", "DNSKEY 5 2 3600 20100416210049 20100317210049 33495 example.com. h3OM5r3roBsgnEQk9fcjTg5L7p3yDptDpVzDN/lgjqpaWxtlz5LsulBH3YzwYyXzT7pG7L0/qT6dcuRECc/rniECviWvmJMJZzEAMry0Of/pk/8ekuGTxABpqwAoCwM5as30sc0cfMJTS7umpJVDA4lRB2zoKGefWnJ3+pREDiY="},
 
 
     // dns01.example.com
     // dns01.example.com
     {"dns01.example.com", "A", "192.0.2.1"},
     {"dns01.example.com", "A", "192.0.2.1"},
@@ -335,8 +340,9 @@ buildZone(Zone& zone, const RRData* records, const bool is_glue) {
         rrset->addRdata(createRdata(rrtype, zone.rrclass, records[i].rdata));
         rrset->addRdata(createRdata(rrtype, zone.rrclass, records[i].rdata));
         if (rrtype == RRType::RRSIG()) {
         if (rrtype == RRType::RRSIG()) {
             prev_rrset->addRRsig(rrset);
             prev_rrset->addRRsig(rrset);
+        } else {
+            prev_rrset = rrset;
         }
         }
-        prev_rrset = rrset;
     }
     }
 }
 }
 
 
@@ -361,12 +367,10 @@ TestDataSrc::init() {
 }
 }
 
 
 void
 void
-TestDataSrc::findClosestEnclosure(NameMatch& match,
-                                  const RRClass& qclass) const
-{
-    const Name& qname = match.qname();
+TestDataSrc::findClosestEnclosure(DataSrcMatch& match) const {
+    const Name& qname = match.getName();
 
 
-    if (qclass != getClass() && qclass != RRClass::ANY()) {
+    if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) {
         return;
         return;
     }
     }
 
 

+ 1 - 2
src/lib/datasrc/tests/test_datasrc.h

@@ -48,8 +48,7 @@ public:
     ~TestDataSrc() {}
     ~TestDataSrc() {}
     //@}
     //@}
 
 
-    void findClosestEnclosure(NameMatch& match,
-                              const isc::dns::RRClass& qclass) const;
+    void findClosestEnclosure(DataSrcMatch& match) const;
 
 
     Result findRRset(const isc::dns::Name& qname,
     Result findRRset(const isc::dns::Name& qname,
                      const isc::dns::RRClass& qclass,
                      const isc::dns::RRClass& qclass,

+ 14 - 0
src/lib/dns/question.h

@@ -229,6 +229,20 @@ public:
     unsigned int toWire(OutputBuffer& buffer) const;
     unsigned int toWire(OutputBuffer& buffer) const;
     //@}
     //@}
 
 
+    ///
+    /// \name Comparison Operator
+    ///
+    //@{
+    /// A comparison operator is needed for this class so it can
+    /// function as an index to std::map.
+    bool operator <(const Question& rhs) const {
+        return (rrclass_ < rhs.rrclass_ ||
+                (rrclass_ == rhs.rrclass_ &&
+                 (rrtype_ < rhs.rrtype_ ||
+                  (rrtype_ == rhs.rrtype_ && (name_ < rhs.name_)))));
+    }
+    //@}
+
 private:
 private:
     Name name_;
     Name name_;
     RRType rrtype_;
     RRType rrtype_;

+ 8 - 0
src/lib/dns/rrsetlist.cc

@@ -40,6 +40,14 @@ RRsetList::addRRset(RRsetPtr rrsetptr) {
     rrsets_.push_back(rrsetptr);
     rrsets_.push_back(rrsetptr);
 }
 }
 
 
+void
+RRsetList::append(RRsetList& source)
+{
+    BOOST_FOREACH(RRsetPtr rrset, source) {
+        addRRset(rrset);
+    }
+}
+
 RRsetPtr
 RRsetPtr
 RRsetList::findRRset(const RRType& rrtype, const RRClass& rrclass) {
 RRsetList::findRRset(const RRType& rrtype, const RRClass& rrclass) {
     BOOST_FOREACH(RRsetPtr rrsetptr, rrsets_) {
     BOOST_FOREACH(RRsetPtr rrsetptr, rrsets_) {

+ 1 - 0
src/lib/dns/rrsetlist.h

@@ -82,6 +82,7 @@ private:
 public:
 public:
     RRsetList() {}
     RRsetList() {}
     void addRRset(RRsetPtr new_rrsetptr);
     void addRRset(RRsetPtr new_rrsetptr);
+    void append(RRsetList& source);
     RRsetPtr findRRset(const RRType& rrtype, const RRClass& rrclass);
     RRsetPtr findRRset(const RRType& rrtype, const RRClass& rrclass);
 
 
     typedef RRsetListIterator<std::vector<RRsetPtr>::iterator,
     typedef RRsetListIterator<std::vector<RRsetPtr>::iterator,

+ 31 - 0
src/lib/dns/tests/question_unittest.cc

@@ -119,4 +119,35 @@ TEST_F(QuestionTest, LeftShiftOperator)
     oss << test_question1;
     oss << test_question1;
     EXPECT_EQ(test_question1.toText(), oss.str());
     EXPECT_EQ(test_question1.toText(), oss.str());
 }
 }
+
+TEST_F(QuestionTest, comparison)
+{
+    const Name a("a"), b("b");
+    const RRClass in(RRClass::IN()), ch(RRClass::CH());
+    const RRType ns(RRType::NS()), aaaa(RRType::AAAA());
+
+    EXPECT_TRUE(Question(a, in, ns) < Question(a, in, aaaa));
+    EXPECT_FALSE(Question(a, in, aaaa) < Question(a, in, ns));
+
+    EXPECT_TRUE(Question(a, in, ns) < Question(a, ch, ns));
+    EXPECT_FALSE(Question(a, ch, ns) < Question(a, in, ns));
+
+    EXPECT_TRUE(Question(a, in, ns) < Question(a, ch, aaaa));
+    EXPECT_FALSE(Question(a, ch, aaaa) < Question(a, in, ns));
+
+    EXPECT_TRUE(Question(a, in, ns) < Question(b, in, ns));
+    EXPECT_FALSE(Question(a, in, ns) < Question(a, in, ns));
+
+    EXPECT_TRUE(Question(a, in, ns) < Question(b, ch, ns));
+    EXPECT_FALSE(Question(b, ch, ns) < Question(a, in, ns));
+
+    EXPECT_TRUE(Question(a, in, ns) < Question(b, ch, aaaa));
+    EXPECT_FALSE(Question(b, ch, aaaa) < Question(a, in, ns));
+
+    EXPECT_FALSE(Question(a, in, ns) < Question(a, in, ns));
+    EXPECT_FALSE(Question(a, ch, ns) < Question(a, ch, ns));
+    EXPECT_FALSE(Question(b, in, ns) < Question(b, in, ns));
+    EXPECT_FALSE(Question(b, in, aaaa) < Question(b, in, aaaa));
+}
+
 }
 }

+ 19 - 0
src/lib/dns/tests/rrsetlist_unittest.cc

@@ -48,6 +48,7 @@ const generic::NS rdata_ns("ns.example.com");
 const generic::SOA rdata_soa(Name("ns.example.com"), Name("root.example.com"),
 const generic::SOA rdata_soa(Name("ns.example.com"), Name("root.example.com"),
                              2010012601, 3600, 300, 3600000, 1200);
                              2010012601, 3600, 300, 3600000, 1200);
 const generic::CNAME rdata_cname("target.example.com");
 const generic::CNAME rdata_cname("target.example.com");
+const generic::DNAME rdata_dname("dtarget.example.com");
 
 
 void
 void
 RRsetListTest::setupList(RRsetList& list) {
 RRsetListTest::setupList(RRsetList& list) {
@@ -86,6 +87,24 @@ TEST_F(RRsetListTest, addRRsets) {
     EXPECT_EQ(list.size(), 5);
     EXPECT_EQ(list.size(), 5);
 }
 }
 
 
+TEST_F(RRsetListTest, append) {
+    RRsetList list1;
+    setupList(list1);
+    RRsetList list2;
+    RRsetPtr dname(new RRset(Name("example.com"), RRClass::IN(),
+                             RRType::DNAME(), example_ttl));
+    dname->addRdata(rdata_dname);
+    list2.addRRset(dname);
+    list1.append(list2);
+    EXPECT_EQ(list2.size(), 1);
+    EXPECT_EQ(list1.size(), 6);
+
+    RRsetPtr rrset = list1.findRRset(RRType::DNAME(), RRClass::IN());
+    EXPECT_EQ(RRType::DNAME(), rrset->getType());
+
+    EXPECT_THROW(list1.append(list2), DuplicateRRset);
+}
+
 TEST_F(RRsetListTest, extraRRset) {
 TEST_F(RRsetListTest, extraRRset) {
     RRsetList list;
     RRsetList list;
     setupList(list);
     setupList(list);