BIND 10 trac1175, updated. 31e04a17289ee2687466591b9df964a9659a375e [1175] - move up an assignment of the BIND10_MSGQ_SOCKET_FILE environment variable (BaseModules uses a constant file name during each testcase) - BaseModules checks whether msgq is ready after it started the msgq object. A SessionTimeout is raised here if not.

BIND 10 source code commits bind10-changes at lists.isc.org
Thu Sep 8 04:08:06 UTC 2011


The branch, trac1175 has been updated
  discards  fa11ddefe6cf767fd41e9cdb7cef2b4a40921139 (commit)
  discards  2b12c885d223c0f1a8250445c4c479f702c9d8ba (commit)
  discards  fe3e470dc64335f7833dced3a6a53e05fee36e03 (commit)
  discards  9d8957f63ba69be13e45e496b95e6bf377c60ee3 (commit)
  discards  d07924557750d327b2cfbe5762e83a6153e0f85e (commit)
  discards  31fff87f0e451bd542549fcbf8ef54111fb8db2d (commit)
  discards  e6ba004df7a6c61a1eb01b8a2c42ffca31108544 (commit)
  discards  551b7acfbb53017b8d333e6e6aee7a0be5e3d41d (commit)
  discards  678ee5b01c108bbd43eb95937dfdda2e766e10f0 (commit)
  discards  8a8c3718955c21d01be54961e203dd5f1ad54703 (commit)
  discards  1fd3b23ae4734b74f4d32ce641e48cce30cc4cb7 (commit)
  discards  4555a37ab2f6e65094d71f7d744f482b034666f0 (commit)
  discards  d6c108d77b4e8ee492839cac4e6b5bc1c97da01d (commit)
  discards  fe89018fedffe750b46647baa25ded6be102a6c7 (commit)
  discards  28ab40e038e2d64a4190f03355845835251be8bf (commit)
  discards  61dc4b1c5853e1bde814eec1e414be0259322e80 (commit)
  discards  8f2b875c605c4975a5edf3ce5bed5b1cff351649 (commit)
  discards  4a399840f7b24aedbd5fab74b5136d61f598bf43 (commit)
  discards  a1fb73b6c54059c6247b036b6a7f8f1b7b008f2d (commit)
  discards  866ad553d4c7ee501cc2ae4db831c814edbc2ba0 (commit)
  discards  558e24b384ba95f3f1bdb7345eb5d7431a747a85 (commit)
  discards  bd1b2c3e4eca04bfac9541b35d7aabcf184c79c2 (commit)
       via  31e04a17289ee2687466591b9df964a9659a375e (commit)
       via  4b6daaaa8d2a5e0dbf8210c21dad131029009c43 (commit)
       via  90de4c4e4fb6b662e9da3ee0dcbae2e54fe2e992 (commit)
       via  d0ad5620c23f7979af4f6aadda2383cad940b8a7 (commit)
       via  bb74e48c48ba3743a3dcc6d907835112fce92def (commit)
       via  0076f83b0dc170cc806f5991f3f36458e0bbb242 (commit)
       via  9ca1103b1578485a4c04f1bbc2d91a7b4ec2ee09 (commit)
       via  0f462b5a992b398fdfb908b7b5555b6ba484a9bb (commit)
       via  f85390bf443c2a2c3d95f0c5c82a2070c636c57b (commit)
       via  b20929fe294591f30dfb7bf2a40c2edd9bfc45f6 (commit)
       via  ff28ef8e9fb8cb68a3f58a85e2744692964cefa4 (commit)
       via  d8c47010e3fd5d291dbb3f427300257f07d63e24 (commit)
       via  20c91f9803ce27454ff5c6af744c0fba8cf002c0 (commit)
       via  ff74c405731bf02ee6132f473029d8b71ff71c19 (commit)
       via  643270555fc44684fc0594b4aee85ca6e22f511d (commit)
       via  68928e70b776e8ae394e1d0e21b8bace3ceda2ef (commit)
       via  3a0c4b465bb37c177b319ad4b4dd2c57b1b799a9 (commit)
       via  d718ed58d4d4ab2d2bfc63a184f0e8066809d113 (commit)
       via  dffa1540f85282c35acbab03aec17a2bbe0f67c4 (commit)
       via  2070a8162567d0b5ddd60e548063a926d3ae8f1f (commit)
       via  116c8de3d0ca86ad0f1a59a0afcf36a266d7f66f (commit)
       via  4946bf927003dec032d6c11ca42827b1e011705f (commit)
       via  7cc9b08f18967fa1a694f5b7e320aad62d0d3e88 (commit)
       via  25e56e5d1bc9197e882e3a42285d0efad21a51f2 (commit)
       via  87d2a8766e610a0dece7d86268ac9be4122d6d82 (commit)
       via  64ac0166d5ea3b565f500f8a770dfa4d7d9f6a28 (commit)
       via  c86612cd4120b9ad3d00978c04ea252e7d501e44 (commit)
       via  c1c2ddf5be4556e6e8cd52a314ddd6d026c7e540 (commit)
       via  ba50f189eced101999efb96672179aa1024204e9 (commit)
       via  6906362bebdbe7e0de66f2c8d10a00bd34911121 (commit)
       via  83a58b817e5c0432d543b66208f502b059fdbe13 (commit)
       via  40126733cc69634035b0cca3a0c90ee3a606ea3b (commit)
       via  bcafb8b98d5df77108a83a6bd8b7746f7c2616d7 (commit)
       via  4ef59f25a452f934408a9ba837cea9b7fab0be48 (commit)
       via  3d069e2745070bc23f14c845cb7d8116d919f0da (commit)
       via  230df584722d08705f2cb3b99940b764b1cb7865 (commit)
       via  fda403b09887a24403c3a90d7ad6c95288f2d641 (commit)
       via  04b04226b726b6e1fea6bba970556b9ed5cc3446 (commit)
       via  3a838eb454ed0de4f073b99e94e02014eca63a56 (commit)
       via  748c3e1aeb833012a19b651af7d98757a8ffc50f (commit)
       via  a0e04c0ad837b4b42caf139573f2a95c86cdac76 (commit)
       via  fcb2409598d37e2078076cf43794ef6c445ac22f (commit)
       via  c6d2a365580709981852007cd0a9a3b32afaa5c3 (commit)
       via  da8bfe82aa18a67b1a99fa459f48cea89ee2a41a (commit)
       via  7980a6c8e598d34f5f733f5c6c3ca83c0a0f1187 (commit)
       via  9c62a36b0ebf9ff4ef3dad1f4d91195d301348ed (commit)
       via  2ec9338d84714ea670ee888f1edf5a4ad220ea9a (commit)
       via  1d907966f7f0fe7089efe46d8b808d9115f0d167 (commit)
       via  93327a85ea63f7043c49a0af2384a1e274ab1dda (commit)
       via  38c8e9a9ccfd7fd57bc5fa5090c86cf7b7920d28 (commit)
       via  ddf9da5175b1182810838861f1464fb05fe00104 (commit)
       via  8fe581570c2ef4f881762f4f22ef4f66c1063491 (commit)
       via  2812fa5cb0c2013ef1696888651390aa71a76b4a (commit)
       via  b131dd71ce147b4efcece9dd8fba16c51fefa492 (commit)
       via  84d83c1d8979e2906971af79f2e41083299beb7e (commit)
       via  8d380bb47dd24c7fd2c4880a4106835d871bf4d5 (commit)
       via  77ba8639c274865c762eee688383c321f18ef889 (commit)
       via  4c98f3dc47545794daccd4978103f6b98236ad82 (commit)
       via  2dfa0983e4680f321a3d4f1bd0d826abd88f455c (commit)
       via  d60bb44c243f27053589b5501529b0001404373f (commit)
       via  92dcac243a4a2924bab85d1519a0c7a20853f9cc (commit)
       via  2bd6dc4ac6ac61705517df297320fa79b308b9e3 (commit)
       via  13e236a3d647d15858b061c7d96288bf7407e090 (commit)
       via  a7fe0d5982813f092f8a497d350620c02b995649 (commit)
       via  d71b7da05d3e1a82047e35c2720c759bdc0fb44f (commit)
       via  a577b387b7e5c9c8afd371767fccc85009e84485 (commit)
       via  8e82cd7374cda9ef55f88504a94d31b06d7e1bd4 (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (fa11ddefe6cf767fd41e9cdb7cef2b4a40921139)
            \
             N -- N -- N (31e04a17289ee2687466591b9df964a9659a375e)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 31e04a17289ee2687466591b9df964a9659a375e
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Thu Sep 8 10:52:19 2011 +0900

    [1175]
     - move up an assignment of the BIND10_MSGQ_SOCKET_FILE environment variable
       (BaseModules uses a constant file name during each testcase)
     - BaseModules checks whether msgq is ready after it started the msgq object.
       A SessionTimeout is raised here if not.

commit 4b6daaaa8d2a5e0dbf8210c21dad131029009c43
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Tue Sep 6 20:27:44 2011 +0900

    [1175] fix a typo

commit 90de4c4e4fb6b662e9da3ee0dcbae2e54fe2e992
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Tue Sep 6 20:17:57 2011 +0900

    [1175]
     - The function get_availaddr uses socket.getaddrinfo for getting the address
       family.
     - The function is_ipv6_enabled uses 3 random ports for checking whether IPv6
       is enabled.

commit d0ad5620c23f7979af4f6aadda2383cad940b8a7
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Tue Sep 6 11:42:42 2011 +0900

    [1175] send the command 'status' to the stats when it started, and then send
           the command 'shutdown', and also check each value returned by each
           invoked command

commit bb74e48c48ba3743a3dcc6d907835112fce92def
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Sep 5 17:14:11 2011 +0900

    [1175]
     - The stats httpd doesn't need to return an argument when it's shutting down.
     - The testcase sends the 'status' command or the 'shutdown' command to the
       stats or the stats httpd when they started, and then their returned values
       are checked.

commit 0076f83b0dc170cc806f5991f3f36458e0bbb242
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Sep 5 05:01:18 2011 +0000

    [1175]
     - add 3-time retry to creating the server object when it fails in the
       ThreadingServerManager class
     - suppress outputs by Msgq, and add dummy sys module and the output
       methods
     - pass Exceptions raised while it's running with a thread

commit 9ca1103b1578485a4c04f1bbc2d91a7b4ec2ee09
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Sep 5 04:53:24 2011 +0000

    [1175] remove a logging name from unittest

commit 0f462b5a992b398fdfb908b7b5555b6ba484a9bb
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Sep 5 04:53:11 2011 +0000

    [1175]
     - remove a logging name from unittest
     - do stats_httpd.stop() in teadDown() instead of each test case
     - send 'shutdown' command to kill stats_httpd when testing address already in use

commit f85390bf443c2a2c3d95f0c5c82a2070c636c57b
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Sep 5 04:46:08 2011 +0000

    [1175] remove -v option from pycoverage

commit b20929fe294591f30dfb7bf2a40c2edd9bfc45f6
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Thu Sep 1 11:34:28 2011 +0000

    [1175] add -v option in pycoverage for debugging the failure in buildbot.
    (unittest.main() with verbosity option is not supported in Python3.1.)

commit ff28ef8e9fb8cb68a3f58a85e2744692964cefa4
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Thu Sep 1 04:19:53 2011 +0000

    [1175]
     - more strictly close the io object whether it's successfully opened or not
     - add verbosity=2 in unittest.main for debugging the failure in the buildbot
     - don't redict sys.stderr in MockMsgq
     - rename the function name to create_specfile
     - switch the verbose in Msgq into True

commit d8c47010e3fd5d291dbb3f427300257f07d63e24
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Fri Aug 26 12:01:47 2011 +0900

    [1175] fix wrong list-type handling in the function get_spec_defaults and add
    more tests into test_get_spec_defaults

commit 20c91f9803ce27454ff5c6af744c0fba8cf002c0
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Wed Aug 24 17:28:29 2011 +0900

    [1175] fix typo and correct changes from trac519

commit ff74c405731bf02ee6132f473029d8b71ff71c19
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Wed Aug 24 16:51:11 2011 +0900

    [1175] deadlock will be killed afer 20 secs

commit 643270555fc44684fc0594b4aee85ca6e22f511d
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Wed Aug 24 15:55:21 2011 +0900

    [1175] fix conflicts with trac519

commit 68928e70b776e8ae394e1d0e21b8bace3ceda2ef
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Tue Aug 23 16:42:18 2011 +0900

    [1175]
     - A hostname (canonical name of host) is not acceptable in listen_on
       configuration.
     - A default port number(starting number for search) is added in args of the
       function get_availaddr.

commit 3a0c4b465bb37c177b319ad4b4dd2c57b1b799a9
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Tue Aug 23 11:22:03 2011 +0900

    [1175] set msgq verbose off

commit d718ed58d4d4ab2d2bfc63a184f0e8066809d113
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Aug 22 18:20:39 2011 +0900

    [1175] add #1175

commit dffa1540f85282c35acbab03aec17a2bbe0f67c4
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Aug 22 18:18:47 2011 +0900

    [1175]
    - don't use time.sleep for waiting threads are starting or finishing
    - correct shutting down of mock modules
    - use _started (threading.Event) where command_handler is invoked
    - add implementation to changing contents of specfile of MyStatsHttpd
    - set "BIND10_MSGQ_SOCKET_FILE" only when it's not set yet

commit 2070a8162567d0b5ddd60e548063a926d3ae8f1f
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Aug 22 18:10:25 2011 +0900

    [1175]
    - add function get_availaddr to get available address and port on the platform
    - add function is_ipv6enabled to check ipv6 enabled on the platform
    - add miscellaneous changes to refactor unittest

commit 116c8de3d0ca86ad0f1a59a0afcf36a266d7f66f
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Aug 22 18:05:57 2011 +0900

    [1175]
    - don't use DEFAULT_CONFIG
    - move up mccs.start and open_httpd to __init__(). It takes time to do these
    functions, and an extra sleep is needed in unittests.
    - set running to False in http stopping
    - use validate_config in module_spec class
    - don't close/open http before it's opened

commit 4946bf927003dec032d6c11ca42827b1e011705f
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Wed Aug 17 13:37:45 2011 +0900

    Revert "[master] Revert trac930 because of failures on biuldbots:"
    
    This reverts commit 5de7909a21a077238567b64e489ed5345824b2a0.
    
    Conflicts:
    
    	ChangeLog

-----------------------------------------------------------------------

Summary of changes:
 doc/Doxyfile                                       |    4 +-
 src/bin/auth/query.cc                              |   38 +-
 src/bin/auth/query.h                               |   10 +-
 src/bin/auth/tests/query_unittest.cc               |  102 +-
 src/lib/datasrc/client.h                           |   63 +-
 src/lib/datasrc/database.cc                        |  179 ++-
 src/lib/datasrc/database.h                         |   71 +-
 src/lib/datasrc/datasrc_messages.mes               |   35 +
 src/lib/datasrc/memory_datasrc.cc                  |    6 +
 src/lib/datasrc/memory_datasrc.h                   |   11 +
 src/lib/datasrc/sqlite3_accessor.cc                |   23 +-
 src/lib/datasrc/sqlite3_accessor.h                 |   17 +
 src/lib/datasrc/tests/Makefile.am                  |    1 +
 src/lib/datasrc/tests/client_unittest.cc           |    3 +
 src/lib/datasrc/tests/database_unittest.cc         | 2155 ++++++++++++++------
 src/lib/datasrc/tests/memory_datasrc_unittest.cc   |    6 +-
 src/lib/datasrc/tests/sqlite3_accessor_unittest.cc |   28 +
 src/lib/datasrc/tests/testdata/Makefile.am         |    4 +
 .../{example2.com.sqlite3 => rwtest.sqlite3}       |  Bin 11264 -> 11264 bytes
 src/lib/datasrc/zone.h                             |  244 +++-
 src/lib/dns/rdata/generic/rrsig_46.cc              |    2 +-
 src/lib/dns/rdata/generic/rrsig_46.h               |    2 +-
 22 files changed, 2305 insertions(+), 699 deletions(-)
 copy src/lib/datasrc/tests/testdata/{example2.com.sqlite3 => rwtest.sqlite3} (67%)

-----------------------------------------------------------------------
diff --git a/doc/Doxyfile b/doc/Doxyfile
index ceb806f..71b0738 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -568,8 +568,8 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = ../src/lib/cc ../src/lib/config \
-    ../src/lib/cryptolink ../src/lib/dns ../src/lib/datasrc \
+INPUT                  = ../src/lib/exceptions ../src/lib/cc \
+    ../src/lib/config ../src/lib/cryptolink ../src/lib/dns ../src/lib/datasrc \
     ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log \
     ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index 3fe03c8..898fff7 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -67,20 +67,21 @@ Query::findAddrs(ZoneFinder& zone, const Name& qname,
     // Find A rrset
     if (qname_ != qname || qtype_ != RRType::A()) {
         ZoneFinder::FindResult a_result = zone.find(qname, RRType::A(), NULL,
-                                              options);
+                                                    options | dnssec_opt_);
         if (a_result.code == ZoneFinder::SUCCESS) {
             response_.addRRset(Message::SECTION_ADDITIONAL,
-                    boost::const_pointer_cast<RRset>(a_result.rrset));
+                    boost::const_pointer_cast<RRset>(a_result.rrset), dnssec_);
         }
     }
 
     // Find AAAA rrset
     if (qname_ != qname || qtype_ != RRType::AAAA()) {
         ZoneFinder::FindResult aaaa_result =
-            zone.find(qname, RRType::AAAA(), NULL, options);
+            zone.find(qname, RRType::AAAA(), NULL, options | dnssec_opt_);
         if (aaaa_result.code == ZoneFinder::SUCCESS) {
             response_.addRRset(Message::SECTION_ADDITIONAL,
-                    boost::const_pointer_cast<RRset>(aaaa_result.rrset));
+                    boost::const_pointer_cast<RRset>(aaaa_result.rrset),
+                    dnssec_);
         }
     }
 }
@@ -88,7 +89,7 @@ Query::findAddrs(ZoneFinder& zone, const Name& qname,
 void
 Query::putSOA(ZoneFinder& zone) const {
     ZoneFinder::FindResult soa_result(zone.find(zone.getOrigin(),
-        RRType::SOA()));
+        RRType::SOA(), NULL, dnssec_opt_));
     if (soa_result.code != ZoneFinder::SUCCESS) {
         isc_throw(NoSOA, "There's no SOA record in zone " <<
             zone.getOrigin().toText());
@@ -99,7 +100,7 @@ Query::putSOA(ZoneFinder& zone) const {
          * to insist.
          */
         response_.addRRset(Message::SECTION_AUTHORITY,
-            boost::const_pointer_cast<RRset>(soa_result.rrset));
+            boost::const_pointer_cast<RRset>(soa_result.rrset), dnssec_);
     }
 }
 
@@ -107,14 +108,15 @@ void
 Query::getAuthAdditional(ZoneFinder& zone) const {
     // Fill in authority and addtional sections.
     ZoneFinder::FindResult ns_result = zone.find(zone.getOrigin(),
-                                                 RRType::NS());
+                                                 RRType::NS(), NULL,
+                                                 dnssec_opt_);
     // zone origin name should have NS records
     if (ns_result.code != ZoneFinder::SUCCESS) {
         isc_throw(NoApexNS, "There's no apex NS records in zone " <<
                 zone.getOrigin().toText());
     } else {
         response_.addRRset(Message::SECTION_AUTHORITY,
-            boost::const_pointer_cast<RRset>(ns_result.rrset));
+            boost::const_pointer_cast<RRset>(ns_result.rrset), dnssec_);
         // Handle additional for authority section
         getAdditional(zone, *ns_result.rrset);
     }
@@ -147,12 +149,14 @@ Query::process() const {
         keep_doing = false;
         std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
         const ZoneFinder::FindResult db_result(
-            result.zone_finder->find(qname_, qtype_, target.get()));
+            result.zone_finder->find(qname_, qtype_, target.get(),
+                                     dnssec_opt_));
         switch (db_result.code) {
             case ZoneFinder::DNAME: {
                 // First, put the dname into the answer
                 response_.addRRset(Message::SECTION_ANSWER,
-                    boost::const_pointer_cast<RRset>(db_result.rrset));
+                    boost::const_pointer_cast<RRset>(db_result.rrset),
+                    dnssec_);
                 /*
                  * Empty DNAME should never get in, as it is impossible to
                  * create one in master file.
@@ -188,7 +192,7 @@ Query::process() const {
                     qname_.getLabelCount() -
                     db_result.rrset->getName().getLabelCount()).
                     concatenate(dname.getDname())));
-                response_.addRRset(Message::SECTION_ANSWER, cname);
+                response_.addRRset(Message::SECTION_ANSWER, cname, dnssec_);
                 break;
             }
             case ZoneFinder::CNAME:
@@ -202,20 +206,23 @@ Query::process() const {
                  * So, just put it there.
                  */
                 response_.addRRset(Message::SECTION_ANSWER,
-                    boost::const_pointer_cast<RRset>(db_result.rrset));
+                    boost::const_pointer_cast<RRset>(db_result.rrset),
+                    dnssec_);
                 break;
             case ZoneFinder::SUCCESS:
                 if (qtype_is_any) {
                     // If quety type is ANY, insert all RRs under the domain
                     // into answer section.
                     BOOST_FOREACH(RRsetPtr rrset, *target) {
-                        response_.addRRset(Message::SECTION_ANSWER, rrset);
+                        response_.addRRset(Message::SECTION_ANSWER, rrset,
+                                           dnssec_);
                         // Handle additional for answer section
                         getAdditional(*result.zone_finder, *rrset.get());
                     }
                 } else {
                     response_.addRRset(Message::SECTION_ANSWER,
-                        boost::const_pointer_cast<RRset>(db_result.rrset));
+                        boost::const_pointer_cast<RRset>(db_result.rrset),
+                        dnssec_);
                     // Handle additional for answer section
                     getAdditional(*result.zone_finder, *db_result.rrset);
                 }
@@ -233,7 +240,8 @@ Query::process() const {
             case ZoneFinder::DELEGATION:
                 response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
                 response_.addRRset(Message::SECTION_AUTHORITY,
-                    boost::const_pointer_cast<RRset>(db_result.rrset));
+                    boost::const_pointer_cast<RRset>(db_result.rrset),
+                    dnssec_);
                 getAdditional(*result.zone_finder, *db_result.rrset);
                 break;
             case ZoneFinder::NXDOMAIN:
diff --git a/src/bin/auth/query.h b/src/bin/auth/query.h
index 13523e8..0ebbed8 100644
--- a/src/bin/auth/query.h
+++ b/src/bin/auth/query.h
@@ -139,11 +139,15 @@ public:
     /// \param qname The query name
     /// \param qtype The RR type of the query
     /// \param response The response message to store the answer to the query.
+    /// \param dnssec If the answer should include signatures and NSEC/NSEC3 if
+    ///     possible.
     Query(const isc::datasrc::DataSourceClient& datasrc_client,
           const isc::dns::Name& qname, const isc::dns::RRType& qtype,
-          isc::dns::Message& response) :
+          isc::dns::Message& response, bool dnssec = false) :
         datasrc_client_(datasrc_client), qname_(qname), qtype_(qtype),
-        response_(response)
+        response_(response), dnssec_(dnssec),
+        dnssec_opt_(dnssec ?  isc::datasrc::ZoneFinder::FIND_DNSSEC :
+                    isc::datasrc::ZoneFinder::FIND_DEFAULT)
     {}
 
     /// Process the query.
@@ -211,6 +215,8 @@ private:
     const isc::dns::Name& qname_;
     const isc::dns::RRType& qtype_;
     isc::dns::Message& response_;
+    const bool dnssec_;
+    const isc::datasrc::ZoneFinder::FindOptions dnssec_opt_;
 };
 
 }
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 68f0a1d..4b8f013 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -111,7 +111,8 @@ public:
         dname_name_("dname.example.com"),
         has_SOA_(true),
         has_apex_NS_(true),
-        rrclass_(RRClass::IN())
+        rrclass_(RRClass::IN()),
+        include_rrsig_anyway_(false)
     {
         stringstream zone_stream;
         zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
@@ -137,11 +138,14 @@ public:
     // the apex NS.
     void setApexNSFlag(bool on) { has_apex_NS_ = on; }
 
+    // Turn this on if you want it to return RRSIGs regardless of FIND_GLUE_OK
+    void setIncludeRRSIGAnyway(bool on) { include_rrsig_anyway_ = on; }
+
 private:
     typedef map<RRType, ConstRRsetPtr> RRsetStore;
     typedef map<Name, RRsetStore> Domains;
     Domains domains_;
-    void loadRRset(ConstRRsetPtr rrset) {
+    void loadRRset(RRsetPtr rrset) {
         domains_[rrset->getName()][rrset->getType()] = rrset;
         if (rrset->getName() == delegation_name_ &&
             rrset->getType() == RRType::NS()) {
@@ -149,6 +153,26 @@ private:
         } else if (rrset->getName() == dname_name_ &&
             rrset->getType() == RRType::DNAME()) {
             dname_rrset_ = rrset;
+        // Add some signatures
+        } else if (rrset->getName() == Name("example.com.") &&
+                   rrset->getType() == RRType::NS()) {
+            rrset->addRRsig(RdataPtr(new generic::RRSIG("NS 5 3 3600 "
+                                                        "20000101000000 "
+                                                        "20000201000000 "
+                                                        "12345 example.com. "
+                                                        "FAKEFAKEFAKE")));
+        } else if (rrset->getType() == RRType::A()) {
+            rrset->addRRsig(RdataPtr(new generic::RRSIG("A 5 3 3600 "
+                                                        "20000101000000 "
+                                                        "20000201000000 "
+                                                        "12345 example.com. "
+                                                        "FAKEFAKEFAKE")));
+        } else if (rrset->getType() == RRType::AAAA()) {
+            rrset->addRRsig(RdataPtr(new generic::RRSIG("AAAA 5 3 3600 "
+                                                        "20000101000000 "
+                                                        "20000201000000 "
+                                                        "12345 example.com. "
+                                                        "FAKEFAKEFAKE")));
         }
     }
 
@@ -161,6 +185,7 @@ private:
     ConstRRsetPtr delegation_rrset_;
     ConstRRsetPtr dname_rrset_;
     const RRClass rrclass_;
+    bool include_rrsig_anyway_;
 };
 
 ZoneFinder::FindResult
@@ -195,7 +220,26 @@ MockZoneFinder::find(const Name& name, const RRType& type,
         RRsetStore::const_iterator found_rrset =
             found_domain->second.find(type);
         if (found_rrset != found_domain->second.end()) {
-            return (FindResult(SUCCESS, found_rrset->second));
+            ConstRRsetPtr rrset;
+            // Strip whatever signature there is in case DNSSEC is not required
+            // Just to make sure the Query asks for it when it is needed
+            if (options & ZoneFinder::FIND_DNSSEC ||
+                include_rrsig_anyway_ ||
+                !found_rrset->second->getRRsig()) {
+                rrset = found_rrset->second;
+            } else {
+                RRsetPtr noconst(new RRset(found_rrset->second->getName(),
+                                           found_rrset->second->getClass(),
+                                           found_rrset->second->getType(),
+                                           found_rrset->second->getTTL()));
+                for (RdataIteratorPtr
+                     i(found_rrset->second->getRdataIterator());
+                     !i->isLast(); i->next()) {
+                    noconst->addRdata(i->getCurrent());
+                }
+                rrset = noconst;
+            }
+            return (FindResult(SUCCESS, rrset));
         }
 
         // If not found but we have a target, fill it with all RRsets here
@@ -304,6 +348,58 @@ TEST_F(QueryTest, exactMatch) {
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
 }
 
+TEST_F(QueryTest, exactMatchIgnoreSIG) {
+    // Check that we do not include the RRSIG when not requested even when
+    // we receive it from the data source.
+    mock_finder->setIncludeRRSIGAnyway(true);
+    Query query(memory_client, qname, qtype, response);
+    EXPECT_NO_THROW(query.process());
+    // find match rrset
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+                  www_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, dnssecPositive) {
+    // Just like exactMatch, but the signatures should be included as well
+    Query query(memory_client, qname, qtype, response, true);
+    EXPECT_NO_THROW(query.process());
+    // find match rrset
+    // We can't let responseCheck to check the additional section as well,
+    // it gets confused by the two RRs for glue.delegation.../RRSIG due
+    // to it's design and fixing it would be hard. Therefore we simply
+    // check manually this one time.
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
+                  (www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
+                                           "A 5 3 3600 20000101000000 "
+                                           "20000201000000 12345 example.com. "
+                                           "FAKEFAKEFAKE\n")).c_str(),
+                  (zone_ns_txt + std::string("example.com. 3600 IN RRSIG NS 5 "
+                                             "3 3600 20000101000000 "
+                                             "20000201000000 12345 "
+                                             "example.com. FAKEFAKEFAKE\n")).
+                  c_str(), NULL);
+    RRsetIterator iterator(response.beginSection(Message::SECTION_ADDITIONAL));
+    const char* additional[] = {
+        "glue.delegation.example.com. 3600 IN A 192.0.2.153\n",
+        "glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
+            "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
+        "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n",
+        "glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 "
+            "20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE\n",
+        "noglue.example.com. 3600 IN A 192.0.2.53\n",
+        "noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
+            "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
+        NULL
+    };
+    for (const char** rr(additional); *rr != NULL; ++ rr) {
+        ASSERT_FALSE(iterator ==
+                     response.endSection(Message::SECTION_ADDITIONAL));
+        EXPECT_EQ(*rr, (*iterator)->toText());
+        iterator ++;
+    }
+    EXPECT_TRUE(iterator == response.endSection(Message::SECTION_ADDITIONAL));
+}
+
 TEST_F(QueryTest, exactAddrMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index c43092d..6a7ae04 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -80,8 +80,8 @@ typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
 /// disruption with a naive copy it's prohibited explicitly.  For the expected
 /// usage of the client classes the restriction should be acceptable.
 ///
-/// \todo This class is not complete. It needs more factory methods, for
-///     accessing the whole zone, updating it, loading it, etc.
+/// \todo This class is still not complete. It will need more factory methods,
+/// e.g. for (re)loading a zone.
 class DataSourceClient : boost::noncopyable {
 public:
     /// \brief A helper structure to represent the search result of
@@ -180,6 +180,65 @@ public:
         isc_throw(isc::NotImplemented,
                   "Data source doesn't support iteration");
     }
+
+    /// Return an updater to make updates to a specific zone.
+    ///
+    /// The RR class of the zone is the one that the client is expected to
+    /// handle (see the detailed description of this class).
+    ///
+    /// If the specified zone is not found via the client, a NULL pointer
+    /// will be returned; in other words a completely new zone cannot be
+    /// created using an updater.  It must be created beforehand (even if
+    /// it's an empty placeholder) in a way specific to the underlying data
+    /// source.
+    ///
+    /// Conceptually, the updater will trigger a separate transaction for
+    /// subsequent updates to the zone within the context of the updater
+    /// (the actual implementation of the "transaction" may vary for the
+    /// specific underlying data source).  Until \c commit() is performed
+    /// on the updater, the intermediate updates won't affect the results
+    /// of other methods (and the result of the object's methods created
+    /// by other factory methods).  Likewise, if the updater is destructed
+    /// without performing \c commit(), the intermediate updates will be
+    /// effectively canceled and will never affect other methods.
+    ///
+    /// If the underlying data source allows concurrent updates, this method
+    /// can be called multiple times while the previously returned updater(s)
+    /// are still active.  In this case each updater triggers a different
+    /// "transaction".  Normally it would be for different zones for such a
+    /// case as handling multiple incoming AXFR streams concurrently, but
+    /// this interface does not even prohibit an attempt of getting more than
+    /// one updater for the same zone, as long as the underlying data source
+    /// allows such an operation (and any conflict resolution is left to the
+    /// specific derived class implementation).
+    ///
+    /// If \c replace is true, any existing RRs of the zone will be
+    /// deleted on successful completion of updates (after \c commit() on
+    /// the updater); if it's false, the existing RRs will be
+    /// intact unless explicitly deleted by \c deleteRRset() on the updater.
+    ///
+    /// A data source can be "read only" or can prohibit partial updates.
+    /// In such cases this method will result in an \c isc::NotImplemented
+    /// exception unconditionally or when \c replace is false).
+    ///
+    /// \note To avoid throwing the exception accidentally with a lazy
+    /// implementation, we still keep this method pure virtual without
+    /// an implementation.  All derived classes must explicitly define this
+    /// method, even if it simply throws the NotImplemented exception.
+    ///
+    /// \exception NotImplemented The underlying data source does not support
+    /// updates.
+    /// \exception DataSourceError Internal error in the underlying data
+    /// source.
+    /// \exception std::bad_alloc Resource allocation failure.
+    ///
+    /// \param name The zone name to be updated
+    /// \param replace Whether to delete existing RRs before making updates
+    ///
+    /// \return A pointer to the updater; it will be NULL if the specified
+    /// zone isn't found.
+    virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+                                      bool replace) const = 0;
 };
 }
 }
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index d9b5458..2c5aaeb 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -12,6 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <string>
 #include <vector>
 
 #include <datasrc/database.h>
@@ -21,6 +22,8 @@
 #include <exceptions/exceptions.h>
 #include <dns/name.h>
 #include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 
@@ -29,17 +32,18 @@
 
 #include <boost/foreach.hpp>
 
-#include <string>
-
 using namespace isc::dns;
-using std::string;
+using namespace std;
+using boost::shared_ptr;
+using namespace isc::dns::rdata;
 
 namespace isc {
 namespace datasrc {
 
-DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
+DatabaseClient::DatabaseClient(RRClass rrclass,
+                               boost::shared_ptr<DatabaseAccessor>
                                accessor) :
-    accessor_(accessor)
+    rrclass_(rrclass), accessor_(accessor)
 {
     if (!accessor_) {
         isc_throw(isc::InvalidParameter,
@@ -604,5 +608,170 @@ DatabaseClient::getIterator(const isc::dns::Name& name) const {
     return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
 }
 
+//
+// Zone updater using some database system as the underlying data source.
+//
+class DatabaseUpdater : public ZoneUpdater {
+public:
+    DatabaseUpdater(shared_ptr<DatabaseAccessor> accessor, int zone_id,
+            const Name& zone_name, const RRClass& zone_class) :
+        committed_(false), accessor_(accessor), zone_id_(zone_id),
+        db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
+        zone_class_(zone_class),
+        finder_(new DatabaseClient::Finder(accessor_, zone_id_, zone_name))
+    {
+        logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
+            .arg(zone_name_).arg(zone_class_).arg(db_name_);
+    }
+
+    virtual ~DatabaseUpdater() {
+        if (!committed_) {
+            try {
+                accessor_->rollbackUpdateZone();
+                logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
+                    .arg(zone_name_).arg(zone_class_).arg(db_name_);
+            } catch (const DataSourceError& e) {
+                // We generally expect that rollback always succeeds, and
+                // it should in fact succeed in a way we execute it.  But
+                // as the public API allows rollbackUpdateZone() to fail and
+                // throw, we should expect it.  Obviously we cannot re-throw
+                // it.  The best we can do is to log it as a critical error.
+                logger.error(DATASRC_DATABASE_UPDATER_ROLLBACKFAIL)
+                    .arg(zone_name_).arg(zone_class_).arg(db_name_)
+                    .arg(e.what());
+            }
+        }
+
+        logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_DESTROYED)
+            .arg(zone_name_).arg(zone_class_).arg(db_name_);
+    }
+
+    virtual ZoneFinder& getFinder() { return (*finder_); }
+
+    virtual void addRRset(const RRset& rrset);
+    virtual void deleteRRset(const RRset& rrset);
+    virtual void commit();
+
+private:
+    bool committed_;
+    shared_ptr<DatabaseAccessor> accessor_;
+    const int zone_id_;
+    const string db_name_;
+    const string zone_name_;
+    const RRClass zone_class_;
+    boost::scoped_ptr<DatabaseClient::Finder> finder_;
+};
+
+void
+DatabaseUpdater::addRRset(const RRset& rrset) {
+    if (committed_) {
+        isc_throw(DataSourceError, "Add attempt after commit to zone: "
+                  << zone_name_ << "/" << zone_class_);
+    }
+    if (rrset.getClass() != zone_class_) {
+        isc_throw(DataSourceError, "An RRset of a different class is being "
+                  << "added to " << zone_name_ << "/" << zone_class_ << ": "
+                  << rrset.toText());
+    }
+    if (rrset.getRRsig()) {
+        isc_throw(DataSourceError, "An RRset with RRSIG is being added to "
+                  << zone_name_ << "/" << zone_class_ << ": "
+                  << rrset.toText());
+    }
+
+    RdataIteratorPtr it = rrset.getRdataIterator();
+    if (it->isLast()) {
+        isc_throw(DataSourceError, "An empty RRset is being added for "
+                  << rrset.getName() << "/" << zone_class_ << "/"
+                  << rrset.getType());
+    }
+
+    string columns[DatabaseAccessor::ADD_COLUMN_COUNT]; // initialized with ""
+    columns[DatabaseAccessor::ADD_NAME] = rrset.getName().toText();
+    columns[DatabaseAccessor::ADD_REV_NAME] =
+        rrset.getName().reverse().toText();
+    columns[DatabaseAccessor::ADD_TTL] = rrset.getTTL().toText();
+    columns[DatabaseAccessor::ADD_TYPE] = rrset.getType().toText();
+    for (; !it->isLast(); it->next()) {
+        if (rrset.getType() == RRType::RRSIG()) {
+            // XXX: the current interface (based on the current sqlite3
+            // data source schema) requires a separate "sigtype" column,
+            // even though it won't be used in a newer implementation.
+            // We should eventually clean up the schema design and simplify
+            // the interface, but until then we have to conform to the schema.
+            const generic::RRSIG& rrsig_rdata =
+                dynamic_cast<const generic::RRSIG&>(it->getCurrent());
+            columns[DatabaseAccessor::ADD_SIGTYPE] =
+                rrsig_rdata.typeCovered().toText();
+        }
+        columns[DatabaseAccessor::ADD_RDATA] = it->getCurrent().toText();
+        accessor_->addRecordToZone(columns);
+    }
+}
+
+void
+DatabaseUpdater::deleteRRset(const RRset& rrset) {
+    if (committed_) {
+        isc_throw(DataSourceError, "Delete attempt after commit on zone: "
+                  << zone_name_ << "/" << zone_class_);
+    }
+    if (rrset.getClass() != zone_class_) {
+        isc_throw(DataSourceError, "An RRset of a different class is being "
+                  << "deleted from " << zone_name_ << "/" << zone_class_
+                  << ": " << rrset.toText());
+    }
+    if (rrset.getRRsig()) {
+        isc_throw(DataSourceError, "An RRset with RRSIG is being deleted from "
+                  << zone_name_ << "/" << zone_class_ << ": "
+                  << rrset.toText());
+    }
+
+    RdataIteratorPtr it = rrset.getRdataIterator();
+    if (it->isLast()) {
+        isc_throw(DataSourceError, "An empty RRset is being deleted for "
+                  << rrset.getName() << "/" << zone_class_ << "/"
+                  << rrset.getType());
+    }
+
+    string params[DatabaseAccessor::DEL_PARAM_COUNT]; // initialized with ""
+    params[DatabaseAccessor::DEL_NAME] = rrset.getName().toText();
+    params[DatabaseAccessor::DEL_TYPE] = rrset.getType().toText();
+    for (; !it->isLast(); it->next()) {
+        params[DatabaseAccessor::DEL_RDATA] = it->getCurrent().toText();
+        accessor_->deleteRecordInZone(params);
+    }
+}
+
+void
+DatabaseUpdater::commit() {
+    if (committed_) {
+        isc_throw(DataSourceError, "Duplicate commit attempt for "
+                  << zone_name_ << "/" << zone_class_ << " on "
+                  << db_name_);
+    }
+    accessor_->commitUpdateZone();
+    committed_ = true; // make sure the destructor won't trigger rollback
+
+    // We release the accessor immediately after commit is completed so that
+    // we don't hold the possible internal resource any longer.
+    accessor_.reset();
+
+    logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_COMMIT)
+        .arg(zone_name_).arg(zone_class_).arg(db_name_);
+}
+
+// The updater factory
+ZoneUpdaterPtr
+DatabaseClient::getUpdater(const isc::dns::Name& name, bool replace) const {
+    shared_ptr<DatabaseAccessor> update_accessor(accessor_->clone());
+    const std::pair<bool, int> zone(update_accessor->startUpdateZone(
+                                        name.toText(), replace));
+    if (!zone.first) {
+        return (ZoneUpdaterPtr());
+    }
+
+    return (ZoneUpdaterPtr(new DatabaseUpdater(update_accessor, zone.second,
+                                               name, rrclass_)));
+}
 }
 }
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index d9e2343..82918ac 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -15,6 +15,14 @@
 #ifndef __DATABASE_DATASRC_H
 #define __DATABASE_DATASRC_H
 
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/rrclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
 #include <datasrc/client.h>
 
 #include <dns/name.h>
@@ -109,6 +117,7 @@ public:
      * classes in polymorphic way.
      */
     virtual ~DatabaseAccessor() { }
+
     /**
      * \brief Retrieve a zone identifier
      *
@@ -420,6 +429,35 @@ public:
     /// to the method or internal database error.
     virtual void rollbackUpdateZone() = 0;
 
+    /// Clone the accessor with the same configuration.
+    ///
+    /// Each derived class implementation of this method will create a new
+    /// accessor of the same derived class with the same configuration
+    /// (such as the database server address) as that of the caller object
+    /// and return it.
+    ///
+    /// Note that other internal states won't be copied to the new accessor
+    /// even though the name of "clone" may indicate so.  For example, even
+    /// if the calling accessor is in the middle of a update transaction,
+    /// the new accessor will not start a transaction to trace the same
+    /// updates.
+    ///
+    /// The intended use case of cloning is to create a separate context
+    /// where a specific set of database operations can be performed
+    /// independently from the original accessor.  The updater will use it
+    /// so that multiple updaters can be created concurrently even if the
+    /// underlying database system doesn't allow running multiple transactions
+    /// in a single database connection.
+    ///
+    /// The underlying database system may not support the functionality
+    /// that would be needed to implement this method.  For example, it
+    /// may not allow a single thread (or process) to have more than one
+    /// database connections.  In such a case the derived class implementation
+    /// should throw a \c DataSourceError exception.
+    ///
+    /// \return A shared pointer to the cloned accessor.
+    virtual boost::shared_ptr<DatabaseAccessor> clone() = 0;
+
     /**
      * \brief Returns a string identifying this dabase backend
      *
@@ -454,16 +492,19 @@ public:
     /**
      * \brief Constructor
      *
-     * It initializes the client with a database.
+     * It initializes the client with a database via the given accessor.
      *
-     * \exception isc::InvalidParameter if database is NULL. It might throw
+     * \exception isc::InvalidParameter if accessor is NULL. It might throw
      * standard allocation exception as well, but doesn't throw anything else.
      *
-     * \param database The database to use to get data. As the parameter
-     *     suggests, the client takes ownership of the database and will
-     *     delete it when itself deleted.
+     * \param rrclass The RR class of the zones that this client will handle.
+     * \param accessor The accessor to the database to use to get data.
+     *  As the parameter suggests, the client takes ownership of the accessor
+     *  and will delete it when itself deleted.
      */
-    DatabaseClient(boost::shared_ptr<DatabaseAccessor> database);
+    DatabaseClient(isc::dns::RRClass rrclass,
+                   boost::shared_ptr<DatabaseAccessor> accessor);
+
     /**
      * \brief Corresponding ZoneFinder implementation
      *
@@ -568,6 +609,7 @@ public:
         boost::shared_ptr<DatabaseAccessor> accessor_;
         const int zone_id_;
         const isc::dns::Name origin_;
+
         /**
          * \brief Searches database for an RRset
          *
@@ -614,6 +656,7 @@ public:
                                                      bool want_ns, const
                                                      isc::dns::Name*
                                                      construct_name = NULL);
+
         /**
          * \brief Checks if something lives below this domain.
          *
@@ -660,7 +703,17 @@ public:
      */
     virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
 
+    /// This implementation internally clones the accessor from the one
+    /// used in the client and starts a separate transaction using the cloned
+    /// accessor.  The returned updater will be able to work separately from
+    /// the original client.
+    virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+                                      bool replace) const;
+
 private:
+    /// \brief The RR class that this client handles.
+    const isc::dns::RRClass rrclass_;
+
     /// \brief The accessor to our database.
     const boost::shared_ptr<DatabaseAccessor> accessor_;
 };
@@ -668,4 +721,8 @@ private:
 }
 }
 
-#endif
+#endif  // __DATABASE_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 5f92407..efb88fd 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -590,3 +590,38 @@ data source.
 % DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
 This indicates a programming error. An internal task of unknown type was
 generated.
+
+% DATASRC_DATABASE_UPDATER_CREATED zone updater created for '%1/%2' on %3
+Debug information.  A zone updater object is created to make updates to
+the shown zone on the shown backend database.
+
+% DATASRC_DATABASE_UPDATER_DESTROYED zone updater destroyed for '%1/%2' on %3
+Debug information.  A zone updater object is destroyed, either successfully
+or after failure of, making updates to the shown zone on the shown backend
+database.
+
+%DATASRC_DATABASE_UPDATER_ROLLBACK zone updates roll-backed for '%1/%2' on %3
+A zone updater is being destroyed without committing the changes.
+This would typically mean the update attempt was aborted due to some
+error, but may also be a bug of the application that forgets committing
+the changes.  The intermediate changes made through the updater won't
+be applied to the underlying database.  The zone name, its class, and
+the underlying database name are shown in the log message.
+
+%DATASRC_DATABASE_UPDATER_ROLLBACKFAIL failed to roll back zone updates for '%1/%2' on %3: %4
+A zone updater is being destroyed without committing the changes to
+the database, and attempts to rollback incomplete updates, but it
+unexpectedly fails.  The higher level implementation does not expect
+it to fail, so this means either a serious operational error in the
+underlying data source (such as a system failure of a database) or
+software bug in the underlying data source implementation.  In either
+case if this message is logged the administrator should carefully
+examine the underlying data source to see what exactly happens and
+whether the data is still valid.  The zone name, its class, and the
+underlying database name as well as the error message thrown from the
+database module are shown in the log message.
+
+% DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3
+Debug information.  A set of updates to a zone has been successfully
+committed to the corresponding database backend.  The zone name,
+its class and the database name are printed.
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 1fc9252..630b1c0 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -17,6 +17,8 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/bind.hpp>
 
+#include <exceptions/exceptions.h>
+
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrsetlist.h>
@@ -793,5 +795,9 @@ InMemoryClient::getIterator(const Name& name) const {
     return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name)));
 }
 
+ZoneUpdaterPtr
+InMemoryClient::getUpdater(const isc::dns::Name&, bool) const {
+    isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
+}
 } // end of namespace datasrc
 } // end of namespace dns
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index 6cd1753..c569548 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -266,6 +266,17 @@ public:
     /// \brief Implementation of the getIterator method
     virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
 
+    /// In-memory data source is read-only, so this derived method will
+    /// result in a NotImplemented exception.
+    ///
+    /// \note We plan to use a database-based data source as a backend
+    /// persistent storage for an in-memory data source.  When it's
+    /// implemented we may also want to allow the user of the in-memory client
+    /// to update via its updater (this may or may not be a good idea and
+    /// is subject to further discussions).
+    virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+                                      bool replace) const;
+
 private:
     // TODO: Do we still need the PImpl if nobody should manipulate this class
     // directly any more (it should be handled through DataSourceClient)?
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index ee81655..956f447 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -111,8 +111,7 @@ public:
     }
 
     void exec() {
-        if (sqlite3_step(dbparameters_.statements_[stmt_id_]) !=
-            SQLITE_DONE) {
+        if (sqlite3_step(dbparameters_.statements_[stmt_id_]) != SQLITE_DONE) {
             sqlite3_reset(dbparameters_.statements_[stmt_id_]);
             isc_throw(DataSourceError, "failed to " << desc_ << ": " <<
                       sqlite3_errmsg(dbparameters_.db_));
@@ -128,6 +127,7 @@ private:
 SQLite3Accessor::SQLite3Accessor(const std::string& filename,
                                  const isc::dns::RRClass& rrclass) :
     dbparameters_(new SQLite3Parameters),
+    filename_(filename),
     class_(rrclass.toText()),
     database_name_("sqlite3_" +
                    isc::util::Filename(filename).nameAndExtension())
@@ -137,6 +137,25 @@ SQLite3Accessor::SQLite3Accessor(const std::string& filename,
     open(filename);
 }
 
+SQLite3Accessor::SQLite3Accessor(const std::string& filename,
+                                 const string& rrclass) :
+    dbparameters_(new SQLite3Parameters),
+    filename_(filename),
+    class_(rrclass),
+    database_name_("sqlite3_" +
+                   isc::util::Filename(filename).nameAndExtension())
+{
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
+
+    open(filename);
+}
+
+boost::shared_ptr<DatabaseAccessor>
+SQLite3Accessor::clone() {
+    return (boost::shared_ptr<DatabaseAccessor>(new SQLite3Accessor(filename_,
+                                                                    class_)));
+}
+
 namespace {
 
 // This is a helper class to initialize a Sqlite3 DB safely.  An object of
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index 8f993c1..fae249b 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -71,6 +71,17 @@ public:
      */
     SQLite3Accessor(const std::string& filename,
                     const isc::dns::RRClass& rrclass);
+
+    /**
+     * \brief Constructor
+     *
+     * Same as the other version, but takes rrclass as a bare string.
+     * we should obsolete the other version and unify the constructor to
+     * this version; the SQLite3Accessor is expected to be "dumb" and
+     * shouldn't care about DNS specific information such as RRClass.
+     */
+    SQLite3Accessor(const std::string& filename, const std::string& rrclass);
+
     /**
      * \brief Destructor
      *
@@ -78,6 +89,10 @@ public:
      */
     ~SQLite3Accessor();
 
+    /// This implementation internally opens a new sqlite3 database for the
+    /// same file name specified in the constructor of the original accessor.
+    virtual boost::shared_ptr<DatabaseAccessor> clone();
+
     /**
      * \brief Look up a zone
      *
@@ -158,6 +173,8 @@ public:
 private:
     /// \brief Private database data
     boost::scoped_ptr<SQLite3Parameters> dbparameters_;
+    /// \brief The filename of the DB (necessary for clone())
+    const std::string filename_;
     /// \brief The class for which the queries are done
     const std::string class_;
     /// \brief Opens the database
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index a1ef054..48cbc76 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -65,3 +65,4 @@ EXTRA_DIST += testdata/sql1.example.com.signed
 EXTRA_DIST += testdata/sql2.example.com.signed
 EXTRA_DIST += testdata/test-root.sqlite3
 EXTRA_DIST += testdata/test.sqlite3
+EXTRA_DIST += testdata/rwtest.sqlite3
diff --git a/src/lib/datasrc/tests/client_unittest.cc b/src/lib/datasrc/tests/client_unittest.cc
index 1a88f18..5b2c91a 100644
--- a/src/lib/datasrc/tests/client_unittest.cc
+++ b/src/lib/datasrc/tests/client_unittest.cc
@@ -32,6 +32,9 @@ public:
     virtual FindResult findZone(const isc::dns::Name&) const {
         return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
     }
+    virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool) const {
+        return (ZoneUpdaterPtr());
+    }
 };
 
 class ClientTest : public ::testing::Test {
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 1fb79bc..4ed9f12 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <boost/foreach.hpp>
+
 #include <gtest/gtest.h>
 
 #include <dns/name.h>
@@ -23,6 +25,7 @@
 #include <datasrc/zone.h>
 #include <datasrc/data_source.h>
 #include <datasrc/iterator.h>
+#include <datasrc/sqlite3_accessor.h>
 
 #include <testutils/dnsmessage_test.h>
 
@@ -35,6 +38,139 @@ using namespace isc::dns;
 
 namespace {
 
+// Imaginary zone IDs used in the mock accessor below.
+const int READONLY_ZONE_ID = 42;
+const int WRITABLE_ZONE_ID = 4200;
+
+// Commonly used test data
+const char* const TEST_RECORDS[][5] = {
+    // some plain data
+    {"www.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"www.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+    {"www.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+
+    {"www2.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"www2.example.org.","AAAA", "3600", "", "2001:db8::1"},
+    {"www2.example.org.", "A", "3600", "", "192.0.2.2"},
+
+    {"cname.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+    // some DNSSEC-'signed' data
+    {"signed1.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+    {"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
+    {"signed1.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+    {"signed1.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+    {"signed1.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+    {"signedcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
+    {"signedcname1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+    // special case might fail; sig is for cname, which isn't there (should be ignored)
+    // (ignoring of 'normal' other type is done above by www.)
+    {"acnamesig1.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"acnamesig1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"acnamesig1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+    // let's pretend we have a database that is not careful
+    // about the order in which it returns data
+    {"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"signed2.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+    {"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
+    {"signed2.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"signed2.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"signed2.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+
+    {"signedcname2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"signedcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+    {"acnamesig2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"acnamesig2.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"acnamesig2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+    {"acnamesig3.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"acnamesig3.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"acnamesig3.example.org.", "A", "3600", "", "192.0.2.1"},
+
+    {"ttldiff1.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"ttldiff1.example.org.", "A", "360", "", "192.0.2.2"},
+
+    {"ttldiff2.example.org.", "A", "360", "", "192.0.2.1"},
+    {"ttldiff2.example.org.", "A", "3600", "", "192.0.2.2"},
+
+    // also add some intentionally bad data
+    {"badcname1.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"badcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+    {"badcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
+    {"badcname2.example.org.", "A", "3600", "", "192.0.2.1"},
+
+    {"badcname3.example.org.", "CNAME", "3600", "", "www.example.org."},
+    {"badcname3.example.org.", "CNAME", "3600", "", "www.example2.org."},
+
+    {"badrdata.example.org.", "A", "3600", "", "bad"},
+
+    {"badtype.example.org.", "BAD_TYPE", "3600", "", "192.0.2.1"},
+
+    {"badttl.example.org.", "A", "badttl", "", "192.0.2.1"},
+
+    {"badsig.example.org.", "A", "badttl", "", "192.0.2.1"},
+    {"badsig.example.org.", "RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+    {"badsigtype.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"badsigtype.example.org.", "RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+    // Data for testing delegation (with NS and DNAME)
+    {"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
+    {"delegation.example.org.", "NS", "3600", "",
+     "ns.delegation.example.org."},
+    {"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
+     "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
+
+    {"dname.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"dname.example.org.", "DNAME", "3600", "", "dname.example.com."},
+    {"dname.example.org.", "RRSIG", "3600", "",
+     "DNAME 5 3 3600 20000101000000 20000201000000 12345 "
+     "example.org. FAKEFAKEFAKE"},
+
+    {"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},
+
+    // Broken NS
+    {"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
+    {"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
+
+    {"brokenns2.example.org.", "NS", "3600", "", "ns.example.com."},
+    {"brokenns2.example.org.", "A", "3600", "", "192.0.2.1"},
+
+    // Now double DNAME, to test failure mode
+    {"baddname.example.org.", "DNAME", "3600", "", "dname1.example.com."},
+    {"baddname.example.org.", "DNAME", "3600", "", "dname2.example.com."},
+
+    // Put some data into apex (including NS) so we can check our NS
+    // doesn't break anything
+    {"example.org.", "NS", "3600", "", "ns.example.com."},
+    {"example.org.", "A", "3600", "", "192.0.2.1"},
+    {"example.org.", "RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
+              "20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+    // This is because of empty domain test
+    {"a.b.example.org.", "A", "3600", "", "192.0.2.1"},
+
+    // Something for wildcards
+    {"*.wild.example.org.", "A", "3600", "", "192.0.2.5"},
+    {"*.wild.example.org.", "RRSIG", "3600", "A", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"cancel.here.wild.example.org.", "AAAA", "3600", "", "2001:db8::5"},
+    {"delegatedwild.example.org.", "NS", "3600", "", "ns.example.com."},
+    {"*.delegatedwild.example.org.", "A", "3600", "", "192.0.2.5"},
+    {"wild.*.foo.example.org.", "A", "3600", "", "192.0.2.5"},
+    {"wild.*.foo.*.bar.example.org.", "A", "3600", "", "192.0.2.5"},
+
+    {NULL, NULL, NULL, NULL, NULL},
+};
+
 /*
  * An accessor with minimum implementation, keeping the original
  * "NotImplemented" methods.
@@ -46,7 +182,7 @@ public:
 
     virtual std::pair<bool, int> getZone(const std::string& name) const {
         if (name == "example.org.") {
-            return (std::pair<bool, int>(true, 42));
+            return (std::pair<bool, int>(true, READONLY_ZONE_ID));
         } else if (name == "null.example.org.") {
             return (std::pair<bool, int>(true, 13));
         } else if (name == "empty.example.org.") {
@@ -58,6 +194,10 @@ public:
         }
     }
 
+    virtual shared_ptr<DatabaseAccessor> clone() {
+        return (shared_ptr<DatabaseAccessor>()); // bogus data, but unused
+    }
+
     virtual std::pair<bool, int> startUpdateZone(const std::string&, bool) {
         // return dummy value.  unused anyway.
         return (pair<bool, int>(true, 0));
@@ -76,30 +216,47 @@ public:
         {
         isc_throw(isc::NotImplemented,
                   "This database datasource can't be iterated");
-    };
+    }
 
     virtual IteratorContextPtr getAllRecords(int) const {
         isc_throw(isc::NotImplemented,
                   "This database datasource can't be iterated");
-    };
+    }
+
 private:
     const std::string database_name_;
 
 };
 
 /*
- * A virtual database connection that pretends it contains single zone --
+ * A virtual database accessor that pretends it contains single zone --
  * example.org.
  *
  * It has the same getZone method as NopConnection, but it provides
  * implementation of the optional functionality.
  */
 class MockAccessor : public NopAccessor {
+    // Type of mock database "row"s
+    typedef std::map<std::string, std::vector< std::vector<std::string> > >
+        Domains;
+
 public:
-    MockAccessor()
-    {
+    MockAccessor() : rollbacked_(false) {
+        readonly_records_ = &readonly_records_master_;
+        update_records_ = &update_records_master_;
+        empty_records_ = &empty_records_master_;
         fillData();
     }
+
+    virtual shared_ptr<DatabaseAccessor> clone() {
+        shared_ptr<MockAccessor> cloned_accessor(new MockAccessor());
+        cloned_accessor->readonly_records_ = &readonly_records_master_;
+        cloned_accessor->update_records_ = &update_records_master_;
+        cloned_accessor->empty_records_ = &empty_records_master_;
+        latest_clone_ = cloned_accessor;
+        return (cloned_accessor);
+    }
+
 private:
     class MockNameIteratorContext : public IteratorContext {
     public:
@@ -118,32 +275,27 @@ private:
                 throw std::exception();
             }
 
-            if (zone_id == 42) {
-                if (subdomains) {
-                    cur_name.clear();
-                    // Just walk everything and check if it is a subdomain.
-                    // If it is, just copy all data from there.
-                    for (Domains::const_iterator
-                         i(mock_accessor.records.begin());
-                         i != mock_accessor.records.end(); ++ i) {
-                        Name local(i->first);
-                        if (local.compare(isc::dns::Name(name)).
-                            getRelation() ==
-                            isc::dns::NameComparisonResult::SUBDOMAIN) {
-                            cur_name.insert(cur_name.end(), i->second.begin(),
-                                            i->second.end());
-                        }
-                    }
-                } else {
+            cur_record_ = 0;
+            const Domains& cur_records = mock_accessor.getMockRecords(zone_id);
+            if (cur_records.count(name) > 0) {
                     // we're not aiming for efficiency in this test, simply
                     // copy the relevant vector from records
-                    if (mock_accessor.records.count(searched_name_) > 0) {
-                        cur_name = mock_accessor.records.find(searched_name_)->
-                            second;
-                    } else {
-                        cur_name.clear();
+                    cur_name = cur_records.find(name)->second;
+            } else if (subdomains) {
+                cur_name.clear();
+                // Just walk everything and check if it is a subdomain.
+                // If it is, just copy all data from there.
+                for (Domains::const_iterator i(cur_records.begin());
+                     i != cur_records.end(); ++i) {
+                    const Name local(i->first);
+                    if (local.compare(Name(name)).getRelation() ==
+                        isc::dns::NameComparisonResult::SUBDOMAIN) {
+                        cur_name.insert(cur_name.end(), i->second.begin(),
+                                        i->second.end());
                     }
                 }
+            } else {
+                cur_name.clear();
             }
         }
 
@@ -258,7 +410,7 @@ private:
     };
 public:
     virtual IteratorContextPtr getAllRecords(int id) const {
-        if (id == 42) {
+        if (id == READONLY_ZONE_ID) {
             return (IteratorContextPtr(new MockIteratorContext()));
         } else if (id == 13) {
             return (IteratorContextPtr());
@@ -274,51 +426,178 @@ public:
     virtual IteratorContextPtr getRecords(const std::string& name, int id,
                                           bool subdomains) const
     {
-        if (id == 42) {
-            return (IteratorContextPtr(new MockNameIteratorContext(*this, id,
-                name, subdomains)));
+        if (id == READONLY_ZONE_ID || id == WRITABLE_ZONE_ID) {
+            return (IteratorContextPtr(
+                        new MockNameIteratorContext(*this, id, name,
+                                                    subdomains)));
         } else {
             isc_throw(isc::Unexpected, "Unknown zone ID");
         }
     }
 
+    virtual pair<bool, int> startUpdateZone(const std::string& zone_name,
+                                            bool replace)
+    {
+        const pair<bool, int> zone_info = getZone(zone_name);
+        if (!zone_info.first) {
+            return (pair<bool, int>(false, 0));
+        }
+
+        // Prepare the record set for update.  If replacing the existing one,
+        // we use an empty set; otherwise we use a writable copy of the
+        // original.
+        if (replace) {
+            update_records_->clear();
+        } else {
+            *update_records_ = *readonly_records_;
+        }
+
+        return (pair<bool, int>(true, WRITABLE_ZONE_ID));
+    }
+    virtual void commitUpdateZone() {
+        *readonly_records_ = *update_records_;
+    }
+    virtual void rollbackUpdateZone() {
+        // Special hook: if something with a name of "throw.example.org"
+        // has been added, trigger an imaginary unexpected event with an
+        // exception.
+        if (update_records_->count("throw.example.org.") > 0) {
+            isc_throw(DataSourceError, "unexpected failure in rollback");
+        }
+
+        rollbacked_ = true;
+    }
+    virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
+        // Copy the current value to cur_name.  If it doesn't exist,
+        // operator[] will create a new one.
+        cur_name_ = (*update_records_)[columns[DatabaseAccessor::ADD_NAME]];
+
+        vector<string> record_columns;
+        record_columns.push_back(columns[DatabaseAccessor::ADD_TYPE]);
+        record_columns.push_back(columns[DatabaseAccessor::ADD_TTL]);
+        record_columns.push_back(columns[DatabaseAccessor::ADD_SIGTYPE]);
+        record_columns.push_back(columns[DatabaseAccessor::ADD_RDATA]);
+        record_columns.push_back(columns[DatabaseAccessor::ADD_NAME]);
+
+        // copy back the added entry
+        cur_name_.push_back(record_columns);
+        (*update_records_)[columns[DatabaseAccessor::ADD_NAME]] = cur_name_;
+
+        // remember this one so that test cases can check it.
+        copy(columns, columns + DatabaseAccessor::ADD_COLUMN_COUNT,
+             columns_lastadded_);
+    }
+
+    // Helper predicate class used in deleteRecordInZone().
+    struct deleteMatch {
+        deleteMatch(const string& type, const string& rdata) :
+            type_(type), rdata_(rdata)
+        {}
+        bool operator()(const vector<string>& row) const {
+            return (row[0] == type_ && row[3] == rdata_);
+        }
+        const string& type_;
+        const string& rdata_;
+    };
+
+    virtual void deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
+        vector<vector<string> >& records =
+            (*update_records_)[params[DatabaseAccessor::DEL_NAME]];
+        records.erase(remove_if(records.begin(), records.end(),
+                                deleteMatch(
+                                    params[DatabaseAccessor::DEL_TYPE],
+                                    params[DatabaseAccessor::DEL_RDATA])),
+                      records.end());
+        if (records.empty()) {
+            (*update_records_).erase(params[DatabaseAccessor::DEL_NAME]);
+        }
+    }
+
+    //
+    // Helper methods to keep track of some update related activities
+    //
+    bool isRollbacked() const {
+        return (rollbacked_);
+    }
+
+    const string* getLastAdded() const {
+        return (columns_lastadded_);
+    }
+
+    // This allows the test code to get the accessor used in an update context
+    shared_ptr<const MockAccessor> getLatestClone() const {
+        return (latest_clone_);
+    }
+
 private:
-    typedef std::map<std::string, std::vector< std::vector<std::string> > >
-        Domains;
+    // The following member variables are storage and/or update work space
+    // of the test zone.  The "master"s are the real objects that contain
+    // the data, and they are shared among all accessors cloned from
+    // an initially created one.  The pointer members allow the sharing.
+    // "readonly" is for normal lookups.  "update" is the workspace for
+    // updates.  When update starts it will be initialized either as an
+    // empty set (when replacing the entire zone) or as a copy of the
+    // "readonly" one.  "empty" is a sentinel to produce negative results.
+    Domains readonly_records_master_;
+    Domains* readonly_records_;
+    Domains update_records_master_;
+    Domains* update_records_;
+    const Domains empty_records_master_;
+    const Domains* empty_records_;
+
     // used as temporary storage during the building of the fake data
-    Domains records;
+
     // used as temporary storage after searchForRecord() and during
     // getNextRecord() calls, as well as during the building of the
     // fake data
-    std::vector< std::vector<std::string> > cur_name;
+    std::vector< std::vector<std::string> > cur_name_;
+
+    // The columns that were most recently added via addRecordToZone()
+    string columns_lastadded_[ADD_COLUMN_COUNT];
+
+    // Whether rollback operation has been performed for the database.
+    // Not useful except for purely testing purpose.
+    bool rollbacked_;
+
+    // Remember the mock accessor that was last cloned
+    boost::shared_ptr<MockAccessor> latest_clone_;
+
+    const Domains& getMockRecords(int zone_id) const {
+        if (zone_id == READONLY_ZONE_ID) {
+            return (*readonly_records_);
+        } else if (zone_id == WRITABLE_ZONE_ID) {
+            return (*update_records_);
+        }
+        return (*empty_records_);
+    }
 
     // Adds one record to the current name in the database
     // The actual data will not be added to 'records' until
     // addCurName() is called
-    void addRecord(const std::string& name,
-                   const std::string& type,
+    void addRecord(const std::string& type,
+                   const std::string& ttl,
                    const std::string& sigtype,
                    const std::string& rdata) {
         std::vector<std::string> columns;
-        columns.push_back(name);
         columns.push_back(type);
+        columns.push_back(ttl);
         columns.push_back(sigtype);
         columns.push_back(rdata);
-        cur_name.push_back(columns);
+        cur_name_.push_back(columns);
     }
 
     // Adds all records we just built with calls to addRecords
-    // to the actual fake database. This will clear cur_name,
+    // to the actual fake database. This will clear cur_name_,
     // so we can immediately start adding new records.
     void addCurName(const std::string& name) {
-        ASSERT_EQ(0, records.count(name));
+        ASSERT_EQ(0, readonly_records_->count(name));
         // Append the name to all of them
         for (std::vector<std::vector<std::string> >::iterator
-             i(cur_name.begin()); i != cur_name.end(); ++ i) {
+             i(cur_name_.begin()); i != cur_name_.end(); ++ i) {
             i->push_back(name);
         }
-        records[name] = cur_name;
-        cur_name.clear();
+        (*readonly_records_)[name] = cur_name_;
+        cur_name_.clear();
     }
 
     // Fills the database with zone data.
@@ -332,155 +611,17 @@ private:
     // might not come in 'normal' order)
     // It shall immediately fail if you try to add the same name twice.
     void fillData() {
-        // some plain data
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("AAAA", "3600", "", "2001:db8::1");
-        addRecord("AAAA", "3600", "", "2001:db8::2");
-        addCurName("www.example.org.");
-
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("AAAA", "3600", "", "2001:db8::1");
-        addRecord("A", "3600", "", "192.0.2.2");
-        addCurName("www2.example.org.");
-
-        addRecord("CNAME", "3600", "", "www.example.org.");
-        addCurName("cname.example.org.");
-
-        // some DNSSEC-'signed' data
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
-        addRecord("AAAA", "3600", "", "2001:db8::1");
-        addRecord("AAAA", "3600", "", "2001:db8::2");
-        addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("signed1.example.org.");
-        addRecord("CNAME", "3600", "", "www.example.org.");
-        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("signedcname1.example.org.");
-        // special case might fail; sig is for cname, which isn't there (should be ignored)
-        // (ignoring of 'normal' other type is done above by www.)
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("acnamesig1.example.org.");
-
-        // let's pretend we have a database that is not careful
-        // about the order in which it returns data
-        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addRecord("AAAA", "3600", "", "2001:db8::2");
-        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addRecord("AAAA", "3600", "", "2001:db8::1");
-        addCurName("signed2.example.org.");
-        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addRecord("CNAME", "3600", "", "www.example.org.");
-        addCurName("signedcname2.example.org.");
-
-        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("acnamesig2.example.org.");
-
-        addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addCurName("acnamesig3.example.org.");
-
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("A", "360", "", "192.0.2.2");
-        addCurName("ttldiff1.example.org.");
-        addRecord("A", "360", "", "192.0.2.1");
-        addRecord("A", "3600", "", "192.0.2.2");
-        addCurName("ttldiff2.example.org.");
-
-        // also add some intentionally bad data
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("CNAME", "3600", "", "www.example.org.");
-        addCurName("badcname1.example.org.");
-
-        addRecord("CNAME", "3600", "", "www.example.org.");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addCurName("badcname2.example.org.");
-
-        addRecord("CNAME", "3600", "", "www.example.org.");
-        addRecord("CNAME", "3600", "", "www.example2.org.");
-        addCurName("badcname3.example.org.");
-
-        addRecord("A", "3600", "", "bad");
-        addCurName("badrdata.example.org.");
-
-        addRecord("BAD_TYPE", "3600", "", "192.0.2.1");
-        addCurName("badtype.example.org.");
-
-        addRecord("A", "badttl", "", "192.0.2.1");
-        addCurName("badttl.example.org.");
-
-        addRecord("A", "badttl", "", "192.0.2.1");
-        addRecord("RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("badsig.example.org.");
-
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("badsigtype.example.org.");
-
-        // Data for testing delegation (with NS and DNAME)
-        addRecord("NS", "3600", "", "ns.example.com.");
-        addRecord("NS", "3600", "", "ns.delegation.example.org.");
-        addRecord("RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
-                  "20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("delegation.example.org.");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addCurName("ns.delegation.example.org.");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addCurName("deep.below.delegation.example.org.");
-
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("DNAME", "3600", "", "dname.example.com.");
-        addRecord("RRSIG", "3600", "", "DNAME 5 3 3600 20000101000000 "
-                  "20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("dname.example.org.");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addCurName("below.dname.example.org.");
-
-        // Broken NS
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("NS", "3600", "", "ns.example.com.");
-        addCurName("brokenns1.example.org.");
-        addRecord("NS", "3600", "", "ns.example.com.");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addCurName("brokenns2.example.org.");
-
-        // Now double DNAME, to test failure mode
-        addRecord("DNAME", "3600", "", "dname1.example.com.");
-        addRecord("DNAME", "3600", "", "dname2.example.com.");
-        addCurName("baddname.example.org.");
-
-        // Put some data into apex (including NS) so we can check our NS
-        // doesn't break anything
-        addRecord("NS", "3600", "", "ns.example.com.");
-        addRecord("A", "3600", "", "192.0.2.1");
-        addRecord("RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
-                  "20000201000000 12345 example.org. FAKEFAKEFAKE");
-        addCurName("example.org.");
-
-        // This is because of empty domain test
-        addRecord("A", "3600", "", "192.0.2.1");
-        addCurName("a.b.example.org.");
-
-        // Something for wildcards
-        addRecord("A", "3600", "", "192.0.2.5");
-        addCurName("*.wild.example.org.");
-        addRecord("AAAA", "3600", "", "2001:db8::5");
-        addCurName("cancel.here.wild.example.org.");
-        addRecord("NS", "3600", "", "ns.example.com.");
-        addCurName("delegatedwild.example.org.");
-        addRecord("A", "3600", "", "192.0.2.5");
-        addCurName("*.delegatedwild.example.org.");
-        addRecord("A", "3600", "", "192.0.2.5");
-        addCurName("wild.*.foo.example.org.");
-        addRecord("A", "3600", "", "192.0.2.5");
-        addCurName("wild.*.foo.*.bar.example.org.");
+        const char* prev_name = NULL;
+        for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
+            if (prev_name != NULL &&
+                strcmp(prev_name, TEST_RECORDS[i][0]) != 0) {
+                addCurName(prev_name);
+            }
+            prev_name = TEST_RECORDS[i][0];
+            addRecord(TEST_RECORDS[i][1], TEST_RECORDS[i][2],
+                      TEST_RECORDS[i][3], TEST_RECORDS[i][4]);
+        }
+        addCurName(prev_name);
     }
 };
 
@@ -497,24 +638,51 @@ TEST(DatabaseConnectionTest, getAllRecords) {
                  isc::NotImplemented);
 }
 
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources.  Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
 class DatabaseClientTest : public ::testing::Test {
 public:
-    DatabaseClientTest() {
+    DatabaseClientTest() : zname_("example.org"), qname_("www.example.org"),
+                           qclass_(RRClass::IN()), qtype_(RRType::A()),
+                           rrttl_(3600)
+    {
         createClient();
+
+        // set up the commonly used finder.
+        DataSourceClient::FindResult zone(client_->findZone(zname_));
+        assert(zone.code == result::SUCCESS);
+        finder_ = dynamic_pointer_cast<DatabaseClient::Finder>(
+            zone.zone_finder);
+
+        // Test IN/A RDATA to be added in update tests.  Intentionally using
+        // different data than the initial data configured in the MockAccessor.
+        rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
+        rrset_->addRdata(rdata::createRdata(rrset_->getType(),
+                                            rrset_->getClass(), "192.0.2.2"));
+
+        // And its RRSIG.  Also different from the configured one.
+        rrsigset_.reset(new RRset(qname_, qclass_, RRType::RRSIG(),
+                                  rrttl_));
+        rrsigset_->addRdata(rdata::createRdata(rrsigset_->getType(),
+                                               rrsigset_->getClass(),
+                                               "A 5 3 0 20000101000000 "
+                                               "20000201000000 0 example.org. "
+                                               "FAKEFAKEFAKE"));
     }
+
     /*
      * We initialize the client from a function, so we can call it multiple
      * times per test.
      */
     void createClient() {
-        current_accessor_ = new MockAccessor();
-        client_.reset(new DatabaseClient(shared_ptr<DatabaseAccessor>(
-             current_accessor_)));
+        current_accessor_ = new ACCESSOR_TYPE();
+        is_mock_ = (dynamic_cast<MockAccessor*>(current_accessor_) != NULL);
+        client_.reset(new DatabaseClient(qclass_,
+                                         shared_ptr<ACCESSOR_TYPE>(
+                                             current_accessor_)));
     }
-    // Will be deleted by client_, just keep the current value for comparison.
-    MockAccessor* current_accessor_;
-    shared_ptr<DatabaseClient> client_;
-    const std::string database_name_;
 
     /**
      * Check the zone finder is a valid one and references the zone ID and
@@ -526,89 +694,195 @@ public:
             dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
         ASSERT_NE(shared_ptr<DatabaseClient::Finder>(), finder) <<
             "Wrong type of finder";
-        EXPECT_EQ(42, finder->zone_id());
+        if (is_mock_) {
+            EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+        }
         EXPECT_EQ(current_accessor_, &finder->getAccessor());
     }
 
     shared_ptr<DatabaseClient::Finder> getFinder() {
-        DataSourceClient::FindResult zone(
-            client_->findZone(Name("example.org")));
+        DataSourceClient::FindResult zone(client_->findZone(zname_));
         EXPECT_EQ(result::SUCCESS, zone.code);
         shared_ptr<DatabaseClient::Finder> finder(
             dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
-        EXPECT_EQ(42, finder->zone_id());
+        if (is_mock_) {
+            EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+        }
 
         return (finder);
     }
 
+    // Helper methods for update tests
+    bool isRollbacked(bool expected = false) const {
+        if (is_mock_) {
+            const MockAccessor& mock_accessor =
+                dynamic_cast<const MockAccessor&>(*update_accessor_);
+            return (mock_accessor.isRollbacked());
+        } else {
+            return (expected);
+        }
+    }
+
+    void checkLastAdded(const char* const expected[]) const {
+        if (is_mock_) {
+            const MockAccessor* mock_accessor =
+                dynamic_cast<const MockAccessor*>(current_accessor_);
+            for (int i = 0; i < DatabaseAccessor::ADD_COLUMN_COUNT; ++i) {
+                EXPECT_EQ(expected[i],
+                          mock_accessor->getLatestClone()->getLastAdded()[i]);
+            }
+        }
+    }
+
+    void setUpdateAccessor() {
+        if (is_mock_) {
+            const MockAccessor* mock_accessor =
+                dynamic_cast<const MockAccessor*>(current_accessor_);
+            update_accessor_ = mock_accessor->getLatestClone();
+        }
+    }
+
+    // Some tests only work for MockAccessor.  We remember whether our accessor
+    // is of that type.
+    bool is_mock_;
+
+    // Will be deleted by client_, just keep the current value for comparison.
+    ACCESSOR_TYPE* current_accessor_;
+    shared_ptr<DatabaseClient> client_;
+    const std::string database_name_;
+
+    // The zone finder of the test zone commonly used in various tests.
+    shared_ptr<DatabaseClient::Finder> finder_;
+
+    // Some shortcut variables for commonly used test parameters
+    const Name zname_; // the zone name stored in the test data source
+    const Name qname_; // commonly used name to be found
+    const RRClass qclass_;      // commonly used RR class used with qname
+    const RRType qtype_;        // commonly used RR type used with qname
+    const RRTTL rrttl_;         // commonly used RR TTL
+    RRsetPtr rrset_;            // for adding/deleting an RRset
+    RRsetPtr rrsigset_;         // for adding/deleting an RRset
+
+    // update related objects to be tested
+    ZoneUpdaterPtr updater_;
+    shared_ptr<const DatabaseAccessor> update_accessor_;
+
+    // placeholders
+    const std::vector<std::string> empty_rdatas_; // for NXRRSET/NXDOMAIN
     std::vector<std::string> expected_rdatas_;
     std::vector<std::string> expected_sig_rdatas_;
 };
 
-TEST_F(DatabaseClientTest, zoneNotFound) {
-    DataSourceClient::FindResult zone(client_->findZone(Name("example.com")));
+class TestSQLite3Accessor : public SQLite3Accessor {
+public:
+    TestSQLite3Accessor() : SQLite3Accessor(
+        TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied",
+        RRClass::IN())
+    {
+        startUpdateZone("example.org.", true);
+        string columns[ADD_COLUMN_COUNT];
+        for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
+            columns[ADD_NAME] = TEST_RECORDS[i][0];
+            columns[ADD_REV_NAME] = Name(columns[ADD_NAME]).reverse().toText();
+            columns[ADD_TYPE] = TEST_RECORDS[i][1];
+            columns[ADD_TTL] = TEST_RECORDS[i][2];
+            columns[ADD_SIGTYPE] = TEST_RECORDS[i][3];
+            columns[ADD_RDATA] = TEST_RECORDS[i][4];
+
+            addRecordToZone(columns);
+        }
+        commitUpdateZone();
+    }
+};
+
+// The following two lines instantiate test cases with concrete accessor
+// classes to be tested.
+typedef ::testing::Types<MockAccessor, TestSQLite3Accessor> TestAccessorTypes;
+TYPED_TEST_CASE(DatabaseClientTest, TestAccessorTypes);
+
+// In some cases the entire test fixture is for the mock accessor only.
+// We use the usual TEST_F for them with the corresponding specialized class
+// to make the code simpler.
+typedef DatabaseClientTest<MockAccessor> MockDatabaseClientTest;
+
+TYPED_TEST(DatabaseClientTest, zoneNotFound) {
+    DataSourceClient::FindResult zone(
+        this->client_->findZone(Name("example.com")));
     EXPECT_EQ(result::NOTFOUND, zone.code);
 }
 
-TEST_F(DatabaseClientTest, exactZone) {
-    DataSourceClient::FindResult zone(client_->findZone(Name("example.org")));
+TYPED_TEST(DatabaseClientTest, exactZone) {
+    DataSourceClient::FindResult zone(
+        this->client_->findZone(Name("example.org")));
     EXPECT_EQ(result::SUCCESS, zone.code);
-    checkZoneFinder(zone);
+    this->checkZoneFinder(zone);
 }
 
-TEST_F(DatabaseClientTest, superZone) {
-    DataSourceClient::FindResult zone(client_->findZone(Name(
+TYPED_TEST(DatabaseClientTest, superZone) {
+    DataSourceClient::FindResult zone(this->client_->findZone(Name(
         "sub.example.org")));
     EXPECT_EQ(result::PARTIALMATCH, zone.code);
-    checkZoneFinder(zone);
+    this->checkZoneFinder(zone);
 }
 
-TEST_F(DatabaseClientTest, noAccessorException) {
+// This test doesn't depend on derived accessor class, so we use TEST().
+TEST(GenericDatabaseClientTest, noAccessorException) {
     // We need a dummy variable here; some compiler would regard it a mere
     // declaration instead of an instantiation and make the test fail.
-    EXPECT_THROW(DatabaseClient dummy((shared_ptr<DatabaseAccessor>())),
+    EXPECT_THROW(DatabaseClient dummy(RRClass::IN(),
+                                      shared_ptr<DatabaseAccessor>()),
                  isc::InvalidParameter);
 }
 
 // If the zone doesn't exist, exception is thrown
-TEST_F(DatabaseClientTest, noZoneIterator) {
-    EXPECT_THROW(client_->getIterator(Name("example.com")), DataSourceError);
+TYPED_TEST(DatabaseClientTest, noZoneIterator) {
+    EXPECT_THROW(this->client_->getIterator(Name("example.com")),
+                 DataSourceError);
 }
 
 // If the zone doesn't exist and iteration is not implemented, it still throws
 // the exception it doesn't exist
-TEST_F(DatabaseClientTest, noZoneNotImplementedIterator) {
-    EXPECT_THROW(DatabaseClient(boost::shared_ptr<DatabaseAccessor>(
-        new NopAccessor())).getIterator(Name("example.com")),
+TEST(GenericDatabaseClientTest, noZoneNotImplementedIterator) {
+    EXPECT_THROW(DatabaseClient(RRClass::IN(),
+                                boost::shared_ptr<DatabaseAccessor>(
+                                    new NopAccessor())).getIterator(
+                                        Name("example.com")),
                  DataSourceError);
 }
 
-TEST_F(DatabaseClientTest, notImplementedIterator) {
-    EXPECT_THROW(DatabaseClient(shared_ptr<DatabaseAccessor>(
+TEST(GenericDatabaseClientTest, notImplementedIterator) {
+    EXPECT_THROW(DatabaseClient(RRClass::IN(), shared_ptr<DatabaseAccessor>(
         new NopAccessor())).getIterator(Name("example.org")),
                  isc::NotImplemented);
 }
 
 // Pretend a bug in the connection and pass NULL as the context
-// Should not crash, but gracefully throw
-TEST_F(DatabaseClientTest, nullIteratorContext) {
-    EXPECT_THROW(client_->getIterator(Name("null.example.org")),
+// Should not crash, but gracefully throw.  Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, nullIteratorContext) {
+    EXPECT_THROW(this->client_->getIterator(Name("null.example.org")),
                  isc::Unexpected);
 }
 
-// It doesn't crash or anything if the zone is completely empty
-TEST_F(DatabaseClientTest, emptyIterator) {
-    ZoneIteratorPtr it(client_->getIterator(Name("empty.example.org")));
+// It doesn't crash or anything if the zone is completely empty.
+// Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, emptyIterator) {
+    ZoneIteratorPtr it(this->client_->getIterator(Name("empty.example.org")));
     EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
     // This is past the end, it should throw
     EXPECT_THROW(it->getNextRRset(), isc::Unexpected);
 }
 
-// Iterate trough a zone
-TEST_F(DatabaseClientTest, iterator) {
-    ZoneIteratorPtr it(client_->getIterator(Name("example.org")));
+// Iterate through a zone
+TYPED_TEST(DatabaseClientTest, iterator) {
+    ZoneIteratorPtr it(this->client_->getIterator(Name("example.org")));
     ConstRRsetPtr rrset(it->getNextRRset());
     ASSERT_NE(ConstRRsetPtr(), rrset);
+
+    // The rest of the checks work only for the mock accessor.
+    if (!this->is_mock_) {
+        return;
+    }
+
     EXPECT_EQ(Name("example.org"), rrset->getName());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRType::SOA(), rrset->getType());
@@ -651,10 +925,10 @@ TEST_F(DatabaseClientTest, iterator) {
 }
 
 // This has inconsistent TTL in the set (the rest, like nonsense in
-// the data is handled in rdata itself).
-TEST_F(DatabaseClientTest, badIterator) {
+// the data is handled in rdata itself).  Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, badIterator) {
     // It should not throw, but get the lowest one of them
-    ZoneIteratorPtr it(client_->getIterator(Name("bad.example.org")));
+    ZoneIteratorPtr it(this->client_->getIterator(Name("bad.example.org")));
     EXPECT_EQ(it->getNextRRset()->getTTL(), isc::dns::RRTTL(300));
 }
 
@@ -678,7 +952,7 @@ checkRRset(isc::dns::ConstRRsetPtr rrset,
 }
 
 void
-doFindTest(shared_ptr<DatabaseClient::Finder> finder,
+doFindTest(ZoneFinder& finder,
            const isc::dns::Name& name,
            const isc::dns::RRType& type,
            const isc::dns::RRType& expected_type,
@@ -691,16 +965,16 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
 {
     SCOPED_TRACE("doFindTest " + name.toText() + " " + type.toText());
     ZoneFinder::FindResult result =
-        finder->find(name, type, NULL, options);
+        finder.find(name, type, NULL, options);
     ASSERT_EQ(expected_result, result.code) << name << " " << type;
     if (!expected_rdatas.empty()) {
         checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
-                   name, finder->getClass(), expected_type, expected_ttl,
+                   name, finder.getClass(), expected_type, expected_ttl,
                    expected_rdatas);
 
         if (!expected_sig_rdatas.empty()) {
             checkRRset(result.rrset->getRRsig(), expected_name != Name(".") ?
-                       expected_name : name, finder->getClass(),
+                       expected_name : name, finder.getClass(),
                        isc::dns::RRType::RRSIG(), expected_ttl,
                        expected_sig_rdatas);
         } else {
@@ -711,544 +985,520 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
     }
 }
 
-TEST_F(DatabaseClientTest, find) {
-    shared_ptr<DatabaseClient::Finder> finder(getFinder());
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    doFindTest(finder, isc::dns::Name("www.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_rdatas_.push_back("192.0.2.2");
-    doFindTest(finder, isc::dns::Name("www2.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("2001:db8::1");
-    expected_rdatas_.push_back("2001:db8::2");
-    doFindTest(finder, isc::dns::Name("www.example.org."),
+TYPED_TEST(DatabaseClientTest, find) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(*finder, isc::dns::Name("www.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.2");
+    doFindTest(*finder, isc::dns::Name("www2.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("2001:db8::1");
+    this->expected_rdatas_.push_back("2001:db8::2");
+    doFindTest(*finder, isc::dns::Name("www.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600),
+               this->rrttl_,
                ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
+               this->expected_rdatas_, this->expected_sig_rdatas_);
 
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("www.example.org."),
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("www.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
-               isc::dns::RRTTL(3600),
+               this->rrttl_,
                ZoneFinder::NXRRSET,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("www.example.org.");
-    doFindTest(finder, isc::dns::Name("cname.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::CNAME,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("www.example.org.");
-    doFindTest(finder, isc::dns::Name("cname.example.org."),
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("www.example.org.");
+    doFindTest(*finder, isc::dns::Name("cname.example.org."),
+               this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+               ZoneFinder::CNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("www.example.org.");
+    doFindTest(*finder, isc::dns::Name("cname.example.org."),
                isc::dns::RRType::CNAME(), isc::dns::RRType::CNAME(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("doesnotexist.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::NXDOMAIN,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("signed1.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("2001:db8::1");
-    expected_rdatas_.push_back("2001:db8::2");
-    expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("signed1.example.org."),
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("doesnotexist.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("signed1.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("2001:db8::1");
+    this->expected_rdatas_.push_back("2001:db8::2");
+    this->expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("signed1.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("signed1.example.org."),
-               isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::NXRRSET,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("www.example.org.");
-    expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("signedcname1.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::CNAME,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("signed2.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("2001:db8::2");
-    expected_rdatas_.push_back("2001:db8::1");
-    expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("signed2.example.org."),
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("signed1.example.org."),
+               isc::dns::RRType::TXT(), isc::dns::RRType::TXT(), this->rrttl_,
+               ZoneFinder::NXRRSET, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("www.example.org.");
+    this->expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("signedcname1.example.org."),
+               this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+               ZoneFinder::CNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("signed2.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("2001:db8::2");
+    this->expected_rdatas_.push_back("2001:db8::1");
+    this->expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("signed2.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("signed2.example.org."),
-               isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::NXRRSET,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("www.example.org.");
-    expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("signedcname2.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::CNAME,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("acnamesig1.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("acnamesig2.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("acnamesig3.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_rdatas_.push_back("192.0.2.2");
-    doFindTest(finder, isc::dns::Name("ttldiff1.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(360),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_rdatas_.push_back("192.0.2.2");
-    doFindTest(finder, isc::dns::Name("ttldiff2.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(360),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("signed2.example.org."),
+               isc::dns::RRType::TXT(), isc::dns::RRType::TXT(), this->rrttl_,
+               ZoneFinder::NXRRSET, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("www.example.org.");
+    this->expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("signedcname2.example.org."),
+               this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+               ZoneFinder::CNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("acnamesig1.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("acnamesig2.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("acnamesig3.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.2");
+    doFindTest(*finder, isc::dns::Name("ttldiff1.example.org."),
+               this->qtype_, this->qtype_, isc::dns::RRTTL(360),
+               ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.2");
+    doFindTest(*finder, isc::dns::Name("ttldiff2.example.org."),
+               this->qtype_, this->qtype_, isc::dns::RRTTL(360),
+               ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
 
     EXPECT_THROW(finder->find(isc::dns::Name("badcname1.example.org."),
-                                              isc::dns::RRType::A(),
+                                              this->qtype_,
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
     EXPECT_THROW(finder->find(isc::dns::Name("badcname2.example.org."),
-                                              isc::dns::RRType::A(),
+                                              this->qtype_,
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
     EXPECT_THROW(finder->find(isc::dns::Name("badcname3.example.org."),
-                                              isc::dns::RRType::A(),
+                                              this->qtype_,
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
     EXPECT_THROW(finder->find(isc::dns::Name("badrdata.example.org."),
-                                              isc::dns::RRType::A(),
+                                              this->qtype_,
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
     EXPECT_THROW(finder->find(isc::dns::Name("badtype.example.org."),
-                                              isc::dns::RRType::A(),
+                                              this->qtype_,
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
     EXPECT_THROW(finder->find(isc::dns::Name("badttl.example.org."),
-                                              isc::dns::RRType::A(),
+                                              this->qtype_,
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
     EXPECT_THROW(finder->find(isc::dns::Name("badsig.example.org."),
-                                              isc::dns::RRType::A(),
+                                              this->qtype_,
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
 
     // Trigger the hardcoded exceptions and see if find() has cleaned up
-    EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.search."),
-                                              isc::dns::RRType::A(),
-                                              NULL, ZoneFinder::FIND_DEFAULT),
-                 DataSourceError);
-    EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.search."),
-                                              isc::dns::RRType::A(),
-                                              NULL, ZoneFinder::FIND_DEFAULT),
-                 isc::Exception);
-    EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.search."),
-                                              isc::dns::RRType::A(),
-                                              NULL, ZoneFinder::FIND_DEFAULT),
-                 std::exception);
-    EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.getnext."),
-                                              isc::dns::RRType::A(),
-                                              NULL, ZoneFinder::FIND_DEFAULT),
-                 DataSourceError);
-    EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.getnext."),
-                                              isc::dns::RRType::A(),
-                                              NULL, ZoneFinder::FIND_DEFAULT),
-                 isc::Exception);
-    EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.getnext."),
-                                              isc::dns::RRType::A(),
-                                              NULL, ZoneFinder::FIND_DEFAULT),
-                 std::exception);
+    if (this->is_mock_) {
+        EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.search."),
+                                  this->qtype_,
+                                  NULL, ZoneFinder::FIND_DEFAULT),
+                     DataSourceError);
+        EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.search."),
+                                  this->qtype_,
+                                  NULL, ZoneFinder::FIND_DEFAULT),
+                     isc::Exception);
+        EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.search."),
+                                  this->qtype_,
+                                  NULL, ZoneFinder::FIND_DEFAULT),
+                     std::exception);
+        EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.getnext."),
+                                  this->qtype_,
+                                  NULL, ZoneFinder::FIND_DEFAULT),
+                     DataSourceError);
+        EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.getnext."),
+                                  this->qtype_,
+                                  NULL, ZoneFinder::FIND_DEFAULT),
+                     isc::Exception);
+        EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.getnext."),
+                                  this->qtype_,
+                                  NULL, ZoneFinder::FIND_DEFAULT),
+                     std::exception);
+    }
 
     // This RRSIG has the wrong sigtype field, which should be
     // an error if we decide to keep using that field
     // Right now the field is ignored, so it does not error
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("badsigtype.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600),
-               ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("badsigtype.example.org."),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
 }
 
-TEST_F(DatabaseClientTest, findDelegation) {
-    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, findDelegation) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
 
     // The apex should not be considered delegation point and we can access
     // data
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    doFindTest(finder, isc::dns::Name("example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
-               expected_sig_rdatas_);
-
-    expected_rdatas_.clear();
-    expected_rdatas_.push_back("ns.example.com.");
-    expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(*finder, isc::dns::Name("example.org."),
+               this->qtype_, this->qtype_,
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("ns.example.com.");
+    this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
                                   "12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("example.org."),
+    doFindTest(*finder, isc::dns::Name("example.org."),
                isc::dns::RRType::NS(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
-               expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
 
     // Check when we ask for something below delegation point, we get the NS
     // (Both when the RRset there exists and doesn't)
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    expected_rdatas_.push_back("ns.example.com.");
-    expected_rdatas_.push_back("ns.delegation.example.org.");
-    expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    this->expected_rdatas_.push_back("ns.example.com.");
+    this->expected_rdatas_.push_back("ns.delegation.example.org.");
+    this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
                                   "12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
-               expected_sig_rdatas_,
+    doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
+               this->qtype_, isc::dns::RRType::NS(),
+               this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+               this->expected_sig_rdatas_,
                isc::dns::Name("delegation.example.org."));
-    doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
+    doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
-               expected_sig_rdatas_,
+               this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+               this->expected_sig_rdatas_,
                isc::dns::Name("delegation.example.org."));
-    doFindTest(finder, isc::dns::Name("deep.below.delegation.example.org."),
+    doFindTest(*finder, isc::dns::Name("deep.below.delegation.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
-               expected_sig_rdatas_,
+               this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+               this->expected_sig_rdatas_,
                isc::dns::Name("delegation.example.org."));
 
     // Even when we check directly at the delegation point, we should get
     // the NS
-    doFindTest(finder, isc::dns::Name("delegation.example.org."),
+    doFindTest(*finder, isc::dns::Name("delegation.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
-               expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
 
     // And when we ask direcly for the NS, we should still get delegation
-    doFindTest(finder, isc::dns::Name("delegation.example.org."),
+    doFindTest(*finder, isc::dns::Name("delegation.example.org."),
                isc::dns::RRType::NS(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
-               expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
 
     // Now test delegation. If it is below the delegation point, we should get
     // the DNAME (the one with data under DNAME is invalid zone, but we test
     // the behaviour anyway just to make sure)
-    expected_rdatas_.clear();
-    expected_rdatas_.push_back("dname.example.com.");
-    expected_sig_rdatas_.clear();
-    expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("dname.example.com.");
+    this->expected_sig_rdatas_.clear();
+    this->expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
                                   "20000201000000 12345 example.org. "
                                   "FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("below.dname.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
-               isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
-               expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
-    doFindTest(finder, isc::dns::Name("below.dname.example.org."),
+    doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+               this->qtype_, isc::dns::RRType::DNAME(),
+               this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+    doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
-               isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
-               expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
-    doFindTest(finder, isc::dns::Name("really.deep.below.dname.example.org."),
+               this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+    doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
-               isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
-               expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+               this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
 
     // Asking direcly for DNAME should give SUCCESS
-    doFindTest(finder, isc::dns::Name("dname.example.org."),
+    doFindTest(*finder, isc::dns::Name("dname.example.org."),
                isc::dns::RRType::DNAME(), isc::dns::RRType::DNAME(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
-               expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
 
     // But we don't delegate at DNAME point
-    expected_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.1");
-    expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("dname.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
-               expected_sig_rdatas_);
-    expected_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("dname.example.org."),
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("dname.example.org."),
+               this->qtype_, this->qtype_,
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+    this->expected_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("dname.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
-               expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
 
     // This is broken dname, it contains two targets
     EXPECT_THROW(finder->find(isc::dns::Name("below.baddname.example.org."),
-                              isc::dns::RRType::A(), NULL,
+                              this->qtype_, NULL,
                               ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
 
     // Broken NS - it lives together with something else
     EXPECT_THROW(finder->find(isc::dns::Name("brokenns1.example.org."),
-                              isc::dns::RRType::A(), NULL,
+                              this->qtype_, NULL,
                               ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
     EXPECT_THROW(finder->find(isc::dns::Name("brokenns2.example.org."),
-                              isc::dns::RRType::A(), NULL,
+                              this->qtype_, NULL,
                               ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
 }
 
-TEST_F(DatabaseClientTest, emptyDomain) {
-    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, emptyDomain) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
 
     // This domain doesn't exist, but a subdomain of it does.
     // Therefore we should pretend the domain is there, but contains no RRsets
-    doFindTest(finder, isc::dns::Name("b.example.org."), isc::dns::RRType::A(),
-               isc::dns::RRType::A(), isc::dns::RRTTL(3600),
-               ZoneFinder::NXRRSET, expected_rdatas_, expected_sig_rdatas_);
+    doFindTest(*finder, isc::dns::Name("b.example.org."), this->qtype_,
+               this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
 }
 
-// Glue-OK mode. Just go trough NS delegations down (but not trough
+// Glue-OK mode. Just go through NS delegations down (but not through
 // DNAME) and pretend it is not there.
-TEST_F(DatabaseClientTest, glueOK) {
-    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, glueOK) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
 
-    expected_rdatas_.clear();
-    expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXRRSET,
-               expected_rdatas_, expected_sig_rdatas_,
+               this->rrttl_, ZoneFinder::NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
                isc::dns::Name("ns.delegation.example.org."),
                ZoneFinder::FIND_GLUE_OK);
-    doFindTest(finder, isc::dns::Name("nothere.delegation.example.org."),
+    doFindTest(*finder, isc::dns::Name("nothere.delegation.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
-               expected_rdatas_, expected_sig_rdatas_,
+               this->rrttl_, ZoneFinder::NXDOMAIN,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
                isc::dns::Name("nothere.delegation.example.org."),
                ZoneFinder::FIND_GLUE_OK);
-    expected_rdatas_.push_back("192.0.2.1");
-    doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_,
+    this->expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
+               this->qtype_, this->qtype_,
+               this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
                isc::dns::Name("ns.delegation.example.org."),
                ZoneFinder::FIND_GLUE_OK);
-    expected_rdatas_.clear();
-    expected_rdatas_.push_back("ns.example.com.");
-    expected_rdatas_.push_back("ns.delegation.example.org.");
-    expected_sig_rdatas_.clear();
-    expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 "
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("ns.example.com.");
+    this->expected_rdatas_.push_back("ns.delegation.example.org.");
+    this->expected_sig_rdatas_.clear();
+    this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 "
                                    "20000201000000 12345 example.org. "
                                    "FAKEFAKEFAKE");
     // When we request the NS, it should be SUCCESS, not DELEGATION
     // (different in GLUE_OK)
-    doFindTest(finder, isc::dns::Name("delegation.example.org."),
+    doFindTest(*finder, isc::dns::Name("delegation.example.org."),
                isc::dns::RRType::NS(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_,
+               this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
                isc::dns::Name("delegation.example.org."),
                ZoneFinder::FIND_GLUE_OK);
-    expected_rdatas_.clear();
-    expected_rdatas_.push_back("dname.example.com.");
-    expected_sig_rdatas_.clear();
-    expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("dname.example.com.");
+    this->expected_sig_rdatas_.clear();
+    this->expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
                                    "20000201000000 12345 example.org. "
                                    "FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("below.dname.example.org."),
-               isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
-               isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
-               expected_sig_rdatas_, isc::dns::Name("dname.example.org."),
-               ZoneFinder::FIND_GLUE_OK);
-    doFindTest(finder, isc::dns::Name("below.dname.example.org."),
+    doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+               this->qtype_, isc::dns::RRType::DNAME(),
+               this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_,
+               isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
+    doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
-               isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
-               expected_sig_rdatas_, isc::dns::Name("dname.example.org."),
-               ZoneFinder::FIND_GLUE_OK);
+               this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+               this->expected_sig_rdatas_,
+               isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
 }
 
-TEST_F(DatabaseClientTest, wildcard) {
-    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, wildcard) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
 
     // First, simple wildcard match
-    expected_rdatas_.push_back("192.0.2.5");
-    doFindTest(finder, isc::dns::Name("a.wild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
-               expected_sig_rdatas_);
-    doFindTest(finder, isc::dns::Name("b.a.wild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
-               expected_sig_rdatas_);
-    expected_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("a.wild.example.org"),
+    // Check also that the RRSIG is added from the wildcard (not modified)
+    this->expected_rdatas_.push_back("192.0.2.5");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
+                                         "20000201000000 12345 example.org. "
+                                         "FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
+               this->qtype_, this->qtype_, this->rrttl_,
+               ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+    doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
-               expected_sig_rdatas_);
-    doFindTest(finder, isc::dns::Name("b.a.wild.example.org"),
+               this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+    doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
-               expected_sig_rdatas_);
-
-    // Direct request for thi wildcard
-    expected_rdatas_.push_back("192.0.2.5");
-    doFindTest(finder, isc::dns::Name("*.wild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
-               expected_sig_rdatas_);
-    expected_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("*.wild.example.org"),
+               this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
+
+    // Direct request for this wildcard
+    this->expected_rdatas_.push_back("192.0.2.5");
+    this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
+                                         "20000201000000 12345 example.org. "
+                                         "FAKEFAKEFAKE");
+    doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+    this->expected_rdatas_.clear();
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
-               expected_sig_rdatas_);
+               this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+               this->expected_sig_rdatas_);
     // This is nonsense, but check it doesn't match by some stupid accident
-    doFindTest(finder, isc::dns::Name("a.*.wild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
-               expected_rdatas_, expected_sig_rdatas_);
+    doFindTest(*finder, isc::dns::Name("a.*.wild.example.org"),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
     // These should be canceled, since it is below a domain which exitsts
-    doFindTest(finder, isc::dns::Name("nothing.here.wild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
-               expected_rdatas_, expected_sig_rdatas_);
-    doFindTest(finder, isc::dns::Name("cancel.here.wild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXRRSET,
-               expected_rdatas_, expected_sig_rdatas_);
-    doFindTest(finder,
+    doFindTest(*finder, isc::dns::Name("nothing.here.wild.example.org"),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+    doFindTest(*finder, isc::dns::Name("cancel.here.wild.example.org"),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+    doFindTest(*finder,
                isc::dns::Name("below.cancel.here.wild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
-               expected_rdatas_, expected_sig_rdatas_);
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
     // And this should be just plain empty non-terminal domain, check
     // the wildcard doesn't hurt it
-    doFindTest(finder, isc::dns::Name("here.wild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::A(),
-               isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
-               expected_sig_rdatas_);
+    doFindTest(*finder, isc::dns::Name("here.wild.example.org"),
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
     // Also make sure that the wildcard doesn't hurt the original data
     // below the wildcard
-    expected_rdatas_.push_back("2001:db8::5");
-    doFindTest(finder, isc::dns::Name("cancel.here.wild.example.org"),
+    this->expected_rdatas_.push_back("2001:db8::5");
+    doFindTest(*finder, isc::dns::Name("cancel.here.wild.example.org"),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
-               isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
-               expected_rdatas_, expected_sig_rdatas_);
-    expected_rdatas_.clear();
+               this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_);
+    this->expected_rdatas_.clear();
 
     // How wildcard go together with delegation
-    expected_rdatas_.push_back("ns.example.com.");
-    doFindTest(finder, isc::dns::Name("below.delegatedwild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
-               expected_sig_rdatas_,
+    this->expected_rdatas_.push_back("ns.example.com.");
+    doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
+               this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
+               ZoneFinder::DELEGATION, this->expected_rdatas_,
+               this->expected_sig_rdatas_,
                isc::dns::Name("delegatedwild.example.org"));
     // FIXME: This doesn't look logically OK, GLUE_OK should make it transparent,
     // so the match should either work or be canceled, but return NXDOMAIN
-    doFindTest(finder, isc::dns::Name("below.delegatedwild.example.org"),
-               isc::dns::RRType::A(), isc::dns::RRType::NS(),
-               isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
-               expected_sig_rdatas_,
+    doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
+               this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
+               ZoneFinder::DELEGATION, this->expected_rdatas_,
+               this->expected_sig_rdatas_,
                isc::dns::Name("delegatedwild.example.org"),
                ZoneFinder::FIND_GLUE_OK);
 
-    expected_rdatas_.clear();
-    expected_rdatas_.push_back("192.0.2.5");
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.5");
     // These are direct matches
     const char* positive_names[] = {
         "wild.*.foo.example.org.",
@@ -1256,14 +1506,14 @@ TEST_F(DatabaseClientTest, wildcard) {
         NULL
     };
     for (const char** name(positive_names); *name != NULL; ++ name) {
-        doFindTest(finder, isc::dns::Name(*name), isc::dns::RRType::A(),
-                   isc::dns::RRType::A(), isc::dns::RRTTL(3600),
-                   ZoneFinder::SUCCESS, expected_rdatas_,
-                   expected_sig_rdatas_);
+        doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_,
+                   this->expected_sig_rdatas_);
     }
 
     // These are wildcard matches against empty nonterminal asterisk
-    expected_rdatas_.clear();
+    this->expected_rdatas_.clear();
     const char* negative_names[] = {
         "a.foo.example.org.",
         "*.foo.example.org.",
@@ -1279,20 +1529,617 @@ TEST_F(DatabaseClientTest, wildcard) {
         NULL
     };
     for (const char** name(negative_names); *name != NULL; ++ name) {
-        doFindTest(finder, isc::dns::Name(*name), isc::dns::RRType::A(),
-                   isc::dns::RRType::A(), isc::dns::RRTTL(3600),
-                   ZoneFinder::NXRRSET, expected_rdatas_,
-                   expected_sig_rdatas_);
+        doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+                   this->expected_rdatas_, this->expected_sig_rdatas_);
     }
 }
 
-TEST_F(DatabaseClientTest, getOrigin) {
-    DataSourceClient::FindResult zone(client_->findZone(Name("example.org")));
+TYPED_TEST(DatabaseClientTest, getOrigin) {
+    DataSourceClient::FindResult zone(this->client_->findZone(this->zname_));
     ASSERT_EQ(result::SUCCESS, zone.code);
     shared_ptr<DatabaseClient::Finder> finder(
         dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
-    EXPECT_EQ(42, finder->zone_id());
-    EXPECT_EQ(isc::dns::Name("example.org"), finder->getOrigin());
+    if (this->is_mock_) {
+        EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+    }
+    EXPECT_EQ(this->zname_, finder->getOrigin());
+}
+
+TYPED_TEST(DatabaseClientTest, updaterFinder) {
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    ASSERT_TRUE(this->updater_);
+
+    // If this update isn't replacing the zone, the finder should work
+    // just like the normal find() case.
+    if (this->is_mock_) {
+        DatabaseClient::Finder& finder = dynamic_cast<DatabaseClient::Finder&>(
+            this->updater_->getFinder());
+        EXPECT_EQ(WRITABLE_ZONE_ID, finder.zone_id());
+    }
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(this->updater_->getFinder(), this->qname_,
+               this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->empty_rdatas_);
+
+    // When replacing the zone, the updater's finder shouldn't see anything
+    // in the zone until something is added.
+    this->updater_.reset();
+    this->updater_ = this->client_->getUpdater(this->zname_, true);
+    ASSERT_TRUE(this->updater_);
+    if (this->is_mock_) {
+        DatabaseClient::Finder& finder = dynamic_cast<DatabaseClient::Finder&>(
+            this->updater_->getFinder());
+        EXPECT_EQ(WRITABLE_ZONE_ID, finder.zone_id());
+    }
+    doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+               this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+               this->empty_rdatas_, this->empty_rdatas_);
+}
+
+TYPED_TEST(DatabaseClientTest, flushZone) {
+    // A simple update case: flush the entire zone
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    // Before update, the name exists.
+    EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+                                                this->qtype_).code);
+
+    // start update in the replace mode.  the normal finder should still
+    // be able to see the record, but the updater's finder shouldn't.
+    this->updater_ = this->client_->getUpdater(this->zname_, true);
+    this->setUpdateAccessor();
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              finder->find(this->qname_, this->qtype_).code);
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              this->updater_->getFinder().find(this->qname_,
+                                               this->qtype_).code);
+
+    // commit the update.  now the normal finder shouldn't see it.
+    this->updater_->commit();
+    EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->find(this->qname_,
+                                                 this->qtype_).code);
+
+    // Check rollback wasn't accidentally performed.
+    EXPECT_FALSE(this->isRollbacked());
+}
+
+TYPED_TEST(DatabaseClientTest, updateCancel) {
+    // similar to the previous test, but destruct the updater before commit.
+
+    ZoneFinderPtr finder = this->client_->findZone(this->zname_).zone_finder;
+    EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+                                                this->qtype_).code);
+
+    this->updater_ = this->client_->getUpdater(this->zname_, true);
+    this->setUpdateAccessor();
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              this->updater_->getFinder().find(this->qname_,
+                                               this->qtype_).code);
+    // DB should not have been rolled back yet.
+    EXPECT_FALSE(this->isRollbacked());
+    this->updater_.reset();            // destruct without commit
+
+    // reset() should have triggered rollback (although it doesn't affect
+    // anything to the mock accessor implementation except for the result of
+    // isRollbacked())
+    EXPECT_TRUE(this->isRollbacked(true));
+    EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+                                                this->qtype_).code);
+}
+
+TYPED_TEST(DatabaseClientTest, exceptionFromRollback) {
+    this->updater_ = this->client_->getUpdater(this->zname_, true);
+
+    this->rrset_.reset(new RRset(Name("throw.example.org"), this->qclass_,
+                                 this->qtype_, this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "192.0.2.1"));
+    this->updater_->addRRset(*this->rrset_);
+    // destruct without commit.  The added name will result in an exception
+    // in the MockAccessor's rollback method.  It shouldn't be propagated.
+    EXPECT_NO_THROW(this->updater_.reset());
 }
 
+TYPED_TEST(DatabaseClientTest, duplicateCommit) {
+    // duplicate commit.  should result in exception.
+    this->updater_ = this->client_->getUpdater(this->zname_, true);
+    this->updater_->commit();
+    EXPECT_THROW(this->updater_->commit(), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetToNewZone) {
+    // Add a single RRset to a fresh empty zone
+    this->updater_ = this->client_->getUpdater(this->zname_, true);
+    this->updater_->addRRset(*this->rrset_);
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.2");
+    {
+        SCOPED_TRACE("add RRset");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+
+    // Similar to the previous case, but with RRSIG
+    this->updater_.reset();
+    this->updater_ = this->client_->getUpdater(this->zname_, true);
+    this->updater_->addRRset(*this->rrset_);
+    this->updater_->addRRset(*this->rrsigset_);
+
+    // confirm the expected columns were passed to the accessor (if checkable).
+    const char* const rrsig_added[] = {
+        "www.example.org.", "org.example.www.", "3600", "RRSIG", "A",
+        "A 5 3 0 20000101000000 20000201000000 0 example.org. FAKEFAKEFAKE"
+    };
+    this->checkLastAdded(rrsig_added);
+
+    this->expected_sig_rdatas_.clear();
+    this->expected_sig_rdatas_.push_back(
+        rrsig_added[DatabaseAccessor::ADD_RDATA]);
+    {
+        SCOPED_TRACE("add RRset with RRSIG");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->expected_sig_rdatas_);
+    }
+
+    // Add the non RRSIG RRset again, to see the attempt of adding RRSIG
+    // causes any unexpected effect, in particular, whether the SIGTYPE
+    // field might remain.
+    this->updater_->addRRset(*this->rrset_);
+    const char* const rrset_added[] = {
+        "www.example.org.", "org.example.www.", "3600", "A", "", "192.0.2.2"
+    };
+    this->checkLastAdded(rrset_added);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetToCurrentZone) {
+    // Similar to the previous test, but not replacing the existing data.
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->addRRset(*this->rrset_);
+
+    // We should see both old and new data.
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.2");
+    {
+        SCOPED_TRACE("add RRset");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+    this->updater_->commit();
+    {
+        SCOPED_TRACE("add RRset after commit");
+        doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+                   this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+                   this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, addMultipleRRs) {
+    // Similar to the previous case, but the added RRset contains multiple
+    // RRs.
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "192.0.2.3"));
+    this->updater_->addRRset(*this->rrset_);
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.2");
+    this->expected_rdatas_.push_back("192.0.2.3");
+    {
+        SCOPED_TRACE("add multiple RRs");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetOfLargerTTL) {
+    // Similar to the previous one, but the TTL of the added RRset is larger
+    // than that of the existing record.  The finder should use the smaller
+    // one.
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->rrset_->setTTL(RRTTL(7200));
+    this->updater_->addRRset(*this->rrset_);
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.2");
+    {
+        SCOPED_TRACE("add RRset of larger TTL");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetOfSmallerTTL) {
+    // Similar to the previous one, but the added RRset has a smaller TTL.
+    // The added TTL should be used by the finder.
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->rrset_->setTTL(RRTTL(1800));
+    this->updater_->addRRset(*this->rrset_);
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.2");
+    {
+        SCOPED_TRACE("add RRset of smaller TTL");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, RRTTL(1800), ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, addSameRR) {
+    // Add the same RR as that is already in the data source.
+    // Currently the add interface doesn't try to suppress the duplicate,
+    // neither does the finder.  We may want to revisit it in future versions.
+
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+                                 this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "192.0.2.1"));
+    this->updater_->addRRset(*this->rrset_);
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.1");
+    {
+        SCOPED_TRACE("add same RR");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, addDeviantRR) {
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+    // RR class mismatch.  This should be detected and rejected.
+    this->rrset_.reset(new RRset(this->qname_, RRClass::CH(), RRType::TXT(),
+                                 this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "test text"));
+    EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+
+    // Out-of-zone owner name.  At a higher level this should be rejected,
+    // but it doesn't happen in this interface.
+    this->rrset_.reset(new RRset(Name("example.com"), this->qclass_,
+                                 this->qtype_, this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "192.0.2.100"));
+    this->updater_->addRRset(*this->rrset_);
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.100");
+    {
+        // Note: with the find() implementation being more strict about
+        // zone cuts, this test may fail.  Then the test should be updated.
+        SCOPED_TRACE("add out-of-zone RR");
+        doFindTest(this->updater_->getFinder(), Name("example.com"),
+                   this->qtype_, this->qtype_, this->rrttl_,
+                   ZoneFinder::SUCCESS, this->expected_rdatas_,
+                   this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, addEmptyRRset) {
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+                                 this->rrttl_));
+    EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addAfterCommit) {
+   this->updater_ = this->client_->getUpdater(this->zname_, false);
+   this->updater_->commit();
+   EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetWithRRSIG) {
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->rrset_->addRRsig(*this->rrsigset_);
+    EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, deleteRRset) {
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+                                 this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "192.0.2.1"));
+
+    // Delete one RR from an RRset
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->deleteRRset(*this->rrset_);
+
+    // Delete the only RR of a name
+    this->rrset_.reset(new RRset(Name("cname.example.org"), this->qclass_,
+                          RRType::CNAME(), this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "www.example.org"));
+    this->updater_->deleteRRset(*this->rrset_);
+
+    // The this->updater_ finder should immediately see the deleted results.
+    {
+        SCOPED_TRACE("delete RRset");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+                   this->empty_rdatas_, this->empty_rdatas_);
+        doFindTest(this->updater_->getFinder(), Name("cname.example.org"),
+                   this->qtype_, this->qtype_, this->rrttl_,
+                   ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+                   this->empty_rdatas_);
+    }
+
+    // before committing the change, the original finder should see the
+    // original record.
+    {
+        SCOPED_TRACE("delete RRset before commit");
+        this->expected_rdatas_.push_back("192.0.2.1");
+        doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+                   this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+                   this->empty_rdatas_);
+
+        this->expected_rdatas_.clear();
+        this->expected_rdatas_.push_back("www.example.org.");
+        doFindTest(*finder, Name("cname.example.org"), this->qtype_,
+                   RRType::CNAME(), this->rrttl_, ZoneFinder::CNAME,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+
+    // once committed, the record should be removed from the original finder's
+    // view, too.
+    this->updater_->commit();
+    {
+        SCOPED_TRACE("delete RRset after commit");
+        doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+                   this->rrttl_, ZoneFinder::NXRRSET, this->empty_rdatas_,
+                   this->empty_rdatas_);
+        doFindTest(*finder, Name("cname.example.org"), this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+                   this->empty_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteRRsetToNXDOMAIN) {
+    // similar to the previous case, but it removes the only record of the
+    // given name.  a subsequent find() should result in NXDOMAIN.
+    this->rrset_.reset(new RRset(Name("cname.example.org"), this->qclass_,
+                           RRType::CNAME(), this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "www.example.org"));
+
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->deleteRRset(*this->rrset_);
+    {
+        SCOPED_TRACE("delete RRset to NXDOMAIN");
+        doFindTest(this->updater_->getFinder(), Name("cname.example.org"),
+                   this->qtype_, this->qtype_, this->rrttl_,
+                   ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+                   this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteMultipleRRs) {
+    this->rrset_.reset(new RRset(this->qname_, this->qclass_, RRType::AAAA(),
+                                 this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "2001:db8::1"));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "2001:db8::2"));
+
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->deleteRRset(*this->rrset_);
+
+    {
+        SCOPED_TRACE("delete multiple RRs");
+        doFindTest(this->updater_->getFinder(), this->qname_, RRType::AAAA(),
+                   this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+                   this->empty_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, partialDelete) {
+    this->rrset_.reset(new RRset(this->qname_, this->qclass_, RRType::AAAA(),
+                                 this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "2001:db8::1"));
+    // This does not exist in the test data source:
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "2001:db8::3"));
+
+    // deleteRRset should succeed "silently", and subsequent find() should
+    // find the remaining RR.
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->deleteRRset(*this->rrset_);
+    {
+        SCOPED_TRACE("partial delete");
+        this->expected_rdatas_.push_back("2001:db8::2");
+        doFindTest(this->updater_->getFinder(), this->qname_, RRType::AAAA(),
+                   RRType::AAAA(), this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteNoMatch) {
+    // similar to the previous test, but there's not even a match in the
+    // specified RRset.  Essentially there's no difference in the result.
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->deleteRRset(*this->rrset_);
+    {
+        SCOPED_TRACE("delete no match");
+        this->expected_rdatas_.push_back("192.0.2.1");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+                   this->expected_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteWithDifferentTTL) {
+    // Our delete interface simply ignores TTL (may change in a future version)
+    this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+                                 RRTTL(1800)));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "192.0.2.1"));
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->updater_->deleteRRset(*this->rrset_);
+    {
+        SCOPED_TRACE("delete RRset with a different TTL");
+        doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+                   this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+                   this->empty_rdatas_, this->empty_rdatas_);
+    }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteDeviantRR) {
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+    // RR class mismatch.  This should be detected and rejected.
+    this->rrset_.reset(new RRset(this->qname_, RRClass::CH(), RRType::TXT(),
+                                 this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "test text"));
+    EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+
+    // Out-of-zone owner name.  At a higher level this should be rejected,
+    // but it doesn't happen in this interface.
+    this->rrset_.reset(new RRset(Name("example.com"), this->qclass_,
+                                 this->qtype_, this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "192.0.2.100"));
+    EXPECT_NO_THROW(this->updater_->deleteRRset(*this->rrset_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteAfterCommit) {
+   this->updater_ = this->client_->getUpdater(this->zname_, false);
+   this->updater_->commit();
+   EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, deleteEmptyRRset) {
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+                                 this->rrttl_));
+    EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, deleteRRsetWithRRSIG) {
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+    this->rrset_->addRRsig(*this->rrsigset_);
+    EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, compoundUpdate) {
+    // This test case performs an arbitrary chosen add/delete operations
+    // in a single update transaction.  Essentially there is nothing new to
+    // test here, but there may be some bugs that was overlooked and can
+    // only happen in the compound update scenario, so we test it.
+
+    this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+    // add a new RR to an existing RRset
+    this->updater_->addRRset(*this->rrset_);
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.1");
+    this->expected_rdatas_.push_back("192.0.2.2");
+    doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+               this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->empty_rdatas_);
+
+    // delete an existing RR
+    this->rrset_.reset(new RRset(Name("www.example.org"), this->qclass_,
+                                 this->qtype_, this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "192.0.2.1"));
+    this->updater_->deleteRRset(*this->rrset_);
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.2");
+    doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+               this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->empty_rdatas_);
+
+    // re-add it
+    this->updater_->addRRset(*this->rrset_);
+    this->expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+               this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->empty_rdatas_);
+
+    // add a new RR with a new name
+    const Name newname("newname.example.org");
+    const RRType newtype(RRType::AAAA());
+    doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+               this->rrttl_, ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+               this->empty_rdatas_);
+    this->rrset_.reset(new RRset(newname, this->qclass_, newtype,
+                                 this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "2001:db8::10"));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "2001:db8::11"));
+    this->updater_->addRRset(*this->rrset_);
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("2001:db8::10");
+    this->expected_rdatas_.push_back("2001:db8::11");
+    doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->empty_rdatas_);
+
+    // delete one RR from the previous set
+    this->rrset_.reset(new RRset(newname, this->qclass_, newtype,
+                                 this->rrttl_));
+    this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+                                              this->rrset_->getClass(),
+                                              "2001:db8::11"));
+    this->updater_->deleteRRset(*this->rrset_);
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("2001:db8::10");
+    doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+               this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->empty_rdatas_);
+
+    // Commit the changes, confirm the entire changes applied.
+    this->updater_->commit();
+    shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("192.0.2.2");
+    this->expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(*finder, this->qname_, this->qtype_, this->qtype_, this->rrttl_,
+               ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->empty_rdatas_);
+
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("2001:db8::10");
+    doFindTest(*finder, newname, newtype, newtype, this->rrttl_,
+               ZoneFinder::SUCCESS, this->expected_rdatas_,
+               this->empty_rdatas_);
+}
 }
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index f47032f..a926935 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -197,6 +197,11 @@ TEST_F(InMemoryClientTest, getZoneCount) {
     EXPECT_EQ(2, memory_client.getZoneCount());
 }
 
+TEST_F(InMemoryClientTest, startUpdateZone) {
+    EXPECT_THROW(memory_client.getUpdater(Name("example.org"), false),
+                 isc::NotImplemented);
+}
+
 // A helper callback of masterLoad() used in InMemoryZoneFinderTest.
 void
 setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
@@ -1097,5 +1102,4 @@ TEST_F(InMemoryZoneFinderTest, getFileName) {
     EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_finder_.getFileName());
     EXPECT_TRUE(rootzone.getFileName().empty());
 }
-
 }
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 022f68e..8b423f8 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -409,6 +409,34 @@ TEST_F(SQLite3Create, lockedtest) {
     SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
 }
 
+TEST_F(SQLite3AccessorTest, clone) {
+    shared_ptr<DatabaseAccessor> cloned = accessor->clone();
+    EXPECT_EQ(accessor->getDBName(), cloned->getDBName());
+
+    // The cloned accessor should have a separate connection and search
+    // context, so it should be able to perform search in concurrent with
+    // the original accessor.
+    string columns1[DatabaseAccessor::COLUMN_COUNT];
+    string columns2[DatabaseAccessor::COLUMN_COUNT];
+
+    const std::pair<bool, int> zone_info1(
+        accessor->getZone("example.com."));
+    DatabaseAccessor::IteratorContextPtr iterator1 =
+        accessor->getRecords("foo.example.com.", zone_info1.second);
+    const std::pair<bool, int> zone_info2(
+        accessor->getZone("example.com."));
+    DatabaseAccessor::IteratorContextPtr iterator2 =
+        cloned->getRecords("foo.example.com.", zone_info2.second);
+
+    ASSERT_TRUE(iterator1->getNext(columns1));
+    checkRecordRow(columns1, "CNAME", "3600", "", "cnametest.example.org.",
+                   "");
+
+    ASSERT_TRUE(iterator2->getNext(columns2));
+    checkRecordRow(columns2, "CNAME", "3600", "", "cnametest.example.org.",
+                   "");
+}
+
 //
 // Commonly used data for update tests
 //
diff --git a/src/lib/datasrc/tests/testdata/Makefile.am b/src/lib/datasrc/tests/testdata/Makefile.am
index 6a35fe3..84d49fc 100644
--- a/src/lib/datasrc/tests/testdata/Makefile.am
+++ b/src/lib/datasrc/tests/testdata/Makefile.am
@@ -1 +1,5 @@
 CLEANFILES = *.copied
+BUILT_SOURCES = rwtest.sqlite3.copied
+
+rwtest.sqlite3.copied: $(srcdir)/rwtest.sqlite3
+	cp $(srcdir)/rwtest.sqlite3 $@
diff --git a/src/lib/datasrc/tests/testdata/rwtest.sqlite3 b/src/lib/datasrc/tests/testdata/rwtest.sqlite3
new file mode 100644
index 0000000..ce95a1d
Binary files /dev/null and b/src/lib/datasrc/tests/testdata/rwtest.sqlite3 differ
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index 0dacc5d..3e8b173 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -15,9 +15,11 @@
 #ifndef __ZONE_H
 #define __ZONE_H 1
 
-#include <datasrc/result.h>
+#include <dns/rrset.h>
 #include <dns/rrsetlist.h>
 
+#include <datasrc/result.h>
+
 namespace isc {
 namespace datasrc {
 
@@ -107,7 +109,11 @@ public:
     /// performed on these values to express compound options.
     enum FindOptions {
         FIND_DEFAULT = 0,       ///< The default options
-        FIND_GLUE_OK = 1        ///< Allow search under a zone cut
+        FIND_GLUE_OK = 1,       ///< Allow search under a zone cut
+        FIND_DNSSEC = 2         ///< Require DNSSEC data in the answer
+                                ///< (RRSIG, NSEC, etc.). The implementation
+                                ///< is allowed to include it even if it is
+                                ///< not set.
     };
 
     ///
@@ -201,14 +207,244 @@ public:
     //@}
 };
 
+/// \brief Operator to combine FindOptions
+///
+/// We would need to manually static-cast the options if we put or
+/// between them, which is undesired with bit-flag options. Therefore
+/// we hide the cast here, which is the simplest solution and it still
+/// provides reasonable level of type safety.
+inline ZoneFinder::FindOptions operator |(ZoneFinder::FindOptions a,
+                                          ZoneFinder::FindOptions b)
+{
+    return (static_cast<ZoneFinder::FindOptions>(static_cast<unsigned>(a) |
+                                                 static_cast<unsigned>(b)));
+}
+
 /// \brief A pointer-like type pointing to a \c ZoneFinder object.
 typedef boost::shared_ptr<ZoneFinder> ZoneFinderPtr;
 
 /// \brief A pointer-like type pointing to a \c ZoneFinder object.
 typedef boost::shared_ptr<const ZoneFinder> ConstZoneFinderPtr;
 
-}
-}
+/// The base class to make updates to a single zone.
+///
+/// On construction, each derived class object will start a "transaction"
+/// for making updates to a specific zone (this means a constructor of
+/// a derived class would normally take parameters to identify the zone
+/// to be updated).  The underlying realization of a "transaction" will differ
+/// for different derived classes; if it uses a general purpose database
+/// as a backend, it will involve performing some form of "begin transaction"
+/// statement for the database.
+///
+/// Updates (adding or deleting RRs) are made via \c addRRset() and
+/// \c deleteRRset() methods.  Until the \c commit() method is called the
+/// changes are local to the updater object.  For example, they won't be
+/// visible via a \c ZoneFinder object except the one returned by the
+/// updater's own \c getFinder() method.  The \c commit() completes the
+/// transaction and makes the changes visible to others.
+///
+/// This class does not provide an explicit "rollback" interface.  If
+/// something wrong or unexpected happens during the updates and the
+/// caller wants to cancel the intermediate updates, the caller should
+/// simply destruct the updater object without calling \c commit().
+/// The destructor is supposed to perform the "rollback" operation,
+/// depending on the internal details of the derived class.
+///
+/// \note This initial implementation provides a quite simple interface of
+/// adding and deleting RRs (see the description of the related methods).
+/// It may be revisited as we gain more experiences.
+class ZoneUpdater {
+protected:
+    /// The default constructor.
+    ///
+    /// This is intentionally defined as protected to ensure that this base
+    /// class is never instantiated directly.
+    ZoneUpdater() {}
+
+public:
+    /// The destructor
+    ///
+    /// Each derived class implementation must ensure that if \c commit()
+    /// has not been performed by the time of the call to it, then it
+    /// "rollbacks" the updates made via the updater so far.
+    virtual ~ZoneUpdater() {}
+
+    /// Return a finder for the zone being updated.
+    ///
+    /// The returned finder provides the functionalities of \c ZoneFinder
+    /// for the zone as updates are made via the updater.  That is, before
+    /// making any update, the finder will be able to find all RRsets that
+    /// exist in the zone at the time the updater is created.  If RRsets
+    /// are added or deleted via \c addRRset() or \c deleteRRset(),
+    /// this finder will find the added ones or miss the deleted ones
+    /// respectively.
+    ///
+    /// The finder returned by this method is effective only while the updates
+    /// are performed, i.e., from the construction of the corresponding
+    /// updater until \c commit() is performed or the updater is destructed
+    /// without commit.  The result of a subsequent call to this method (or
+    /// the use of the result) after that is undefined.
+    ///
+    /// \return A reference to a \c ZoneFinder for the updated zone
+    virtual ZoneFinder& getFinder() = 0;
+
+    /// Add an RRset to a zone via the updater
+    ///
+    /// This may be revisited in a future version, but right now the intended
+    /// behavior of this method is simple: It "naively" adds the specified
+    /// RRset to the zone specified on creation of the updater.
+    /// It performs minimum level of validation on the specified RRset:
+    /// - Whether the RR class is identical to that for the zone to be updated
+    /// - Whether the RRset is not empty, i.e., it has at least one RDATA
+    /// - Whether the RRset is not associated with an RRSIG, i.e.,
+    ///   whether \c getRRsig() on the RRset returns a NULL pointer.
+    ///
+    /// and otherwise does not check any oddity.  For example, it doesn't
+    /// check whether the owner name of the specified RRset is a subdomain
+    /// of the zone's origin; it doesn't care whether or not there is already
+    /// an RRset of the same name and RR type in the zone, and if there is,
+    /// whether any of the existing RRs have duplicate RDATA with the added
+    /// ones.  If these conditions matter the calling application must examine
+    /// the existing data beforehand using the \c ZoneFinder returned by
+    /// \c getFinder().
+    ///
+    /// The validation requirement on the associated RRSIG is temporary.
+    /// If we find it more reasonable and useful to allow adding a pair of
+    /// RRset and its RRSIG RRset as we gain experiences with the interface,
+    /// we may remove this restriction.  Until then we explicitly check it
+    /// to prevent accidental misuse.
+    ///
+    /// Conceptually, on successful call to this method, the zone will have
+    /// the specified RRset, and if there is already an RRset of the same
+    /// name and RR type, these two sets will be "merged".  "Merged" means
+    /// that a subsequent call to \c ZoneFinder::find() for the name and type
+    /// will result in success and the returned RRset will contain all
+    /// previously existing and newly added RDATAs with the TTL being the
+    /// minimum of the two RRsets.  The underlying representation of the
+    /// "merged" RRsets may vary depending on the characteristic of the
+    /// underlying data source.  For example, if it uses a general purpose
+    /// database that stores each RR of the same RRset separately, it may
+    /// simply be a larger sets of RRs based on both the existing and added
+    /// RRsets; the TTLs of the RRs may be different within the database, and
+    /// there may even be duplicate RRs in different database rows.  As long
+    /// as the RRset returned via \c ZoneFinder::find() conforms to the
+    /// concept of "merge", the actual internal representation is up to the
+    /// implementation.
+    ///
+    /// This method must not be called once commit() is performed.  If it
+    /// calls after \c commit() the implementation must throw a
+    /// \c DataSourceError exception.
+    ///
+    /// \todo As noted above we may have to revisit the design details as we
+    /// gain experiences:
+    ///
+    /// - we may want to check (and maybe reject) if there is already a
+    /// duplicate RR (that has the same RDATA).
+    /// - we may want to check (and maybe reject) if there is already an
+    /// RRset of the same name and RR type with different TTL
+    /// - we may even want to check if there is already any RRset of the
+    /// same name and RR type.
+    /// - we may want to add an "options" parameter that can control the
+    /// above points
+    /// - we may want to have this method return a value containing the
+    /// information on whether there's a duplicate, etc.
+    ///
+    /// \exception DataSourceError Called after \c commit(), RRset is invalid
+    /// (see above), internal data source error
+    /// \exception std::bad_alloc Resource allocation failure
+    ///
+    /// \param rrset The RRset to be added
+    virtual void addRRset(const isc::dns::RRset& rrset) = 0;
+
+    /// Delete an RRset from a zone via the updater
+    ///
+    /// Like \c addRRset(), the detailed semantics and behavior of this method
+    /// may have to be revisited in a future version.  The following are
+    /// based on the initial implementation decisions.
+    ///
+    /// On successful completion of this method, it will remove from the zone
+    /// the RRs of the specified owner name and RR type that match one of
+    /// the RDATAs of the specified RRset.  There are several points to be
+    /// noted:
+    /// - Existing RRs that don't match any of the specified RDATAs will
+    ///   remain in the zone.
+    /// - Any RRs of the specified RRset that doesn't exist in the zone will
+    ///   simply be ignored; the implementation of this method is not supposed
+    ///   to check that condition.
+    /// - The TTL of the RRset is ignored; matching is only performed by
+    ///   the owner name, RR type and RDATA
+    ///
+    /// Ignoring the TTL may not look sensible, but it's based on the
+    /// observation that it will result in more intuitive result, especially
+    /// when the underlying data source is a general purpose database.
+    /// See also \c DatabaseAccessor::deleteRecordInZone() on this point.
+    /// It also matches the dynamic update protocol (RFC2136), where TTLs
+    /// are ignored when deleting RRs.
+    ///
+    /// \note Since the TTL is ignored, this method could take the RRset
+    /// to be deleted as a tuple of name, RR type, and a list of RDATAs.
+    /// But in practice, it's quite likely that the caller has the RRset
+    /// in the form of the \c RRset object (e.g., extracted from a dynamic
+    /// update request message), so this interface would rather be more
+    /// convenient.  If it turns out not to be true we can change or extend
+    /// the method signature.
+    ///
+    /// This method performs minimum level of validation on the specified
+    /// RRset:
+    /// - Whether the RR class is identical to that for the zone to be updated
+    /// - Whether the RRset is not empty, i.e., it has at least one RDATA
+    /// - Whether the RRset is not associated with an RRSIG, i.e.,
+    ///   whether \c getRRsig() on the RRset returns a NULL pointer.
+    ///
+    /// This method must not be called once commit() is performed.  If it
+    /// calls after \c commit() the implementation must throw a
+    /// \c DataSourceError exception.
+    ///
+    /// \todo As noted above we may have to revisit the design details as we
+    /// gain experiences:
+    ///
+    /// - we may want to check (and maybe reject) if some or all of the RRs
+    ///   for the specified RRset don't exist in the zone
+    /// - we may want to allow an option to "delete everything" for specified
+    ///   name and/or specified name + RR type.
+    /// - as mentioned above, we may want to include the TTL in matching the
+    ///   deleted RRs
+    /// - we may want to add an "options" parameter that can control the
+    ///   above points
+    /// - we may want to have this method return a value containing the
+    ///   information on whether there's any RRs that are specified but don't
+    ///   exit, the number of actually deleted RRs, etc.
+    ///
+    /// \exception DataSourceError Called after \c commit(), RRset is invalid
+    /// (see above), internal data source error
+    /// \exception std::bad_alloc Resource allocation failure
+    ///
+    /// \param rrset The RRset to be deleted
+    virtual void deleteRRset(const isc::dns::RRset& rrset) = 0;
+
+    /// Commit the updates made in the updater to the zone
+    ///
+    /// This method completes the "transaction" started at the creation
+    /// of the updater.  After successful completion of this method, the
+    /// updates will be visible outside the scope of the updater.
+    /// The actual internal behavior will defer for different derived classes.
+    /// For a derived class with a general purpose database as a backend,
+    /// for example, this method would perform a "commit" statement for the
+    /// database.
+    ///
+    /// This operation can only be performed at most once.  A duplicate call
+    /// must result in a DatasourceError exception.
+    ///
+    /// \exception DataSourceError Duplicate call of the method,
+    /// internal data source error
+    virtual void commit() = 0;
+};
+
+/// \brief A pointer-like type pointing to a \c ZoneUpdater object.
+typedef boost::shared_ptr<ZoneUpdater> ZoneUpdaterPtr;
+
+} // end of datasrc
+} // end of isc
 
 #endif  // __ZONE_H
 
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index fc8e340..59ff030 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -244,7 +244,7 @@ RRSIG::compare(const Rdata& other) const {
 }
 
 const RRType&
-RRSIG::typeCovered() {
+RRSIG::typeCovered() const {
     return (impl_->covered_);
 }
 
diff --git a/src/lib/dns/rdata/generic/rrsig_46.h b/src/lib/dns/rdata/generic/rrsig_46.h
index b8e6306..b32c17f 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.h
+++ b/src/lib/dns/rdata/generic/rrsig_46.h
@@ -40,7 +40,7 @@ public:
     ~RRSIG();
 
     // specialized methods
-    const RRType& typeCovered();
+    const RRType& typeCovered() const;
 private:
     RRSIGImpl* impl_;
 };




More information about the bind10-changes mailing list