Commit 2b755575 authored by Jelte Jansen's avatar Jelte Jansen
Browse files

Merge branch 'trac1183'

parents 58d7fda0 7f08fc31
......@@ -178,12 +178,20 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
bool want_ns)
{
RRsigStore sig_store;
database_->searchForRecords(zone_id_, name.toText());
bool records_found = false;
isc::dns::RRsetPtr result_rrset;
// Request the context
DatabaseAccessor::IteratorContextPtr
context(database_->getRecords(name.toText(), zone_id_));
// It must not return NULL, that's a bug of the implementation
if (!context) {
isc_throw(isc::Unexpected, "Iterator context null at " +
name.toText());
}
std::string columns[DatabaseAccessor::COLUMN_COUNT];
while (database_->getNextRecord(columns, DatabaseAccessor::COLUMN_COUNT)) {
while (context->getNext(columns)) {
if (!records_found) {
records_found = true;
}
......@@ -284,7 +292,6 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
return (std::pair<bool, isc::dns::RRsetPtr>(records_found, result_rrset));
}
ZoneFinder::FindResult
DatabaseClient::Finder::find(const isc::dns::Name& name,
const isc::dns::RRType& type,
......@@ -301,76 +308,56 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
.arg(database_->getDBName()).arg(name).arg(type);
try {
// First, do we have any kind of delegation (NS/DNAME) here?
Name origin(getOrigin());
size_t origin_label_count(origin.getLabelCount());
size_t current_label_count(name.getLabelCount());
// This is how many labels we remove to get origin
size_t remove_labels(current_label_count - origin_label_count);
// Now go trough all superdomains from origin down
for (int i(remove_labels); i > 0; --i) {
Name superdomain(name.split(i));
// Look if there's NS or DNAME (but ignore the NS in origin)
found = getRRset(superdomain, NULL, false, true,
i != remove_labels && !glue_ok);
if (found.second) {
// We found something redirecting somewhere else
// (it can be only NS or DNAME here)
result_rrset = found.second;
if (result_rrset->getType() == isc::dns::RRType::NS()) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_DELEGATION).
arg(database_->getDBName()).arg(superdomain);
result_status = DELEGATION;
} else {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_DNAME).
arg(database_->getDBName()).arg(superdomain);
result_status = DNAME;
}
// Don't search more
break;
}
}
if (!result_rrset) { // Only if we didn't find a redirect already
// Try getting the final result and extract it
// It is special if there's a CNAME or NS, DNAME is ignored here
// And we don't consider the NS in origin
found = getRRset(name, &type, true, false,
name != origin && !glue_ok);
records_found = found.first;
// First, do we have any kind of delegation (NS/DNAME) here?
const Name origin(getOrigin());
const size_t origin_label_count(origin.getLabelCount());
const size_t current_label_count(name.getLabelCount());
// This is how many labels we remove to get origin
const size_t remove_labels(current_label_count - origin_label_count);
// Now go trough all superdomains from origin down
for (int i(remove_labels); i > 0; --i) {
const Name superdomain(name.split(i));
// Look if there's NS or DNAME (but ignore the NS in origin)
found = getRRset(superdomain, NULL, false, true,
i != remove_labels && !glue_ok);
if (found.second) {
// We found something redirecting somewhere else
// (it can be only NS or DNAME here)
result_rrset = found.second;
if (result_rrset && name != origin && !glue_ok &&
result_rrset->getType() == isc::dns::RRType::NS()) {
if (result_rrset->getType() == isc::dns::RRType::NS()) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
arg(database_->getDBName()).arg(name);
DATASRC_DATABASE_FOUND_DELEGATION).
arg(database_->getDBName()).arg(superdomain);
result_status = DELEGATION;
} else if (result_rrset && type != isc::dns::RRType::CNAME() &&
result_rrset->getType() == isc::dns::RRType::CNAME()) {
result_status = CNAME;
} else {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_DNAME).
arg(database_->getDBName()).arg(superdomain);
result_status = DNAME;
}
// Don't search more
break;
}
}
if (!result_rrset) { // Only if we didn't find a redirect already
// Try getting the final result and extract it
// It is special if there's a CNAME or NS, DNAME is ignored here
// And we don't consider the NS in origin
found = getRRset(name, &type, true, false, name != origin && !glue_ok);
records_found = found.first;
result_rrset = found.second;
if (result_rrset && name != origin && !glue_ok &&
result_rrset->getType() == isc::dns::RRType::NS()) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
arg(database_->getDBName()).arg(name);
result_status = DELEGATION;
} else if (result_rrset && type != isc::dns::RRType::CNAME() &&
result_rrset->getType() == isc::dns::RRType::CNAME()) {
result_status = CNAME;
}
} catch (const DataSourceError& dse) {
logger.error(DATASRC_DATABASE_FIND_ERROR)
.arg(database_->getDBName()).arg(dse.what());
// call cleanup and rethrow
database_->resetSearch();
throw;
} catch (const isc::Exception& isce) {
logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR)
.arg(database_->getDBName()).arg(isce.what());
// cleanup, change it to a DataSourceError and rethrow
database_->resetSearch();
isc_throw(DataSourceError, isce.what());
} catch (const std::exception& ex) {
logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ERROR)
.arg(database_->getDBName()).arg(ex.what());
database_->resetSearch();
throw;
}
if (!result_rrset) {
......@@ -463,7 +450,7 @@ private:
// Load next row of data
void getData() {
string data[DatabaseAccessor::COLUMN_COUNT];
data_ready_ = context_->getNext(data, DatabaseAccessor::COLUMN_COUNT);
data_ready_ = context_->getNext(data);
name_ = data[DatabaseAccessor::NAME_COLUMN];
rtype_ = data[DatabaseAccessor::TYPE_COLUMN];
ttl_ = data[DatabaseAccessor::TTL_COLUMN];
......@@ -494,7 +481,7 @@ DatabaseClient::getIterator(const isc::dns::Name& name) const {
}
// Request the context
DatabaseAccessor::IteratorContextPtr
context(database_->getAllRecords(name, zone.second));
context(database_->getAllRecords(zone.second));
// It must not return NULL, that's a bug of the implementation
if (context == DatabaseAccessor::IteratorContextPtr()) {
isc_throw(isc::Unexpected, "Iterator context null at " +
......
......@@ -48,6 +48,28 @@ namespace datasrc {
*/
class DatabaseAccessor : boost::noncopyable {
public:
/**
* Definitions of the fields as they are required to be filled in
* by IteratorContext::getNext()
*
* When implementing getNext(), the columns array should
* be filled with the values as described in this enumeration,
* in this order, i.e. TYPE_COLUMN should be the first element
* (index 0) of the array, TTL_COLUMN should be the second element
* (index 1), etc.
*/
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
NAME_COLUMN = 4, ///< The domain name of this RR
COLUMN_COUNT = 5 ///< The total number of columns, MUST be value of
///< the largest other element in this enum plus 1.
};
/**
* \brief Destructor
*
......@@ -103,135 +125,75 @@ public:
* \brief Function to provide next resource record
*
* This function should provide data about the next resource record
* from the iterated zone. The data are not converted yet.
* from the data that is searched. The data is not converted yet.
*
* Depending on how the iterator was constructed, there is a difference
* in behaviour; for a 'full zone iterator', created with
* getAllRecords(), all COLUMN_COUNT elements of the array are
* overwritten.
* For a 'name iterator', created with getRecords(), the column
* NAME_COLUMN is untouched, since what would be added here is by
* definition already known to the caller (it already passes it as
* an argument to getRecords()).
*
* \note The order of RRs is not strictly set, but the RRs for single
* RRset must not be interleaved with any other RRs (eg. RRsets must be
* "together").
*
* \param columns The data will be returned through here. The order
* is specified by the RecordColumns enum.
* \param Size of the columns array. Must be equal to COLUMN_COUNT,
* otherwise DataSourceError is thrown.
* is specified by the RecordColumns enum, and the size must be
* COLUMN_COUNT
* \todo Do we consider databases where it is stored in binary blob
* format?
* \throw DataSourceError if there's database-related error. If the
* exception (or any other in case of derived class) is thrown,
* the iterator can't be safely used any more.
* \return true if a record was found, and the columns array was
* updated. false if there was no more data, in which case
* the columns array is untouched.
*/
virtual bool getNext(std::string columns[], size_t column_data) = 0;
virtual bool getNext(std::string (&columns)[COLUMN_COUNT]) = 0;
};
typedef boost::shared_ptr<IteratorContext> IteratorContextPtr;
/**
* \brief Creates an iterator context for the whole zone.
* \brief Creates an iterator context for a specific name.
*
* Returns an IteratorContextPtr that contains all records of the
* given name from the given zone.
*
* This should create a new iterator context to be used by
* DatabaseConnection's ZoneIterator. It can be created based on the name
* or the ID (returned from getZone()), what is more comfortable for the
* database implementation. Both are provided (and are guaranteed to match,
* the DatabaseClient first looks up the zone ID and then calls this).
* The implementation of the iterator that is returned may leave the
* NAME_COLUMN column of the array passed to getNext() untouched, as that
* data is already known (it is the same as the name argument here)
*
* The default implementation throws isc::NotImplemented, to allow
* "minimal" implementations of the connection not supporting optional
* functionality.
* \exception any Since any implementation can be used, the caller should
* expect any exception to be thrown.
*
* \param name The name of the zone.
* \param name The name to search for. This should be a FQDN.
* \param id The ID of the zone, returned from getZone().
* \return Newly created iterator context. Must not be NULL.
*/
virtual IteratorContextPtr getAllRecords(const isc::dns::Name& name,
int id) const
{
/*
* This is a compromise. We need to document the parameters in doxygen,
* so they need a name, but then it complains about unused parameter.
* This is a NOP that "uses" the parameters.
*/
static_cast<void>(name);
static_cast<void>(id);
isc_throw(isc::NotImplemented,
"This database datasource can't be iterated");
}
/**
* \brief Starts a new search for records of the given name in the given zone
*
* The data searched by this call can be retrieved with subsequent calls to
* getNextRecord().
*
* \exception DataSourceError if there is a problem connecting to the
* backend database
*
* \param zone_id The zone to search in, as returned by getZone()
* \param name The name of the records to find
*/
virtual void searchForRecords(int zone_id, const std::string& name) = 0;
virtual IteratorContextPtr getRecords(const std::string& name,
int id) const = 0;
/**
* \brief Retrieves the next record from the search started with searchForRecords()
*
* 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::COLUMN_COUNT 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 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::string columns[], size_t column_count) = 0;
/**
* \brief Resets the current search initiated with searchForRecords()
* \brief Creates an iterator context for the whole zone.
*
* 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()
* Returns an IteratorContextPtr that contains all records of the
* zone with the given zone id.
*
* 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.
* Each call to getNext() on the returned iterator should copy all
* column fields of the array that is passed, as defined in the
* RecordColumns enum.
*
* 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()
* \exception any Since any implementation can be used, the caller should
* expect any exception to be thrown.
*
* When implementing getNextRecord(), the columns array should
* be filled with the values as described in this enumeration,
* in this order, i.e. TYPE_COLUMN should be the first element
* (index 0) of the array, TTL_COLUMN should be the second element
* (index 1), etc.
* \param id The ID of the zone, returned from getZone().
* \return Newly created iterator context. Must not be NULL.
*/
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
NAME_COLUMN = 4 ///< The domain name of this RR
};
/// The number of fields the columns array passed to getNextRecord should have
static const size_t COLUMN_COUNT = 5;
virtual IteratorContextPtr getAllRecords(int id) const = 0;
/**
* \brief Returns a string identifying this dabase backend
......
......@@ -63,12 +63,6 @@ The maximum allowed number of items of the hotspot cache is set to the given
number. If there are too many, some of them will be dropped. The size of 0
means no limit.
% DATASRC_DATABASE_FIND_ERROR error retrieving data from datasource %1: %2
This was an internal error while reading data from a datasource. This can either
mean the specific data source implementation is not behaving correctly, or the
data it provides is invalid. The current search is aborted.
The error message contains specific information about the error.
% DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3
Debug information. The database data source is looking up records with the given
name and type in the database.
......@@ -79,18 +73,6 @@ different TTL values. This isn't allowed on the wire and is considered
an error, so we set it to the lowest value we found (but we don't modify the
database). The data in database should be checked and fixed.
% DATASRC_DATABASE_FIND_UNCAUGHT_ERROR uncaught general error retrieving data from datasource %1: %2
There was an uncaught general exception while reading data from a datasource.
This most likely points to a logic error in the code, and can be considered a
bug. The current search is aborted. Specific information about the exception is
printed in this error message.
% DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR uncaught error retrieving data from datasource %1: %2
There was an uncaught ISC exception while reading data from a datasource. This
most likely points to a logic error in the code, and can be considered a bug.
The current search is aborted. Specific information about the exception is
printed in this error message.
% DATASRC_DATABASE_FOUND_DELEGATION Found delegation at %2 in %1
When searching for a domain, the program met a delegation to a different zone
at the given domain name. It will return that one instead.
......
......@@ -27,7 +27,7 @@ namespace datasrc {
struct SQLite3Parameters {
SQLite3Parameters() :
db_(NULL), version_(-1),
q_zone_(NULL), q_any_(NULL)
q_zone_(NULL)
/*q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
q_prevnsec3_(NULL) */
......@@ -35,7 +35,6 @@ struct SQLite3Parameters {
sqlite3* db_;
int version_;
sqlite3_stmt* q_zone_;
sqlite3_stmt* q_any_;
/*
TODO: Yet unneeded statements
sqlite3_stmt* q_record_;
......@@ -49,7 +48,7 @@ struct SQLite3Parameters {
};
SQLite3Database::SQLite3Database(const std::string& filename,
const isc::dns::RRClass& rrclass) :
const isc::dns::RRClass& rrclass) :
dbparameters_(new SQLite3Parameters),
class_(rrclass.toText()),
database_name_("sqlite3_" +
......@@ -75,9 +74,6 @@ public:
if (params_.q_zone_ != NULL) {
sqlite3_finalize(params_.q_zone_);
}
if (params_.q_any_ != NULL) {
sqlite3_finalize(params_.q_any_);
}
/*
if (params_.q_record_ != NULL) {
sqlite3_finalize(params_.q_record_);
......@@ -138,9 +134,13 @@ const char* const SCHEMA_LIST[] = {
const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata, name "
// note that the order of the SELECT values is specifically chosen to match
// the enum values in RecordColumns
const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
"FROM records WHERE zone_id=?1 AND name=?2";
// note that the order of the SELECT values is specifically chosen to match
// the enum values in RecordColumns
const char* const q_iterate_str = "SELECT rdtype, ttl, sigtype, rdata, name FROM records "
"WHERE zone_id = ?1 "
"ORDER BY name, rdtype";
......@@ -210,7 +210,6 @@ checkAndSetupSchema(Initializer* initializer) {
}
initializer->params_.q_zone_ = prepare(db, q_zone_str);
initializer->params_.q_any_ = prepare(db, q_any_str);
/* TODO: Yet unneeded statements
initializer->params_.q_record_ = prepare(db, q_record_str);
initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
......@@ -239,7 +238,7 @@ SQLite3Database::open(const std::string& name) {
}
checkAndSetupSchema(&initializer);
initializer.move(dbparameters_);
initializer.move(dbparameters_.get());
}
SQLite3Database::~SQLite3Database() {
......@@ -247,7 +246,6 @@ SQLite3Database::~SQLite3Database() {
if (dbparameters_->db_ != NULL) {
close();
}
delete dbparameters_;
}
void
......@@ -262,9 +260,6 @@ SQLite3Database::close(void) {
sqlite3_finalize(dbparameters_->q_zone_);
dbparameters_->q_zone_ = NULL;
sqlite3_finalize(dbparameters_->q_any_);
dbparameters_->q_any_ = NULL;
/* TODO: Once they are needed or not, uncomment or drop
sqlite3_finalize(dbparameters->q_record_);
dbparameters->q_record_ = NULL;
......@@ -333,63 +328,54 @@ SQLite3Database::getZone(const isc::dns::Name& name) const {
return (std::pair<bool, int>(false, 0));
}
namespace {
// This helper function converts from the unsigned char* type (used by
// sqlite3) to char* (wanted by std::string). Technically these types
// might not be directly convertable
// In case sqlite3_column_text() returns NULL, we just make it an
// empty string.
// The sqlite3parameters value is only used to check the error code if
// ucp == NULL
const char*
convertToPlainChar(const unsigned char* ucp,
SQLite3Parameters* dbparameters) {
if (ucp == NULL) {
// The field can really be NULL, in which case we return an
// empty string, or sqlite may have run out of memory, in
// which case we raise an error
if (dbparameters != NULL &&
sqlite3_errcode(dbparameters->db_) == SQLITE_NOMEM) {
isc_throw(DataSourceError,
"Sqlite3 backend encountered a memory allocation "
"error in sqlite3_column_text()");
} else {
return ("");
}
}
const void* p = ucp;
return (static_cast<const char*>(p));
}
}
// TODO: Once we want to have iterator returned from searchForRecords, this
// class can be reused. It should be modified to take the sqlite3 statement
// instead of creating it in constructor, it doesn't have to care which one
// it is, just provide data from it.
class SQLite3Database::Context : public DatabaseAccessor::IteratorContext {
public:
// Construct an iterator for all records. When constructed this
// way, the getNext() call will copy all fields
Context(const boost::shared_ptr<const SQLite3Database>& database, int id) :
iterator_type_(ITT_ALL),
database_(database),
statement(NULL)
statement_(NULL),
name_("")
{
// We create the statement now and then just keep getting data from it
statement = prepare(database->dbparameters_->db_, q_iterate_str);
if (sqlite3_bind_int(statement, 1, id) != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not bind " << id <<
" to SQL statement (iterate)");
}
statement_ = prepare(database->dbparameters_->db_, q_iterate_str);
bindZoneId(id);
}
bool getNext(std::string data[], size_t size) {
if (size != COLUMN_COUNT) {
isc_throw(DataSourceError, "getNext received size of " << size <<
", not " << COLUMN_COUNT);
}
// Construct an iterator for records with a specific name. When constructed
// this way, the getNext() call will copy all fields except name
Context(const boost::shared_ptr<const SQLite3Database>& database, int id,
const std::string& name) :
iterator_type_(ITT_NAME),
database_(database),
statement_(NULL),
name_(name)
{
// We create the statement now and then just keep getting data from it
statement_ = prepare(database->dbparameters_->db_, q_any_str);
bindZoneId(id);
bindName(name_);
}
bool getNext(std::string (&data)[COLUMN_COUNT]) {
// If there's another row, get it
int rc(sqlite3_step(statement));
// If finalize has been called (e.g. when previous getNext() got
// SQLITE_DONE), directly return false
if (statement_ == NULL) {
return false;
}
const int rc(sqlite3_step(statement_));
if (rc == SQLITE_ROW) {
for (size_t i(0); i < size; ++ i) {
data[i] = convertToPlainChar(sqlite3_column_text(statement, i),
database_->dbparameters_);
// For both types, we copy the first four columns
copyColumn(data, TYPE_COLUMN);
copyColumn(data, TTL_COLUMN);
copyColumn(data, SIGTYPE_COLUMN);
copyColumn(data, RDATA_COLUMN);
// Only copy Name if we are iterating over every record
if (iterator_type_ == ITT_ALL) {
copyColumn(data, NAME_COLUMN);
}
return (true);
} else if (rc != SQLITE_DONE) {
......@@ -397,79 +383,89 @@ public:
"Unexpected failure in sqlite3_step: " <<
sqlite3_errmsg(database_->dbparameters_->db_));
}
finalize();
return (false);
}
virtual ~Context() {
if (statement) {
sqlite3_finalize(statement);
}
finalize();
}
private:
boost::shared_ptr<const SQLite3Database> database_;
sqlite3_stmt *statement;
};
DatabaseAccessor::IteratorContextPtr
SQLite3Database::getAllRecords(const isc::dns::Name&, int id) const {
return (IteratorContextPtr(new Context(shared_from_this(), id)));
}
void
SQLite3Database::searchForRecords(int zone_id, const std::string& name) {
resetSearch();