Browse Source

[1062] addressed review comments

Jelte Jansen 13 years ago
parent
commit
f82dc7b09f

+ 136 - 122
src/lib/datasrc/database.cc

@@ -71,93 +71,87 @@ DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseConnection>
 { }
 
 namespace {
-    // Adds the given Rdata to the given RRset
-    // If the rrset is an empty pointer, a new one is
-    // created with the given name, class, type and ttl
-    // The type is checked if the rrset exists, but the
-    // name is not.
-    //
-    // Then adds the given rdata to the set
-    //
-    // Raises a DataSourceError if the type does not
-    // match, or if the given rdata string does not
-    // parse correctly for the given type and class
-    void addOrCreate(isc::dns::RRsetPtr& rrset,
-                     const isc::dns::Name& name,
-                     const isc::dns::RRClass& cls,
-                     const isc::dns::RRType& type,
-                     const isc::dns::RRTTL& ttl,
-                     const std::string& rdata_str)
-    {
-        if (!rrset) {
-            rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
-        } else {
-            if (ttl < rrset->getTTL()) {
-                rrset->setTTL(ttl);
-            }
-            // make sure the type is correct
-            if (type != rrset->getType()) {
-                isc_throw(DataSourceError,
-                          "attempt to add multiple types to RRset in find()");
-            }
+// Adds the given Rdata to the given RRset
+// If the rrset is an empty pointer, a new one is
+// created with the given name, class, type and ttl
+// The type is checked if the rrset exists, but the
+// name is not.
+//
+// Then adds the given rdata to the set
+//
+// Raises a DataSourceError if the type does not
+// match, or if the given rdata string does not
+// parse correctly for the given type and class
+void addOrCreate(isc::dns::RRsetPtr& rrset,
+                    const isc::dns::Name& name,
+                    const isc::dns::RRClass& cls,
+                    const isc::dns::RRType& type,
+                    const isc::dns::RRTTL& ttl,
+                    const std::string& rdata_str)
+{
+    if (!rrset) {
+        rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
+    } else {
+        if (ttl < rrset->getTTL()) {
+            rrset->setTTL(ttl);
         }
-        if (rdata_str != "") {
-            try {
-                rrset->addRdata(isc::dns::rdata::createRdata(type, cls,
-                                                             rdata_str));
-            } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
-                // at this point, rrset may have been initialised for no reason,
-                // and won't be used. But the caller would drop the shared_ptr
-                // on such an error anyway, so we don't care.
-                isc_throw(DataSourceError,
-                          "bad rdata in database for " << name.toText() << " "
-                          << type.toText() << " " << ivrt.what());
-            }
+        // make sure the type is correct
+        // TODO Assert?
+        if (type != rrset->getType()) {
+            isc_throw(DataSourceError,
+                        "attempt to add multiple types to RRset in find()");
         }
     }
+    try {
+        rrset->addRdata(isc::dns::rdata::createRdata(type, cls, rdata_str));
+    } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
+        // at this point, rrset may have been initialised for no reason,
+        // and won't be used. But the caller would drop the shared_ptr
+        // on such an error anyway, so we don't care.
+        isc_throw(DataSourceError,
+                    "bad rdata in database for " << name << " "
+                    << type << ": " << ivrt.what());
+    }
+}
 
-    // This class keeps a short-lived store of RRSIG records encountered
-    // during a call to find(). If the backend happens to return signatures
-    // before the actual data, we might not know which signatures we will need
-    // So if they may be relevant, we store the in this class.
-    //
-    // (If this class seems useful in other places, we might want to move
-    // it to util. That would also provide an opportunity to add unit tests)
-    class RRsigStore {
-    public:
-        // Adds the given signature Rdata to the store
-        // The signature rdata MUST be of the RRSIG rdata type
-        // (the caller must make sure of this)
-        void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
-            const isc::dns::RRType& type_covered =
-                static_cast<isc::dns::rdata::generic::RRSIG*>(
-                    sig_rdata.get())->typeCovered();
-            if (!haveSigsFor(type_covered)) {
-                sigs[type_covered] = std::vector<isc::dns::rdata::RdataPtr>();
-            }
-            sigs.find(type_covered)->second.push_back(sig_rdata);
-        }
-
-        // Returns true if this store contains signatures covering the
-        // given type
-        bool haveSigsFor(isc::dns::RRType type) const {
-            return (sigs.count(type) > 0);
-        }
+// This class keeps a short-lived store of RRSIG records encountered
+// during a call to find(). If the backend happens to return signatures
+// before the actual data, we might not know which signatures we will need
+// So if they may be relevant, we store the in this class.
+//
+// (If this class seems useful in other places, we might want to move
+// it to util. That would also provide an opportunity to add unit tests)
+class RRsigStore {
+public:
+    // Adds the given signature Rdata to the store
+    // The signature rdata MUST be of the RRSIG rdata type
+    // (the caller must make sure of this).
+    // NOTE: if we move this class to a public namespace,
+    // we should add a type_covered argument, so as not
+    // to have to do this cast here.
+    void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
+        const isc::dns::RRType& type_covered =
+            static_cast<isc::dns::rdata::generic::RRSIG*>(
+                sig_rdata.get())->typeCovered();
+        sigs[type_covered].push_back(sig_rdata);
+    }
 
-        // If the store contains signatures for the type of the given
-        // rrset, they are appended to it.
-        void appendSignatures(isc::dns::RRsetPtr& rrset) const {
-            if (haveSigsFor(rrset->getType())) {
-                BOOST_FOREACH(isc::dns::rdata::RdataPtr sig,
-                              sigs.find(rrset->getType())->second) {
-                    rrset->addRRsig(sig);
-                }
+    // If the store contains signatures for the type of the given
+    // rrset, they are appended to it.
+    void appendSignatures(isc::dns::RRsetPtr& rrset) const {
+        std::map<isc::dns::RRType,
+                 std::vector<isc::dns::rdata::RdataPtr> >::const_iterator
+            found = sigs.find(rrset->getType());
+        if (found != sigs.end()) {
+            BOOST_FOREACH(isc::dns::rdata::RdataPtr sig, found->second) {
+                rrset->addRRsig(sig);
             }
         }
+    }
 
-    private:
-        std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
-    };
+private:
+    std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
+};
 }
 
 
@@ -174,55 +168,75 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
     ZoneFinder::Result result_status = SUCCESS;
     RRsigStore sig_store;
 
-    connection_->searchForRecords(zone_id_, name.toText());
+    try {
+        connection_->searchForRecords(zone_id_, name.toText());
 
-    std::vector<std::string> columns;
-    while (connection_->getNextRecord(columns)) {
-        if (!records_found) {
-            records_found = true;
-        }
+        std::string columns[DatabaseConnection::RecordColumnCount];
+        while (connection_->getNextRecord(columns,
+                                        DatabaseConnection::RecordColumnCount)) {
+            if (!records_found) {
+                records_found = true;
+            }
 
-        if (columns.size() != 4) {
-            isc_throw(DataSourceError, "Datasource backend did not return 4 "
-                      "columns in getNextRecord()");
-        }
+            try {
+                const isc::dns::RRType cur_type(columns[DatabaseConnection::TYPE_COLUMN]);
+                const isc::dns::RRTTL cur_ttl(columns[DatabaseConnection::TTL_COLUMN]);
+                // Ths sigtype column was an optimization for finding the relevant
+                // RRSIG RRs for a lookup. Currently this column is not used in this
+                // revised datasource implementation. We should either start using it
+                // again, or remove it from use completely (i.e. also remove it from
+                // the schema and the backend implementation).
+                // Note that because we don't use it now, we also won't notice it if
+                // the value is wrong (i.e. if the sigtype column contains an rrtype
+                // that is different from the actual value of the 'type covered' field
+                // in the RRSIG Rdata).
+                //cur_sigtype(columns[SIGTYPE_COLUMN]);
 
-        try {
-            const isc::dns::RRType cur_type(columns[0]);
-            const isc::dns::RRTTL cur_ttl(columns[1]);
-            //cur_sigtype(columns[2]);
-
-            if (cur_type == type) {
-                addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
-                            columns[3]);
-            } else if (cur_type == isc::dns::RRType::CNAME()) {
-                // There should be no other data, so cur_rrset should be empty,
-                if (result_rrset) {
-                    isc_throw(DataSourceError, "CNAME found but it is not "
-                              "the only record for " + name.toText());
+                if (cur_type == type) {
+                    addOrCreate(result_rrset, name, getClass(), cur_type,
+                                cur_ttl, columns[DatabaseConnection::RDATA_COLUMN]);
+                } else if (cur_type == isc::dns::RRType::CNAME()) {
+                    // There should be no other data, so result_rrset should be empty.
+                    if (result_rrset) {
+                        isc_throw(DataSourceError, "CNAME found but it is not "
+                                "the only record for " + name.toText());
+                    }
+                    addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+                                columns[DatabaseConnection::RDATA_COLUMN]);
+                    result_status = CNAME;
+                } else if (cur_type == isc::dns::RRType::RRSIG()) {
+                    // If we get signatures before we get the actual data, we
+                    // can't know which ones to keep and which to drop...
+                    // So we keep a separate store of any signature that may be
+                    // relevant and add them to the final RRset when we are done.
+                    // A possible optimization here is to not store them for types
+                    // we are certain we don't need
+                    sig_store.addSig(isc::dns::rdata::createRdata(cur_type,
+                                    getClass(),
+                                    columns[DatabaseConnection::RDATA_COLUMN]));
                 }
-                addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
-                            columns[3]);
-                result_status = CNAME;
-            } else if (cur_type == isc::dns::RRType::RRSIG()) {
-                // If we get signatures before we get the actual data, we
-                // can't know which ones to keep and which to drop...
-                // So we keep a separate store of any signature that may be
-                // relevant and add them to the final RRset when we are done.
-                // A possible optimization here is to not store them for types
-                // we are certain we don't need
-                isc::dns::rdata::RdataPtr cur_rrsig(
-                    isc::dns::rdata::createRdata(cur_type, getClass(),
-                                                 columns[3]));
-                sig_store.addSig(cur_rrsig);
+            } catch (const isc::dns::InvalidRRType& irt) {
+                isc_throw(DataSourceError, "Invalid RRType in database for " <<
+                        name << ": " << columns[DatabaseConnection::TYPE_COLUMN]);
+            } catch (const isc::dns::InvalidRRTTL& irttl) {
+                isc_throw(DataSourceError, "Invalid TTL in database for " <<
+                        name << ": " << columns[DatabaseConnection::TTL_COLUMN]);
+            } catch (const isc::dns::rdata::InvalidRdataText& ird) {
+                isc_throw(DataSourceError, "Invalid rdata in database for " <<
+                        name << ": " << columns[DatabaseConnection::RDATA_COLUMN]);
             }
-        } catch (const isc::dns::InvalidRRType& irt) {
-            isc_throw(DataSourceError, "Invalid RRType in database for " <<
-                      name << ": " << columns[0]);
-        } catch (const isc::dns::InvalidRRTTL& irttl) {
-            isc_throw(DataSourceError, "Invalid TTL in database for " <<
-                      name << ": " << columns[1]);
         }
+    } catch (const DataSourceError& dse) {
+        // call cleanup and rethrow
+        connection_->resetSearch();
+        throw;
+    } catch (const isc::Exception& isce) {
+//         // cleanup, change it to a DataSourceError and rethrow
+        connection_->resetSearch();
+        isc_throw(DataSourceError, isce.what());
+    } catch (const std::exception& ex) {
+        connection_->resetSearch();
+        throw;
     }
 
     if (!result_rrset) {

+ 49 - 4
src/lib/datasrc/database.h

@@ -92,14 +92,59 @@ public:
      * Returns a boolean specifying whether or not there was more data to read.
      * In the case of a database error, a DatasourceError is thrown.
      *
+     * The columns passed is an array of std::strings consisting of
+     * DatabaseConnection::RecordColumnCount elements, the elements of which
+     * are defined in DatabaseConnection::RecordColumns, in their basic
+     * string representation.
+     * 
+     * If you are implementing a derived database connection class, you
+     * should have this method check the column_count value, and fill the
+     * array with strings conforming to their description in RecordColumn.
+     *
      * \exception DatasourceError if there was an error reading from the database
      *
-     * \param columns This vector will be cleared, and the fields of the record will
-     *                be appended here as strings (in the order rdtype, ttl, sigtype,
-     *                and rdata). If there was no data, the vector is untouched.
+     * \param columns The elements of this array will be filled with the data
+     *                for one record as defined by RecordColumns
+     *                If there was no data, the array is untouched.
      * \return true if there was a next record, false if there was not
      */
-    virtual bool getNextRecord(std::vector<std::string>& columns) = 0;
+    virtual bool getNextRecord(std::string columns[], size_t column_count) = 0;
+
+    /**
+     * \brief Resets the current search initiated with searchForRecords()
+     *
+     * This method will be called when the called of searchForRecords() and
+     * getNextRecord() finds bad data, and aborts the current search.
+     * It should clean up whatever handlers searchForRecords() created, and
+     * any other state modified or needed by getNextRecord()
+     *
+     * Of course, the implementation of getNextRecord may also use it when
+     * it is done with a search. If it does, the implementation of this
+     * method should make sure it can handle being called multiple times.
+     *
+     * The implementation for this method should make sure it never throws.
+     */
+    virtual void resetSearch() = 0;
+
+    /**
+     * Definitions of the fields as they are required to be filled in
+     * by getNextRecord()
+     * 
+     * When implementing getNextRecord(), the columns array should
+     * be filled with the values as described in this enumeration,
+     * in this order.
+     */
+    enum RecordColumns {
+        TYPE_COLUMN = 0,    ///< The RRType of the record (A/NS/TXT etc.)
+        TTL_COLUMN = 1,     ///< The TTL of the record (a
+        SIGTYPE_COLUMN = 2, ///< For RRSIG records, this contains the RRTYPE
+                            ///< the RRSIG covers. In the current implementation,
+                            ///< this field is ignored.
+        RDATA_COLUMN = 3    ///< Full text representation of the record's RDATA
+    };
+
+    /// The number of fields the columns array passed to getNextRecord should have
+    static const size_t RecordColumnCount = 4;
 };
 
 /**

+ 52 - 24
src/lib/datasrc/sqlite3_connection.cc

@@ -321,15 +321,30 @@ SQLite3Connection::getZone(const isc::dns::Name& name) const {
 
 void
 SQLite3Connection::searchForRecords(int zone_id, const std::string& name) {
-    sqlite3_reset(dbparameters_->q_any_);
-    sqlite3_clear_bindings(dbparameters_->q_any_);
-    sqlite3_bind_int(dbparameters_->q_any_, 1, zone_id);
+    resetSearch();
+    int result;
+    result = sqlite3_bind_int(dbparameters_->q_any_, 1, zone_id);
+    if (result != SQLITE_OK) {
+        isc_throw(DataSourceError,
+                  "Error in sqlite3_bind_int() for zone_id " <<
+                  zone_id << ", sqlite3 result code: " << result);
+    }
     // use transient since name is a ref and may disappear
-    sqlite3_bind_text(dbparameters_->q_any_, 2, name.c_str(), -1,
-                      SQLITE_TRANSIENT);
+    result = sqlite3_bind_text(dbparameters_->q_any_, 2, name.c_str(), -1,
+                               SQLITE_TRANSIENT);
+    if (result != SQLITE_OK) {
+        isc_throw(DataSourceError,
+                  "Error in sqlite3_bind_text() for name " <<
+                  name << ", sqlite3 result code: " << result);
+    }
 };
 
 namespace {
+// This helper function converts from the unsigned char* type (used by
+// sqlite3) to char* (wanted by std::string). Technically these types
+// might not be directly convertable
+// In case sqlite3_column_text() returns NULL, we just make it an
+// empty string.
 const char*
 convertToPlainChar(const unsigned char* ucp) {
     if (ucp == NULL) {
@@ -341,31 +356,44 @@ convertToPlainChar(const unsigned char* ucp) {
 }
 
 bool
-SQLite3Connection::getNextRecord(std::vector<std::string>& columns) {
-    sqlite3_stmt* current_stmt = dbparameters_->q_any_;
-    const int rc = sqlite3_step(current_stmt);
+SQLite3Connection::getNextRecord(std::string columns[], size_t column_count) {
+    try {
+        sqlite3_stmt* current_stmt = dbparameters_->q_any_;
+        const int rc = sqlite3_step(current_stmt);
+
+        if (column_count != RecordColumnCount) {
+                isc_throw(DataSourceError,
+                        "Datasource backend caller did not pass a column array "
+                        "of size " << RecordColumnCount <<
+                        " to getNextRecord()");
+        }
 
-    if (rc == SQLITE_ROW) {
-        columns.clear();
-        for (int column = 0; column < 4; ++column) {
-            columns.push_back(convertToPlainChar(sqlite3_column_text(
-                                                 current_stmt, column)));
+        if (rc == SQLITE_ROW) {
+            for (int column = 0; column < column_count; ++column) {
+                columns[column] = convertToPlainChar(sqlite3_column_text(
+                                                    current_stmt, column));
+            }
+            return (true);
+        } else if (rc == SQLITE_DONE) {
+            // reached the end of matching rows
+            resetSearch();
+            return (false);
         }
-        return (true);
-    } else if (rc == SQLITE_DONE) {
-        // reached the end of matching rows
-        sqlite3_reset(current_stmt);
-        sqlite3_clear_bindings(current_stmt);
-        return (false);
+        resetSearch();
+        isc_throw(DataSourceError,
+                "Unexpected failure in sqlite3_step (sqlite result code " << rc << ")");
+    } catch (std::bad_alloc) {
+        isc_throw(DataSourceError, "bad_alloc in Sqlite3Connection::getNextRecord");
     }
-    sqlite3_reset(current_stmt);
-    sqlite3_clear_bindings(current_stmt);
-    isc_throw(DataSourceError,
-              "Unexpected failure in sqlite3_step (sqlite result code " << rc << ")");
-
     // Compilers might not realize isc_throw always throws
     return (false);
 }
 
+void
+SQLite3Connection::resetSearch() {
+    sqlite3_reset(dbparameters_->q_any_);
+    sqlite3_clear_bindings(dbparameters_->q_any_);
+}
+
 }
 }

+ 24 - 2
src/lib/datasrc/sqlite3_connection.h

@@ -95,6 +95,9 @@ public:
      * This implements the searchForRecords from DatabaseConnection.
      * This particular implementation does not raise DataSourceError.
      *
+     * \exception DataSourceError when sqlite3_bind_int() or
+     *                            sqlite3_bind_text() fails
+     *
      * \param zone_id The zone to seach in, as returned by getZone()
      * \param name The name to find records for
      */
@@ -107,12 +110,31 @@ public:
      * This implements the getNextRecord from DatabaseConnection.
      * See the documentation there for more information.
      *
+     * If this method raises an exception, the contents of columns are undefined.
+     *
+     * \exception DataSourceError if there is an error returned by sqlite_step()
+     *                            When this exception is raised, the current
+     *                            search as initialized by searchForRecords() is
+     *                            NOT reset, and the caller is expected to take
+     *                            care of that.
      * \param columns This vector will be cleared, and the fields of the record will
      *                be appended here as strings (in the order rdtype, ttl, sigtype,
-     *                and rdata). If there was no data, the vector is untouched.
+     *                and rdata). If there was no data (i.e. if this call returns
+     *                false), the vector is untouched.
      * \return true if there was a next record, false if there was not
      */
-    virtual bool getNextRecord(std::vector<std::string>& columns);
+    virtual bool getNextRecord(std::string columns[], size_t column_count);
+
+    /**
+     * \brief Resets any state created by searchForRecords
+     *
+     * This implements the resetSearch from DatabaseConnection.
+     * See the documentation there for more information.
+     * 
+     * This function never throws.
+     */
+    virtual void resetSearch();
+
 private:
     /// \brief Private database data
     SQLite3Parameters* dbparameters_;

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

@@ -15,6 +15,7 @@
 #include <gtest/gtest.h>
 
 #include <dns/name.h>
+#include <dns/rrttl.h>
 #include <exceptions/exceptions.h>
 
 #include <datasrc/database.h>
@@ -36,7 +37,7 @@ namespace {
  */
 class MockConnection : public DatabaseConnection {
 public:
-    MockConnection() { fillData(); }
+    MockConnection() : search_running_(false) { fillData(); }
 
     virtual std::pair<bool, int> getZone(const Name& name) const {
         if (name == Name("example.org")) {
@@ -47,10 +48,23 @@ public:
     }
 
     virtual void searchForRecords(int zone_id, const std::string& name) {
+        search_running_ = true;
+
+        // 'hardcoded' name to trigger exceptions (for testing
+        // the error handling of find() (the other on is below in 
+        // if the name is "exceptiononsearch" it'll raise an exception here
+        if (name == "dsexception.in.search.") {
+            isc_throw(DataSourceError, "datasource exception on search");
+        } else if (name == "iscexception.in.search.") {
+            isc_throw(isc::Exception, "isc exception on search");
+        } else if (name == "basicexception.in.search.") {
+            throw std::exception();
+        }
+        searched_name_ = name;
+
         // we're not aiming for efficiency in this test, simply
         // copy the relevant vector from records
         cur_record = 0;
-
         if (zone_id == 42) {
             if (records.count(name) > 0) {
                 cur_name = records.find(name)->second;
@@ -62,15 +76,38 @@ public:
         }
     };
 
-    virtual bool getNextRecord(std::vector<std::string>& columns) {
+    virtual bool getNextRecord(std::string columns[], size_t column_count) {
+        if (searched_name_ == "dsexception.in.getnext.") {
+            isc_throw(DataSourceError, "datasource exception on getnextrecord");
+        } else if (searched_name_ == "iscexception.in.getnext.") {
+            isc_throw(isc::Exception, "isc exception on getnextrecord");
+        } else if (searched_name_ == "basicexception.in.getnext.") {
+            throw std::exception();
+        }
+
+        if (column_count != DatabaseConnection::RecordColumnCount) {
+            isc_throw(DataSourceError, "Wrong column count in getNextRecord");
+        }
         if (cur_record < cur_name.size()) {
-            columns = cur_name[cur_record++];
+            for (size_t i = 0; i < column_count; ++i) {
+                columns[i] = cur_name[cur_record][i];
+            }
+            cur_record++;
             return (true);
         } else {
+            resetSearch();
             return (false);
         }
     };
 
+    virtual void resetSearch() {
+        search_running_ = false;
+    };
+
+    bool searchRunning() const {
+        return (search_running_);
+    }
+
 private:
     std::map<std::string, std::vector< std::vector<std::string> > > records;
     // used as internal index for getNextRecord()
@@ -80,6 +117,14 @@ private:
     // fake data
     std::vector< std::vector<std::string> > cur_name;
 
+    // This boolean is used to make sure find() calls resetSearch
+    // when it encounters an error
+    bool search_running_;
+
+    // We store the name passed to searchForRecords, so we can
+    // hardcode some exceptions into getNextRecord
+    std::string searched_name_;
+
     // Adds one record to the current name in the database
     // The actual data will not be added to 'records' until
     // addCurName() is called
@@ -121,6 +166,11 @@ private:
         addRecord("AAAA", "3600", "", "2001:db8::2");
         addCurName("www.example.org.");
 
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("AAAA", "3600", "", "2001:db8::1");
+        addRecord("A", "3600", "", "192.0.2.2");
+        addCurName("www2.example.org.");
+
         addRecord("CNAME", "3600", "", "www.example.org.");
         addCurName("cname.example.org.");
 
@@ -165,18 +215,42 @@ private:
         addRecord("A", "3600", "", "192.0.2.1");
         addCurName("acnamesig3.example.org.");
 
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("A", "360", "", "192.0.2.2");
+        addCurName("ttldiff1.example.org.");
+        addRecord("A", "360", "", "192.0.2.1");
+        addRecord("A", "3600", "", "192.0.2.2");
+        addCurName("ttldiff2.example.org.");
+
         // also add some intentionally bad data
-        cur_name.push_back(std::vector<std::string>());
-        addCurName("emptyvector.example.org.");
         addRecord("A", "3600", "", "192.0.2.1");
         addRecord("CNAME", "3600", "", "www.example.org.");
-        addCurName("badcname.example.org.");
+        addCurName("badcname1.example.org.");
+
+        addRecord("CNAME", "3600", "", "www.example.org.");
+        addRecord("A", "3600", "", "192.0.2.1");
+        addCurName("badcname2.example.org.");
+
+        addRecord("CNAME", "3600", "", "www.example.org.");
+        addRecord("CNAME", "3600", "", "www.example2.org.");
+        addCurName("badcname3.example.org.");
+
         addRecord("A", "3600", "", "bad");
         addCurName("badrdata.example.org.");
+
         addRecord("BAD_TYPE", "3600", "", "192.0.2.1");
         addCurName("badtype.example.org.");
+
         addRecord("A", "badttl", "", "192.0.2.1");
         addCurName("badttl.example.org.");
+
+        addRecord("A", "badttl", "", "192.0.2.1");
+        addRecord("RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addCurName("badsig.example.org.");
+
+        addRecord("A", "3600", "", "192.0.2.1");
+        addRecord("RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+        addCurName("badsigtype.example.org.");
     }
 };
 
@@ -241,18 +315,21 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
            const isc::dns::Name& name,
            const isc::dns::RRType& type,
            const isc::dns::RRType& expected_type,
+           const isc::dns::RRTTL expected_ttl,
            ZoneFinder::Result expected_result,
            unsigned int expected_rdata_count,
            unsigned int expected_signature_count)
 {
-    ZoneFinder::FindResult result = finder->find(name, type,
-                                                 NULL, ZoneFinder::FIND_DEFAULT);
-    ASSERT_EQ(expected_result, result.code) << name.toText() << " " << type.toText();
+    ZoneFinder::FindResult result =
+        finder->find(name, type, NULL, ZoneFinder::FIND_DEFAULT);
+    ASSERT_EQ(expected_result, result.code) << name << " " << type;
     if (expected_rdata_count > 0) {
         EXPECT_EQ(expected_rdata_count, result.rrset->getRdataCount());
+        EXPECT_EQ(expected_ttl, result.rrset->getTTL());
         EXPECT_EQ(expected_type, result.rrset->getType());
         if (expected_signature_count > 0) {
-            EXPECT_EQ(expected_signature_count, result.rrset->getRRsig()->getRdataCount());
+            EXPECT_EQ(expected_signature_count,
+                      result.rrset->getRRsig()->getRdataCount());
         } else {
             EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset->getRRsig());
         }
@@ -268,79 +345,189 @@ TEST_F(DatabaseClientTest, find) {
     shared_ptr<DatabaseClient::Finder> finder(
         dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
     EXPECT_EQ(42, finder->zone_id());
-    const isc::dns::Name name("www.example.org.");
+    EXPECT_FALSE(current_connection_->searchRunning());
 
     doFindTest(finder, isc::dns::Name("www.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 1, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    doFindTest(finder, isc::dns::Name("www2.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS, 2, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("www.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 2, 0);
     doFindTest(finder, isc::dns::Name("www.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::NXRRSET, 0, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("cname.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::CNAME, 1, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    doFindTest(finder, isc::dns::Name("cname.example.org."),
+               isc::dns::RRType::CNAME(), isc::dns::RRType::CNAME(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS, 1, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("doesnotexist.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::NXDOMAIN, 0, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("signed1.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 1, 2);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("signed1.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 2, 1);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("signed1.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::NXRRSET, 0, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("signedcname1.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::CNAME, 1, 1);
+    EXPECT_FALSE(current_connection_->searchRunning());
 
     doFindTest(finder, isc::dns::Name("signed2.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 1, 2);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("signed2.example.org."),
                isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 2, 1);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("signed2.example.org."),
                isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::NXRRSET, 0, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("signedcname2.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::CNAME, 1, 1);
+    EXPECT_FALSE(current_connection_->searchRunning());
 
     doFindTest(finder, isc::dns::Name("acnamesig1.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 1, 1);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("acnamesig2.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 1, 1);
+    EXPECT_FALSE(current_connection_->searchRunning());
     doFindTest(finder, isc::dns::Name("acnamesig3.example.org."),
                isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
                ZoneFinder::SUCCESS, 1, 1);
+    EXPECT_FALSE(current_connection_->searchRunning());
 
-    EXPECT_THROW(finder->find(isc::dns::Name("emptyvector.example.org."),
+    doFindTest(finder, isc::dns::Name("ttldiff1.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(360),
+               ZoneFinder::SUCCESS, 2, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    doFindTest(finder, isc::dns::Name("ttldiff2.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(360),
+               ZoneFinder::SUCCESS, 2, 0);
+    EXPECT_FALSE(current_connection_->searchRunning());
+
+    EXPECT_THROW(finder->find(isc::dns::Name("badcname1.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badcname2.example.org."),
                                               isc::dns::RRType::A(),
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
-    EXPECT_THROW(finder->find(isc::dns::Name("badcname.example.org."),
+    EXPECT_FALSE(current_connection_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badcname3.example.org."),
                                               isc::dns::RRType::A(),
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
     EXPECT_THROW(finder->find(isc::dns::Name("badrdata.example.org."),
                                               isc::dns::RRType::A(),
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
     EXPECT_THROW(finder->find(isc::dns::Name("badtype.example.org."),
                                               isc::dns::RRType::A(),
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
     EXPECT_THROW(finder->find(isc::dns::Name("badttl.example.org."),
                                               isc::dns::RRType::A(),
                                               NULL, ZoneFinder::FIND_DEFAULT),
                  DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("badsig.example.org."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
+
+    // Trigger the hardcoded exceptions and see if find() has cleaned up
+    /*
+    EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.search."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.search."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.search."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 std::exception);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    */
+    EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.getnext."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.getnext."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 DataSourceError);
+    EXPECT_FALSE(current_connection_->searchRunning());
+    EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.getnext."),
+                                              isc::dns::RRType::A(),
+                                              NULL, ZoneFinder::FIND_DEFAULT),
+                 std::exception);
+    EXPECT_FALSE(current_connection_->searchRunning());
+
+    // This RRSIG has the wrong sigtype field, which should be
+    // an error if we decide to keep using that field
+    // Right now the field is ignored, so it does not error
+    doFindTest(finder, isc::dns::Name("badsigtype.example.org."),
+               isc::dns::RRType::A(), isc::dns::RRType::A(),
+               isc::dns::RRTTL(3600),
+               ZoneFinder::SUCCESS, 1, 1);
+    EXPECT_FALSE(current_connection_->searchRunning());
 
 }
 

+ 122 - 30
src/lib/datasrc/tests/sqlite3_connection_unittest.cc

@@ -11,8 +11,6 @@
 // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
-#include <memory>
-
 #include <datasrc/sqlite3_connection.h>
 #include <datasrc/data_source.h>
 
@@ -20,6 +18,7 @@
 
 #include <gtest/gtest.h>
 #include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
 
 using namespace isc::datasrc;
 using isc::data::ConstElementPtr;
@@ -76,7 +75,7 @@ public:
         conn.reset(new SQLite3Connection(filename, rrclass));
     }
     // The tested connection
-    boost::shared_ptr<SQLite3Connection> conn;
+    boost::scoped_ptr<SQLite3Connection> conn;
 };
 
 // This zone exists in the data, so it should be found
@@ -102,22 +101,19 @@ TEST_F(SQLite3Conn, noClass) {
     EXPECT_FALSE(conn->getZone(Name("example.com")).first);
 }
 
-namespace {
-    // Simple function to count the number of records for
-    // any name
-    size_t countRecords(boost::shared_ptr<SQLite3Connection>& conn,
-                        int zone_id, const std::string& name)
-    {
-        conn->searchForRecords(zone_id, name);
-        size_t count = 0;
-        std::vector<std::string> columns;
-        while (conn->getNextRecord(columns)) {
-            EXPECT_EQ(4, columns.size());
-            ++count;
-        }
-        return (count);
-    }
-}
+// Simple function to cound the number of records for
+// any name
+void
+checkRecordRow(const std::string columns[],
+               const std::string& field0,
+               const std::string& field1,
+               const std::string& field2,
+               const std::string& field3)
+{
+    EXPECT_EQ(field0, columns[0]);
+    EXPECT_EQ(field1, columns[1]);
+    EXPECT_EQ(field2, columns[2]);
+    EXPECT_EQ(field3, columns[3]);
 }
 
 TEST_F(SQLite3Conn, getRecords) {
@@ -127,16 +123,112 @@ TEST_F(SQLite3Conn, getRecords) {
     const int zone_id = zone_info.second;
     ASSERT_EQ(1, zone_id);
 
-    // without search, getNext() should return false
-    std::vector<std::string> columns;
-    EXPECT_FALSE(conn->getNextRecord(columns));
-    EXPECT_EQ(0, columns.size());
-
-    EXPECT_EQ(4, countRecords(conn, zone_id, "foo.example.com."));
-    EXPECT_EQ(15, countRecords(conn, zone_id, "example.com."));
-    EXPECT_EQ(0, countRecords(conn, zone_id, "foo.bar."));
-    EXPECT_EQ(0, countRecords(conn, zone_id, ""));
+    const size_t column_count = DatabaseConnection::RecordColumnCount;
+    std::string columns[column_count];
 
-    EXPECT_FALSE(conn->getNextRecord(columns));
-    EXPECT_EQ(0, columns.size());
+    // without search, getNext() should return false
+    EXPECT_FALSE(conn->getNextRecord(columns,
+        column_count));
+    checkRecordRow(columns, "", "", "", "");
+
+    conn->searchForRecords(zone_id, "foo.bar.");
+    EXPECT_FALSE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "", "", "", "");
+
+    conn->searchForRecords(zone_id, "");
+    EXPECT_FALSE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "", "", "", "");
+
+    // Should error on a bad number of columns
+    EXPECT_THROW(conn->getNextRecord(columns, 3), DataSourceError);
+    EXPECT_THROW(conn->getNextRecord(columns, 5), DataSourceError);
+
+    // now try some real searches
+    conn->searchForRecords(zone_id, "foo.example.com.");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "CNAME", "3600", "",
+                   "cnametest.example.org.");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "CNAME",
+                   "CNAME 5 3 3600 20100322084538 20100220084538 33495 "
+                   "example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NSEC", "7200", "",
+                   "mail.example.com. CNAME RRSIG NSEC");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "7200", "NSEC",
+                   "NSEC 5 3 7200 20100322084538 20100220084538 33495 "
+                   "example.com. FAKEFAKEFAKEFAKE");
+    EXPECT_FALSE(conn->getNextRecord(columns, column_count));
+    // with no more records, the array should not have been modified
+    checkRecordRow(columns, "RRSIG", "7200", "NSEC",
+                   "NSEC 5 3 7200 20100322084538 20100220084538 33495 "
+                   "example.com. FAKEFAKEFAKEFAKE");
+
+    conn->searchForRecords(zone_id, "example.com.");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "SOA", "3600", "",
+                   "master.example.com. admin.example.com. "
+                   "1234 3600 1800 2419200 7200");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "SOA",
+                   "SOA 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NS", "1200", "", "dns01.example.com.");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NS", "3600", "", "dns02.example.com.");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NS", "1800", "", "dns03.example.com.");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "NS",
+                   "NS 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "MX", "3600", "", "10 mail.example.com.");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "MX", "3600", "",
+                   "20 mail.subzone.example.com.");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "MX",
+                   "MX 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "NSEC", "7200", "",
+                   "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "7200", "NSEC",
+                   "NSEC 5 2 7200 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "DNSKEY", "3600", "",
+                   "256 3 5 AwEAAcOUBllYc1hf7ND9uDy+Yz1BF3sI0m4q NGV7W"
+                   "cTD0WEiuV7IjXgHE36fCmS9QsUxSSOV o1I/FMxI2PJVqTYHkX"
+                   "FBS7AzLGsQYMU7UjBZ SotBJ6Imt5pXMu+lEDNy8TOUzG3xm7g"
+                   "0qcbW YF6qCEfvZoBtAqi5Rk7Mlrqs8agxYyMx");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "DNSKEY", "3600", "",
+                   "257 3 5 AwEAAe5WFbxdCPq2jZrZhlMj7oJdff3W7syJ tbvzg"
+                   "62tRx0gkoCDoBI9DPjlOQG0UAbj+xUV 4HQZJStJaZ+fHU5AwV"
+                   "NT+bBZdtV+NujSikhd THb4FYLg2b3Cx9NyJvAVukHp/91HnWu"
+                   "G4T36 CzAFrfPwsHIrBz9BsaIQ21VRkcmj7DswfI/i DGd8j6b"
+                   "qiODyNZYQ+ZrLmF0KIJ2yPN3iO6Zq 23TaOrVTjB7d1a/h31OD"
+                   "fiHAxFHrkY3t3D5J R9Nsl/7fdRmSznwtcSDgLXBoFEYmw6p86"
+                   "Acv RyoYNcL1SXjaKVLG5jyU3UR+LcGZT5t/0xGf oIK/aKwEN"
+                   "rsjcKZZj660b1M=");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
+                   "DNSKEY 5 2 3600 20100322084538 20100220084538 "
+                   "4456 example.com. FAKEFAKEFAKEFAKE");
+    ASSERT_TRUE(conn->getNextRecord(columns, column_count));
+    checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
+                   "DNSKEY 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
+    EXPECT_FALSE(conn->getNextRecord(columns, column_count));
+    // getnextrecord returning false should mean array is not altered
+    checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
+                   "DNSKEY 5 2 3600 20100322084538 20100220084538 "
+                   "33495 example.com. FAKEFAKEFAKEFAKE");
 }
+
+} // end anonymous namespace