BIND 10 master, updated. 87d2a8766e610a0dece7d86268ac9be4122d6d82 [master] Merge branch 'trac1068review2' with fixing conflicts in database_unittest.cc.

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Sep 7 17:22:12 UTC 2011


The branch, master has been updated
       via  87d2a8766e610a0dece7d86268ac9be4122d6d82 (commit)
       via  83a58b817e5c0432d543b66208f502b059fdbe13 (commit)
       via  40126733cc69634035b0cca3a0c90ee3a606ea3b (commit)
       via  bcafb8b98d5df77108a83a6bd8b7746f7c2616d7 (commit)
       via  4ef59f25a452f934408a9ba837cea9b7fab0be48 (commit)
       via  3d069e2745070bc23f14c845cb7d8116d919f0da (commit)
       via  230df584722d08705f2cb3b99940b764b1cb7865 (commit)
       via  fda403b09887a24403c3a90d7ad6c95288f2d641 (commit)
       via  748c3e1aeb833012a19b651af7d98757a8ffc50f (commit)
       via  a0e04c0ad837b4b42caf139573f2a95c86cdac76 (commit)
       via  fcb2409598d37e2078076cf43794ef6c445ac22f (commit)
       via  da8bfe82aa18a67b1a99fa459f48cea89ee2a41a (commit)
       via  7980a6c8e598d34f5f733f5c6c3ca83c0a0f1187 (commit)
       via  9c62a36b0ebf9ff4ef3dad1f4d91195d301348ed (commit)
       via  2ec9338d84714ea670ee888f1edf5a4ad220ea9a (commit)
       via  1d907966f7f0fe7089efe46d8b808d9115f0d167 (commit)
       via  93327a85ea63f7043c49a0af2384a1e274ab1dda (commit)
       via  8fe581570c2ef4f881762f4f22ef4f66c1063491 (commit)
       via  2812fa5cb0c2013ef1696888651390aa71a76b4a (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)
      from  c86612cd4120b9ad3d00978c04ea252e7d501e44 (commit)

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 87d2a8766e610a0dece7d86268ac9be4122d6d82
Merge: c86612c 83a58b8
Author: JINMEI Tatuya <jinmei at isc.org>
Date:   Wed Sep 7 10:21:47 2011 -0700

    [master] Merge branch 'trac1068review2'
    with fixing conflicts in database_unittest.cc.

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

Summary of changes:
 doc/Doxyfile                                       |    4 +-
 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/client_unittest.cc           |    3 +
 src/lib/datasrc/tests/database_unittest.cc         |  994 +++++++++++++++++---
 src/lib/datasrc/tests/memory_datasrc_unittest.cc   |    6 +-
 src/lib/datasrc/tests/sqlite3_accessor_unittest.cc |   28 +
 .../{example2.com.sqlite3 => rwtest.sqlite3}       |  Bin 11264 -> 11264 bytes
 src/lib/datasrc/zone.h                             |  225 +++++-
 src/lib/dns/rdata/generic/rrsig_46.cc              |    2 +-
 src/lib/dns/rdata/generic/rrsig_46.h               |    2 +-
 17 files changed, 1523 insertions(+), 146 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/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/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 cf671bb..6e8a80c 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>
@@ -35,6 +37,10 @@ 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;
+
 /*
  * An accessor with minimum implementation, keeping the original
  * "NotImplemented" methods.
@@ -46,7 +52,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 +64,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 +86,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 +145,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 +280,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 +296,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.
@@ -500,22 +649,44 @@ TEST(DatabaseConnectionTest, getAllRecords) {
 
 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_)));
+        client_.reset(new DatabaseClient(qclass_,
+                                         shared_ptr<DatabaseAccessor>(
+                                             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
@@ -527,21 +698,59 @@ 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());
+        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());
+        EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
 
         return (finder);
     }
 
+    // Helper methods for update tests
+    bool isRollbacked() const {
+        return (update_accessor_->isRollbacked());
+    }
+
+    void checkLastAdded(const char* const expected[]) const {
+        for (int i = 0; i < DatabaseAccessor::ADD_COLUMN_COUNT; ++i) {
+            EXPECT_EQ(expected[i],
+                      current_accessor_->getLatestClone()->getLastAdded()[i]);
+        }
+    }
+
+    void setUpdateAccessor() {
+        update_accessor_ = current_accessor_->getLatestClone();
+    }
+
+    // Will be deleted by client_, just keep the current value for comparison.
+    MockAccessor* 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 MockAccessor> 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_;
 };
@@ -567,7 +776,8 @@ TEST_F(DatabaseClientTest, superZone) {
 TEST_F(DatabaseClientTest, 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);
 }
 
@@ -579,13 +789,15 @@ TEST_F(DatabaseClientTest, noZoneIterator) {
 // 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")),
+    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>(
+    EXPECT_THROW(DatabaseClient(RRClass::IN(), shared_ptr<DatabaseAccessor>(
         new NopAccessor())).getIterator(Name("example.org")),
                  isc::NotImplemented);
 }
@@ -679,7 +891,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,
@@ -692,16 +904,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 {
@@ -718,7 +930,7 @@ TEST_F(DatabaseClientTest, find) {
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
     expected_rdatas_.push_back("192.0.2.1");
-    doFindTest(finder, isc::dns::Name("www.example.org."),
+    doFindTest(*finder, isc::dns::Name("www.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -728,7 +940,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("www2.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -738,7 +950,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("www.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -746,7 +958,7 @@ TEST_F(DatabaseClientTest, find) {
 
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("www.example.org."),
+    doFindTest(*finder, isc::dns::Name("www.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
                isc::dns::RRTTL(3600),
                ZoneFinder::NXRRSET,
@@ -755,7 +967,7 @@ TEST_F(DatabaseClientTest, find) {
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
     expected_rdatas_.push_back("www.example.org.");
-    doFindTest(finder, isc::dns::Name("cname.example.org."),
+    doFindTest(*finder, isc::dns::Name("cname.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
                isc::dns::RRTTL(3600),
                ZoneFinder::CNAME,
@@ -764,7 +976,7 @@ TEST_F(DatabaseClientTest, find) {
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
     expected_rdatas_.push_back("www.example.org.");
-    doFindTest(finder, isc::dns::Name("cname.example.org."),
+    doFindTest(*finder, isc::dns::Name("cname.example.org."),
                isc::dns::RRType::CNAME(), isc::dns::RRType::CNAME(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -772,7 +984,7 @@ TEST_F(DatabaseClientTest, find) {
 
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("doesnotexist.example.org."),
+    doFindTest(*finder, isc::dns::Name("doesnotexist.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::NXDOMAIN,
@@ -783,7 +995,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("signed1.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -794,7 +1006,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("signed1.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -802,7 +1014,7 @@ TEST_F(DatabaseClientTest, find) {
 
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("signed1.example.org."),
+    doFindTest(*finder, isc::dns::Name("signed1.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
                isc::dns::RRTTL(3600),
                ZoneFinder::NXRRSET,
@@ -812,7 +1024,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("signedcname1.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
                isc::dns::RRTTL(3600),
                ZoneFinder::CNAME,
@@ -823,7 +1035,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("signed2.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -834,7 +1046,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("signed2.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -842,7 +1054,7 @@ TEST_F(DatabaseClientTest, find) {
 
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("signed2.example.org."),
+    doFindTest(*finder, isc::dns::Name("signed2.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
                isc::dns::RRTTL(3600),
                ZoneFinder::NXRRSET,
@@ -852,7 +1064,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("signedcname2.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
                isc::dns::RRTTL(3600),
                ZoneFinder::CNAME,
@@ -862,7 +1074,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("acnamesig1.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -872,7 +1084,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("acnamesig2.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -882,7 +1094,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("acnamesig3.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -892,7 +1104,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("ttldiff1.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(360),
                ZoneFinder::SUCCESS,
@@ -902,7 +1114,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("ttldiff2.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(360),
                ZoneFinder::SUCCESS,
@@ -970,7 +1182,7 @@ TEST_F(DatabaseClientTest, find) {
     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."),
+    doFindTest(*finder, isc::dns::Name("badsigtype.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
                isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS,
@@ -985,7 +1197,7 @@ TEST_F(DatabaseClientTest, findDelegation) {
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
     expected_rdatas_.push_back("192.0.2.1");
-    doFindTest(finder, isc::dns::Name("example.org."),
+    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_);
@@ -994,7 +1206,7 @@ TEST_F(DatabaseClientTest, findDelegation) {
     expected_rdatas_.push_back("ns.example.com.");
     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_);
@@ -1007,17 +1219,17 @@ TEST_F(DatabaseClientTest, findDelegation) {
     expected_rdatas_.push_back("ns.delegation.example.org.");
     expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
                                   "12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
+    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_,
                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_,
                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_,
@@ -1025,13 +1237,13 @@ TEST_F(DatabaseClientTest, findDelegation) {
 
     // 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_);
 
     // 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_);
@@ -1045,21 +1257,21 @@ TEST_F(DatabaseClientTest, findDelegation) {
     expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
                                   "20000201000000 12345 example.org. "
                                   "FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("below.dname.example.org."),
+    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."),
                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."),
+    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."));
 
     // 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_);
@@ -1068,12 +1280,12 @@ TEST_F(DatabaseClientTest, findDelegation) {
     expected_rdatas_.clear();
     expected_rdatas_.push_back("192.0.2.1");
     expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("dname.example.org."),
+    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."),
+    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_);
@@ -1100,7 +1312,7 @@ TEST_F(DatabaseClientTest, emptyDomain) {
 
     // 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(),
+    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_);
 }
@@ -1112,20 +1324,20 @@ TEST_F(DatabaseClientTest, glueOK) {
 
     expected_rdatas_.clear();
     expected_sig_rdatas_.clear();
-    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::AAAA(),
                isc::dns::RRTTL(3600), ZoneFinder::NXRRSET,
                expected_rdatas_, 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_,
                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."),
+    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_,
@@ -1140,7 +1352,7 @@ TEST_F(DatabaseClientTest, glueOK) {
                                    "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_,
@@ -1152,12 +1364,12 @@ TEST_F(DatabaseClientTest, glueOK) {
     expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
                                    "20000201000000 12345 example.org. "
                                    "FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("below.dname.example.org."),
+    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."),
                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."),
@@ -1172,21 +1384,21 @@ TEST_F(DatabaseClientTest, wildcard) {
     expected_rdatas_.push_back("192.0.2.5");
     expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 "
                                    "12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("a.wild.example.org"),
+    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"),
+    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();
     expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("a.wild.example.org"),
+    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"),
+    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_);
@@ -1195,45 +1407,45 @@ TEST_F(DatabaseClientTest, wildcard) {
     expected_rdatas_.push_back("192.0.2.5");
     expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 "
                                    "12345 example.org. FAKEFAKEFAKE");
-    doFindTest(finder, isc::dns::Name("*.wild.example.org"),
+    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();
     expected_sig_rdatas_.clear();
-    doFindTest(finder, isc::dns::Name("*.wild.example.org"),
+    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 is nonsense, but check it doesn't match by some stupid accident
-    doFindTest(finder, isc::dns::Name("a.*.wild.example.org"),
+    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_);
     // These should be canceled, since it is below a domain which exitsts
-    doFindTest(finder, isc::dns::Name("nothing.here.wild.example.org"),
+    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"),
+    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("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_);
     // 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"),
+    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_);
     // 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"),
+    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_);
@@ -1241,14 +1453,14 @@ TEST_F(DatabaseClientTest, wildcard) {
 
     // How wildcard go together with delegation
     expected_rdatas_.push_back("ns.example.com.");
-    doFindTest(finder, isc::dns::Name("below.delegatedwild.example.org"),
+    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_,
                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"),
+    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_,
@@ -1264,7 +1476,7 @@ TEST_F(DatabaseClientTest, wildcard) {
         NULL
     };
     for (const char** name(positive_names); *name != NULL; ++ name) {
-        doFindTest(finder, isc::dns::Name(*name), isc::dns::RRType::A(),
+        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_);
@@ -1287,7 +1499,7 @@ TEST_F(DatabaseClientTest, wildcard) {
         NULL
     };
     for (const char** name(negative_names); *name != NULL; ++ name) {
-        doFindTest(finder, isc::dns::Name(*name), isc::dns::RRType::A(),
+        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_);
@@ -1299,8 +1511,546 @@ TEST_F(DatabaseClientTest, getOrigin) {
     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(READONLY_ZONE_ID, finder->zone_id());
     EXPECT_EQ(isc::dns::Name("example.org"), finder->getOrigin());
 }
 
+TEST_F(DatabaseClientTest, updaterFinder) {
+    updater_ = client_->getUpdater(zname_, false);
+    ASSERT_TRUE(updater_);
+
+    // If this update isn't replacing the zone, the finder should work
+    // just like the normal find() case.
+    EXPECT_EQ(WRITABLE_ZONE_ID, dynamic_cast<DatabaseClient::Finder&>(
+                  updater_->getFinder()).zone_id());
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(updater_->getFinder(), qname_,
+               qtype_, qtype_, rrttl_, ZoneFinder::SUCCESS,
+               expected_rdatas_, empty_rdatas_);
+
+    // When replacing the zone, the updater's finder shouldn't see anything
+    // in the zone until something is added.
+    updater_.reset();
+    updater_ = client_->getUpdater(zname_, true);
+    ASSERT_TRUE(updater_);
+    EXPECT_EQ(WRITABLE_ZONE_ID, dynamic_cast<DatabaseClient::Finder&>(
+                  updater_->getFinder()).zone_id());
+    doFindTest(updater_->getFinder(), qname_, qtype_, qtype_, rrttl_,
+               ZoneFinder::NXDOMAIN, empty_rdatas_, empty_rdatas_);
+}
+
+TEST_F(DatabaseClientTest, flushZone) {
+    // A simple update case: flush the entire zone
+    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+
+    // Before update, the name exists.
+    EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(qname_, 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.
+    updater_ = client_->getUpdater(zname_, true);
+    setUpdateAccessor();
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              finder->find(qname_, qtype_).code);
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              updater_->getFinder().find(qname_, qtype_).code);
+
+    // commit the update.  now the normal finder shouldn't see it.
+    updater_->commit();
+    EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->find(qname_, qtype_).code);
+
+    // Check rollback wasn't accidentally performed.
+    EXPECT_FALSE(isRollbacked());
+}
+
+TEST_F(DatabaseClientTest, updateCancel) {
+    // similar to the previous test, but destruct the updater before commit.
+
+    ZoneFinderPtr finder = client_->findZone(zname_).zone_finder;
+    EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(qname_, qtype_).code);
+
+    updater_ = client_->getUpdater(zname_, true);
+    setUpdateAccessor();
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              updater_->getFinder().find(qname_, qtype_).code);
+    // DB should not have been rolled back yet.
+    EXPECT_FALSE(isRollbacked());
+    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(isRollbacked());
+    EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(qname_, qtype_).code);
+}
+
+TEST_F(DatabaseClientTest, exceptionFromRollback) {
+    updater_ = client_->getUpdater(zname_, true);
+
+    rrset_.reset(new RRset(Name("throw.example.org"), qclass_, qtype_,
+                           rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(),
+                                        rrset_->getClass(), "192.0.2.1"));
+    updater_->addRRset(*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(updater_.reset());
+}
+
+TEST_F(DatabaseClientTest, duplicateCommit) {
+    // duplicate commit.  should result in exception.
+    updater_ = client_->getUpdater(zname_, true);
+    updater_->commit();
+    EXPECT_THROW(updater_->commit(), DataSourceError);
+}
+
+TEST_F(DatabaseClientTest, addRRsetToNewZone) {
+    // Add a single RRset to a fresh empty zone
+    updater_ = client_->getUpdater(zname_, true);
+    updater_->addRRset(*rrset_);
+
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.2");
+    {
+        SCOPED_TRACE("add RRset");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+
+    // Similar to the previous case, but with RRSIG
+    updater_.reset();
+    updater_ = client_->getUpdater(zname_, true);
+    updater_->addRRset(*rrset_);
+    updater_->addRRset(*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"
+    };
+    checkLastAdded(rrsig_added);
+
+    expected_sig_rdatas_.clear();
+    expected_sig_rdatas_.push_back(rrsig_added[DatabaseAccessor::ADD_RDATA]);
+    {
+        SCOPED_TRACE("add RRset with RRSIG");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, 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.
+    updater_->addRRset(*rrset_);
+    const char* const rrset_added[] = {
+        "www.example.org.", "org.example.www.", "3600", "A", "", "192.0.2.2"
+    };
+    checkLastAdded(rrset_added);
+}
+
+TEST_F(DatabaseClientTest, addRRsetToCurrentZone) {
+    // Similar to the previous test, but not replacing the existing data.
+    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+
+    updater_ = client_->getUpdater(zname_, false);
+    updater_->addRRset(*rrset_);
+
+    // We should see both old and new data.
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.1");
+    expected_rdatas_.push_back("192.0.2.2");
+    {
+        SCOPED_TRACE("add RRset");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+    updater_->commit();
+    {
+        SCOPED_TRACE("add RRset after commit");
+        doFindTest(*finder, qname_, qtype_, qtype_,
+                   rrttl_, ZoneFinder::SUCCESS, expected_rdatas_,
+                   empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, addMultipleRRs) {
+    // Similar to the previous case, but the added RRset contains multiple
+    // RRs.
+    updater_ = client_->getUpdater(zname_, false);
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "192.0.2.3"));
+    updater_->addRRset(*rrset_);
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.1");
+    expected_rdatas_.push_back("192.0.2.2");
+    expected_rdatas_.push_back("192.0.2.3");
+    {
+        SCOPED_TRACE("add multiple RRs");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(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.
+    updater_ = client_->getUpdater(zname_, false);
+    rrset_->setTTL(RRTTL(7200));
+    updater_->addRRset(*rrset_);
+
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.1");
+    expected_rdatas_.push_back("192.0.2.2");
+    {
+        SCOPED_TRACE("add RRset of larger TTL");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, addRRsetOfSmallerTTL) {
+    // Similar to the previous one, but the added RRset has a smaller TTL.
+    // The added TTL should be used by the finder.
+    updater_ = client_->getUpdater(zname_, false);
+    rrset_->setTTL(RRTTL(1800));
+    updater_->addRRset(*rrset_);
+
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.1");
+    expected_rdatas_.push_back("192.0.2.2");
+    {
+        SCOPED_TRACE("add RRset of smaller TTL");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, RRTTL(1800), ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(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.
+
+    updater_ = client_->getUpdater(zname_, false);
+    rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "192.0.2.1"));
+    updater_->addRRset(*rrset_);
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.1");
+    expected_rdatas_.push_back("192.0.2.1");
+    {
+        SCOPED_TRACE("add same RR");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, addDeviantRR) {
+    updater_ = client_->getUpdater(zname_, false);
+
+    // RR class mismatch.  This should be detected and rejected.
+    rrset_.reset(new RRset(qname_, RRClass::CH(), RRType::TXT(), rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "test text"));
+    EXPECT_THROW(updater_->addRRset(*rrset_), DataSourceError);
+
+    // Out-of-zone owner name.  At a higher level this should be rejected,
+    // but it doesn't happen in this interface.
+    rrset_.reset(new RRset(Name("example.com"), qclass_, qtype_, rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "192.0.2.100"));
+    updater_->addRRset(*rrset_);
+
+    expected_rdatas_.clear();
+    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(updater_->getFinder(), Name("example.com"),
+                   qtype_, qtype_, rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, addEmptyRRset) {
+    updater_ = client_->getUpdater(zname_, false);
+    rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
+    EXPECT_THROW(updater_->addRRset(*rrset_), DataSourceError);
+}
+
+TEST_F(DatabaseClientTest, addRRsetWithRRSIG) {
+    updater_ = client_->getUpdater(zname_, false);
+    rrset_->addRRsig(*rrsigset_);
+    EXPECT_THROW(updater_->addRRset(*rrset_), DataSourceError);
+}
+
+TEST_F(DatabaseClientTest, addAfterCommit) {
+   updater_ = client_->getUpdater(zname_, false);
+   updater_->commit();
+   EXPECT_THROW(updater_->addRRset(*rrset_), DataSourceError);
+}
+
+TEST_F(DatabaseClientTest, deleteRRset) {
+    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+
+    rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "192.0.2.1"));
+
+    // Delete one RR from an RRset
+    updater_ = client_->getUpdater(zname_, false);
+    updater_->deleteRRset(*rrset_);
+
+    // Delete the only RR of a name
+    rrset_.reset(new RRset(Name("cname.example.org"), qclass_,
+                          RRType::CNAME(), rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "www.example.org"));
+    updater_->deleteRRset(*rrset_);
+
+    // The updater_ finder should immediately see the deleted results.
+    {
+        SCOPED_TRACE("delete RRset");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::NXRRSET,
+                   empty_rdatas_, empty_rdatas_);
+        doFindTest(updater_->getFinder(), Name("cname.example.org"),
+                   qtype_, qtype_, rrttl_, ZoneFinder::NXDOMAIN,
+                   empty_rdatas_, empty_rdatas_);
+    }
+
+    // before committing the change, the original finder should see the
+    // original record.
+    {
+        SCOPED_TRACE("delete RRset before commit");
+        expected_rdatas_.push_back("192.0.2.1");
+        doFindTest(*finder, qname_, qtype_, qtype_,
+                   rrttl_, ZoneFinder::SUCCESS, expected_rdatas_,
+                   empty_rdatas_);
+
+        expected_rdatas_.clear();
+        expected_rdatas_.push_back("www.example.org.");
+        doFindTest(*finder, Name("cname.example.org"), qtype_,
+                   RRType::CNAME(), rrttl_, ZoneFinder::CNAME,
+                   expected_rdatas_, empty_rdatas_);
+    }
+
+    // once committed, the record should be removed from the original finder's
+    // view, too.
+    updater_->commit();
+    {
+        SCOPED_TRACE("delete RRset after commit");
+        doFindTest(*finder, qname_, qtype_, qtype_,
+                   rrttl_, ZoneFinder::NXRRSET, empty_rdatas_,
+                   empty_rdatas_);
+        doFindTest(*finder, Name("cname.example.org"),
+                   qtype_, qtype_, rrttl_, ZoneFinder::NXDOMAIN,
+                   empty_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, deleteRRsetToNXDOMAIN) {
+    // similar to the previous case, but it removes the only record of the
+    // given name.  a subsequent find() should result in NXDOMAIN.
+    rrset_.reset(new RRset(Name("cname.example.org"), qclass_,
+                           RRType::CNAME(), rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "www.example.org"));
+
+    updater_ = client_->getUpdater(zname_, false);
+    updater_->deleteRRset(*rrset_);
+    {
+        SCOPED_TRACE("delete RRset to NXDOMAIN");
+        doFindTest(updater_->getFinder(), Name("cname.example.org"),
+                   qtype_, qtype_, rrttl_, ZoneFinder::NXDOMAIN,
+                   empty_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, deleteMultipleRRs) {
+    rrset_.reset(new RRset(qname_, qclass_, RRType::AAAA(), rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "2001:db8::1"));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "2001:db8::2"));
+
+    updater_ = client_->getUpdater(zname_, false);
+    updater_->deleteRRset(*rrset_);
+
+    {
+        SCOPED_TRACE("delete multiple RRs");
+        doFindTest(updater_->getFinder(), qname_, RRType::AAAA(),
+                   qtype_, rrttl_, ZoneFinder::NXRRSET,
+                   empty_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, partialDelete) {
+    rrset_.reset(new RRset(qname_, qclass_, RRType::AAAA(), rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "2001:db8::1"));
+    // This does not exist in the test data source:
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "2001:db8::3"));
+
+    // deleteRRset should succeed "silently", and subsequent find() should
+    // find the remaining RR.
+    updater_ = client_->getUpdater(zname_, false);
+    updater_->deleteRRset(*rrset_);
+    {
+        SCOPED_TRACE("partial delete");
+        expected_rdatas_.push_back("2001:db8::2");
+        doFindTest(updater_->getFinder(), qname_, RRType::AAAA(),
+                   RRType::AAAA(), rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(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.
+    updater_ = client_->getUpdater(zname_, false);
+    updater_->deleteRRset(*rrset_);
+    {
+        SCOPED_TRACE("delete no match");
+        expected_rdatas_.push_back("192.0.2.1");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::SUCCESS,
+                   expected_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, deleteWithDifferentTTL) {
+    // Our delete interface simply ignores TTL (may change in a future version)
+    rrset_.reset(new RRset(qname_, qclass_, qtype_, RRTTL(1800)));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "192.0.2.1"));
+    updater_ = client_->getUpdater(zname_, false);
+    updater_->deleteRRset(*rrset_);
+    {
+        SCOPED_TRACE("delete RRset with a different TTL");
+        doFindTest(updater_->getFinder(), qname_, qtype_,
+                   qtype_, rrttl_, ZoneFinder::NXRRSET,
+                   empty_rdatas_, empty_rdatas_);
+    }
+}
+
+TEST_F(DatabaseClientTest, deleteDeviantRR) {
+    updater_ = client_->getUpdater(zname_, false);
+
+    // RR class mismatch.  This should be detected and rejected.
+    rrset_.reset(new RRset(qname_, RRClass::CH(), RRType::TXT(), rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "test text"));
+    EXPECT_THROW(updater_->deleteRRset(*rrset_), DataSourceError);
+
+    // Out-of-zone owner name.  At a higher level this should be rejected,
+    // but it doesn't happen in this interface.
+    rrset_.reset(new RRset(Name("example.com"), qclass_, qtype_, rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(), rrset_->getClass(),
+                                        "192.0.2.100"));
+    EXPECT_NO_THROW(updater_->deleteRRset(*rrset_));
+}
+
+TEST_F(DatabaseClientTest, deleteAfterCommit) {
+   updater_ = client_->getUpdater(zname_, false);
+   updater_->commit();
+   EXPECT_THROW(updater_->deleteRRset(*rrset_), DataSourceError);
+}
+
+TEST_F(DatabaseClientTest, deleteEmptyRRset) {
+    updater_ = client_->getUpdater(zname_, false);
+    rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
+    EXPECT_THROW(updater_->deleteRRset(*rrset_), DataSourceError);
+}
+
+TEST_F(DatabaseClientTest, deleteRRsetWithRRSIG) {
+    updater_ = client_->getUpdater(zname_, false);
+    rrset_->addRRsig(*rrsigset_);
+    EXPECT_THROW(updater_->deleteRRset(*rrset_), DataSourceError);
+}
+
+TEST_F(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.
+
+    updater_ = client_->getUpdater(zname_, false);
+
+    // add a new RR to an existing RRset
+    updater_->addRRset(*rrset_);
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.1");
+    expected_rdatas_.push_back("192.0.2.2");
+    doFindTest(updater_->getFinder(), qname_, qtype_, qtype_, rrttl_,
+               ZoneFinder::SUCCESS, expected_rdatas_, empty_rdatas_);
+
+    // delete an existing RR
+    rrset_.reset(new RRset(Name("www.example.org"), qclass_, qtype_, rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(),
+                                        rrset_->getClass(), "192.0.2.1"));
+    updater_->deleteRRset(*rrset_);
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.2");
+    doFindTest(updater_->getFinder(), qname_, qtype_, qtype_, rrttl_,
+               ZoneFinder::SUCCESS, expected_rdatas_, empty_rdatas_);
+
+    // re-add it
+    updater_->addRRset(*rrset_);
+    expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(updater_->getFinder(), qname_, qtype_, qtype_, rrttl_,
+               ZoneFinder::SUCCESS, expected_rdatas_, empty_rdatas_);
+
+    // add a new RR with a new name
+    const Name newname("newname.example.org");
+    const RRType newtype(RRType::AAAA());
+    doFindTest(updater_->getFinder(), newname, newtype, newtype, rrttl_,
+               ZoneFinder::NXDOMAIN, empty_rdatas_, empty_rdatas_);
+    rrset_.reset(new RRset(newname, qclass_, newtype, rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(),
+                                        rrset_->getClass(), "2001:db8::10"));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(),
+                                        rrset_->getClass(), "2001:db8::11"));
+    updater_->addRRset(*rrset_);
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("2001:db8::10");
+    expected_rdatas_.push_back("2001:db8::11");
+    doFindTest(updater_->getFinder(), newname, newtype, newtype, rrttl_,
+               ZoneFinder::SUCCESS, expected_rdatas_, empty_rdatas_);
+
+    // delete one RR from the previous set
+    rrset_.reset(new RRset(newname, qclass_, newtype, rrttl_));
+    rrset_->addRdata(rdata::createRdata(rrset_->getType(),
+                                        rrset_->getClass(), "2001:db8::11"));
+    updater_->deleteRRset(*rrset_);
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("2001:db8::10");
+    doFindTest(updater_->getFinder(), newname, newtype, newtype, rrttl_,
+               ZoneFinder::SUCCESS, expected_rdatas_, empty_rdatas_);
+
+    // Commit the changes, confirm the entire changes applied.
+    updater_->commit();
+    shared_ptr<DatabaseClient::Finder> finder(getFinder());
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("192.0.2.2");
+    expected_rdatas_.push_back("192.0.2.1");
+    doFindTest(*finder, qname_, qtype_, qtype_, rrttl_,
+               ZoneFinder::SUCCESS, expected_rdatas_, empty_rdatas_);
+
+    expected_rdatas_.clear();
+    expected_rdatas_.push_back("2001:db8::10");
+    doFindTest(*finder, newname, newtype, newtype, rrttl_,
+               ZoneFinder::SUCCESS, expected_rdatas_, 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/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 989b250..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 {
 
@@ -224,8 +226,225 @@ 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