]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[master] Merge branch 'trac1068review1'
authorJINMEI Tatuya <jinmei@isc.org>
Thu, 25 Aug 2011 20:14:08 +0000 (13:14 -0700)
committerJINMEI Tatuya <jinmei@isc.org>
Thu, 25 Aug 2011 20:14:08 +0000 (13:14 -0700)
with fixing conflicts:
src/lib/datasrc/database.cc
src/lib/datasrc/database.h
src/lib/datasrc/sqlite3_accessor.cc
src/lib/datasrc/tests/Makefile.am
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc

1  2 
src/lib/datasrc/database.cc
src/lib/datasrc/database.h
src/lib/datasrc/sqlite3_accessor.cc
src/lib/datasrc/sqlite3_accessor.h
src/lib/datasrc/tests/database_unittest.cc
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc

index 347e3abe4787352a3b5ca7a456f802114a3503eb,1a073ed324e764815bf847b8316dffabfb0167b4..d9b54585066201e76ae40bffb0b280c892d242d0
@@@ -49,7 -49,7 +49,7 @@@ DatabaseClient::DatabaseClient(boost::s
  
  DataSourceClient::FindResult
  DatabaseClient::findZone(const Name& name) const {
-     std::pair<bool, int> zone(database_->getZone(name.toText()));
 -    std::pair<bool, int> zone(accessor_->getZone(name));
++    std::pair<bool, int> zone(accessor_->getZone(name.toText()));
      // Try exact first
      if (zone.first) {
          return (FindResult(result::SUCCESS,
      // Start from 1, as 0 is covered above
      for (size_t i(1); i < name.getLabelCount(); ++i) {
          isc::dns::Name superdomain(name.split(i));
-         zone = database_->getZone(superdomain.toText());
 -        zone = accessor_->getZone(superdomain);
++        zone = accessor_->getZone(superdomain.toText());
          if (zone.first) {
              return (FindResult(result::PARTIALMATCH,
-                                ZoneFinderPtr(new Finder(database_,
+                                ZoneFinderPtr(new Finder(accessor_,
                                                          zone.second,
                                                          superdomain))));
          }
@@@ -184,7 -182,7 +183,7 @@@ DatabaseClient::Finder::getRRset(const 
  
      // Request the context
      DatabaseAccessor::IteratorContextPtr
-         context(database_->getRecords(name.toText(), zone_id_));
 -        context(accessor_->getRecords(name, zone_id_));
++        context(accessor_->getRecords(name.toText(), zone_id_));
      // It must not return NULL, that's a bug of the implementation
      if (!context) {
          isc_throw(isc::Unexpected, "Iterator context null at " +
                      isc_throw(DataSourceError, "NS found together with data"
                                " in non-apex domain " + name.toText());
                  }
 -                addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
 +                addOrCreate(result_rrset, *construct_name, getClass(),
 +                            cur_type, cur_ttl,
                              columns[DatabaseAccessor::RDATA_COLUMN],
-                             *database_);
+                             *accessor_);
              } else if (type != NULL && cur_type == *type) {
                  if (result_rrset &&
                      result_rrset->getType() == isc::dns::RRType::CNAME()) {
                      isc_throw(DataSourceError, "NS found together with data"
                                " in non-apex domain " + name.toText());
                  }
 -                addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
 +                addOrCreate(result_rrset, *construct_name, getClass(),
 +                            cur_type, cur_ttl,
                              columns[DatabaseAccessor::RDATA_COLUMN],
-                             *database_);
+                             *accessor_);
              } else if (want_cname && cur_type == isc::dns::RRType::CNAME()) {
                  // There should be no other data, so result_rrset should
                  // be empty.
                      isc_throw(DataSourceError, "CNAME found but it is not "
                                "the only record for " + name.toText());
                  }
 -                addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
 +                addOrCreate(result_rrset, *construct_name, getClass(),
 +                            cur_type, cur_ttl,
                              columns[DatabaseAccessor::RDATA_COLUMN],
-                             *database_);
+                             *accessor_);
              } else if (want_dname && cur_type == isc::dns::RRType::DNAME()) {
                  // There should be max one RR of DNAME present
                  if (result_rrset &&
                      isc_throw(DataSourceError, "DNAME with multiple RRs in " +
                                name.toText());
                  }
 -                addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
 +                addOrCreate(result_rrset, *construct_name, getClass(),
 +                            cur_type, cur_ttl,
                              columns[DatabaseAccessor::RDATA_COLUMN],
-                             *database_);
+                             *accessor_);
              } else if (cur_type == isc::dns::RRType::RRSIG()) {
                  // If we get signatures before we get the actual data, we
                  // can't know which ones to keep and which to drop...
      return (std::pair<bool, isc::dns::RRsetPtr>(records_found, result_rrset));
  }
  
-         context(database_->getRecords(name, zone_id_, true));
 +bool
 +DatabaseClient::Finder::hasSubdomains(const std::string& name) {
 +    // Request the context
 +    DatabaseAccessor::IteratorContextPtr
++        context(accessor_->getRecords(name, zone_id_, true));
 +    // It must not return NULL, that's a bug of the implementation
 +    if (!context) {
 +        isc_throw(isc::Unexpected, "Iterator context null at " + name);
 +    }
 +
 +    std::string columns[DatabaseAccessor::COLUMN_COUNT];
 +    return (context->getNext(columns));
 +}
 +
  ZoneFinder::FindResult
  DatabaseClient::Finder::find(const isc::dns::Name& name,
                               const isc::dns::RRType& type,
      ZoneFinder::Result result_status = SUCCESS;
      std::pair<bool, isc::dns::RRsetPtr> found;
      logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
-         .arg(database_->getDBName()).arg(name).arg(type);
+         .arg(accessor_->getDBName()).arg(name).arg(type);
 +    // In case we are in GLUE_OK mode and start matching wildcards,
 +    // we can't do it under NS, so we store it here to check
 +    isc::dns::RRsetPtr first_ns;
  
      // First, do we have any kind of delegation (NS/DNAME) here?
 -    const Name origin(getOrigin());
 -    const size_t origin_label_count(origin.getLabelCount());
 -    const size_t current_label_count(name.getLabelCount());
 +    Name origin(getOrigin());
 +    size_t origin_label_count(origin.getLabelCount());
 +    // Number of labels in the last known non-empty domain
 +    size_t last_known(origin_label_count);
 +    size_t current_label_count(name.getLabelCount());
      // This is how many labels we remove to get origin
 -    const size_t remove_labels(current_label_count - origin_label_count);
 +    size_t remove_labels(current_label_count - origin_label_count);
  
      // Now go trough all superdomains from origin down
      for (int i(remove_labels); i > 0; --i) {
                     result_rrset->getType() == isc::dns::RRType::CNAME()) {
              result_status = CNAME;
          }
-                     arg(database_->getDBName()).arg(name);
 +
 +        if (!result_rrset && !records_found) {
 +            // Nothing lives here.
 +            // But check if something lives below this
 +            // domain and if so, pretend something is here as well.
 +            if (hasSubdomains(name.toText())) {
 +                LOG_DEBUG(logger, DBG_TRACE_DETAILED,
 +                          DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
-                                 arg(database_->getDBName()).arg(wildcard).
++                    arg(accessor_->getDBName()).arg(name);
 +                records_found = true;
 +            } else {
 +                // It's not empty non-terminal. So check for wildcards.
 +                // We remove labels one by one and look for the wildcard there.
 +                // Go up to first non-empty domain.
 +
 +                remove_labels = current_label_count - last_known;
 +                Name star("*");
 +                for (size_t i(1); i <= remove_labels; ++ i) {
 +                    // Construct the name with *
 +                    // TODO: Once the underlying DatabaseAccessor takes
 +                    // string, do the concatenation on strings, not
 +                    // Names
 +                    Name superdomain(name.split(i));
 +                    Name wildcard(star.concatenate(superdomain));
 +                    // TODO What do we do about DNAME here?
 +                    found = getRRset(wildcard, &type, true, false, true,
 +                                     &name);
 +                    if (found.first) {
 +                        if (first_ns) {
 +                            // In case we are under NS, we don't
 +                            // wildcard-match, but return delegation
 +                            result_rrset = first_ns;
 +                            result_status = DELEGATION;
 +                            records_found = true;
 +                            // We pretend to switch to non-glue_ok mode
 +                            glue_ok = false;
 +                            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
 +                                      DATASRC_DATABASE_WILDCARD_CANCEL_NS).
-                                 arg(database_->getDBName()).arg(wildcard).
++                                arg(accessor_->getDBName()).arg(wildcard).
 +                                arg(first_ns->getName());
 +                        } else if (!hasSubdomains(name.split(i - 1).toText()))
 +                        {
 +                            // Nothing we added as part of the * can exist
 +                            // directly, as we go up only to first existing
 +                            // domain, but it could be empty non-terminal. In
 +                            // that case, we need to cancel the match.
 +                            records_found = true;
 +                            result_rrset = found.second;
 +                            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
 +                                      DATASRC_DATABASE_WILDCARD).
-                                 arg(database_->getDBName()).arg(wildcard).
++                                arg(accessor_->getDBName()).arg(wildcard).
 +                                arg(name);
 +                        } else {
 +                            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
 +                                      DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
-                             arg(database_->getDBName()).arg(wildcard).
++                                arg(accessor_->getDBName()).arg(wildcard).
 +                                arg(name).arg(superdomain);
 +                        }
 +                        break;
 +                    } else if (hasSubdomains(wildcard.toText())) {
 +                        // Empty non-terminal asterisk
 +                        records_found = true;
 +                        LOG_DEBUG(logger, DBG_TRACE_DETAILED,
 +                                  DATASRC_DATABASE_WILDCARD_EMPTY).
++                            arg(accessor_->getDBName()).arg(wildcard).
 +                            arg(name);
 +                        break;
 +                    }
 +                }
 +            }
 +        }
      }
  
      if (!result_rrset) {
@@@ -580,7 -471,7 +579,7 @@@ private
  ZoneIteratorPtr
  DatabaseClient::getIterator(const isc::dns::Name& name) const {
      // Get the zone
-     std::pair<bool, int> zone(database_->getZone(name.toText()));
 -    std::pair<bool, int> zone(accessor_->getZone(name));
++    std::pair<bool, int> zone(accessor_->getZone(name.toText()));
      if (!zone.first) {
          // No such zone, can't continue
          isc_throw(DataSourceError, "Zone " + name.toText() +
index 1120a3e6a59971790004242ac2ae2ca80c42dc0a,efa370663ab8cc4d9afa03a7d985d2fd6a3e7052..d9e23438ee89f5a3fc12a2acbe3e6c0e20563f20
@@@ -65,11 -68,41 +65,43 @@@ public
                              ///< the RRSIG covers. In the current implementation,
                              ///< this field is ignored.
          RDATA_COLUMN = 3,   ///< Full text representation of the record's RDATA
 -        NAME_COLUMN = 4     ///< The domain name of this RR
 +        NAME_COLUMN = 4,    ///< The domain name of this RR
 +        COLUMN_COUNT = 5    ///< The total number of columns, MUST be value of
 +                            ///< the largest other element in this enum plus 1.
      };
  
+     /**
+      * Definitions of the fields to be passed to addRecordToZone().
+      *
+      * Each derived implementation of addRecordToZone() should expect
+      * the "columns" vector to be filled with the values as described in this
+      * enumeration, in this order.
+      */
+     enum AddRecordColumns {
+         ADD_NAME = 0, ///< The owner name of the record (a domain name)
+         ADD_REV_NAME = 1, ///< Reversed name of NAME (used for DNSSEC)
+         ADD_TTL = 2,     ///< The TTL of the record (in numeric form)
+         ADD_TYPE = 3,    ///< The RRType of the record (A/NS/TXT etc.)
+         ADD_SIGTYPE = 4, ///< For RRSIG records, this contains the RRTYPE
+                             ///< the RRSIG covers.
+         ADD_RDATA = 5,    ///< Full text representation of the record's RDATA
+         ADD_COLUMN_COUNT = 6 ///< Number of columns
+     };
+     /**
+      * Definitions of the fields to be passed to deleteRecordInZone().
+      *
+      * Each derived implementation of deleteRecordInZone() should expect
+      * the "params" vector to be filled with the values as described in this
+      * enumeration, in this order.
+      */
+     enum DeleteRecordParams {
+         DEL_NAME = 0, ///< The owner name of the record (a domain name)
+         DEL_TYPE = 1, ///< The RRType of the record (A/NS/TXT etc.)
+         DEL_RDATA = 2, ///< Full text representation of the record's RDATA
+         DEL_PARAM_COUNT = 3 ///< Number of parameters
+     };
      /**
       * \brief Destructor
       *
       * \param id The ID of the zone, returned from getZone().
       * \return Newly created iterator context. Must not be NULL.
       */
 -    virtual IteratorContextPtr getAllRecords(int id) const
 -    {
 -        /*
 -         * This is a compromise. We need to document the parameters in doxygen,
 -         * so they need a name, but then it complains about unused parameter.
 -         * This is a NOP that "uses" the parameters.
 -         */
 -        static_cast<void>(id);
 -
 -        isc_throw(isc::NotImplemented,
 -                  "This database datasource can't be iterated");
 -    }
 +    virtual IteratorContextPtr getAllRecords(int id) const = 0;
  
 -
+     /// Start a transaction for updating a zone.
+     ///
+     /// Each derived class version of this method starts a database
+     /// transaction to make updates to the given name of zone (whose class was
+     /// specified at the construction of the class).
+     ///
+     /// If \c replace is true, any existing records of the zone will be
+     /// deleted on successful completion of updates (after
+     /// \c commitUpdateZone()); if it's false, the existing records will be
+     /// intact unless explicitly deleted by \c deleteRecordInZone().
+     ///
+     /// A single \c DatabaseAccessor instance can perform at most one update
+     /// transaction; a duplicate call to this method before
+     /// \c commitUpdateZone() or \c rollbackUpdateZone() will result in
+     /// a \c DataSourceError exception.  If multiple update attempts need
+     /// to be performed concurrently (and if the underlying database allows
+     /// such operation), separate \c DatabaseAccessor instance must be
+     /// created.
+     ///
+     /// \note The underlying database may not allow concurrent updates to
+     /// the same database instance even if different "connections" (or
+     /// something similar specific to the database implementation) are used
+     /// for different sets of updates.  For example, it doesn't seem to be
+     /// possible for SQLite3 unless different databases are used.  MySQL
+     /// allows concurrent updates to different tables of the same database,
+     /// but a specific operation may block others.  As such, this interface
+     /// doesn't require derived classes to allow concurrent updates with
+     /// multiple \c DatabaseAccessor instances; however, the implementation
+     /// is encouraged to do the best for making it more likely to succeed
+     /// as long as the underlying database system allows concurrent updates.
+     ///
+     /// This method returns a pair of \c bool and \c int.  Its first element
+     /// indicates whether the given name of zone is found.  If it's false,
+     /// the transaction isn't considered to be started; a subsequent call to
+     /// this method with an existing zone name should succeed.  Likewise,
+     /// if a call to this method results in an exception, the transaction
+     /// isn't considered to be started.  Note also that if the zone is not
+     /// found this method doesn't try to create a new one in the database.
+     /// It must have been created by some other means beforehand.
+     ///
+     /// The second element is the internal zone ID used for subsequent
+     /// updates.  Depending on implementation details of the actual derived
+     /// class method, it may be different from the one returned by
+     /// \c getZone(); for example, a specific implementation may use a
+     /// completely new zone ID when \c replace is true.
+     ///
+     /// \exception DataSourceError Duplicate call to this method, or some
+     /// internal database related error.
+     ///
+     /// \param zone_name A string representation of the zone name to be updated
+     /// \param replace Whether to replace the entire zone (see above)
+     ///
+     /// \return A pair of bool and int, indicating whether the specified zone
+     /// exists and (if so) the zone ID to be used for the update, respectively.
+     virtual std::pair<bool, int> startUpdateZone(const std::string& zone_name,
+                                                  bool replace) = 0;
+     /// Add a single record to the zone to be updated.
+     ///
+     /// This method provides a simple interface to insert a new record
+     /// (a database "row") to the zone in the update context started by
+     /// \c startUpdateZone().  The zone to which the record to be added
+     /// is the one specified at the time of the \c startUpdateZone() call.
+     ///
+     /// A successful call to \c startUpdateZone() must have preceded to
+     /// this call; otherwise a \c DataSourceError exception will be thrown.
+     ///
+     /// The row is defined as a vector of strings that has exactly
+     /// ADD_COLUMN_COUNT number of elements.  See AddRecordColumns for
+     /// the semantics of each element.
+     ///
+     /// Derived class methods are not required to check whether the given
+     /// values in \c columns are valid in terms of the expected semantics;
+     /// in general, it's the caller's responsibility.
+     /// For example, TTLs would normally be expected to be a textual
+     /// representation of decimal numbers, but this interface doesn't require
+     /// the implementation to perform this level of validation.  It may check
+     /// the values, however, and in that case if it detects an error it
+     /// should throw a \c DataSourceError exception.
+     ///
+     /// Likewise, derived class methods are not required to detect any
+     /// duplicate record that is already in the zone.
+     ///
+     /// \note The underlying database schema may not have a trivial mapping
+     /// from this style of definition of rows to actual database records.
+     /// It's the implementation's responsibility to implement the mapping
+     /// in the actual derived method.
+     ///
+     /// \exception DataSourceError Invalid call without starting a transaction,
+     /// or other internal database error.
+     ///
+     /// \param columns An array of strings that defines a record to be added
+     /// to the zone.
+     virtual void addRecordToZone(
+         const std::string (&columns)[ADD_COLUMN_COUNT]) = 0;
+     /// Delete a single record from the zone to be updated.
+     ///
+     /// This method provides a simple interface to delete a record
+     /// (a database "row") from the zone in the update context started by
+     /// \c startUpdateZone().  The zone from which the record to be deleted
+     /// is the one specified at the time of the \c startUpdateZone() call.
+     ///
+     /// A successful call to \c startUpdateZone() must have preceded to
+     /// this call; otherwise a \c DataSourceError exception will be thrown.
+     ///
+     /// The record to be deleted is specified by a vector of strings that has
+     /// exactly DEL_PARAM_COUNT number of elements.  See DeleteRecordParams
+     /// for the semantics of each element.
+     ///
+     /// \note In IXFR, TTL may also be specified, but we intentionally
+     /// ignore that in this interface, because it's not guaranteed
+     /// that all records have the same TTL (unlike the RRset
+     /// assumption) and there can even be multiple records for the
+     /// same name, type and rdata with different TTLs.  If we only
+     /// delete one of them, subsequent lookup will still return a
+     /// positive answer, which would be confusing.  It's a higher
+     /// layer's responsibility to check if there is at least one
+     /// record in the database that has the given TTL.
+     ///
+     /// Like \c addRecordToZone, derived class methods are not required to
+     /// validate the semantics of the given parameters or to check if there
+     /// is a record that matches the specified parameter; if there isn't
+     /// it simply ignores the result.
+     ///
+     /// \exception DataSourceError Invalid call without starting a transaction,
+     /// or other internal database error.
+     ///
+     /// \param params An array of strings that defines a record to be deleted
+     /// from the zone.
+     virtual void deleteRecordInZone(
+         const std::string (&params)[DEL_PARAM_COUNT]) = 0;
+     /// Commit updates to the zone.
+     ///
+     /// This method completes a transaction of making updates to the zone
+     /// in the context started by startUpdateZone.
+     ///
+     /// A successful call to \c startUpdateZone() must have preceded to
+     /// this call; otherwise a \c DataSourceError exception will be thrown.
+     /// Once this method successfully completes, the transaction isn't
+     /// considered to exist any more.  So a new transaction can now be
+     /// started.  On the other hand, a duplicate call to this method after
+     /// a successful completion of it is invalid and should result in
+     /// a \c DataSourceError exception.
+     ///
+     /// If some internal database error happens, a \c DataSourceError
+     /// exception must be thrown.  In that case the transaction is still
+     /// considered to be valid; the caller must explicitly rollback it
+     /// or (if it's confident that the error is temporary) try to commit it
+     /// again.
+     ///
+     /// \exception DataSourceError Call without a transaction, duplicate call
+     /// to the method or internal database error.
+     virtual void commitUpdateZone() = 0;
+     /// Rollback updates to the zone made so far.
+     ///
+     /// This method rollbacks a transaction of making updates to the zone
+     /// in the context started by startUpdateZone.  When it succeeds
+     /// (it normally should, but see below), the underlying database should
+     /// be reverted to the point before performing the corresponding
+     /// \c startUpdateZone().
+     ///
+     /// A successful call to \c startUpdateZone() must have preceded to
+     /// this call; otherwise a \c DataSourceError exception will be thrown.
+     /// Once this method successfully completes, the transaction isn't
+     /// considered to exist any more.  So a new transaction can now be
+     /// started.  On the other hand, a duplicate call to this method after
+     /// a successful completion of it is invalid and should result in
+     /// a \c DataSourceError exception.
+     ///
+     /// Normally this method should not fail.  But it may not always be
+     /// possible to guarantee it depending on the characteristics of the
+     /// underlying database system.  So this interface doesn't require the
+     /// actual implementation for the error free property.  But if a specific
+     /// implementation of this method can fail, it is encouraged to document
+     /// when that can happen with its implication.
+     ///
+     /// \exception DataSourceError Call without a transaction, duplicate call
+     /// to the method or internal database error.
+     virtual void rollbackUpdateZone() = 0;
      /**
       * \brief Returns a string identifying this dabase backend
       *
@@@ -395,19 -623,9 +611,20 @@@ public
                                                       type,
                                                       bool want_cname,
                                                       bool want_dname,
 -                                                     bool want_ns);
 +                                                     bool want_ns, const
 +                                                     isc::dns::Name*
 +                                                     construct_name = NULL);
 +        /**
 +         * \brief Checks if something lives below this domain.
 +         *
 +         * This looks if there's any subdomain of the given name. It can be
 +         * used to test if domain is empty non-terminal.
 +         *
 +         * \param name The domain to check.
 +         */
 +        bool hasSubdomains(const std::string& name);
      };
      /**
       * \brief Find a zone in the database
       *
index 3cbaeabdd0607b84a2b15496182c8131d143023a,b87ab2dc0e783eaf8d139119d413214e6de99433..ee81655cf255db6e111d5fe6812832da575cd2cc
  #include <datasrc/data_source.h>
  #include <util/filename.h>
  
- #include <boost/lexical_cast.hpp>
+ using namespace std;
  
 +#define SQLITE_SCHEMA_VERSION 1
 +
  namespace isc {
  namespace datasrc {
  
 -    BEGIN = 2,
 -    COMMIT = 3,
 -    ROLLBACK = 4,
 -    DEL_ZONE_RECORDS = 5,
 -    ADD_RECORD = 6,
 -    DEL_RECORD = 7,
 -    ITERATE = 8,
 -    NUM_STATEMENTS = 9
+ // The following enum and char* array define the SQL statements commonly
+ // used in this implementation.  Corresponding prepared statements (of
+ // type sqlite3_stmt*) are maintained in the statements_ array of the
+ // SQLite3Parameters structure.
+ enum StatementID {
+     ZONE = 0,
+     ANY = 1,
++    ANY_SUB = 2,
++    BEGIN = 3,
++    COMMIT = 4,
++    ROLLBACK = 5,
++    DEL_ZONE_RECORDS = 6,
++    ADD_RECORD = 7,
++    DEL_RECORD = 8,
++    ITERATE = 9,
++    NUM_STATEMENTS = 10
+ };
+ const char* const text_statements[NUM_STATEMENTS] = {
+     // note for ANY and ITERATE: the order of the SELECT values is
+     // specifically chosen to match the enum values in RecordColumns
+     "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2", // ZONE
+     "SELECT rdtype, ttl, sigtype, rdata FROM records "     // ANY
+     "WHERE zone_id=?1 AND name=?2",
++    "SELECT rdtype, ttl, sigtype, rdata " // ANY_SUB
++    "FROM records WHERE zone_id=?1 AND name LIKE (\"%.\" || ?2)",
+     "BEGIN",                    // BEGIN
+     "COMMIT",                   // COMMIT
+     "ROLLBACK",                 // ROLLBACK
+     "DELETE FROM records WHERE zone_id=?1", // DEL_ZONE_RECORDS
+     "INSERT INTO records "      // ADD_RECORD
+     "(zone_id, name, rname, ttl, rdtype, sigtype, rdata) "
+     "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
+     "DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
+     "AND rdtype=?3 AND rdata=?4",
+     "SELECT rdtype, ttl, sigtype, rdata, name FROM records " // ITERATE
+     "WHERE zone_id = ?1 ORDER BY name, rdtype"
+ };
  struct SQLite3Parameters {
      SQLite3Parameters() :
-         db_(NULL), version_(-1),
-         q_zone_(NULL)
-     {}
+         db_(NULL), version_(-1), updating_zone(false), updated_zone_id(-1)
+     {
+         for (int i = 0; i < NUM_STATEMENTS; ++i) {
+             statements_[i] = NULL;
+         }
+     }
++
      sqlite3* db_;
      int version_;
-     sqlite3_stmt* q_zone_;
+     sqlite3_stmt* statements_[NUM_STATEMENTS];
+     bool updating_zone;         // whether or not updating the zone
+     int updated_zone_id;        // valid only when updating_zone is true
+ };
+ // This is a helper class to encapsulate the code logic of executing
+ // a specific SQLite3 statement, ensuring the corresponding prepared
+ // statement is always reset whether the execution is completed successfully
+ // or it results in an exception.
+ // Note that an object of this class is intended to be used for "ephemeral"
+ // statement, which is completed with a single "step" (normally within a
+ // single call to an SQLite3Database method).  In particular, it cannot be
+ // used for "SELECT" variants, which generally expect multiple matching rows.
+ class StatementProcessor {
+ public:
+     // desc will be used on failure in the what() message of the resulting
+     // DataSourceError exception.
+     StatementProcessor(SQLite3Parameters& dbparameters, StatementID stmt_id,
+                        const char* desc) :
+         dbparameters_(dbparameters), stmt_id_(stmt_id), desc_(desc)
+     {
+         sqlite3_clear_bindings(dbparameters_.statements_[stmt_id_]);
+     }
+     ~StatementProcessor() {
+         sqlite3_reset(dbparameters_.statements_[stmt_id_]);
+     }
+     void exec() {
+         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_));
+         }
+     }
+ private:
+     SQLite3Parameters& dbparameters_;
+     const StatementID stmt_id_;
+     const char* const desc_;
  };
  
- SQLite3Database::SQLite3Database(const std::string& filename,
+ SQLite3Accessor::SQLite3Accessor(const std::string& filename,
                                   const isc::dns::RRClass& rrclass) :
      dbparameters_(new SQLite3Parameters),
      class_(rrclass.toText()),
@@@ -181,101 -192,33 +198,95 @@@ prepare(sqlite3* const db, const char* 
      return (prepared);
  }
  
 -void
 -checkAndSetupSchema(Initializer* initializer) {
 -    sqlite3* const db = initializer->params_.db_;
 +// small function to sleep for 0.1 seconds, needed when waiting for
 +// exclusive database locks (which should only occur on startup, and only
 +// when the database has not been created yet)
- void do_sleep() {
++void doSleep() {
 +    struct timespec req;
 +    req.tv_sec = 0;
 +    req.tv_nsec = 100000000;
 +    nanosleep(&req, NULL);
 +}
  
- int check_schema_version(sqlite3* db) {
 +// returns the schema version if the schema version table exists
 +// returns -1 if it does not
++int checkSchemaVersion(sqlite3* db) {
      sqlite3_stmt* prepared = NULL;
 -    if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
 -                           &prepared, NULL) == SQLITE_OK &&
 -        sqlite3_step(prepared) == SQLITE_ROW) {
 -        initializer->params_.version_ = sqlite3_column_int(prepared, 0);
 -        sqlite3_finalize(prepared);
 -    } else {
 -        logger.info(DATASRC_SQLITE_SETUP);
 -        if (prepared != NULL) {
 -            sqlite3_finalize(prepared);
 +    // At this point in time, the database might be exclusively locked, in
 +    // which case even prepare() will return BUSY, so we may need to try a
 +    // few times
 +    for (size_t i = 0; i < 50; ++i) {
-         int rc = sqlite3_prepare_v2(db, q_version_str, -1, &prepared, NULL);
++        int rc = sqlite3_prepare_v2(db, "SELECT version FROM schema_version",
++                                    -1, &prepared, NULL);
 +        if (rc == SQLITE_ERROR) {
 +            // this is the error that is returned when the table does not
 +            // exist
 +            return (-1);
 +        } else if (rc == SQLITE_OK) {
 +            break;
 +        } else if (rc != SQLITE_BUSY || i == 50) {
 +            isc_throw(SQLite3Error, "Unable to prepare version query: "
 +                        << rc << " " << sqlite3_errmsg(db));
 +        }
-         do_sleep();
++        doSleep();
 +    }
 +    if (sqlite3_step(prepared) != SQLITE_ROW) {
 +        isc_throw(SQLite3Error,
 +                    "Unable to query version: " << sqlite3_errmsg(db));
 +    }
 +    int version = sqlite3_column_int(prepared, 0);
 +    sqlite3_finalize(prepared);
 +    return (version);
 +}
 +
 +// return db version
 +int create_database(sqlite3* db) {
 +    // try to get an exclusive lock. Once that is obtained, do the version
 +    // check *again*, just in case this process was racing another
 +    //
 +    // try for 5 secs (50*0.1)
 +    int rc;
 +    logger.info(DATASRC_SQLITE_SETUP);
 +    for (size_t i = 0; i < 50; ++i) {
 +        rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
 +                            NULL);
 +        if (rc == SQLITE_OK) {
 +            break;
 +        } else if (rc != SQLITE_BUSY || i == 50) {
 +            isc_throw(SQLite3Error, "Unable to acquire exclusive lock "
 +                        "for database creation: " << sqlite3_errmsg(db));
          }
-         do_sleep();
++        doSleep();
 +    }
-     int schema_version = check_schema_version(db);
++    int schema_version = checkSchemaVersion(db);
 +    if (schema_version == -1) {
          for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
              if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
                  SQLITE_OK) {
                  isc_throw(SQLite3Error,
 -                          "Failed to set up schema " << SCHEMA_LIST[i]);
 +                        "Failed to set up schema " << SCHEMA_LIST[i]);
              }
          }
-     int schema_version = check_schema_version(db);
 +        sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
 +        return (SQLITE_SCHEMA_VERSION);
 +    } else {
 +        return (schema_version);
 +    }
 +}
 +
 +void
 +checkAndSetupSchema(Initializer* initializer) {
 +    sqlite3* const db = initializer->params_.db_;
 +
++    int schema_version = checkSchemaVersion(db);
 +    if (schema_version != SQLITE_SCHEMA_VERSION) {
 +        schema_version = create_database(db);
      }
 +    initializer->params_.version_ = schema_version;
  
-     initializer->params_.q_zone_ = prepare(db, q_zone_str);
-     /* TODO: Yet unneeded statements
-     initializer->params_.q_record_ = prepare(db, q_record_str);
-     initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
-     initializer->params_.q_referral_ = prepare(db, q_referral_str);
-     initializer->params_.q_count_ = prepare(db, q_count_str);
-     initializer->params_.q_previous_ = prepare(db, q_previous_str);
-     initializer->params_.q_nsec3_ = prepare(db, q_nsec3_str);
-     initializer->params_.q_prevnsec3_ = prepare(db, q_prevnsec3_str);
-     */
+     for (int i = 0; i < NUM_STATEMENTS; ++i) {
+         initializer->params_.statements_[i] = prepare(db, text_statements[i]);
+     }
  }
  
  }
@@@ -345,8 -267,14 +335,9 @@@ SQLite3Accessor::close(void) 
  }
  
  std::pair<bool, int>
- SQLite3Database::getZone(const std::string& name) const {
 -SQLite3Accessor::getZone(const isc::dns::Name& name) const {
 -    return (getZone(name.toText()));
 -}
 -
 -std::pair<bool, int>
 -SQLite3Accessor::getZone(const string& name) const {
++SQLite3Accessor::getZone(const std::string& name) const {
      int rc;
+     sqlite3_stmt* const stmt = dbparameters_->statements_[ZONE];
  
      // Take the statement (simple SELECT id FROM zones WHERE...)
      // and prepare it (bind the parameters to it)
      return (std::pair<bool, int>(false, 0));
  }
  
- class SQLite3Database::Context : public DatabaseAccessor::IteratorContext {
 -namespace {
 -// This helper function converts from the unsigned char* type (used by
 -// sqlite3) to char* (wanted by std::string). Technically these types
 -// might not be directly convertable
 -// In case sqlite3_column_text() returns NULL, we just make it an
 -// empty string.
 -// The sqlite3parameters value is only used to check the error code if
 -// ucp == NULL
 -const char*
 -convertToPlainChar(const unsigned char* ucp,
 -                   SQLite3Parameters* dbparameters) {
 -    if (ucp == NULL) {
 -        // The field can really be NULL, in which case we return an
 -        // empty string, or sqlite may have run out of memory, in
 -        // which case we raise an error
 -        if (dbparameters != NULL &&
 -            sqlite3_errcode(dbparameters->db_) == SQLITE_NOMEM) {
 -            isc_throw(DataSourceError,
 -                      "Sqlite3 backend encountered a memory allocation "
 -                      "error in sqlite3_column_text()");
 -        } else {
 -            return ("");
 -        }
 -    }
 -    const void* p = ucp;
 -    return (static_cast<const char*>(p));
 -}
 -}
 -
+ class SQLite3Accessor::Context : public DatabaseAccessor::IteratorContext {
  public:
      // Construct an iterator for all records. When constructed this
      // way, the getNext() call will copy all fields
-     Context(const boost::shared_ptr<const SQLite3Database>& database, int id) :
+     Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id) :
          iterator_type_(ITT_ALL),
-         database_(database),
+         accessor_(accessor),
 -        statement_(NULL)
 +        statement_(NULL),
 +        name_("")
      {
          // We create the statement now and then just keep getting data from it
-         statement_ = prepare(database->dbparameters_->db_, q_iterate_str);
+         statement_ = prepare(accessor->dbparameters_->db_,
+                              text_statements[ITERATE]);
          bindZoneId(id);
      }
  
      // Construct an iterator for records with a specific name. When constructed
      // this way, the getNext() call will copy all fields except name
-     Context(const boost::shared_ptr<const SQLite3Database>& database, int id,
+     Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id,
 -            const isc::dns::Name& name) :
 +            const std::string& name, bool subdomains) :
          iterator_type_(ITT_NAME),
-         database_(database),
+         accessor_(accessor),
 -        statement_(NULL)
 +        statement_(NULL),
 +        name_(name)
++
      {
          // We create the statement now and then just keep getting data from it
-         statement_ = prepare(database->dbparameters_->db_,
-                              subdomains ? q_any_sub_str : q_any_str);
+         statement_ = prepare(accessor->dbparameters_->db_,
++                             subdomains ? text_statements[ANY_SUB] :
+                              text_statements[ANY]);
          bindZoneId(id);
 -        bindName(name);
 +        bindName(name_);
      }
  
      bool getNext(std::string (&data)[COLUMN_COUNT]) {
          } else if (rc != SQLITE_DONE) {
              isc_throw(DataSourceError,
                        "Unexpected failure in sqlite3_step: " <<
-                       sqlite3_errmsg(database_->dbparameters_->db_));
+                       sqlite3_errmsg(accessor_->dbparameters_->db_));
          }
 +        finalize();
          return (false);
      }
  
@@@ -464,63 -411,30 +454,63 @@@ private
  
      void bindZoneId(const int zone_id) {
          if (sqlite3_bind_int(statement_, 1, zone_id) != SQLITE_OK) {
 +            finalize();
              isc_throw(SQLite3Error, "Could not bind int " << zone_id <<
                        " to SQL statement: " <<
-                       sqlite3_errmsg(database_->dbparameters_->db_));
+                       sqlite3_errmsg(accessor_->dbparameters_->db_));
          }
      }
  
 -    void bindName(const isc::dns::Name& name) {
 -        if (sqlite3_bind_text(statement_, 2, name.toText().c_str(), -1,
 +    void bindName(const std::string& name) {
 +        if (sqlite3_bind_text(statement_, 2, name.c_str(), -1,
-                               SQLITE_STATIC) != SQLITE_OK) {
-             const char* errmsg = sqlite3_errmsg(database_->dbparameters_->db_);
+                               SQLITE_TRANSIENT) != SQLITE_OK) {
+             const char* errmsg = sqlite3_errmsg(accessor_->dbparameters_->db_);
 -            sqlite3_finalize(statement_);
 +            finalize();
              isc_throw(SQLite3Error, "Could not bind text '" << name <<
                        "' to SQL statement: " << errmsg);
          }
      }
  
-             if (sqlite3_errcode(database_->dbparameters_->db_)
 +    void finalize() {
 +        sqlite3_finalize(statement_);
 +        statement_ = NULL;
 +    }
 +
 +    // This helper method converts from the unsigned char* type (used by
 +    // sqlite3) to char* (wanted by std::string). Technically these types
 +    // might not be directly convertable
 +    // In case sqlite3_column_text() returns NULL, we just make it an
 +    // empty string, unless it was caused by a memory error
 +    const char* convertToPlainChar(const unsigned char* ucp) {
 +        if (ucp == NULL) {
 +            // The field can really be NULL, in which case we return an
 +            // empty string, or sqlite may have run out of memory, in
 +            // which case we raise an error
++            if (sqlite3_errcode(accessor_->dbparameters_->db_)
 +                                == SQLITE_NOMEM) {
 +                isc_throw(DataSourceError,
 +                        "Sqlite3 backend encountered a memory allocation "
 +                        "error in sqlite3_column_text()");
 +            } else {
 +                return ("");
 +            }
 +        }
 +        const void* p = ucp;
 +        return (static_cast<const char*>(p));
 +    }
 +
      const IteratorType iterator_type_;
-     boost::shared_ptr<const SQLite3Database> database_;
+     boost::shared_ptr<const SQLite3Accessor> accessor_;
      sqlite3_stmt *statement_;
 +    const std::string name_;
  };
  
  DatabaseAccessor::IteratorContextPtr
- SQLite3Database::getRecords(const std::string& name, int id,
 -SQLite3Accessor::getRecords(const isc::dns::Name& name, int id) const {
 -    return (IteratorContextPtr(new Context(shared_from_this(), id, name)));
++SQLite3Accessor::getRecords(const std::string& name, int id,
 +                            bool subdomains) const
 +{
 +    return (IteratorContextPtr(new Context(shared_from_this(), id, name,
 +                                           subdomains)));
  }
  
  DatabaseAccessor::IteratorContextPtr
Simple merge
index b91f3aa337e41fedd94de01fe104eb7f07150cad,5725c81219382bead8ce0b0db08743cbcb75143c..c9db3f146f294445b019b426fe83201f1f008b46
  
  #include <gtest/gtest.h>
  #include <boost/scoped_ptr.hpp>
 +#include <fstream>
 +#include <sqlite3.h>
  
+ using namespace std;
  using namespace isc::datasrc;
+ using boost::shared_ptr;
  using isc::data::ConstElementPtr;
  using isc::data::Element;
  using isc::dns::RRClass;
@@@ -45,14 -48,10 +51,14 @@@ std::string SQLITE_DBFILE_EXAMPLE_ORG 
  // The "nodir", a non existent directory, is inserted for this purpose.
  std::string SQLITE_DBFILE_NOTEXIST = TEST_DATA_DIR "/nodir/notexist";
  
- const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILD_DIR "/newdb.sqlite3";
 +// new db file, we don't need this to be a std::string, and given the
 +// raw calls we use it in a const char* is more convenient
++const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILDDIR "/newdb.sqlite3";
 +
  // Opening works (the content is tested in different tests)
  TEST(SQLite3Open, common) {
-     EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_EXAMPLE,
-                                        RRClass::IN()));
+     EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE,
+                                              RRClass::IN()));
  }
  
  // The file can't be opened
@@@ -88,117 -87,62 +94,116 @@@ public
  };
  
  // This zone exists in the data, so it should be found
- TEST_F(SQLite3Access, getZone) {
-     std::pair<bool, int> result(db->getZone("example.com."));
+ TEST_F(SQLite3AccessorTest, getZone) {
 -    std::pair<bool, int> result(accessor->getZone(Name("example.com")));
++    std::pair<bool, int> result(accessor->getZone("example.com."));
      EXPECT_TRUE(result.first);
      EXPECT_EQ(1, result.second);
  }
  
  // But it should find only the zone, nothing below it
- TEST_F(SQLite3Access, subZone) {
-     EXPECT_FALSE(db->getZone("sub.example.com.").first);
+ TEST_F(SQLite3AccessorTest, subZone) {
 -    EXPECT_FALSE(accessor->getZone(Name("sub.example.com")).first);
++    EXPECT_FALSE(accessor->getZone("sub.example.com.").first);
  }
  
  // This zone is not there at all
- TEST_F(SQLite3Access, noZone) {
-     EXPECT_FALSE(db->getZone("example.org.").first);
+ TEST_F(SQLite3AccessorTest, noZone) {
 -    EXPECT_FALSE(accessor->getZone(Name("example.org")).first);
++    EXPECT_FALSE(accessor->getZone("example.org.").first);
  }
  
  // This zone is there, but in different class
- TEST_F(SQLite3Access, noClass) {
+ TEST_F(SQLite3AccessorTest, noClass) {
      initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::CH());
-     EXPECT_FALSE(db->getZone("example.com.").first);
 -    EXPECT_FALSE(accessor->getZone(Name("example.com")).first);
++    EXPECT_FALSE(accessor->getZone("example.com.").first);
  }
  
  // This tests the iterator context
- TEST_F(SQLite3Access, iterator) {
+ TEST_F(SQLite3AccessorTest, iterator) {
      // Our test zone is conveniently small, but not empty
 -    initAccessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
 +    initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, RRClass::IN());
 +
-     const std::pair<bool, int> zone_info(db->getZone("example.org."));
++    const std::pair<bool, int> zone_info(accessor->getZone("example.org."));
 +    ASSERT_TRUE(zone_info.first);
  
      // Get the iterator context
 -    DatabaseAccessor::IteratorContextPtr context(accessor->getAllRecords(1));
 +    DatabaseAccessor::IteratorContextPtr
-         context(db->getAllRecords(zone_info.second));
-     ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
-               context);
++        context(accessor->getAllRecords(zone_info.second));
+     ASSERT_NE(DatabaseAccessor::IteratorContextPtr(), context);
  
 -    const size_t size(5);
 -    std::string data[size];
 +    std::string data[DatabaseAccessor::COLUMN_COUNT];
      // Get and check the first and only record
      EXPECT_TRUE(context->getNext(data));
 -    EXPECT_EQ("example2.com.", data[4]);
 -    EXPECT_EQ("SOA", data[0]);
 -    EXPECT_EQ("master.example2.com. admin.example2.com. "
 -              "1234 3600 1800 2419200 7200", data[3]);
 -    EXPECT_EQ("3600", data[1]);
 -    // Check there's no other
 -    EXPECT_FALSE(context->getNext(data));
 -}
 +    EXPECT_EQ("DNAME", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("dname.example.info.", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("dname.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
  
 -TEST_F(SQLite3AccessorTest, iteratorColumnCount) {
 -    // Our test zone is conveniently small, but not empty
 -    initAccessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("DNAME", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("dname2.example.info.", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("dname2.foo.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
  
 -    // Get the iterator context
 -    DatabaseAccessor::IteratorContextPtr
 -        context(accessor->getAllRecords(1));
 -    ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
 -              context);
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("MX", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("10 mail.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
  
 -    std::string data[DatabaseAccessor::COLUMN_COUNT];
 -    EXPECT_NO_THROW(context->getNext(data));
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("NS", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("ns1.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
 +
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("NS", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("ns2.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
 +
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("NS", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("ns3.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
 +
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("SOA", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("ns1.example.org. admin.example.org. "
 +              "1234 3600 1800 2419200 7200",
 +              data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
 +
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("A", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("192.0.2.10", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("mail.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
 +
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("A", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("192.0.2.101", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("ns.sub.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
 +
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("NS", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("ns.sub.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("sub.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
 +
 +    EXPECT_TRUE(context->getNext(data));
 +    EXPECT_EQ("A", data[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ("192.0.2.1", data[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ("www.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
 +
 +    // Check there's no other
 +    EXPECT_FALSE(context->getNext(data));
 +
 +    // And make sure calling it again won't cause problems.
 +    EXPECT_FALSE(context->getNext(data));
  }
  
  TEST(SQLite3Open, getDBNameExample2) {
@@@ -221,31 -165,33 +226,31 @@@ checkRecordRow(const std::string column
                 const std::string& field3,
                 const std::string& field4)
  {
 -    EXPECT_EQ(field0, columns[0]);
 -    EXPECT_EQ(field1, columns[1]);
 -    EXPECT_EQ(field2, columns[2]);
 -    EXPECT_EQ(field3, columns[3]);
 -    EXPECT_EQ(field4, columns[4]);
 +    EXPECT_EQ(field0, columns[DatabaseAccessor::TYPE_COLUMN]);
 +    EXPECT_EQ(field1, columns[DatabaseAccessor::TTL_COLUMN]);
 +    EXPECT_EQ(field2, columns[DatabaseAccessor::SIGTYPE_COLUMN]);
 +    EXPECT_EQ(field3, columns[DatabaseAccessor::RDATA_COLUMN]);
 +    EXPECT_EQ(field4, columns[DatabaseAccessor::NAME_COLUMN]);
  }
  
- TEST_F(SQLite3Access, getRecords) {
-     const std::pair<bool, int> zone_info(db->getZone("example.com."));
+ TEST_F(SQLite3AccessorTest, getRecords) {
 -    const std::pair<bool, int> zone_info(
 -        accessor->getZone(Name("example.com")));
++    const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
      ASSERT_TRUE(zone_info.first);
  
      const int zone_id = zone_info.second;
      ASSERT_EQ(1, zone_id);
  
 -    const size_t column_count = DatabaseAccessor::COLUMN_COUNT;
 -    std::string columns[column_count];
 +    std::string columns[DatabaseAccessor::COLUMN_COUNT];
  
      DatabaseAccessor::IteratorContextPtr
-         context(db->getRecords("foo.bar", 1));
 -        context(accessor->getRecords(Name("foo.bar"), 1));
++        context(accessor->getRecords("foo.bar", 1));
      ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
                context);
      EXPECT_FALSE(context->getNext(columns));
      checkRecordRow(columns, "", "", "", "", "");
  
      // now try some real searches
-     context = db->getRecords("foo.example.com.", zone_id);
 -    context = accessor->getRecords(Name("foo.example.com."), zone_id);
++    context = accessor->getRecords("foo.example.com.", zone_id);
      ASSERT_TRUE(context->getNext(columns));
      checkRecordRow(columns, "CNAME", "3600", "",
                     "cnametest.example.org.", "");
                     "NSEC 5 3 7200 20100322084538 20100220084538 33495 "
                     "example.com. FAKEFAKEFAKEFAKE", "");
  
-     context = db->getRecords("example.com.", zone_id);
 -    context = accessor->getRecords(Name("example.com."), zone_id);
++    context = accessor->getRecords("example.com.", zone_id);
      ASSERT_TRUE(context->getNext(columns));
      checkRecordRow(columns, "SOA", "3600", "",
                     "master.example.com. admin.example.com. "
      checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
                     "DNSKEY 5 2 3600 20100322084538 20100220084538 "
                     "33495 example.com. FAKEFAKEFAKEFAKE", "");
-     context = db->getRecords("bar.example.com.", zone_id, true);
 +
 +    // check that another getNext does not cause problems
 +    EXPECT_FALSE(context->getNext(columns));
 +
 +    // Try searching for subdomain
 +    // There's foo.bar.example.com in the data
-     context = db->getRecords("ix.example.com.", zone_id, true);
++    context = accessor->getRecords("bar.example.com.", zone_id, true);
 +    ASSERT_TRUE(context->getNext(columns));
 +    checkRecordRow(columns, "A", "3600", "", "192.0.2.1", "");
 +    EXPECT_FALSE(context->getNext(columns));
 +    // But we shouldn't match mix.example.com here
-     SQLite3Database db(SQLITE_NEW_DBFILE, RRClass::IN());
++    context = accessor->getRecords("ix.example.com.", zone_id, true);
 +    EXPECT_FALSE(context->getNext(columns));
 +}
 +
 +// Test fixture for creating a db that automatically deletes it before start,
 +// and when done
 +class SQLite3Create : public ::testing::Test {
 +public:
 +    SQLite3Create() {
 +        remove(SQLITE_NEW_DBFILE);
 +    }
 +
 +    ~SQLite3Create() {
 +        remove(SQLITE_NEW_DBFILE);
 +    }
 +};
 +
 +bool exists(const char* filename) {
 +    std::ifstream f(filename);
 +    return (f != NULL);
 +}
 +
 +TEST_F(SQLite3Create, creationtest) {
 +    ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
 +    // Should simply be created
-     SQLite3Database db2(SQLITE_NEW_DBFILE, RRClass::IN());
++    SQLite3Accessor accessor(SQLITE_NEW_DBFILE, RRClass::IN());
 +    ASSERT_TRUE(exists(SQLITE_NEW_DBFILE));
 +}
 +
 +TEST_F(SQLite3Create, emptytest) {
 +    ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
 +
 +    // open one manualle
 +    sqlite3* db;
 +    ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
 +
 +    // empty, but not locked, so creating it now should work
-     SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
++    SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN());
 +
 +    sqlite3_close(db);
 +
 +    // should work now that we closed it
-     EXPECT_THROW(SQLite3Database db2(SQLITE_NEW_DBFILE, RRClass::IN()),
++    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
 +}
 +
 +TEST_F(SQLite3Create, lockedtest) {
 +    ASSERT_FALSE(exists(SQLITE_NEW_DBFILE));
 +
 +    // open one manually
 +    sqlite3* db;
 +    ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
 +    sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
 +
 +    // should not be able to open it
-     SQLite3Database db3(SQLITE_NEW_DBFILE, RRClass::IN());
++    EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN()),
 +                 SQLite3Error);
 +
 +    sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
 +
 +    // should work now that we closed it
++    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
  }
  
 -        zone_id = accessor->getZone(Name("example.com")).second;
+ //
+ // Commonly used data for update tests
+ //
+ const char* const common_expected_data[] = {
+     // Test record already stored in the tested sqlite3 DB file.
+     "foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
+     "192.0.2.1"
+ };
+ const char* const new_data[] = {
+     // Newly added data commonly used by some of the tests below
+     "newdata.example.com.", "com.example.newdata.", "3600", "A", "",
+     "192.0.2.1"
+ };
+ const char* const deleted_data[] = {
+     // Existing data to be removed commonly used by some of the tests below
+     "foo.bar.example.com.", "A", "192.0.2.1"
+ };
+ class SQLite3Update : public SQLite3AccessorTest {
+ protected:
+     SQLite3Update() {
+         // Note: if "installing" the test file fails some of the subsequent
+         // tests will fail and we should be able to notice that.
+         system(INSTALL_PROG " " TEST_DATA_DIR
+                "/test.sqlite3 " TEST_DATA_BUILDDIR "/test.sqlite3.copied");
+         initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", RRClass::IN());
 -        accessor.getRecords(Name(name), zone_id);
++        zone_id = accessor->getZone("example.com.").second;
+         another_accessor.reset(new SQLite3Accessor(
+                                    TEST_DATA_BUILDDIR "/test.sqlite3.copied",
+                                    RRClass::IN()));
+         expected_stored.push_back(common_expected_data);
+     }
+     int zone_id;
+     std::string get_columns[DatabaseAccessor::COLUMN_COUNT];
+     std::string add_columns[DatabaseAccessor::ADD_COLUMN_COUNT];
+     std::string del_params[DatabaseAccessor::DEL_PARAM_COUNT];
+     vector<const char* const*> expected_stored; // placeholder for checkRecords
+     vector<const char* const*> empty_stored; // indicate no corresponding data
+     // Another accessor, emulating one running on a different process/thread
+     shared_ptr<SQLite3Accessor> another_accessor;
+     DatabaseAccessor::IteratorContextPtr iterator;
+ };
+ void
+ checkRecords(SQLite3Accessor& accessor, int zone_id, const std::string& name,
+              vector<const char* const*> expected_rows)
+ {
+     DatabaseAccessor::IteratorContextPtr iterator =
 -    iterator = accessor->getRecords(Name("example.com"), zone_id);
++        accessor.getRecords(name, zone_id);
+     std::string columns[DatabaseAccessor::COLUMN_COUNT];
+     vector<const char* const*>::const_iterator it = expected_rows.begin();
+     while (iterator->getNext(columns)) {
+         ASSERT_TRUE(it != expected_rows.end());
+         checkRecordRow(columns, (*it)[3], (*it)[2], (*it)[4], (*it)[5], "");
+         ++it;
+     }
+     EXPECT_TRUE(it == expected_rows.end());
+ }
+ TEST_F(SQLite3Update, emptyUpdate) {
+     // If we do nothing between start and commit, the zone content
+     // should be intact.
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+     zone_id = accessor->startUpdateZone("example.com.", false).second;
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+     accessor->commitUpdateZone();
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ }
+ TEST_F(SQLite3Update, flushZone) {
+     // With 'replace' being true startUpdateZone() will flush the existing
+     // zone content.
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+     zone_id = accessor->startUpdateZone("example.com.", true).second;
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+     accessor->commitUpdateZone();
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+ }
+ TEST_F(SQLite3Update, readWhileUpdate) {
+     zone_id = accessor->startUpdateZone("example.com.", true).second;
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+     // Until commit is done, the other accessor should see the old data
+     checkRecords(*another_accessor, zone_id, "foo.bar.example.com.",
+                  expected_stored);
+     // Once the changes are committed, the other accessor will see the new
+     // data.
+     accessor->commitUpdateZone();
+     checkRecords(*another_accessor, zone_id, "foo.bar.example.com.",
+                  empty_stored);
+ }
+ TEST_F(SQLite3Update, rollback) {
+     zone_id = accessor->startUpdateZone("example.com.", true).second;
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+     // Rollback will revert the change made by startUpdateZone(, true).
+     accessor->rollbackUpdateZone();
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ }
+ TEST_F(SQLite3Update, rollbackFailure) {
+     // This test emulates a rare scenario of making rollback attempt fail.
+     // The iterator is paused in the middle of getting records, which prevents
+     // the rollback operation at the end of the test.
+     string columns[DatabaseAccessor::COLUMN_COUNT];
 -    iterator = another_accessor->getRecords(Name("foo.example.com"), zone_id);
++    iterator = accessor->getRecords("example.com.", zone_id);
+     EXPECT_TRUE(iterator->getNext(columns));
+     accessor->startUpdateZone("example.com.", true);
+     EXPECT_THROW(accessor->rollbackUpdateZone(), DataSourceError);
+ }
+ TEST_F(SQLite3Update, commitConflict) {
+     // Start reading the DB by another accessor.  We should stop at a single
+     // call to getNextRecord() to keep holding the lock.
++    iterator = another_accessor->getRecords("foo.example.com.", zone_id);
+     EXPECT_TRUE(iterator->getNext(get_columns));
+     // Due to getNextRecord() above, the other accessor holds a DB lock,
+     // which will prevent commit.
+     zone_id = accessor->startUpdateZone("example.com.", true).second;
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+     EXPECT_THROW(accessor->commitUpdateZone(), DataSourceError);
+     accessor->rollbackUpdateZone();   // rollback should still succeed
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ }
+ TEST_F(SQLite3Update, updateConflict) {
+     // Similar to the previous case, but this is a conflict with another
+     // update attempt.  Note that these two accessors modify disjoint sets
+     // of data; sqlite3 only has a coarse-grained lock so we cannot allow
+     // these updates to run concurrently.
+     EXPECT_TRUE(another_accessor->startUpdateZone("sql1.example.com.",
+                                                   true).first);
+     EXPECT_THROW(accessor->startUpdateZone("example.com.", true),
+                  DataSourceError);
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+     // Once we rollback the other attempt of change, we should be able to
+     // start and commit the transaction using the main accessor.
+     another_accessor->rollbackUpdateZone();
+     accessor->startUpdateZone("example.com.", true);
+     accessor->commitUpdateZone();
+ }
+ TEST_F(SQLite3Update, duplicateUpdate) {
+     accessor->startUpdateZone("example.com.", false);
+     EXPECT_THROW(accessor->startUpdateZone("example.com.", false),
+                  DataSourceError);
+ }
+ TEST_F(SQLite3Update, commitWithoutTransaction) {
+     EXPECT_THROW(accessor->commitUpdateZone(), DataSourceError);
+ }
+ TEST_F(SQLite3Update, rollbackWithoutTransaction) {
+     EXPECT_THROW(accessor->rollbackUpdateZone(), DataSourceError);
+ }
+ TEST_F(SQLite3Update, addRecord) {
+     // Before update, there should be no record for this name
+     checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
+     zone_id = accessor->startUpdateZone("example.com.", false).second;
+     copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+          add_columns);
+     accessor->addRecordToZone(add_columns);
+     expected_stored.clear();
+     expected_stored.push_back(new_data);
+     checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+     // Commit the change, and confirm the new data is still there.
+     accessor->commitUpdateZone();
+     checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+ }
+ TEST_F(SQLite3Update, addThenRollback) {
+     zone_id = accessor->startUpdateZone("example.com.", false).second;
+     copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+          add_columns);
+     accessor->addRecordToZone(add_columns);
+     expected_stored.clear();
+     expected_stored.push_back(new_data);
+     checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+     accessor->rollbackUpdateZone();
+     checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
+ }
+ TEST_F(SQLite3Update, duplicateAdd) {
+     const char* const dup_data[] = {
+         "foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
+         "192.0.2.1"
+     };
+     expected_stored.clear();
+     expected_stored.push_back(dup_data);
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+     // Adding exactly the same data.  As this backend is "dumb", another
+     // row of the same content will be inserted.
+     copy(dup_data, dup_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+          add_columns);
+     zone_id = accessor->startUpdateZone("example.com.", false).second;
+     accessor->addRecordToZone(add_columns);
+     expected_stored.push_back(dup_data);
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ }
+ TEST_F(SQLite3Update, invalidAdd) {
+     // An attempt of add before an explicit start of transaction
+     EXPECT_THROW(accessor->addRecordToZone(add_columns), DataSourceError);
+ }
+ TEST_F(SQLite3Update, deleteRecord) {
+     zone_id = accessor->startUpdateZone("example.com.", false).second;
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+     copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+          del_params);
+     accessor->deleteRecordInZone(del_params);
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+     // Commit the change, and confirm the deleted data still isn't there.
+     accessor->commitUpdateZone();
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+ }
+ TEST_F(SQLite3Update, deleteThenRollback) {
+     zone_id = accessor->startUpdateZone("example.com.", false).second;
+     copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+          del_params);
+     accessor->deleteRecordInZone(del_params);
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+     // Rollback the change, and confirm the data still exists.
+     accessor->rollbackUpdateZone();
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ }
+ TEST_F(SQLite3Update, deleteNonexistent) {
+     zone_id = accessor->startUpdateZone("example.com.", false).second;
+     copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+          del_params);
+     // Replace the name with a non existent one, then try to delete it.
+     // nothing should happen.
+     del_params[DatabaseAccessor::DEL_NAME] = "no-such-name.example.com.";
+     checkRecords(*accessor, zone_id, "no-such-name.example.com.",
+                  empty_stored);
+     accessor->deleteRecordInZone(del_params);
+     checkRecords(*accessor, zone_id, "no-such-name.example.com.",
+                  empty_stored);
+     // Name exists but the RR type is different.  Delete attempt shouldn't
+     // delete only by name.
+     copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+          del_params);
+     del_params[DatabaseAccessor::DEL_TYPE] = "AAAA";
+     accessor->deleteRecordInZone(del_params);
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+     // Similar to the previous case, but RDATA is different.
+     copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+          del_params);
+     del_params[DatabaseAccessor::DEL_RDATA] = "192.0.2.2";
+     accessor->deleteRecordInZone(del_params);
+     checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ }
+ TEST_F(SQLite3Update, invalidDelete) {
+     // An attempt of delete before an explicit start of transaction
+     EXPECT_THROW(accessor->deleteRecordInZone(del_params), DataSourceError);
+ }
  } // end anonymous namespace