[svn] commit: r3268 - in /branches/trac374/src/lib/datasrc: ./ tests/ tests/testdata/

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Oct 19 08:16:09 UTC 2010


Author: jelte
Date: Tue Oct 19 08:16:09 2010
New Revision: 3268

Log:
c++ support for writable datasources from branches/trac232

Added:
    branches/trac374/src/lib/datasrc/tests/testdata/ixfr_bad_remove_nonexisting.rrs
    branches/trac374/src/lib/datasrc/tests/testdata/ixfr_ok.rrs
    branches/trac374/src/lib/datasrc/tests/testdata/update1.packet
    branches/trac374/src/lib/datasrc/tests/testdata/update2.packet
    branches/trac374/src/lib/datasrc/tests/testdata/update3.packet
    branches/trac374/src/lib/datasrc/tests/testdata/update4.packet
    branches/trac374/src/lib/datasrc/tests/testdata/update5.packet
    branches/trac374/src/lib/datasrc/tests/testdata/update6.packet
    branches/trac374/src/lib/datasrc/tests/testdata/update7.packet
Modified:
    branches/trac374/src/lib/datasrc/data_source.cc
    branches/trac374/src/lib/datasrc/data_source.h
    branches/trac374/src/lib/datasrc/sqlite3_datasrc.cc
    branches/trac374/src/lib/datasrc/sqlite3_datasrc.h
    branches/trac374/src/lib/datasrc/tests/Makefile.am
    branches/trac374/src/lib/datasrc/tests/sqlite3_unittest.cc

Modified: branches/trac374/src/lib/datasrc/data_source.cc
==============================================================================
--- branches/trac374/src/lib/datasrc/data_source.cc (original)
+++ branches/trac374/src/lib/datasrc/data_source.cc Tue Oct 19 08:16:09 2010
@@ -32,6 +32,7 @@
 #include <dns/buffer.h>
 #include <dns/message.h>
 #include <dns/name.h>
+#include <dns/opcode.h>
 #include <dns/rcode.h>
 #include <dns/rdataclass.h>
 #include <dns/rrset.h>
@@ -1255,6 +1256,368 @@
     return (NOT_IMPLEMENTED);
 }
 
+DataSrc::WriteResult
+DataSrc::startTransaction(DataSrcTransaction& transaction UNUSED_PARAM,
+                          const bool create_zone UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::commitTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::rollbackTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::addRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+                  const isc::dns::ConstRRsetPtr rrset UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::delRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+                  isc::dns::ConstRRsetPtr rrset UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::delZone(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+DataSrc::replaceZone(DataSrcTransaction& transaction UNUSED_PARAM,
+                     isc::dns::RRsetPtr (*nextRRset)(void*, void*) UNUSED_PARAM,
+                     void* arg1 UNUSED_PARAM, void* arg2 UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+static bool
+equalRRsets(ConstRRsetPtr a, ConstRRsetPtr b) {
+    if (a->getName() != b->getName() ||
+        a->getClass() != b->getClass() ||
+        a->getType() != b->getType() ||
+        a->getRdataCount() != b->getRdataCount()) {
+        return (false);
+    }
+    RdataIteratorPtr ita = a->getRdataIterator();
+    RdataIteratorPtr itb = b->getRdataIterator();
+    itb->first();
+    for (ita->first(); !ita->isLast(); ita->next()) {
+        if (ita->getCurrent().compare(itb->getCurrent()) != 0) {
+            return (false);
+        }
+        itb->next();
+    }
+    return (true);
+}
+
+bool
+DataSrc::haveRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+                   ConstRRsetPtr rrset) {
+    RRsetList rrset_list;
+    DataSrc::Result result;
+    uint32_t flags = 0;
+
+    if (rrset->getClass() == RRClass::ANY() || rrset->getClass() == RRClass::NONE()) {
+        result = findExactRRset(rrset->getName(), RRClass::IN(),
+                                         rrset->getType(), rrset_list, flags, NULL);
+    } else {
+        result = findExactRRset(rrset->getName(), rrset->getClass(),
+                                         rrset->getType(), rrset_list, flags, NULL);
+    }
+    if (result != DataSrc::SUCCESS || rrset_list.size() == 0) {
+        return (false);
+    }
+    if (rrset->getRdataCount() > 0) {
+        return (equalRRsets(rrset, *(rrset_list.begin())));
+    } else {
+        return ((*(rrset_list.begin()))->getRdataCount() > 0);
+    }
+}
+
+isc::dns::Rcode
+DataSrc::updateCheckPrerequisite(DataSrcTransaction& transaction,
+                                 ConstRRsetPtr prereq)
+{
+    // section 3.2 of RFC2136
+    if (prereq->getClass() == RRClass::ANY()) {
+        if (prereq->getTTL().getValue() != 0 ||
+            prereq->getRdataCount() != 0) {
+            return (Rcode::FORMERR());
+        }
+        if (prereq->getType() == RRType::ANY()) {
+            if (!haveRRset(transaction, prereq)) {
+                return (Rcode::NXDOMAIN());
+            }
+        } else if (!haveRRset(transaction, prereq)) {
+            return (Rcode::NXRRSET());
+        }
+    } else if (prereq->getClass() == RRClass::NONE()) {
+        if (prereq->getTTL().getValue() != 0 ||
+            prereq->getRdataCount() != 0) {
+            return (Rcode::FORMERR());
+        }
+        if (prereq->getType() == RRType::ANY()) {
+            if (haveRRset(transaction, prereq)) {
+                return (Rcode::YXDOMAIN());
+            }
+        } else if (haveRRset(transaction, prereq)) {
+            return (Rcode::YXRRSET());
+        }
+    } else if (prereq->getClass() == transaction.getZoneClass()) {
+        if (prereq->getTTL().getValue() != 0) {
+            return (Rcode::FORMERR());
+        }
+        // 3.2.3 talks about rebuilding sets, but we already have full rrsets
+        if (!haveRRset(transaction, prereq)) {
+            return (Rcode::NXRRSET());
+        }
+    } else {
+        return (Rcode::FORMERR());
+    }
+    
+    return (Rcode::NOERROR());
+}
+
+Rcode
+DataSrc::updateProcessUpdate(DataSrcTransaction& transaction,
+                                    isc::dns::RRsetPtr update)
+{
+    // The RFC says to pre-scan them, but since we use a transaction
+    // we can roll back, we can process the RRsets one at a time
+
+    // TODO, NOTZONE check
+    
+    RRType update_type = update->getType();
+    if (update->getClass() != RRClass::ANY()) {
+        // do we have a direct check in rrtype to see if a specific
+        // type is known and not a meta type?
+        if (update_type == RRType::ANY() ||
+            update_type == RRType::IXFR() ||
+            update_type == RRType::AXFR()) {
+            return (Rcode::FORMERR());
+        }
+    } else if (update->getClass() == RRClass::ANY()) {
+        if (update->getTTL().getValue() != 0) {
+            return (Rcode::FORMERR());
+        }
+        if (update_type == RRType::IXFR() ||
+            update_type == RRType::AXFR()) {
+            return (Rcode::FORMERR());
+        }
+        if (update->getRdataCount() > 0) {
+            return (Rcode::FORMERR());
+        }
+    } else if (update->getClass() == RRClass::NONE()) {
+        if (update->getTTL().getValue() != 0) {
+            return (Rcode::FORMERR());
+        }
+    }
+
+    // Most types are blindly added, but some require special handling
+    if (update_type == RRType::SOA()) {
+        // check serial and delete old
+        RRsetList soa_list;
+        uint32_t flags = 0;
+        if (findExactRRset(update->getName(),
+                           update->getClass(),
+                           update_type,
+                           soa_list,
+                           flags,
+                           NULL) != SUCCESS ||
+            soa_list.size() != 1) {
+            return (Rcode::SERVFAIL());
+        } else {
+            // TODO: no serial arithmetic yet?
+            if (delRRset(transaction, *(soa_list.begin())) != W_SUCCESS) {
+                return (Rcode::SERVFAIL());
+            }
+            if (addRRset(transaction, update) != W_SUCCESS) {
+                return (Rcode::SERVFAIL());
+            }
+        }
+    // which other types need special handling? CNAME, WKS,...
+    // addRRset and delRRset should do 'the right thing' regarding
+    // types (any/none/specific) and rdata count
+    } else {
+        if (update->getClass() == RRClass::ANY() ||
+            update->getClass() == RRClass::NONE()) {
+            if (delRRset(transaction, update) != W_SUCCESS) {
+                return (Rcode::SERVFAIL());
+            }
+        } else {
+            if (addRRset(transaction, update) != W_SUCCESS) {
+                return (Rcode::SERVFAIL());
+            }
+        }
+    }
+    return (Rcode::NOERROR());
+}
+
+isc::dns::RRsetPtr
+callbackHelperRRsetIterator(void* arg1, void* arg2)
+{
+    RRsetIterator* cur = static_cast<RRsetIterator*>(arg1);
+    RRsetIterator* end = static_cast<RRsetIterator*>(arg2);
+
+    if (cur == NULL || end == NULL || *cur == *end) {
+        return RRsetPtr();
+    } else {
+        RRsetPtr result = **cur;
+        (*cur)++;
+        return (result);
+    }
+}
+
+isc::dns::RRsetPtr
+callbackHelperRRsetVector(void *arg1,
+                          void *arg2)
+{
+    std::vector<RRsetPtr>* v = static_cast<std::vector<RRsetPtr>* >(arg1);
+    size_t* i = static_cast<size_t*>(arg2);
+
+    if (v && i && *i < v->size()) {
+        RRsetPtr result = ((*v)[(*i)++]);
+        return (result);
+    } else {
+        return (RRsetPtr());
+    }
+}
+
+DataSrc::Result
+DataSrc::doIXFR(DataSrcTransaction& transaction UNUSED_PARAM,
+                isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+                void* arg1, void* arg2)
+{
+    if (transaction.getState() != DataSrcTransaction::RUNNING) {
+        return (DataSrc::ERROR);
+    }
+
+    RRsetPtr final_soa = nextRRset(arg1, arg2);
+    if (!final_soa) {
+        return (DataSrc::ERROR);
+    }
+    RRsetPtr first_soa = nextRRset(arg1, arg2);
+    if (!final_soa) {
+        return (DataSrc::ERROR);
+    }
+
+    RRsetPtr next_rrset, last_rrset;
+
+    if (first_soa->getType() == RRType::SOA()) {
+        if (!haveRRset(transaction, first_soa)) {
+            return (DataSrc::ERROR);
+        }
+    } else {
+        // Not a SOA, then this must be an AXFR-type response
+        replaceZone(transaction, NULL);
+        
+        while (next_rrset = nextRRset(arg1, arg2)) {
+            addRRset(transaction, next_rrset);
+        }
+        return (DataSrc::SUCCESS);
+    }
+    bool deleting = true;
+
+    while (next_rrset = nextRRset(arg1, arg2)) {
+        
+        // If we see a SOA, it means we are switching operations (either
+        // we start deleting or adding depending on what we were doing
+        // before.
+        // We don't delete the actual SOA itself, 
+        if (next_rrset->getType() == RRType::SOA()) {
+            // TODO: check if serial has increased compared to the last soa we saw
+            deleting = !deleting;
+            last_rrset = next_rrset;
+        } else {
+            if (deleting) {
+                // check if rrset exists, if not, something is very wrong, abort
+                if (haveRRset(transaction, next_rrset)) {
+                    delRRset(transaction, next_rrset);
+                } else {
+                    return (DataSrc::ERROR);
+                }
+            } else {
+                addRRset(transaction, next_rrset);
+            }
+        }
+    }
+    if (equalRRsets(last_rrset, final_soa)) {
+        // Finally replace the SOA
+        delRRset(transaction, first_soa);
+        addRRset(transaction, final_soa);
+        return (DataSrc::SUCCESS);
+    } else {
+        return (DataSrc::ERROR);
+    }
+    return (NOT_IMPLEMENTED);
+}
+
+
+DataSrc::Result
+DataSrc::doUpdate(DataSrcTransaction& transaction UNUSED_PARAM,
+                         isc::dns::Message& msg UNUSED_PARAM) {
+    if (msg.getOpcode() != isc::dns::Opcode::UPDATE()) {
+        return ERROR;
+    }
+
+    // hmz, zone already in transaction. should we do transaction here?
+    // (and not as an argument) for now, simply check it
+    if (msg.getRRCount(isc::dns::Section::QUESTION()) != 1) {
+        return ERROR;
+    }
+    QuestionPtr question = *(msg.beginQuestion());
+    if (question->getName() != transaction.getZoneName()) {
+        return ERROR;
+    }
+    if (question->getType() != isc::dns::RRType::SOA()) {
+        return ERROR;
+    }
+    if (question->getClass() != transaction.getZoneClass()) {
+        return ERROR;
+    }
+
+    // check the prerequisites
+    RRsetIterator it;
+    for (it = msg.beginSection(isc::dns::Section::ANSWER());
+         it != msg.endSection(isc::dns::Section::ANSWER());
+         it++) {
+        RRsetPtr cur_prereq = *it;
+        isc::dns::Rcode prereq_result = updateCheckPrerequisite(transaction, cur_prereq);
+        if (prereq_result != Rcode::NOERROR()) {
+            msg.clear(Message::RENDER);
+            msg.setRcode(prereq_result);
+            return ERROR;
+        }
+    }
+
+    for (it = msg.beginSection(isc::dns::Section::AUTHORITY());
+         it != msg.endSection(isc::dns::Section::AUTHORITY());
+         it++) {
+        RRsetPtr cur_update = *it;
+        Rcode result = updateProcessUpdate(transaction, cur_update);
+        if (result != Rcode::NOERROR()) {
+            return ERROR;
+        }
+    }
+
+    // do we need to do anything with additional?
+    
+    return SUCCESS;
+}
+
 DataSrc::Result
 MetaDataSrc::findRRset(const isc::dns::Name& qname UNUSED_PARAM,
                        const isc::dns::RRClass& qclass UNUSED_PARAM,
@@ -1313,5 +1676,63 @@
     return (NOT_IMPLEMENTED);
 }
 
-}
-}
+DataSrcTransaction::~DataSrcTransaction() {
+    try {
+        if (getState() == RUNNING) {
+            _data_source->rollbackTransaction(*this);
+        }
+    } catch (...) {
+        // if we have an exception-free logging system we might
+        // want to log something. Otherwise ignore
+    }
+}
+
+DataSrc::WriteResult
+MetaDataSrc::startTransaction(DataSrcTransaction& transaction UNUSED_PARAM,
+                              const bool create_zone UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::commitTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::rollbackTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::addRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+                      isc::dns::ConstRRsetPtr rrset UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::delRRset(DataSrcTransaction& transaction UNUSED_PARAM,
+                      isc::dns::ConstRRsetPtr rrset UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::delZone(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+DataSrc::WriteResult
+MetaDataSrc::replaceZone(DataSrcTransaction& transaction UNUSED_PARAM,
+                         isc::dns::RRsetPtr (*nextRRset)(void*, void*) UNUSED_PARAM,
+                         void* arg1 UNUSED_PARAM, void* arg2 UNUSED_PARAM)
+{
+    return (W_NOT_IMPLEMENTED);
+}
+
+}
+}

Modified: branches/trac374/src/lib/datasrc/data_source.h
==============================================================================
--- branches/trac374/src/lib/datasrc/data_source.h (original)
+++ branches/trac374/src/lib/datasrc/data_source.h Tue Oct 19 08:16:09 2010
@@ -27,7 +27,9 @@
 
 #include <dns/name.h>
 #include <dns/rrclass.h>
+#include <dns/rrset.h>
 #include <cc/data.h>
+#include <dns/message.h>
 
 namespace isc {
 
@@ -55,6 +57,50 @@
         isc::Exception(file, line, what) {}
 };
 
+/// \brief Helper function for use as ready-made callback argument
+///        (for instance for DataSrc::replaceZone and DataSrc::doIXFR)
+/// This function takes a pointer to an RRsetIterator (arg1) and a
+/// pointer to an RRsetIterator end value (arg2).
+/// On each call, the next RRsetPtr in the iterator is returned, and
+/// the iterator is advanced. If the last element is reached, and
+/// empty RRsetPtr is returned.
+///
+/// Example usage:
+///
+/// \code
+/// isc::dns::Message ixfr_response = some_other_function();
+/// isc::dns::RRsetIterator begin = ixfr_response.beginSection(isc::dns::Section::ANSWER());
+/// isc::dns::RRsetIterator end = ixfr_response.endSection(isc::dns::Section::ANSWER());
+/// data_source.doIXFR(transaction,
+///                    isc::datasrc::callbackHelperRRsetIterator,
+///                    &begin, &end);
+/// \endcode
+///
+/// \param arg1 pointer to an RRsetIterator object, for instance as
+///             returned by isc::dns::message::beginSection(). This
+///             iterator is advanced on each call to this function
+/// \param arg2 pointer to an RRsetIterator end object, for instance
+///             as returned by isc::dns::message::endSection().
+/// \return The RRsetPtr as pointed to by the iterator in arg1, or
+///         an empty RRsetPtr if the end is reached.
+isc::dns::RRsetPtr
+callbackHelperRRsetIterator(void* arg1, void* arg2);
+
+/// \brief Helper function for use as ready-made callback argument
+///        (for instance for DataSrc::replaceZone and DataSrc::doIXFR)
+/// This function takes a pointer to an std::vector<RRsetPtr> (arg1)
+/// and a pointer to a size_t value (arg2).
+/// On each call, the RRsetPtr in the vector at arg1 at the position 
+/// at arg2 is returned, and arg2 is incremented with 1.
+/// If the size_t value at arg2 is is equal to or greater than the
+/// size of the vector, an empty ElementPtr() is returned.
+///
+/// \param arg1, pointer to an std::vector<RRsetPtr> object
+/// \param arg2, pointer to a size_t value. This value is incremented
+///              on each call to this function
+/// \return The RRsetPtr in the vector *arg1 at position *arg2.
+isc::dns::RRsetPtr
+callbackHelperRRsetVector(void* arg1, void* arg2);
 
 class AbstractDataSrc {
     ///
@@ -80,6 +126,17 @@
         SUCCESS,
         ERROR,
         NOT_IMPLEMENTED
+    };
+
+    // For write-related methods, we have a separate list of
+    // return values, since the errors that can occur are different
+    enum WriteResult {
+        W_SUCCESS,              // ok
+        W_ERROR,                // general (code) error
+        W_DB_ERROR,             // (write) error in the backend
+        W_NOT_IMPLEMENTED,      // write functions not implemented
+        W_NO_SUCH_ZONE,         // zone not known to this data source
+        W_NO_SUCH_DATA          // attempt to delete nonexistant data
     };
 
     // These flags indicate conditions encountered while processing a query.
@@ -159,6 +216,115 @@
    virtual Result findCoveringNSEC3(const isc::dns::Name& zonename,
                                     std::string& hash,
                                     isc::dns::RRsetList& target) const = 0;
+
+};
+
+///
+/// \brief The \c DataSrcTransaction serves as a data container and
+/// resource holder for transactions on writable data sources.
+///
+/// Instantiating this class does not actually start a transaction.
+/// Rather, it is passed to the actual data source's startTransaction()
+/// method, which starts the transaction and fills this object with
+/// the data that data source needs in order to perform the write
+/// commands later. When this transaction object is destroyed, and
+/// the transaction has been started, but not committed (with
+/// commitTransaction()), rollbackTransaction() in the
+/// data source is called. That way, we can make sure unfinished 
+/// transactions do not linger.
+///
+/// To help prevent logic errors in the use of the transaction object,
+/// it keeps a 'state', which is one of INIT, RUNNING, or DONE.
+/// startTransaction() in a data source MUST check that state is INIT,
+/// and set it to RUNNING (on success, of course).
+/// Any actual operation within the transaction MUST check the state is
+/// RUNNING.
+/// commitTransaction() and rollbackTransaction() MUST check the state
+/// is RUNNING, and set it to DONE.
+///
+/// Apart from holding the resource for the open transaction, this
+/// class is also intended to store data-source specific data during
+/// that transaction. One example is that for the Sqlite3-data source,
+/// it stores the zone_id value. Datasources can store arbitrary data
+/// in the ElementPtr() returned by getData().
+/// 
+/// This class always stores some basic information, given at 
+/// instantiation time. Apart from the getters and setters for these, 
+/// and the desctructor, this class should not have any methods that 
+/// contain logic.
+///
+class DataSrcTransaction {
+public:
+    enum states { INIT, RUNNING, DONE };
+
+    /// \brief Constructs a DataSrcTransaction object
+    ///
+    /// Constructs a DataSrcTransaction object for use with
+    /// the given data source, for operations on the given zone name,
+    /// in the given zone class. If the data source is a meta data
+    /// source that supports searching for the real one, the internal
+    /// data source pointer can be set to the data source that actually
+    /// holds the zone, but this will not happen until the transaction
+    /// is actually started (by the data sources startTransaction()
+    /// method).
+    ///
+    /// After construction, and upon success, the transaction state will
+    /// be INIT.
+    ///
+    /// \param data_source The data source to perform operations on
+    /// \param zone_name The name of the zone to perform operations on
+    /// \param zone_class The RRClass of the zone to perform operations on.
+    ///
+    explicit DataSrcTransaction(DataSrc *data_source,
+                                const isc::dns::Name& zone_name,
+                                const isc::dns::RRClass& zone_class) :
+                                _zone_name(zone_name),
+                                _zone_class(zone_class),
+                                _data_source(data_source),
+                                _state(INIT) {};
+    ~DataSrcTransaction();
+
+    /// \brief Returns the current state of the transaction
+    /// \return state
+    states getState() { return (_state); };
+
+    /// \brief Sets the state of the transaction
+    /// \param state The new state
+    void setState(states state) { _state = state; };
+
+    /// \brief Returns the base ElementPtr of the arbitrary
+    /// datasource-specific data.
+    /// The exact contents of this ptr are determined by the
+    /// data source, and no assumptions about its contents should
+    /// be made by the caller.
+    /// \return ElementPtr containing transaction data
+    isc::data::ElementPtr getData() { return (_data); };
+
+    /// \brief Sets the base ElementsPtr of the arbitrary
+    /// datasource-specific data. This function should only be called
+    /// by the data source, in the startTransaction() method.
+    /// \param data The ElementPtr containing data
+    void setData(isc::data::ElementPtr data) { _data = data; };
+    
+    /// \brief Returns the name of the zone this transaction is about
+    /// \return zone name
+    const isc::dns::Name getZoneName() { return (_zone_name); };
+
+    /// \brief Returns the RRClass of the zone this transaction is about
+    /// \return RRClass
+    const isc::dns::RRClass getZoneClass() { return (_zone_class); };
+
+    /// \brief Returns a pointer to the data source the zone was
+    /// found in.
+    /// \return DataSrc* pointer
+    DataSrc* getDataSource() { return (_data_source); };
+
+private:
+    isc::data::ElementPtr _data;
+    isc::dns::Name _zone_name;
+    isc::dns::RRClass _zone_class;
+    DataSrc* _data_source;
+    states _state;
 };
 
 // Base class for a DNS Data Source
@@ -224,6 +390,169 @@
                                     std::string& hash,
                                     isc::dns::RRsetList& target) const = 0;
 
+    //
+    // Writable data sources
+    //
+
+    // For datasource that support writing, overwrite these
+    // methods. By default they will return NOT_IMPLEMENTED.
+    ///
+    /// \name Low-level functions for writable data sources.
+    /// The default functions for these return W_NOT_IMPLEMENTED
+    /// A data source that supports writes MUST override these methods.
+    ///
+    //@{
+    /// \brief Start a transaction
+    /// The given transaction must be in state INIT.
+    /// On success, the transaction must be in state RUNNING.
+    /// The data source checks for the zone, starts a backend
+    /// transaction, and sets any data it will need later in the given
+    /// transaction object.
+    /// \param transaction, a transaction that has been initialized for
+    /// a zone held by this data source.
+    /// \param create_zone, if set to true, and the zone does not exist,
+    ///        it is created within this transaction. If false, and
+    ///        the zone does not exist, W_NO_SUCH_ZONE is returned
+    ///        (defaults to false)
+    /// \return W_SUCCESS on success, error value otherwise
+    virtual WriteResult startTransaction(DataSrcTransaction&  transaction,
+                                         const bool create_zone = false);
+
+    /// \brief Commit the changes made in the given transaction
+    /// The transaction object must be in state RUNNING.
+    /// On successs, the transaction will be in state DONE.
+    /// \param transaction to be committed.
+    /// \return W_SUCCESS on success, error value otherwise
+    virtual WriteResult commitTransaction(DataSrcTransaction&  transaction);
+    
+    /// \brief Roll back the changes made in the given transaction
+    /// The transaction object must be in state RUNNING.
+    /// On successs, the transaction will be in state DONE.
+    /// \param transaction to be rolled back.
+    /// \return W_SUCCESS on success, error value otherwise
+    virtual WriteResult rollbackTransaction(DataSrcTransaction&  transaction);
+
+    /// \brief Add an RRset to the zone.
+    /// \param transaction The transaction in which this addition is
+    ///        performed.
+    /// \param RRsetPtr The RRset to add
+    /// \param W_SUCCESS on success.
+    virtual WriteResult addRRset(DataSrcTransaction&  transaction,
+                                       isc::dns::ConstRRsetPtr rrset);
+    
+    /// \brief Delete an RRset from the zone.
+    /// If the given RRset contains no Rdata parts, all RRs with
+    /// the name, type and class of the given rrset are deleted.
+    /// Otherwise every RR in the given RRset is deleted.
+    /// \param transaction The transaction in which this deletion is
+    ///        performed.
+    /// \param RRsetPtr The RRset to delete
+    /// \param W_SUCCESS on success.
+    virtual WriteResult delRRset(DataSrcTransaction&  transaction,
+                                       isc::dns::ConstRRsetPtr rrset);
+                                       
+    /// \brief Remove an entire zone from the data source
+    /// Removes all records for the zone, and the zone itself from
+    /// the datasource.
+    /// \param transaction The transaction in which this deletion is
+    ///        performed.
+    /// \return W_SUCCESS on success
+    virtual WriteResult delZone(DataSrcTransaction&  transaction);
+
+    /// \brief Replace the contents of the zone by the rrsets returned
+    /// by the given function callback.
+    /// The provided function should return exactly one RRsetPtr on
+    /// each call.
+    /// This deletes *all* current RRsets in the zone.
+    ///
+    /// Note: Developers do not necessarily need to write a callback
+    /// function. For instance, if you have an std::vector of rrsets,
+    /// you can use the helper function callbackHelperRRsetVector. See its
+    /// description for more information on how to do this.
+    ///
+    /// \param transaction The transaction to perform the operations on
+    /// \param nextRRset Function that returns the next RRsetPtr on
+    ///        each call to it
+    /// \param arg1 This will be passed as the first argument to the
+    ///             nextRRset function (defaults to NULL)
+    /// \param arg2 This will be passed as the second argument to the
+    ///             nextRRset function (defaults to NULL)
+    /// \return W_SUCCESS on success
+    virtual WriteResult replaceZone(DataSrcTransaction& transaction,
+                                    isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+                                    void* arg1 = NULL, void* arg2 = NULL);
+    //@}
+
+    /// \name High-level functions for writable data sources
+    /// These functions have a default implementation, and do not
+    /// need to be overridden. They can be, if the data source can make
+    /// use of specific knowledge about the backend.
+    
+    //@{
+    /// \brief Checks whether an RRset exists
+    /// If the given RRset is of class NONE or ANY, it will check if
+    /// there are rrs for class IN.
+    /// If the given rrset has 0 rdata records; any RR with the given
+    /// name, class and type will result in a positive result,
+    /// otherwise the rrset in the data source must be exactly the
+    /// same.
+    ///
+    /// \param transaction The transaction in which the check is
+    /// performed
+    /// \param rrset The rrset to check for
+    /// \return true if the given rrset is equal to the one in the
+    ///         database, or if the given rrset has a 0 rdata count,
+    ///         and there are records in the database with the same
+    ///         name, class and type. False otherwise.
+    virtual bool haveRRset(DataSrcTransaction& transaction,
+                           isc::dns::ConstRRsetPtr rrset);
+
+    /// \brief Checks a prerequisite as described in section 3.2 of
+    /// RFC2136
+    /// \param transaction The transaction to perform the check in
+    /// \param prereq the prerequisite RRset
+    /// \return The DNS Rcode as defined by the rules in RFC2136
+    virtual isc::dns::Rcode updateCheckPrerequisite(DataSrcTransaction& transaction,
+                                            isc::dns::ConstRRsetPtr prereq);
+
+    /// \brief Handles a single update as described in section 3.4 of
+    /// RFC2136
+    /// \param transaction The transaction to perform the update
+    /// \param update the update RRset
+    /// \return The DNS Rcode as defined by the rules in RFC2136
+    virtual isc::dns::Rcode updateProcessUpdate(DataSrcTransaction& transaction,
+                                        isc::dns::RRsetPtr update);
+
+    /// \brief Perform an IXFR operation with data provided by the
+    ///        given function.
+    /// The given function should return one RRset at a time, which
+    /// are processed as described in RFC1995
+    ///
+    /// Note: if you have an isc::dns::Message object that contains
+    /// a complete IXFR response, you can use the helper function
+    /// callbackHelperRRsetIterator() here. See the documentation
+    /// of that function on how to use it.
+    ///
+    /// \param transaction the transaction to perform the operation in
+    /// \param nextRRset Function that returns the next RRsetPtr on
+    ///        each call to it
+    /// \param arg1 This will be passed as the first argument to the
+    ///             nextRRset function (defaults to NULL)
+    /// \param arg2 This will be passed as the second argument to the
+    ///             nextRRset function (defaults to NULL)
+    /// \return SUCCESS on success, ERROR on failure.
+    virtual Result doIXFR(DataSrcTransaction& transaction,
+                          isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+                          void* arg1 = NULL, void* arg2 = NULL);
+
+    /// \brief Perform a DNS Dynamic update as desribed in RFC2136
+    /// \param transaction the transaction to perform the udpate in
+    /// \param msg the DNS UPDATE msg to handle
+    /// \return SUCCESS on success, ERROR on failure.
+    virtual Result doUpdate(DataSrcTransaction& transaction,
+                            isc::dns::Message& msg);
+    //@}
+
 private:
     isc::dns::RRClass rrclass;
 };
@@ -292,6 +621,24 @@
                                     std::string& hash,
                                     isc::dns::RRsetList& target) const;
 
+    //
+    // Writable data sources
+    //
+    // see comments at DataSrc
+    virtual WriteResult startTransaction(DataSrcTransaction&  transaction,
+                                         const bool create_zone);
+    virtual WriteResult commitTransaction(DataSrcTransaction&  transaction);
+    virtual WriteResult rollbackTransaction(DataSrcTransaction&  transaction);
+    
+    virtual WriteResult addRRset(DataSrcTransaction&  transaction,
+                                       isc::dns::ConstRRsetPtr rrset);
+    virtual WriteResult delRRset(DataSrcTransaction&  transaction,
+                                       isc::dns::ConstRRsetPtr rrset);
+    virtual WriteResult delZone(DataSrcTransaction&  transaction);
+    virtual WriteResult replaceZone(DataSrcTransaction& transaction,
+                                    isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+                                    void* arg1 = NULL, void* arg2 = NULL);
+    // end of writable data sources part
 private:
     std::vector<ConstDataSrcPtr> data_sources;
 };

Modified: branches/trac374/src/lib/datasrc/sqlite3_datasrc.cc
==============================================================================
--- branches/trac374/src/lib/datasrc/sqlite3_datasrc.cc (original)
+++ branches/trac374/src/lib/datasrc/sqlite3_datasrc.cc Tue Oct 19 08:16:09 2010
@@ -14,6 +14,8 @@
 
 // $Id$
 
+#include <config.h>
+
 #include <string>
 #include <sstream>
 
@@ -27,6 +29,10 @@
 #include <dns/rrset.h>
 #include <dns/rrsetlist.h>
 
+#include <cc/data.h>
+
+#include <boost/foreach.hpp>
+
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
@@ -38,7 +44,8 @@
     Sqlite3Parameters() :  db_(NULL), version_(-1),
         q_zone_(NULL), q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
         q_any_(NULL), q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
-        q_prevnsec3_(NULL)
+        q_prevnsec3_(NULL), w_add_rr_(NULL), w_add_zone_(NULL), w_del_rr_(NULL),
+        w_del_all_(NULL), w_del_zone_(NULL)
     {}
     sqlite3* db_;
     int version_;
@@ -51,6 +58,11 @@
     sqlite3_stmt* q_previous_;
     sqlite3_stmt* q_nsec3_;
     sqlite3_stmt* q_prevnsec3_;
+    sqlite3_stmt* w_add_rr_;
+    sqlite3_stmt* w_add_zone_;
+    sqlite3_stmt* w_del_rr_;
+    sqlite3_stmt* w_del_all_;
+    sqlite3_stmt* w_del_zone_;
 };
 
 namespace {
@@ -111,7 +123,30 @@
 const char* const q_prevnsec3_str = "SELECT hash FROM nsec3 "
     "WHERE zone_id = ?1 AND hash <= $2 ORDER BY hash DESC LIMIT 1";
 
-}
+const char* const w_add_rr_str = "INSERT INTO records "
+    "(zone_id, name, rname, ttl, rdtype, sigtype, rdata) "
+    "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)";
+
+const char* const w_add_zone_str = "INSERT INTO zones "
+    "(name, rdclass) "
+    "VALUES (?1, ?2)";
+
+const char* const w_del_rr_str = "DELETE FROM records WHERE "
+    "zone_id = ?1 AND name = ?2 AND rdtype LIKE ?3 AND rdata LIKE ?4";
+
+const char* const w_del_all_str = "DELETE FROM records WHERE zone_id = ?1";
+
+const char* const w_del_zone_str = "DELETE FROM zones WHERE id = ?1";
+
+const char* const w_start_transaction_str = "BEGIN TRANSACTION";
+
+const char* const w_commit_transaction_str = "COMMIT TRANSACTION";
+
+const char* const w_rollback_transaction_str = "ROLLBACK TRANSACTION";
+
+
+}
+
 
 //
 //  Find the exact zone match.  Return -1 if not found, or the zone's
@@ -605,6 +640,12 @@
         if (params_.q_prevnsec3_ != NULL) {
             sqlite3_finalize(params_.q_prevnsec3_);
         }
+        if (params_.w_add_rr_ != NULL) {
+            sqlite3_finalize(params_.w_add_rr_);
+        }
+        if (params_.w_del_rr_ != NULL) {
+            sqlite3_finalize(params_.w_del_rr_);
+        }
         if (params_.db_ != NULL) {
             sqlite3_close(params_.db_);
         }
@@ -658,6 +699,12 @@
     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);
+    initializer->params_.w_add_rr_ = prepare(db, w_add_rr_str);
+    initializer->params_.w_add_zone_ = prepare(db, w_add_zone_str);
+    initializer->params_.w_del_rr_ = prepare(db, w_del_rr_str);
+    initializer->params_.w_del_all_ = prepare(db, w_del_all_str);
+    initializer->params_.w_del_zone_ = prepare(db, w_del_zone_str);
+    
 }
 }
 
@@ -672,8 +719,9 @@
 
     Sqlite3Initializer initializer;
 
-    if (sqlite3_open(name.c_str(), &initializer.params_.db_) != 0) {
-        isc_throw(Sqlite3Error, "Cannot open SQLite database file: " << name);
+    int sq_result = sqlite3_open(name.c_str(), &initializer.params_.db_);
+    if (sq_result != 0) {
+        isc_throw(Sqlite3Error, "Cannot open SQLite database file " << name << ": error " << sq_result << " (" << sqlite3_errmsg(dbparameters->db_) << ")");
     }
 
     checkAndSetupSchema(&initializer);
@@ -718,11 +766,454 @@
     sqlite3_finalize(dbparameters->q_nsec3_);
     dbparameters->q_nsec3_ = NULL;
 
+    sqlite3_finalize(dbparameters->w_add_rr_);
+    dbparameters->w_add_rr_ = NULL;
+
+    sqlite3_finalize(dbparameters->w_add_zone_);
+    dbparameters->w_add_zone_ = NULL;
+
+    sqlite3_finalize(dbparameters->w_del_rr_);
+    dbparameters->w_del_rr_ = NULL;
+
+    sqlite3_finalize(dbparameters->w_del_all_);
+    dbparameters->w_del_rr_ = NULL;
+
+    sqlite3_finalize(dbparameters->w_del_zone_);
+    dbparameters->w_del_rr_ = NULL;
+
     sqlite3_close(dbparameters->db_);
     dbparameters->db_ = NULL;
 
     return (SUCCESS);
 }
 
-}
-}
+DataSrc::WriteResult
+Sqlite3DataSrc::startTransaction(DataSrcTransaction& transaction, bool new_zone)
+{
+    if (transaction.getState() != DataSrcTransaction::INIT) {
+        return DataSrc::W_ERROR;
+    }
+    int result = sqlite3_exec(dbparameters->db_, w_start_transaction_str, NULL, NULL, NULL);
+    if (result == SQLITE_OK) {
+        int zone_id = hasExactZone(transaction.getZoneName().toText().c_str());
+        if (zone_id <= 0) {
+            if (new_zone) {
+                DataSrc::WriteResult add_zone_res = 
+                    addZone(transaction.getZoneName(), isc::dns::RRClass::IN());
+                if (add_zone_res != DataSrc::W_SUCCESS) {
+                    return add_zone_res;
+                }
+                zone_id = hasExactZone(transaction.getZoneName().toText().c_str());
+            } else {
+                return DataSrc::W_NO_SUCH_ZONE;
+            }
+        }
+        isc::data::ElementPtr trans_data = isc::data::Element::createMap();
+        trans_data->set("zone_id", isc::data::Element::create(zone_id));
+        transaction.setData(trans_data);
+        transaction.setState(DataSrcTransaction::RUNNING);
+        return DataSrc::W_SUCCESS;
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::commitTransaction(DataSrcTransaction& transaction UNUSED_PARAM)
+{
+    if (transaction.getState() != DataSrcTransaction::RUNNING) {
+        return DataSrc::W_ERROR;
+    }
+
+    int result = sqlite3_exec(dbparameters->db_, w_commit_transaction_str, NULL, NULL, NULL);
+    
+    if (result == SQLITE_OK) {
+        transaction.setState(DataSrcTransaction::DONE);
+        return DataSrc::W_SUCCESS;
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::rollbackTransaction(DataSrcTransaction& transaction)
+{
+    if (transaction.getState() != DataSrcTransaction::RUNNING) {
+        return DataSrc::W_ERROR;
+    }
+    int result = sqlite3_exec(dbparameters->db_, w_rollback_transaction_str, NULL, NULL, NULL);
+
+    if (result == SQLITE_OK) {
+        transaction.setState(DataSrcTransaction::DONE);
+        return DataSrc::W_SUCCESS;
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::addZone(const isc::dns::Name& name,
+                        const isc::dns::RRClass& rrclass)
+{
+    sqlite3_stmt* query = dbparameters->w_add_zone_;
+    
+    sqlite3_reset(query);
+    sqlite3_clear_bindings(query);
+
+    const string s_name = name.toText();
+    int rc;
+    rc = sqlite3_bind_text(query, 1, s_name.c_str(), -1, SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+                  " to SQL statement (query)");
+    }
+    
+    rc = sqlite3_bind_text(query, 2, rrclass.toText().c_str(), -1,
+                           SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind zone class " <<
+                  rrclass.toText() << " to SQL statement (query)");
+    }
+
+    int result = sqlite3_step(query);
+
+    sqlite3_reset(query);
+
+    if (result == SQLITE_DONE) {
+        return DataSrc::W_SUCCESS;
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::addRR(int zone_id,
+                      const isc::dns::Name& name,
+                      const isc::dns::RRType& rrtype,
+                      const isc::dns::RRTTL& rrttl,
+                      const isc::dns::rdata::Rdata& rdata)
+{
+
+    sqlite3_stmt* query = dbparameters->w_add_rr_;
+    
+    sqlite3_reset(query);
+    sqlite3_clear_bindings(query);
+
+    int rc;
+    rc = sqlite3_bind_int(query, 1, zone_id);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+                  " to SQL statement (query)");
+    }
+    const string s_name = name.toText();
+    rc = sqlite3_bind_text(query, 2, s_name.c_str(), -1, SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+                  " to SQL statement (query)");
+    }
+
+    // reverse name
+    const string r_name = name.reverse().toText();
+    rc = sqlite3_bind_text(query, 3, r_name.c_str(), -1, SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+                  " to SQL statement (query)");
+    }
+
+    rc = sqlite3_bind_int(query, 4, rrttl.getValue());
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind RR TTL " <<
+                  rrttl.toText() << " to SQL statement (query)");
+    }
+
+    rc = sqlite3_bind_text(query, 5, rrtype.toText().c_str(), -1,
+                           SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind RR type " <<
+                  rrtype.toText() << " to SQL statement (query)");
+    }
+
+    // TODO: sigtype
+
+    rc = sqlite3_bind_text(query, 7, rdata.toText().c_str(), -1,
+                           SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind RR rdata " <<
+                  rdata.toText() << " to SQL statement (query)");
+    }
+
+    int result = sqlite3_step(query);
+
+    sqlite3_reset(query);
+
+    if (result == SQLITE_DONE) {
+        return DataSrc::W_SUCCESS;
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::addRRset(DataSrcTransaction& transaction,
+                         isc::dns::ConstRRsetPtr rrset)
+{
+    if (transaction.getState() != DataSrcTransaction::RUNNING) {
+        return DataSrc::W_ERROR;
+    }
+    if (rrset->getRdataCount() == 0) {
+        return DataSrc::W_ERROR;
+    }
+    int zone_id = transaction.getData()->get("zone_id")->intValue();
+    RdataIteratorPtr rdp = rrset->getRdataIterator();
+    rdp->first();
+
+    DataSrc::WriteResult result = DataSrc::W_SUCCESS;
+    do {
+        result = addRR(zone_id, rrset->getName(), rrset->getType(),
+                       rrset->getTTL(), rdp->getCurrent());
+        if (result != DataSrc::W_SUCCESS) {
+            return result;
+        } else {
+            rdp->next();
+        }
+    } while (!rdp->isLast());
+    return result;
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delRR(int zone_id,
+                      const isc::dns::Name& name,
+                      const isc::dns::RRType& rrtype)
+{
+    sqlite3_stmt* query = dbparameters->w_del_rr_;
+    
+    sqlite3_reset(query);
+    sqlite3_clear_bindings(query);
+
+    int rc;
+    rc = sqlite3_bind_int(query, 1, zone_id);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+                  " to SQL statement (query)");
+    }
+    const string s_name = name.toText();
+    rc = sqlite3_bind_text(query, 2, s_name.c_str(), -1, SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+                  " to SQL statement (query)");
+    }
+
+    if (rrtype == RRType::ANY()) {
+        rc = sqlite3_bind_text(query, 3, "%", -1,
+                               SQLITE_STATIC);
+    } else {
+        rc = sqlite3_bind_text(query, 3, rrtype.toText().c_str(), -1,
+                               SQLITE_TRANSIENT);
+    }
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind RR type " <<
+                  rrtype.toText() << " to SQL statement (query)");
+    }
+
+    rc = sqlite3_bind_text(query, 4, "%", -1,
+                           SQLITE_STATIC);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind '%' rdata "
+                                " to SQL statement (query)");
+    }
+
+    int result = sqlite3_step(query);
+    int deleted_rows =  sqlite3_total_changes(dbparameters->db_);
+
+    sqlite3_reset(query);
+
+    if (result == SQLITE_DONE) {
+        if (deleted_rows > 0) {
+            return DataSrc::W_SUCCESS;
+        } else {
+            return DataSrc::W_NO_SUCH_DATA;
+        }
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delRR(int zone_id,
+                      const isc::dns::Name& name,
+                      const isc::dns::RRType& rrtype,
+                      const isc::dns::rdata::Rdata& rdata)
+{
+    sqlite3_stmt* query = dbparameters->w_del_rr_;
+    
+    sqlite3_reset(query);
+    sqlite3_clear_bindings(query);
+
+    int rc;
+    rc = sqlite3_bind_int(query, 1, zone_id);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+                  " to SQL statement (query)");
+    }
+    const string s_name = name.toText();
+    rc = sqlite3_bind_text(query, 2, s_name.c_str(), -1, SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind name " << s_name <<
+                  " to SQL statement (query)");
+    }
+
+    rc = sqlite3_bind_text(query, 3, rrtype.toText().c_str(), -1,
+                           SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind RR type " <<
+                  rrtype.toText() << " to SQL statement (query)");
+    }
+
+    rc = sqlite3_bind_text(query, 4, rdata.toText().c_str(), -1,
+                           SQLITE_TRANSIENT);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind RR rdata " <<
+                  rdata.toText() << " to SQL statement (query)");
+    }
+
+    int result = sqlite3_step(query);
+    int deleted_rows =  sqlite3_total_changes(dbparameters->db_);
+
+    sqlite3_reset(query);
+
+    if (result == SQLITE_DONE) {
+        if (deleted_rows > 0) {
+            return DataSrc::W_SUCCESS;
+        } else {
+            return DataSrc::W_NO_SUCH_DATA;
+        }
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delAll(int zone_id)
+{
+    sqlite3_stmt* query = dbparameters->w_del_all_;
+    
+    sqlite3_reset(query);
+    sqlite3_clear_bindings(query);
+
+    int rc;
+    rc = sqlite3_bind_int(query, 1, zone_id);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+                  " to SQL statement (query)");
+    }
+
+    int result = sqlite3_step(query);
+
+    sqlite3_reset(query);
+
+    if (result == SQLITE_DONE) {
+        return DataSrc::W_SUCCESS;
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::replaceZone(DataSrcTransaction& transaction,
+                            isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+                            void* arg1, void* arg2)
+{
+    if (transaction.getState() != DataSrcTransaction::RUNNING) {
+        return DataSrc::W_ERROR;
+    }
+
+    int zone_id = transaction.getData()->get("zone_id")->intValue();
+
+    DataSrc::WriteResult result = delAll(zone_id);
+    if (result != DataSrc::W_SUCCESS) {
+        return result;
+    }
+
+    if (!nextRRset) {
+        return DataSrc::W_SUCCESS;
+    }
+    RRsetPtr next_rrset = nextRRset(arg1, arg2);
+    while (next_rrset) {
+        result = addRRset(transaction, next_rrset);
+        if (result != DataSrc::W_SUCCESS) {
+            return result;
+        }
+        next_rrset = nextRRset(arg1, arg2);
+    }
+    return DataSrc::W_SUCCESS;
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delZone(DataSrcTransaction& transaction)
+{
+    if (transaction.getState() != DataSrcTransaction::RUNNING) {
+        return DataSrc::W_ERROR;
+    }
+
+    int zone_id = transaction.getData()->get("zone_id")->intValue();
+
+    DataSrc::WriteResult result = delAll(zone_id);
+    if (result != DataSrc::W_SUCCESS) {
+        return result;
+    }
+
+    sqlite3_stmt* query = dbparameters->w_del_zone_;
+    
+    sqlite3_reset(query);
+    sqlite3_clear_bindings(query);
+
+    int rc;
+    rc = sqlite3_bind_int(query, 1, zone_id);
+    if (rc != SQLITE_OK) {
+        isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
+                  " to SQL statement (query)");
+    }
+
+    int q_result = sqlite3_step(query);
+
+    sqlite3_reset(query);
+
+    if (q_result == SQLITE_DONE) {
+        return DataSrc::W_SUCCESS;
+    } else {
+        return DataSrc::W_DB_ERROR;
+    }
+}
+
+DataSrc::WriteResult
+Sqlite3DataSrc::delRRset(DataSrcTransaction& transaction,
+                         isc::dns::ConstRRsetPtr rrset)
+{
+    if (transaction.getState() != DataSrcTransaction::RUNNING) {
+        return DataSrc::W_ERROR;
+    }
+
+    int zone_id = transaction.getData()->get("zone_id")->intValue();
+    
+    DataSrc::WriteResult result = DataSrc::W_SUCCESS;
+
+    if (rrset->getRdataCount() > 0) {
+        RdataIteratorPtr rdp = rrset->getRdataIterator();
+        rdp->first();
+        do {
+            result = delRR(zone_id, rrset->getName(), rrset->getType(), rdp->getCurrent());
+            if (result != DataSrc::W_SUCCESS) {
+                return result;
+            } else {
+                rdp->next();
+            }
+        } while (!rdp->isLast());
+    } else {
+        result = delRR(zone_id, rrset->getName(), rrset->getType());
+    }
+    return result;
+}
+
+}
+}

Modified: branches/trac374/src/lib/datasrc/sqlite3_datasrc.h
==============================================================================
--- branches/trac374/src/lib/datasrc/sqlite3_datasrc.h (original)
+++ branches/trac374/src/lib/datasrc/sqlite3_datasrc.h Tue Oct 19 08:16:09 2010
@@ -22,6 +22,12 @@
 #include <exceptions/exceptions.h>
 
 #include <datasrc/data_source.h>
+
+#include <dns/name.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+#include <dns/message.h>
 
 namespace isc {
 
@@ -98,6 +104,40 @@
     Result init(const isc::data::ConstElementPtr config);
     Result close();
 
+
+    // write access
+
+    WriteResult startTransaction(DataSrcTransaction& transaction,
+                                 bool create_zone = false);
+    WriteResult commitTransaction(DataSrcTransaction& transaction);
+    WriteResult rollbackTransaction(DataSrcTransaction& transaction);
+    
+    WriteResult addRRset(DataSrcTransaction& transaction,
+                               isc::dns::ConstRRsetPtr rrset);
+    WriteResult delRRset(DataSrcTransaction& transaction,
+                               isc::dns::ConstRRsetPtr rrset);
+    WriteResult replaceZone(DataSrcTransaction& transaction,
+                            isc::dns::RRsetPtr (*nextRRset)(void*, void*),
+                            void* arg1 = NULL, void* arg2 = NULL);
+    WriteResult delZone(DataSrcTransaction& transaction);
+
+private:
+    DataSrc::WriteResult addRR(int zone_id,
+                          const isc::dns::Name& name,
+                          const isc::dns::RRType& rrtype,
+                          const isc::dns::RRTTL& rrttl,
+                          const isc::dns::rdata::Rdata& rdata);
+    DataSrc::WriteResult addZone(const isc::dns::Name& name,
+                                 const isc::dns::RRClass& rrclass);
+    DataSrc::WriteResult delRR(int zone_id,
+                                     const isc::dns::Name& name,
+                                     const isc::dns::RRType& rrtype);
+    DataSrc::WriteResult delRR(int zone_id,
+                                     const isc::dns::Name& name,
+                                     const isc::dns::RRType& rrtype,
+                                     const isc::dns::rdata::Rdata& rdata);
+    DataSrc::WriteResult delAll(int zone_id);
+
 private:
     enum Mode {
         NORMAL,

Modified: branches/trac374/src/lib/datasrc/tests/Makefile.am
==============================================================================
--- branches/trac374/src/lib/datasrc/tests/Makefile.am (original)
+++ branches/trac374/src/lib/datasrc/tests/Makefile.am Tue Oct 19 08:16:09 2010
@@ -1,6 +1,8 @@
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_OUT_DIR=\"$(builddir)/testdata\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(top_srcdir)/install-sh\"
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -46,3 +48,5 @@
 EXTRA_DIST += testdata/sql2.example.com.signed
 EXTRA_DIST += testdata/test-root.sqlite3
 EXTRA_DIST += testdata/test.sqlite3
+EXTRA_DIST += testdata/ixfr_ok.rrs
+EXTRA_DIST += testdata/ixfr_bad_remove_nonexisting.rrs

Modified: branches/trac374/src/lib/datasrc/tests/sqlite3_unittest.cc
==============================================================================
--- branches/trac374/src/lib/datasrc/tests/sqlite3_unittest.cc (original)
+++ branches/trac374/src/lib/datasrc/tests/sqlite3_unittest.cc Tue Oct 19 08:16:09 2010
@@ -14,18 +14,23 @@
 
 // $Id$
 
+#include <config.h>
+
 #include <stdint.h>
 
 #include <algorithm>
 #include <string>
 #include <vector>
+#include <fstream>
 
 #include <sqlite3.h>
 #include <gtest/gtest.h>
 
 #include <dns/name.h>
 #include <dns/message.h>
+#include <dns/opcode.h>
 #include <dns/rdata.h>
+#include <dns/rcode.h>
 #include <dns/rrclass.h>
 #include <dns/rrtype.h>
 #include <dns/rdataclass.h>
@@ -53,6 +58,8 @@
     "{ \"database_file\": \"" TEST_DATA_DIR "/brokendb.sqlite3\"}");
 ConstElementPtr SQLITE_DBFILE_MEMORY = Element::fromJSON(
     "{ \"database_file\": \":memory:\"}");
+ElementPtr SQLITE_DBFILE_WRITE = Element::fromJSON(
+    "{ \"database_file\": \"" TEST_DATA_OUT_DIR "/write_test.sqlite3\"}");
 
 // The following file must be non existent and must be non"creatable";
 // the sqlite3 library will try to create a new DB file if it doesn't exist,
@@ -102,7 +109,8 @@
     Sqlite3DataSourceTest() : rrclass(RRClass::IN()),
                               rrclass_notmatch(RRClass::CH()),
                               rrtype(RRType::A()), rrttl(RRTTL(3600)),
-                              find_flags(0), rrset_matched(0), rrset_count(0)
+                              find_flags(0), rrset_matched(0), rrset_count(0),
+                              zone_name("example.com")
     {
         data_source.init(SQLITE_DBFILE_EXAMPLE);
 
@@ -172,6 +180,13 @@
         nsec3_sig_data.push_back("NSEC3 5 4 7200 20100410172647 "
                                  "20100311172647 63192 sql2.example.com. "
                                  + nsec3_signature);
+
+        // initialize data for writable tests
+        new_rrset = RRsetPtr(new RRset(Name("new_rr.example.com"),
+                                       RRClass::IN(),
+                                       RRType::A(),
+                                       RRTTL(3600)));
+        new_rrset->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.3")); 
     }
     Sqlite3DataSrc data_source;
     // we allow these to be modified in the test
@@ -227,6 +242,10 @@
     vector<string> child_sig_data;
     vector<string> nsec3_data;
     vector<string> nsec3_sig_data;
+
+    // common data for writable tests
+    Name zone_name;
+    RRsetPtr new_rrset;
 
     void findReferralRRsetCommon(const Name& qname, const RRClass& qclass);
     void findAddressRRsetCommon(const RRClass& qclass);
@@ -935,4 +954,962 @@
     findAddressRRsetCommon(RRClass::ANY());
 }
 
-}
+//
+// We copy one of the example databases to a writable one
+// (the source one may not be writable, and we modify it, so if
+// one of the tests fail halfway, we want to restart with a clean
+// database next time)
+//
+int
+install_writable_database()
+{
+    return system(INSTALL_PROG " " TEST_DATA_DIR "/test.sqlite3 " TEST_DATA_OUT_DIR "/write_test.sqlite3" );
+}
+
+TEST_F(Sqlite3DataSourceTest, Transactions) {
+    // first writables test, reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+    
+    DataSrcTransaction transaction(&data_source, zone_name, RRClass::IN());
+    EXPECT_EQ(DataSrcTransaction::INIT, transaction.getState());
+    
+    // start doing things without calling startTransaction()
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.commitTransaction(transaction));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.rollbackTransaction(transaction));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.addRRset(transaction, new_rrset));
+    RRsetIterator rrset_it = RRsetIterator();
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.replaceZone(transaction, NULL));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.delZone(transaction));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.delRRset(transaction, new_rrset));
+    // need rrsetit for doIXFR (but doIXFR should probably be moved up to datasource itself)
+
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.startTransaction(transaction));
+    EXPECT_EQ(DataSrcTransaction::RUNNING, transaction.getState());
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.startTransaction(transaction));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.rollbackTransaction(transaction));
+    EXPECT_EQ(DataSrcTransaction::DONE, transaction.getState());
+
+    // state now 'done', everything should error again
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.commitTransaction(transaction));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.rollbackTransaction(transaction));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.addRRset(transaction, new_rrset));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.replaceZone(transaction, NULL));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.delZone(transaction));
+    EXPECT_EQ(DataSrc::W_ERROR,
+              data_source.delRRset(transaction, new_rrset));
+}
+
+TEST_F(Sqlite3DataSourceTest, addRRset) {
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    // check whether our new record does not exist
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+
+    // add it, but roll back
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.startTransaction(transaction1));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.addRRset(transaction1, new_rrset));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.rollbackTransaction(transaction1));
+    
+    // check whether our new record does not exist
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+
+    // add it with commit
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.startTransaction(transaction2));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.addRRset(transaction2, new_rrset));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.commitTransaction(transaction2));
+
+    // now check whether it does exist
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(result_sets.size(), 1);
+}
+
+TEST_F(Sqlite3DataSourceTest, delRRset) {
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    // check whether our new record exists (added by previous test)
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(result_sets.size(), 1);
+
+    // add it, but roll back
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.startTransaction(transaction1));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.delRRset(transaction1, new_rrset));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.rollbackTransaction(transaction1));
+
+    // check whether our new record still exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(result_sets.size(), 1);
+
+    // add it, and commit
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.startTransaction(transaction2));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.delRRset(transaction2, new_rrset));
+    EXPECT_EQ(DataSrc::W_SUCCESS,
+              data_source.commitTransaction(transaction2));
+
+    // now check whether it does not exist now
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findAddrs(new_rrset->getName(), new_rrset->getClass(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+}
+
+TEST_F(Sqlite3DataSourceTest, replaceZone_container) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    // check whether an A exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+    // Replace them, roll back
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction1, NULL));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+    // check whether it still exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+    // Replace them, commit
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction2, NULL));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+    
+    // check whether it's gone now
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+}
+
+RRsetPtr
+getRRsetCallback_empty(void *arg1 UNUSED_PARAM,
+                       void* arg2 UNUSED_PARAM) {
+    return RRsetPtr();
+}
+
+TEST_F(Sqlite3DataSourceTest, replaceZone_callback_empty) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    // check whether an A exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+    // Replace them, roll back
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction1, getRRsetCallback_empty));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+    // check whether it still exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+    // Replace them, commit
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction2, getRRsetCallback_empty));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+    
+    // check whether it's gone now
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+}
+
+TEST_F(Sqlite3DataSourceTest, replaceZone_callback_vector) {
+    std::vector<RRsetPtr> rrsets;
+
+    size_t i = 0;
+    
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    // Let's take the existing SOA for now
+    RRsetList soa_rrset;
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findExactRRset(zone_name, RRClass::IN(), RRType::SOA(),
+                                    soa_rrset, find_flags, &zone_name));
+    ASSERT_EQ(DataSrc::SUCCESS, find_flags);
+    ASSERT_EQ(1, soa_rrset.size());
+
+    rrsets.push_back(soa_rrset.findRRset(RRType::SOA(), RRClass::IN()));
+    rrsets.push_back(new_rrset);
+
+    // Replace them, roll back
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction1, isc::datasrc::callbackHelperRRsetVector, &rrsets, &i));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+    // check whether it still exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+    // Replace them, commit
+    i = 0;
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.replaceZone(transaction2, isc::datasrc::callbackHelperRRsetVector, &rrsets, &i));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+    
+    // check whether the original rrs are gone now
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::NAME_NOT_FOUND, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+    
+    // and check if the new record exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findExactRRset(new_rrset->getName(), new_rrset->getClass(), new_rrset->getType(),
+                                    result_sets, find_flags, &zone_name));
+    //std::cout << "[XX] done, exit" << std::endl;
+    //data_source.close();
+    //exit(0);
+    ASSERT_EQ(DataSrc::SUCCESS, find_flags);
+    ASSERT_EQ(1, result_sets.size());
+}
+
+TEST_F(Sqlite3DataSourceTest, delZone) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    // check whether an A exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+    // Delete zone, roll back
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.delZone(transaction1));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+    // check whether it still exists
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+    // Delete zone, commit
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.delZone(transaction2));
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+    
+    // check whether it's gone now
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(www_name, RRClass::IN(), RRType::A(),
+                                    result_sets, find_flags, &zone_name));
+    EXPECT_EQ(DataSrc::NO_SUCH_ZONE, find_flags);
+    EXPECT_EQ(1, result_sets.size());
+
+    // try to start a transaction for the zone that is now gone
+    DataSrcTransaction transaction3(&data_source, zone_name, RRClass::IN());
+    EXPECT_EQ(DataSrc::W_NO_SUCH_ZONE, data_source.startTransaction(transaction3));
+    
+}
+
+
+static QuestionPtr
+questionFromFile(ifstream& file)
+{
+    string s;
+    file >> s;
+    if (s == "") {
+        return QuestionPtr();
+    }
+    if (s.size() > 0 && s[0] == ';') {
+        std::string ignore_rest_of_line;
+        getline(file, ignore_rest_of_line);
+        return QuestionPtr();
+    }
+    Name n(s);
+    file >> s;
+    RRClass rrclass(s);
+    file >> s;
+    RRType rrtype(s);
+    string line;
+    getline(file, line);
+
+    QuestionPtr question(new Question(n, rrclass, rrtype));
+    return question;
+}
+
+static RRsetPtr
+rrsetFromFile(ifstream& file)
+{
+    string s;
+    file >> s;
+    if (s == "") {
+        return RRsetPtr();
+    }
+    if (s.size() > 0 && s[0] == ';') {
+        std::string ignore_rest_of_line;
+        getline(file, ignore_rest_of_line);
+        return RRsetPtr();
+    }
+    Name n(s);
+
+    file >> s;
+    RRTTL ttl(s);
+
+    file >> s;
+    RRClass rrclass(0);
+    if (s == "ANY") {
+        rrclass = RRClass::ANY();
+    } else if (s == "NONE") {
+        rrclass = RRClass::NONE();
+    } else {
+        rrclass = RRClass(s);
+    }
+
+    file >> s;
+    RRType rrtype(0);
+    if (s == "ANY") {
+        rrtype = RRType::ANY();
+    } else {
+        rrtype = RRType(s);
+    }
+
+    RRsetPtr rrset = RRsetPtr(new RRset(n,
+                              rrclass,
+                              rrtype,
+                              ttl));
+    string line;
+    getline(file, line);
+    while (line[0] == ' ' || line[0] == '\t') {
+        line.erase(0, 1);
+    }
+
+    if (line != "") {
+        RdataPtr rdata = createRdata(rrtype, rrclass, line);
+        rrset->addRdata(rdata);
+    }
+    return rrset;
+}
+
+// initialize the message with flags and codes,
+// read a list of rrs and put them in that order in the answer
+// section of the given message
+
+// reads rrsets from the given file into the given section
+// stops at eof or if empty line or line starting with ; is
+// read
+// returns 1 if empty line/comment has been seen
+// returns 0 if eof reached
+
+int
+rrsetsFromFile(ifstream& file, Message& msg, Section section)
+{
+    RRsetPtr prev_rrset = RRsetPtr();
+    while (! file.eof() ) {
+        RRsetPtr rrset = rrsetFromFile(file);
+        if (!rrset) {
+            if (prev_rrset) {
+                msg.addRRset(section, prev_rrset);
+            }
+            return 1;
+        }
+        if (prev_rrset) {
+            if (rrset) {
+                if (prev_rrset->getName() == rrset->getName() &&
+                    prev_rrset->getType() == rrset->getType() &&
+                    prev_rrset->getType() != RRType::SOA() &&
+                    prev_rrset->getClass() == rrset->getClass()) {
+                    RdataIteratorPtr it = rrset->getRdataIterator();
+                    for (it->first(); !it->isLast(); it->next()) {
+                        prev_rrset->addRdata(it->getCurrent());
+                    }
+                    rrset = RRsetPtr();
+                }
+            }
+            msg.addRRset(section, prev_rrset);
+            prev_rrset = rrset;
+        } else {
+            prev_rrset = rrset;
+        }
+    }
+    return 0;
+}
+
+int
+rrsetsFromFile(ifstream& file, std::vector<RRsetPtr>& container)
+{
+    RRsetPtr prev_rrset = RRsetPtr();
+    while (! file.eof() ) {
+        RRsetPtr rrset = rrsetFromFile(file);
+        if (!rrset) {
+            if (prev_rrset) {
+                container.push_back(prev_rrset);
+            }
+            return 1;
+        }
+        if (prev_rrset) {
+            if (rrset) {
+                if (prev_rrset->getName() == rrset->getName() &&
+                    prev_rrset->getType() == rrset->getType() &&
+                    prev_rrset->getType() != RRType::SOA() &&
+                    prev_rrset->getClass() == rrset->getClass()) {
+                    RdataIteratorPtr it = rrset->getRdataIterator();
+                    for (it->first(); !it->isLast(); it->next()) {
+                        prev_rrset->addRdata(it->getCurrent());
+                    }
+                    rrset = RRsetPtr();
+                }
+            }
+            container.push_back(prev_rrset);
+            prev_rrset = rrset;
+        } else {
+            prev_rrset = rrset;
+        }
+    }
+    return 0;
+}
+
+int
+rrsetsFromFile(const char* file_name, std::vector<RRsetPtr>& container)
+{
+    ifstream myfile(file_name);
+    if (myfile.is_open()) {
+        rrsetsFromFile(myfile, container);
+        myfile.close();
+    } else {
+        return -1;
+    }
+    return 0;
+}
+
+static int
+ixfrFromFile(Message& m, const char* file_name) {
+    ifstream myfile(file_name);
+
+    m.setHeaderFlag(MessageFlag::AA());
+    m.setHeaderFlag(MessageFlag::QR());
+    m.setQid(1234);
+    m.setOpcode(isc::dns::Opcode::QUERY());
+    m.setRcode(isc::dns::Rcode::NOERROR());
+    if (myfile.is_open()) {
+        rrsetsFromFile(myfile, m, Section::ANSWER());
+        myfile.close();
+    } else {
+        return -1;
+    }
+    return 0;
+}
+
+static int
+updateFromFile(Message& m, const char* file_name) {
+    ifstream myfile(file_name);
+
+    m.clear(Message::RENDER);
+    m.setQid(1234);
+    m.setOpcode(isc::dns::Opcode::UPDATE());
+    m.setRcode(isc::dns::Rcode::NOERROR());
+    int stage = 1;
+    QuestionPtr question;
+    if (myfile.is_open()) {
+        while (! myfile.eof() ) {
+            switch (stage) {
+            case 1:
+                question = questionFromFile(myfile);
+                if (question) {
+                    m.addQuestion(question);
+                } else {
+                    stage++;
+                }
+                break;
+            case 2:
+                rrsetsFromFile(myfile, m, Section::ANSWER());
+                rrsetsFromFile(myfile, m, Section::AUTHORITY());
+                rrsetsFromFile(myfile, m, Section::ADDITIONAL());
+            }
+        }
+    } else {
+        return -1;
+    }
+    return 0;
+}
+
+// check a single rrset for exact match including rdata
+static void
+checkSingleRRset(const DataSrc& data_source,
+                 const std::string& expected,
+                 const Name& name,
+                 const RRClass& rrclass,
+                 const RRType& rrtype)
+{
+    RRsetList result_sets;
+    uint32_t find_flags;
+
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.findRRset(name, rrclass, rrtype,
+                                    result_sets, find_flags, &zone_name));
+
+    //EXPECT_EQ(DataSrc::SUCCESS, find_flags);
+    if (expected == "") {
+        // noerror/nodata gives an rdatalen=0 rrset back?
+        if (result_sets.size() == 1) {
+            EXPECT_EQ(0, (*result_sets.begin())->getRdataCount());
+        } else {
+            EXPECT_EQ(0, result_sets.size());
+        }
+    } else {
+        EXPECT_EQ(1, result_sets.size());
+        EXPECT_EQ(expected, (*result_sets.begin())->toText());
+    }
+}
+
+/*
+TEST_F(Sqlite3DataSourceTest, ixfr_ok_message) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.1\n",
+                     www_name, RRClass::IN(), RRType::A());
+    
+    Message ixfr_msg(Message::RENDER);
+    ASSERT_EQ(0, ixfrFromFile(ixfr_msg, TEST_DATA_DIR "/ixfr_ok.rrs"));
+
+    // do IXFR, roll back
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.doIXFR(transaction1,
+                                 ixfr_msg.beginSection(Section::ANSWER()),
+                                 ixfr_msg.endSection(Section::ANSWER()))
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.1\n",
+                     www_name, RRClass::IN(), RRType::A());
+
+    // do IXFR, commit
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.doIXFR(transaction2,
+                                 ixfr_msg.beginSection(Section::ANSWER()),
+                                 ixfr_msg.endSection(Section::ANSWER()))
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.3\n",
+                     www_name, RRClass::IN(), RRType::A());
+
+    // same IXFR should now fail, because the start serial doesn't match
+    DataSrcTransaction transaction3(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction3));
+    EXPECT_EQ(DataSrc::ERROR,
+              data_source.doIXFR(transaction3,
+                                 ixfr_msg.beginSection(Section::ANSWER()),
+                                 ixfr_msg.endSection(Section::ANSWER()))
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction3));
+}
+*/
+
+TEST_F(Sqlite3DataSourceTest, ixfr_ok_message) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.1\n",
+                     www_name, RRClass::IN(), RRType::A());
+    
+    Message ixfr_msg(Message::RENDER);
+    ASSERT_EQ(0, ixfrFromFile(ixfr_msg, TEST_DATA_DIR "/ixfr_ok.rrs"));
+
+    // do IXFR, roll back
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    RRsetIterator begin, end;
+    begin = ixfr_msg.beginSection(Section::ANSWER());
+    end = ixfr_msg.endSection(Section::ANSWER());
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.doIXFR(transaction1,
+                                 callbackHelperRRsetIterator,
+                                 &begin,
+                                 &end)
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.1\n",
+                     www_name, RRClass::IN(), RRType::A());
+
+    // do IXFR, commit
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+    begin = ixfr_msg.beginSection(Section::ANSWER());
+    end = ixfr_msg.endSection(Section::ANSWER());
+    EXPECT_EQ(DataSrc::SUCCESS,
+              data_source.doIXFR(transaction2,
+                                 isc::datasrc::callbackHelperRRsetIterator,
+                                 &begin,
+                                 &end)
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.3\n",
+                     www_name, RRClass::IN(), RRType::A());
+
+    // same IXFR should now fail, because the start serial doesn't match
+    DataSrcTransaction transaction3(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction3));
+    begin = ixfr_msg.beginSection(Section::ANSWER());
+    end = ixfr_msg.endSection(Section::ANSWER());
+    EXPECT_EQ(DataSrc::ERROR,
+              data_source.doIXFR(transaction3,
+                                 isc::datasrc::callbackHelperRRsetIterator,
+                                 &begin,
+                                 &end)
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction3));
+}
+
+TEST_F(Sqlite3DataSourceTest, ixfr_ok_function) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.1\n",
+                     www_name, RRClass::IN(), RRType::A());
+
+    std::vector<RRsetPtr> rrsets;
+    ASSERT_EQ(0, rrsetsFromFile(TEST_DATA_DIR "/ixfr_ok.rrs", rrsets));
+
+    // do IXFR, roll back
+    size_t i = 0;
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    ASSERT_EQ(DataSrc::SUCCESS,
+              data_source.doIXFR(transaction1,
+                                 isc::datasrc::callbackHelperRRsetVector, &rrsets, &i)
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.1\n",
+                     www_name, RRClass::IN(), RRType::A());
+
+    // do IXFR, commit
+    i = 0;
+    DataSrcTransaction transaction2(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction2));
+    ASSERT_EQ(DataSrc::SUCCESS,
+              data_source.doIXFR(transaction2,
+                                 isc::datasrc::callbackHelperRRsetVector, &rrsets, &i)
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction2));
+
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.3\n",
+                     www_name, RRClass::IN(), RRType::A());
+
+    // same IXFR should now fail, because the start serial doesn't match
+    i = 0;
+    DataSrcTransaction transaction3(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction3));
+    ASSERT_EQ(DataSrc::ERROR,
+              data_source.doIXFR(transaction3,
+                                 isc::datasrc::callbackHelperRRsetVector, &rrsets, &i)
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction3));
+}
+
+TEST_F(Sqlite3DataSourceTest, ixfr_bad) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    // a bad ixfr, tries to remove a non-existant RR
+    Message ixfr_msg(Message::RENDER);
+    ASSERT_EQ(0, ixfrFromFile(ixfr_msg, TEST_DATA_DIR
+              "/ixfr_bad_remove_nonexisting.rrs"));
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    RRsetIterator begin, end;
+    begin = ixfr_msg.beginSection(Section::ANSWER());
+    end = ixfr_msg.endSection(Section::ANSWER());
+    EXPECT_EQ(DataSrc::ERROR,
+              data_source.doIXFR(transaction1,
+                                 isc::datasrc::callbackHelperRRsetIterator,
+                                 &begin,
+                                 &end)
+    );
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.rollbackTransaction(transaction1));
+}
+
+TEST_F(Sqlite3DataSourceTest, dynamic_update) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+    
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    Message update_msg(Message::RENDER);
+
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    update_msg.setQid(1234);
+
+    // bad qtype
+    update_msg.setOpcode(isc::dns::Opcode::QUERY());
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+    update_msg.setOpcode(isc::dns::Opcode::UPDATE());
+
+    // no/bad question section
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+    QuestionPtr bad_question(new Question(Name("example.com"), RRClass::CH(), RRType::A()));
+    update_msg.addQuestion(bad_question);
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+    // removeQuestion not implemented, reset msg
+    // checks, we delete one set, one name, one rr, add a set, and add to a set
+    // delete ip46 A rrset
+    // delete all from www.example.com 
+    // delete 1 mx record
+    // add newaddr.example.com 192.0.2.21
+    // add dns04.example.com
+
+    // check if they are (not) there first
+    checkSingleRRset(data_source,
+                     "ip46.example.com. 3600 IN A 192.0.2.1\n",
+                     Name("ip46.example.com"), RRClass::IN(), RRType::A());
+    checkSingleRRset(data_source,
+                     "www.example.com. 3600 IN A 192.0.2.1\n",
+                     www_name, RRClass::IN(), RRType::A());
+    checkSingleRRset(data_source,
+                     "example.com. 3600 IN MX 10 mail.example.com.\n"
+                     "example.com. 3600 IN MX 20 mail.subzone.example.com.\n",
+                     Name("example.com"), RRClass::IN(), RRType::MX());
+    checkSingleRRset(data_source,
+                     "",
+                     Name("newaddr.example.com"), RRClass::IN(), RRType::A());
+    checkSingleRRset(data_source,
+                     "example.com. 3600 IN NS dns01.example.com.\n"
+                     "example.com. 3600 IN NS dns02.example.com.\n"
+                     "example.com. 3600 IN NS dns03.example.com.\n"
+                     ,
+                     Name("example.com"), RRClass::IN(), RRType::NS());
+
+    ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update1.packet"));
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.doUpdate(transaction1, update_msg));
+
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.commitTransaction(transaction1));
+    checkSingleRRset(data_source,
+                     "",
+                     Name("ip46.example.com"), RRClass::IN(), RRType::A());
+    checkSingleRRset(data_source,
+                     "",
+                     www_name, RRClass::IN(), RRType::A());
+    checkSingleRRset(data_source,
+                     "example.com. 3600 IN MX 20 mail.subzone.example.com.\n",
+                     Name("example.com"), RRClass::IN(), RRType::MX());
+    checkSingleRRset(data_source,
+                     "newaddr.example.com. 3600 IN A 192.0.2.12\n",
+                     Name("newaddr.example.com"), RRClass::IN(), RRType::A());
+    checkSingleRRset(data_source,
+                     "example.com. 3600 IN NS dns01.example.com.\n"
+                     "example.com. 3600 IN NS dns02.example.com.\n"
+                     "example.com. 3600 IN NS dns03.example.com.\n"
+                     "example.com. 3600 IN NS dns04.example.com.\n"
+                     ,
+                     Name("example.com"), RRClass::IN(), RRType::NS());
+    
+}
+
+TEST_F(Sqlite3DataSourceTest, dynamic_update_bad_class) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+    
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    Message update_msg(Message::RENDER);
+
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::CH());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+    update_msg.setQid(1234);
+
+    update_msg.setOpcode(isc::dns::Opcode::UPDATE());
+
+    // no/bad question section
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+}
+
+TEST_F(Sqlite3DataSourceTest, dynamic_update_prereq_fails) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+    
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    Message update_msg(Message::RENDER);
+
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+
+    ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update2.packet"));
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+    ASSERT_EQ(Rcode::NXRRSET(), update_msg.getRcode());
+
+    ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update3.packet"));
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+    ASSERT_EQ(Rcode::NXDOMAIN(), update_msg.getRcode());
+
+    ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update4.packet"));
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+    ASSERT_EQ(Rcode::NXRRSET(), update_msg.getRcode());
+
+    ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update5.packet"));
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+    ASSERT_EQ(Rcode::YXRRSET(), update_msg.getRcode());
+
+    ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update6.packet"));
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+    ASSERT_EQ(Rcode::YXDOMAIN(), update_msg.getRcode());
+
+}
+
+TEST_F(Sqlite3DataSourceTest, dynamic_update_bad_update) {
+    // reset database
+    ASSERT_EQ(0, install_writable_database());
+    
+    // use our copied writable datasource db
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.close());
+    ASSERT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_WRITE));
+
+    Message update_msg(Message::RENDER);
+
+    DataSrcTransaction transaction1(&data_source, zone_name, RRClass::IN());
+    ASSERT_EQ(DataSrc::W_SUCCESS, data_source.startTransaction(transaction1));
+
+    ASSERT_EQ(0, updateFromFile(update_msg, TEST_DATA_DIR "/update7.packet"));
+    ASSERT_EQ(DataSrc::ERROR, data_source.doUpdate(transaction1, update_msg));
+
+}
+
+}




More information about the bind10-changes mailing list