Browse Source

[1068] Merge branch 'trac1068' into trac1068review2 except the updated tests
for database_unittests (due to too many conflicts).

JINMEI Tatuya 13 years ago
parent
commit
2812fa5cb0

+ 14 - 0
src/lib/datasrc/client.h

@@ -180,6 +180,20 @@ public:
         isc_throw(isc::NotImplemented,
         isc_throw(isc::NotImplemented,
                   "Data source doesn't support iteration");
                   "Data source doesn't support iteration");
     }
     }
+
+    /// TBD
+    ///
+    /// We allow having a read-only data source.  For such data source
+    /// this method will result in a NotImplemented exception.
+    ///
+    /// 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 write the
+    /// definition of this method, even if it simply throws the NotImplemented
+    /// exception.
+    virtual ZoneUpdaterPtr startUpdateZone(const isc::dns::Name& name,
+                                           bool replace)
+        const = 0;
 };
 };
 }
 }
 }
 }

+ 139 - 5
src/lib/datasrc/database.cc

@@ -12,6 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#include <string>
 #include <vector>
 #include <vector>
 
 
 #include <datasrc/database.h>
 #include <datasrc/database.h>
@@ -21,6 +22,8 @@
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
 #include <dns/rdata.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
 
 
@@ -29,17 +32,18 @@
 
 
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 
 
-#include <string>
-
 using namespace isc::dns;
 using namespace isc::dns;
-using std::string;
+using namespace std;
+using boost::shared_ptr;
+using namespace isc::dns::rdata;
 
 
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
 
 
-DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
+DatabaseClient::DatabaseClient(RRClass rrclass,
+                               boost::shared_ptr<DatabaseAccessor>
                                accessor) :
                                accessor) :
-    accessor_(accessor)
+    rrclass_(rrclass), accessor_(accessor)
 {
 {
     if (!accessor_) {
     if (!accessor_) {
         isc_throw(isc::InvalidParameter,
         isc_throw(isc::InvalidParameter,
@@ -604,5 +608,135 @@ DatabaseClient::getIterator(const isc::dns::Name& name) const {
     return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
     return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
 }
 }
 
 
+ZoneUpdaterPtr
+DatabaseClient::startUpdateZone(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 Updater(update_accessor, zone.second,
+                                        name, rrclass_)));
+}
+
+DatabaseClient::Updater::Updater(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 Finder(accessor_, zone_id_, zone_name))
+{
+    logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
+        .arg(zone_name_).arg(zone_class_).arg(db_name_);
+}
+
+DatabaseClient::Updater::~Updater() {
+    if (!committed_) {
+        accessor_->rollbackUpdateZone();
+        logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
+            .arg(zone_name_).arg(zone_class_).arg(db_name_);
+    }
+
+    logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_DESTROYED)
+        .arg(zone_name_).arg(zone_class_).arg(db_name_);
+}
+
+ZoneFinder&
+DatabaseClient::Updater::getFinder() {
+    return (*finder_);
+}
+
+void
+DatabaseClient::Updater::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());
+    }
+
+    RdataIteratorPtr it = rrset.getRdataIterator();
+    if (it->isLast()) {
+        isc_throw(DataSourceError, "An empty RRset is being added for "
+                  << rrset.getName() << "/" << zone_class_ << "/"
+                  << rrset.getType());
+    }
+
+    add_columns_[DatabaseAccessor::ADD_NAME] = rrset.getName().toText();
+    add_columns_[DatabaseAccessor::ADD_REV_NAME] =
+        rrset.getName().reverse().toText();
+    add_columns_[DatabaseAccessor::ADD_TTL] = rrset.getTTL().toText();
+    add_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());
+            add_columns_[DatabaseAccessor::ADD_SIGTYPE] =
+                rrsig_rdata.typeCovered().toText();
+        }
+        add_columns_[DatabaseAccessor::ADD_RDATA] = it->getCurrent().toText();
+        accessor_->addRecordToZone(add_columns_);
+    }
+}
+
+void
+DatabaseClient::Updater::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());
+    }
+
+    RdataIteratorPtr it = rrset.getRdataIterator();
+    if (it->isLast()) {
+        isc_throw(DataSourceError, "An empty RRset is being deleted for "
+                  << rrset.getName() << "/" << zone_class_ << "/"
+                  << rrset.getType());
+    }
+
+    del_params_[DatabaseAccessor::DEL_NAME] = rrset.getName().toText();
+    del_params_[DatabaseAccessor::DEL_TYPE] = rrset.getType().toText();
+    for (; !it->isLast(); it->next()) {
+        del_params_[DatabaseAccessor::DEL_RDATA] =
+            it->getCurrent().toText();
+        accessor_->deleteRecordInZone(del_params_);
+    }
+}
+
+void
+DatabaseClient::Updater::commit() {
+    if (committed_) {
+        isc_throw(DataSourceError, "Duplicate commit attempt for "
+                  << zone_name_ << "/" << zone_class_ << " on "
+                  << db_name_);
+    }
+    accessor_->commitUpdateZone();
+
+    // We release the accessor immediately after commit is completed so that
+    // we don't hold the possible internal resource any longer.
+    accessor_.reset();
+
+    committed_ = true;
+
+    logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_COMMIT)
+        .arg(zone_name_).arg(zone_class_).arg(db_name_);
+}
 }
 }
 }
 }

+ 53 - 2
src/lib/datasrc/database.h

@@ -15,6 +15,14 @@
 #ifndef __DATABASE_DATASRC_H
 #ifndef __DATABASE_DATASRC_H
 #define __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 <datasrc/client.h>
 
 
 #include <dns/name.h>
 #include <dns/name.h>
@@ -109,6 +117,7 @@ public:
      * classes in polymorphic way.
      * classes in polymorphic way.
      */
      */
     virtual ~DatabaseAccessor() { }
     virtual ~DatabaseAccessor() { }
+
     /**
     /**
      * \brief Retrieve a zone identifier
      * \brief Retrieve a zone identifier
      *
      *
@@ -420,6 +429,9 @@ public:
     /// to the method or internal database error.
     /// to the method or internal database error.
     virtual void rollbackUpdateZone() = 0;
     virtual void rollbackUpdateZone() = 0;
 
 
+    /// TBD
+    virtual boost::shared_ptr<DatabaseAccessor> clone() = 0;
+
     /**
     /**
      * \brief Returns a string identifying this dabase backend
      * \brief Returns a string identifying this dabase backend
      *
      *
@@ -459,11 +471,14 @@ public:
      * \exception isc::InvalidParameter if database is NULL. It might throw
      * \exception isc::InvalidParameter if database is NULL. It might throw
      * standard allocation exception as well, but doesn't throw anything else.
      * standard allocation exception as well, but doesn't throw anything else.
      *
      *
+     * \param rrclass The RR class of the zones that this client will handle.
      * \param database The database to use to get data. As the parameter
      * \param database The database to use to get data. As the parameter
      *     suggests, the client takes ownership of the database and will
      *     suggests, the client takes ownership of the database and will
      *     delete it when itself deleted.
      *     delete it when itself deleted.
      */
      */
-    DatabaseClient(boost::shared_ptr<DatabaseAccessor> database);
+    DatabaseClient(isc::dns::RRClass rrclass,
+                   boost::shared_ptr<DatabaseAccessor> database);
+
     /**
     /**
      * \brief Corresponding ZoneFinder implementation
      * \brief Corresponding ZoneFinder implementation
      *
      *
@@ -568,6 +583,7 @@ public:
         boost::shared_ptr<DatabaseAccessor> accessor_;
         boost::shared_ptr<DatabaseAccessor> accessor_;
         const int zone_id_;
         const int zone_id_;
         const isc::dns::Name origin_;
         const isc::dns::Name origin_;
+
         /**
         /**
          * \brief Searches database for an RRset
          * \brief Searches database for an RRset
          *
          *
@@ -614,6 +630,7 @@ public:
                                                      bool want_ns, const
                                                      bool want_ns, const
                                                      isc::dns::Name*
                                                      isc::dns::Name*
                                                      construct_name = NULL);
                                                      construct_name = NULL);
+
         /**
         /**
          * \brief Checks if something lives below this domain.
          * \brief Checks if something lives below this domain.
          *
          *
@@ -625,6 +642,29 @@ public:
         bool hasSubdomains(const std::string& name);
         bool hasSubdomains(const std::string& name);
     };
     };
 
 
+    class Updater : public ZoneUpdater {
+    public:
+        Updater(boost::shared_ptr<DatabaseAccessor> database, int zone_id,
+                const isc::dns::Name& zone_name,
+                const isc::dns::RRClass& zone_class);
+        ~Updater();
+        virtual ZoneFinder& getFinder();
+        virtual void addRRset(const isc::dns::RRset& rrset);
+        virtual void deleteRRset(const isc::dns::RRset& rrset);
+        virtual void commit();
+
+    private:
+        bool committed_;
+        boost::shared_ptr<DatabaseAccessor> accessor_;
+        const int zone_id_;
+        std::string db_name_;
+        const std::string zone_name_;
+        const isc::dns::RRClass zone_class_;
+        boost::scoped_ptr<Finder::Finder> finder_;
+        std::string add_columns_[DatabaseAccessor::ADD_COLUMN_COUNT];
+        std::string del_params_[DatabaseAccessor::DEL_PARAM_COUNT];
+    };
+
     /**
     /**
      * \brief Find a zone in the database
      * \brief Find a zone in the database
      *
      *
@@ -660,7 +700,14 @@ public:
      */
      */
     virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
     virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
 
 
+    /// TBD
+    virtual ZoneUpdaterPtr startUpdateZone(const isc::dns::Name& name,
+                                           bool replace) const;
+
 private:
 private:
+    /// \brief The RR class that this client handles.
+    const isc::dns::RRClass rrclass_;
+
     /// \brief The accessor to our database.
     /// \brief The accessor to our database.
     const boost::shared_ptr<DatabaseAccessor> accessor_;
     const boost::shared_ptr<DatabaseAccessor> accessor_;
 };
 };
@@ -668,4 +715,8 @@ private:
 }
 }
 }
 }
 
 
-#endif
+#endif  // __DATABASE_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 22 - 0
src/lib/datasrc/datasrc_messages.mes

@@ -590,3 +590,25 @@ data source.
 % DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
 % DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
 This indicates a programming error. An internal task of unknown type was
 This indicates a programming error. An internal task of unknown type was
 generated.
 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_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.

+ 6 - 0
src/lib/datasrc/memory_datasrc.cc

@@ -17,6 +17,8 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
 
 
+#include <exceptions/exceptions.h>
+
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
 #include <dns/rrsetlist.h>
 #include <dns/rrsetlist.h>
@@ -793,5 +795,9 @@ InMemoryClient::getIterator(const Name& name) const {
     return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name)));
     return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name)));
 }
 }
 
 
+ZoneUpdaterPtr
+InMemoryClient::startUpdateZone(const isc::dns::Name&, bool) const {
+    isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
+}
 } // end of namespace datasrc
 } // end of namespace datasrc
 } // end of namespace dns
 } // end of namespace dns

+ 5 - 0
src/lib/datasrc/memory_datasrc.h

@@ -266,6 +266,11 @@ public:
     /// \brief Implementation of the getIterator method
     /// \brief Implementation of the getIterator method
     virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
     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 (once merged) exception.
+    virtual ZoneUpdaterPtr startUpdateZone(const isc::dns::Name& name,
+                                           bool replace) const;
+
 private:
 private:
     // TODO: Do we still need the PImpl if nobody should manipulate this class
     // TODO: Do we still need the PImpl if nobody should manipulate this class
     // directly any more (it should be handled through DataSourceClient)?
     // directly any more (it should be handled through DataSourceClient)?

+ 20 - 0
src/lib/datasrc/sqlite3_accessor.cc

@@ -128,6 +128,7 @@ private:
 SQLite3Accessor::SQLite3Accessor(const std::string& filename,
 SQLite3Accessor::SQLite3Accessor(const std::string& filename,
                                  const isc::dns::RRClass& rrclass) :
                                  const isc::dns::RRClass& rrclass) :
     dbparameters_(new SQLite3Parameters),
     dbparameters_(new SQLite3Parameters),
+    filename_(filename),
     class_(rrclass.toText()),
     class_(rrclass.toText()),
     database_name_("sqlite3_" +
     database_name_("sqlite3_" +
                    isc::util::Filename(filename).nameAndExtension())
                    isc::util::Filename(filename).nameAndExtension())
@@ -137,6 +138,25 @@ SQLite3Accessor::SQLite3Accessor(const std::string& filename,
     open(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 {
 namespace {
 
 
 // This is a helper class to initialize a Sqlite3 DB safely.  An object of
 // This is a helper class to initialize a Sqlite3 DB safely.  An object of

+ 16 - 0
src/lib/datasrc/sqlite3_accessor.h

@@ -71,6 +71,17 @@ public:
      */
      */
     SQLite3Accessor(const std::string& filename,
     SQLite3Accessor(const std::string& filename,
                     const isc::dns::RRClass& rrclass);
                     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
      * \brief Destructor
      *
      *
@@ -78,6 +89,9 @@ public:
      */
      */
     ~SQLite3Accessor();
     ~SQLite3Accessor();
 
 
+    /// TBD
+    virtual boost::shared_ptr<DatabaseAccessor> clone();
+
     /**
     /**
      * \brief Look up a zone
      * \brief Look up a zone
      *
      *
@@ -158,6 +172,8 @@ public:
 private:
 private:
     /// \brief Private database data
     /// \brief Private database data
     boost::scoped_ptr<SQLite3Parameters> dbparameters_;
     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
     /// \brief The class for which the queries are done
     const std::string class_;
     const std::string class_;
     /// \brief Opens the database
     /// \brief Opens the database

+ 3 - 0
src/lib/datasrc/tests/client_unittest.cc

@@ -32,6 +32,9 @@ public:
     virtual FindResult findZone(const isc::dns::Name&) const {
     virtual FindResult findZone(const isc::dns::Name&) const {
         return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
         return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
     }
     }
+    virtual ZoneUpdaterPtr startUpdateZone(const isc::dns::Name&, bool) const {
+        return (ZoneUpdaterPtr());
+    }
 };
 };
 
 
 class ClientTest : public ::testing::Test {
 class ClientTest : public ::testing::Test {

+ 14 - 6
src/lib/datasrc/tests/database_unittest.cc

@@ -58,6 +58,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) {
     virtual std::pair<bool, int> startUpdateZone(const std::string&, bool) {
         // return dummy value.  unused anyway.
         // return dummy value.  unused anyway.
         return (pair<bool, int>(true, 0));
         return (pair<bool, int>(true, 0));
@@ -508,8 +512,9 @@ public:
      */
      */
     void createClient() {
     void createClient() {
         current_accessor_ = new MockAccessor();
         current_accessor_ = new MockAccessor();
-        client_.reset(new DatabaseClient(shared_ptr<DatabaseAccessor>(
-             current_accessor_)));
+        client_.reset(new DatabaseClient(RRClass::IN(),
+                                         shared_ptr<DatabaseAccessor>(
+                                             current_accessor_)));
     }
     }
     // Will be deleted by client_, just keep the current value for comparison.
     // Will be deleted by client_, just keep the current value for comparison.
     MockAccessor* current_accessor_;
     MockAccessor* current_accessor_;
@@ -566,7 +571,8 @@ TEST_F(DatabaseClientTest, superZone) {
 TEST_F(DatabaseClientTest, noAccessorException) {
 TEST_F(DatabaseClientTest, noAccessorException) {
     // We need a dummy variable here; some compiler would regard it a mere
     // We need a dummy variable here; some compiler would regard it a mere
     // declaration instead of an instantiation and make the test fail.
     // 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);
                  isc::InvalidParameter);
 }
 }
 
 
@@ -578,13 +584,15 @@ TEST_F(DatabaseClientTest, noZoneIterator) {
 // If the zone doesn't exist and iteration is not implemented, it still throws
 // If the zone doesn't exist and iteration is not implemented, it still throws
 // the exception it doesn't exist
 // the exception it doesn't exist
 TEST_F(DatabaseClientTest, noZoneNotImplementedIterator) {
 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);
                  DataSourceError);
 }
 }
 
 
 TEST_F(DatabaseClientTest, notImplementedIterator) {
 TEST_F(DatabaseClientTest, notImplementedIterator) {
-    EXPECT_THROW(DatabaseClient(shared_ptr<DatabaseAccessor>(
+    EXPECT_THROW(DatabaseClient(RRClass::IN(), shared_ptr<DatabaseAccessor>(
         new NopAccessor())).getIterator(Name("example.org")),
         new NopAccessor())).getIterator(Name("example.org")),
                  isc::NotImplemented);
                  isc::NotImplemented);
 }
 }

+ 5 - 1
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -197,6 +197,11 @@ TEST_F(InMemoryClientTest, getZoneCount) {
     EXPECT_EQ(2, memory_client.getZoneCount());
     EXPECT_EQ(2, memory_client.getZoneCount());
 }
 }
 
 
+TEST_F(InMemoryClientTest, startUpdateZone) {
+    EXPECT_THROW(memory_client.startUpdateZone(Name("example.org"), false),
+                 isc::NotImplemented);
+}
+
 // A helper callback of masterLoad() used in InMemoryZoneFinderTest.
 // A helper callback of masterLoad() used in InMemoryZoneFinderTest.
 void
 void
 setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
 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_EQ(TEST_DATA_DIR "/root.zone", zone_finder_.getFileName());
     EXPECT_TRUE(rootzone.getFileName().empty());
     EXPECT_TRUE(rootzone.getFileName().empty());
 }
 }
-
 }
 }

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

@@ -409,6 +409,34 @@ TEST_F(SQLite3Create, lockedtest) {
     SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
     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
 // Commonly used data for update tests
 //
 //

+ 4 - 0
src/lib/datasrc/tests/testdata/Makefile.am

@@ -1 +1,5 @@
+BUILT_SOURCES = rwtest.sqlite3.copied
 CLEANFILES = *.copied
 CLEANFILES = *.copied
+
+rwtest.sqlite3.copied: rwtest.sqlite3
+	cp $(srcdir)/rwtest.sqlite3 $@

BIN
src/lib/datasrc/tests/testdata/rwtest.sqlite3


+ 48 - 3
src/lib/datasrc/zone.h

@@ -15,9 +15,11 @@
 #ifndef __ZONE_H
 #ifndef __ZONE_H
 #define __ZONE_H 1
 #define __ZONE_H 1
 
 
-#include <datasrc/result.h>
+#include <dns/rrset.h>
 #include <dns/rrsetlist.h>
 #include <dns/rrsetlist.h>
 
 
+#include <datasrc/result.h>
+
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
 
 
@@ -207,8 +209,51 @@ typedef boost::shared_ptr<ZoneFinder> ZoneFinderPtr;
 /// \brief A pointer-like type pointing to a \c ZoneFinder object.
 /// \brief A pointer-like type pointing to a \c ZoneFinder object.
 typedef boost::shared_ptr<const ZoneFinder> ConstZoneFinderPtr;
 typedef boost::shared_ptr<const ZoneFinder> ConstZoneFinderPtr;
 
 
-}
-}
+/// The base class to make updates to a single zone.
+class ZoneUpdater {
+protected:
+    ZoneUpdater() {}
+
+public:
+    virtual ~ZoneUpdater() {}
+
+    /// TBD
+    ///
+    /// The finder is not expected to provide meaningful data once commit()
+    /// was performed.
+    virtual ZoneFinder& getFinder() = 0;
+
+    /// TBD
+    ///
+    /// Notes about unexpected input: class mismatch will be rejected.
+    /// The owner name isn't checked; it's the caller's responsibility.
+    ///
+    /// Open issues: we may eventually want to return result values such as
+    /// there's a duplicate, etc.
+    ///
+    /// The added RRset must not be empty (i.e., it must have at least one
+    /// RDATA).
+    ///
+    /// This method must not be called once commit() is performed.
+    virtual void addRRset(const isc::dns::RRset& rrset) = 0;
+
+    /// TBD
+    ///
+    /// how to handle TTL?
+    virtual void deleteRRset(const isc::dns::RRset& rrset) = 0;
+
+    /// TBD
+    ///
+    /// This operation can only be performed at most once.  A duplicate call
+    /// must result in a DatasourceError exception.
+    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
 #endif  // __ZONE_H
 
 

+ 1 - 1
src/lib/dns/rdata/generic/rrsig_46.cc

@@ -244,7 +244,7 @@ RRSIG::compare(const Rdata& other) const {
 }
 }
 
 
 const RRType&
 const RRType&
-RRSIG::typeCovered() {
+RRSIG::typeCovered() const {
     return (impl_->covered_);
     return (impl_->covered_);
 }
 }
 
 

+ 1 - 1
src/lib/dns/rdata/generic/rrsig_46.h

@@ -40,7 +40,7 @@ public:
     ~RRSIG();
     ~RRSIG();
 
 
     // specialized methods
     // specialized methods
-    const RRType& typeCovered();
+    const RRType& typeCovered() const;
 private:
 private:
     RRSIGImpl* impl_;
     RRSIGImpl* impl_;
 };
 };