Commit 977f822d authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge branch 'work/database-client'

parents 0081ce40 d00042b0
......@@ -122,8 +122,8 @@ public:
masterLoad(zone_stream, origin_, rrclass_,
boost::bind(&MockZoneFinder::loadRRset, this, _1));
}
virtual const isc::dns::Name& getOrigin() const { return (origin_); }
virtual const isc::dns::RRClass& getClass() const { return (rrclass_); }
virtual isc::dns::Name getOrigin() const { return (origin_); }
virtual isc::dns::RRClass getClass() const { return (rrclass_); }
virtual FindResult find(const isc::dns::Name& name,
const isc::dns::RRType& type,
RRsetList* target = NULL,
......
......@@ -22,6 +22,8 @@ libdatasrc_la_SOURCES += zone.h
libdatasrc_la_SOURCES += result.h
libdatasrc_la_SOURCES += logger.h logger.cc
libdatasrc_la_SOURCES += client.h
libdatasrc_la_SOURCES += database.h database.cc
libdatasrc_la_SOURCES += sqlite3_accessor.h sqlite3_accessor.cc
nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
......
......@@ -15,6 +15,8 @@
#ifndef __DATA_SOURCE_CLIENT_H
#define __DATA_SOURCE_CLIENT_H 1
#include <boost/noncopyable.hpp>
#include <datasrc/zone.h>
namespace isc {
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// 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 <datasrc/database.h>
#include <exceptions/exceptions.h>
#include <dns/name.h>
using isc::dns::Name;
namespace isc {
namespace datasrc {
DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
database) :
database_(database)
{
if (database_.get() == NULL) {
isc_throw(isc::InvalidParameter,
"No database provided to DatabaseClient");
}
}
DataSourceClient::FindResult
DatabaseClient::findZone(const Name& name) const {
std::pair<bool, int> zone(database_->getZone(name));
// Try exact first
if (zone.first) {
return (FindResult(result::SUCCESS,
ZoneFinderPtr(new Finder(database_,
zone.second))));
}
// Than super domains
// Start from 1, as 0 is covered above
for (size_t i(1); i < name.getLabelCount(); ++i) {
zone = database_->getZone(name.split(i));
if (zone.first) {
return (FindResult(result::PARTIALMATCH,
ZoneFinderPtr(new Finder(database_,
zone.second))));
}
}
// No, really nothing
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor>
database, int zone_id) :
database_(database),
zone_id_(zone_id)
{ }
ZoneFinder::FindResult
DatabaseClient::Finder::find(const isc::dns::Name&,
const isc::dns::RRType&,
isc::dns::RRsetList*,
const FindOptions) const
{
// TODO Implement
return (FindResult(SUCCESS, isc::dns::ConstRRsetPtr()));
}
Name
DatabaseClient::Finder::getOrigin() const {
// TODO Implement
return (Name("."));
}
isc::dns::RRClass
DatabaseClient::Finder::getClass() const {
// TODO Implement
return isc::dns::RRClass::IN();
}
}
}
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// 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.
#ifndef __DATABASE_DATASRC_H
#define __DATABASE_DATASRC_H
#include <datasrc/client.h>
namespace isc {
namespace datasrc {
/**
* \brief Abstraction of lowlevel database with DNS data
*
* This class is defines interface to databases. Each supported database
* will provide methods for accessing the data stored there in a generic
* manner. The methods are meant to be low-level, without much or any knowledge
* about DNS and should be possible to translate directly to queries.
*
* On the other hand, how the communication with database is done and in what
* schema (in case of relational/SQL database) is up to the concrete classes.
*
* This class is non-copyable, as copying connections to database makes little
* sense and will not be needed.
*
* \todo Is it true this does not need to be copied? For example the zone
* iterator might need it's own copy. But a virtual clone() method might
* be better for that than copy constructor.
*
* \note The same application may create multiple connections to the same
* database, having multiple instances of this class. If the database
* allows having multiple open queries at one connection, the connection
* class may share it.
*/
class DatabaseAccessor : boost::noncopyable {
public:
/**
* \brief Destructor
*
* It is empty, but needs a virtual one, since we will use the derived
* classes in polymorphic way.
*/
virtual ~DatabaseAccessor() { }
/**
* \brief Retrieve a zone identifier
*
* This method looks up a zone for the given name in the database. It
* should match only exact zone name (eg. name is equal to the zone's
* apex), as the DatabaseClient will loop trough the labels itself and
* find the most suitable zone.
*
* It is not specified if and what implementation of this method may throw,
* so code should expect anything.
*
* \param name The name of the zone's apex to be looked up.
* \return The first part of the result indicates if a matching zone
* was found. In case it was, the second part is internal zone ID.
* This one will be passed to methods finding data in the zone.
* It is not required to keep them, in which case whatever might
* be returned - the ID is only passed back to the database as
* an opaque handle.
*/
virtual std::pair<bool, int> getZone(const isc::dns::Name& name) const = 0;
};
/**
* \brief Concrete data source client oriented at database backends.
*
* This class (together with corresponding versions of ZoneFinder,
* ZoneIterator, etc.) translates high-level data source queries to
* low-level calls on DatabaseAccessor. It calls multiple queries
* if necessary and validates data from the database, allowing the
* DatabaseAccessor to be just simple translation to SQL/other
* queries to database.
*
* While it is possible to subclass it for specific database in case
* of special needs, it is not expected to be needed. This should just
* work as it is with whatever DatabaseAccessor.
*/
class DatabaseClient : public DataSourceClient {
public:
/**
* \brief Constructor
*
* It initializes the client with a database.
*
* \exception isc::InvalidParameter if database is NULL. It might throw
* standard allocation exception as well, but doesn't throw anything else.
*
* \param database The database to use to get data. As the parameter
* suggests, the client takes ownership of the database and will
* delete it when itself deleted.
*/
DatabaseClient(boost::shared_ptr<DatabaseAccessor> database);
/**
* \brief Corresponding ZoneFinder implementation
*
* The zone finder implementation for database data sources. Similarly
* to the DatabaseClient, it translates the queries to methods of the
* database.
*
* Application should not come directly in contact with this class
* (it should handle it trough generic ZoneFinder pointer), therefore
* it could be completely hidden in the .cc file. But it is provided
* to allow testing and for rare cases when a database needs slightly
* different handling, so it can be subclassed.
*
* Methods directly corresponds to the ones in ZoneFinder.
*/
class Finder : public ZoneFinder {
public:
/**
* \brief Constructor
*
* \param database The database (shared with DatabaseClient) to
* be used for queries (the one asked for ID before).
* \param zone_id The zone ID which was returned from
* DatabaseAccessor::getZone and which will be passed to further
* calls to the database.
*/
Finder(boost::shared_ptr<DatabaseAccessor> database, int zone_id);
// The following three methods are just implementations of inherited
// ZoneFinder's pure virtual methods.
virtual isc::dns::Name getOrigin() const;
virtual isc::dns::RRClass getClass() const;
virtual FindResult find(const isc::dns::Name& name,
const isc::dns::RRType& type,
isc::dns::RRsetList* target = NULL,
const FindOptions options = FIND_DEFAULT)
const;
/**
* \brief The zone ID
*
* This function provides the stored zone ID as passed to the
* constructor. This is meant for testing purposes and normal
* applications shouldn't need it.
*/
int zone_id() const { return (zone_id_); }
/**
* \brief The database.
*
* This function provides the database stored inside as
* passed to the constructor. This is meant for testing purposes and
* normal applications shouldn't need it.
*/
const DatabaseAccessor& database() const {
return (*database_);
}
private:
boost::shared_ptr<DatabaseAccessor> database_;
const int zone_id_;
};
/**
* \brief Find a zone in the database
*
* This queries database's getZone to find the best matching zone.
* It will propagate whatever exceptions are thrown from that method
* (which is not restricted in any way).
*
* \param name Name of the zone or data contained there.
* \return FindResult containing the code and an instance of Finder, if
* anything is found. However, application should not rely on the
* ZoneFinder being instance of Finder (possible subclass of this class
* may return something else and it may change in future versions), it
* should use it as a ZoneFinder only.
*/
virtual FindResult findZone(const isc::dns::Name& name) const;
private:
/// \brief Our database.
const boost::shared_ptr<DatabaseAccessor> database_;
};
}
}
#endif
......@@ -400,12 +400,22 @@ enough information for it. The code is 1 for error, 2 for not implemented.
% DATASRC_SQLITE_CLOSE closing SQLite database
Debug information. The SQLite data source is closing the database file.
% DATASRC_SQLITE_CONNOPEN Opening sqlite database file '%1'
The database file is being opened so it can start providing data.
% DATASRC_SQLITE_CONNCLOSE Closing sqlite database
The database file is no longer needed and is being closed.
% DATASRC_SQLITE_CREATE SQLite data source created
Debug information. An instance of SQLite data source is being created.
% DATASRC_SQLITE_DESTROY SQLite data source destroyed
Debug information. An instance of SQLite data source is being destroyed.
% DATASRC_SQLITE_DROPCONN SQLite3Database is being deinitialized
The object around a database connection is being destroyed.
% DATASRC_SQLITE_ENCLOSURE looking for zone containing '%1'
Debug information. The SQLite data source is trying to identify which zone
should hold this domain.
......@@ -458,6 +468,9 @@ source.
The SQLite data source was asked to provide a NSEC3 record for given zone.
But it doesn't contain that zone.
% DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized
A wrapper object to hold database connection is being initialized.
% DATASRC_SQLITE_OPEN opening SQLite database '%1'
Debug information. The SQLite data source is loading an SQLite database in
the provided file.
......@@ -496,4 +509,3 @@ data source.
% DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
This indicates a programming error. An internal task of unknown type was
generated.
......@@ -606,12 +606,12 @@ InMemoryZoneFinder::~InMemoryZoneFinder() {
delete impl_;
}
const Name&
Name
InMemoryZoneFinder::getOrigin() const {
return (impl_->origin_);
}
const RRClass&
RRClass
InMemoryZoneFinder::getClass() const {
return (impl_->zone_class_);
}
......
......@@ -58,10 +58,10 @@ public:
//@}
/// \brief Returns the origin of the zone.
virtual const isc::dns::Name& getOrigin() const;
virtual isc::dns::Name getOrigin() const;
/// \brief Returns the class of the zone.
virtual const isc::dns::RRClass& getClass() const;
virtual isc::dns::RRClass getClass() const;
/// \brief Looks up an RRset in the zone.
///
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// 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 <sqlite3.h>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/logger.h>
#include <datasrc/data_source.h>
namespace isc {
namespace datasrc {
struct SQLite3Parameters {
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) */
{}
sqlite3* db_;
int version_;
sqlite3_stmt* q_zone_;
/*
TODO: Yet unneeded statements
sqlite3_stmt* q_record_;
sqlite3_stmt* q_addrs_;
sqlite3_stmt* q_referral_;
sqlite3_stmt* q_any_;
sqlite3_stmt* q_count_;
sqlite3_stmt* q_previous_;
sqlite3_stmt* q_nsec3_;
sqlite3_stmt* q_prevnsec3_;
*/
};
SQLite3Database::SQLite3Database(const std::string& filename,
const isc::dns::RRClass& rrclass) :
dbparameters_(new SQLite3Parameters),
class_(rrclass.toText())
{
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
open(filename);
}
namespace {
// This is a helper class to initialize a Sqlite3 DB safely. An object of
// this class encapsulates all temporary resources that are necessary for
// the initialization, and release them in the destructor. Once everything
// is properly initialized, the move() method moves the allocated resources
// to the main object in an exception free manner. This way, the main code
// for the initialization can be exception safe, and can provide the strong
// exception guarantee.
class Initializer {
public:
~Initializer() {
if (params_.q_zone_ != NULL) {
sqlite3_finalize(params_.q_zone_);
}
/*
if (params_.q_record_ != NULL) {
sqlite3_finalize(params_.q_record_);
}
if (params_.q_addrs_ != NULL) {
sqlite3_finalize(params_.q_addrs_);
}
if (params_.q_referral_ != NULL) {
sqlite3_finalize(params_.q_referral_);
}
if (params_.q_any_ != NULL) {
sqlite3_finalize(params_.q_any_);
}
if (params_.q_count_ != NULL) {
sqlite3_finalize(params_.q_count_);
}
if (params_.q_previous_ != NULL) {
sqlite3_finalize(params_.q_previous_);
}
if (params_.q_nsec3_ != NULL) {
sqlite3_finalize(params_.q_nsec3_);
}
if (params_.q_prevnsec3_ != NULL) {
sqlite3_finalize(params_.q_prevnsec3_);
}
*/
if (params_.db_ != NULL) {
sqlite3_close(params_.db_);
}
}
void move(SQLite3Parameters* dst) {
*dst = params_;
params_ = SQLite3Parameters(); // clear everything
}
SQLite3Parameters params_;
};
const char* const SCHEMA_LIST[] = {
"CREATE TABLE schema_version (version INTEGER NOT NULL)",
"INSERT INTO schema_version VALUES (1)",
"CREATE TABLE zones (id INTEGER PRIMARY KEY, "
"name STRING NOT NULL COLLATE NOCASE, "
"rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN', "
"dnssec BOOLEAN NOT NULL DEFAULT 0)",
"CREATE INDEX zones_byname ON zones (name)",
"CREATE TABLE records (id INTEGER PRIMARY KEY, "
"zone_id INTEGER NOT NULL, name STRING NOT NULL COLLATE NOCASE, "
"rname STRING NOT NULL COLLATE NOCASE, ttl INTEGER NOT NULL, "
"rdtype STRING NOT NULL COLLATE NOCASE, sigtype STRING COLLATE NOCASE, "
"rdata STRING NOT NULL)",
"CREATE INDEX records_byname ON records (name)",
"CREATE INDEX records_byrname ON records (rname)",
"CREATE TABLE nsec3 (id INTEGER PRIMARY KEY, zone_id INTEGER NOT NULL, "
"hash STRING NOT NULL COLLATE NOCASE, "
"owner STRING NOT NULL COLLATE NOCASE, "
"ttl INTEGER NOT NULL, rdtype STRING NOT NULL COLLATE NOCASE, "
"rdata STRING NOT NULL)",
"CREATE INDEX nsec3_byhash ON nsec3 (hash)",
NULL
};
const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
/* TODO: Prune the statements, not everything will be needed maybe?
const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
"FROM records WHERE zone_id=?1 AND name=?2 AND "
"((rdtype=?3 OR sigtype=?3) OR "
"(rdtype='CNAME' OR sigtype='CNAME') OR "
"(rdtype='NS' OR sigtype='NS'))";
const char* const q_addrs_str = "SELECT rdtype, ttl, sigtype, rdata "
"FROM records WHERE zone_id=?1 AND name=?2 AND "
"(rdtype='A' OR sigtype='A' OR rdtype='AAAA' OR sigtype='AAAA')";
const char* const q_referral_str = "SELECT rdtype, ttl, sigtype, rdata FROM "
"records WHERE zone_id=?1 AND name=?2 AND"
"(rdtype='NS' OR sigtype='NS' OR rdtype='DS' OR sigtype='DS' OR "
"rdtype='DNAME' OR sigtype='DNAME')";
const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
"FROM records WHERE zone_id=?1 AND name=?2";
const char* const q_count_str = "SELECT COUNT(*) FROM records "
"WHERE zone_id=?1 AND rname LIKE (?2 || '%');";
const char* const q_previous_str = "SELECT name FROM records "
"WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
"rname < $2 ORDER BY rname DESC LIMIT 1";
const char* const q_nsec3_str = "SELECT rdtype, ttl, rdata FROM nsec3 "
"WHERE zone_id = ?1 AND hash = $2";
const char* const q_prevnsec3_str = "SELECT hash FROM nsec3 "
"WHERE zone_id = ?1 AND hash <= $2 ORDER BY hash DESC LIMIT 1";
*/
sqlite3_stmt*
prepare(sqlite3* const db, const char* const statement) {
sqlite3_stmt* prepared = NULL;
if (sqlite3_prepare_v2(db, statement, -1, &prepared, NULL) != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not prepare SQLite statement: " <<
statement);
}
return (prepared);
}
void
checkAndSetupSchema(Initializer* initializer) {
sqlite3* const db = initializer->params_.db_;
sqlite3_stmt* prepared = NULL;
if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
&prepared, NULL) == SQLITE_OK &&
sqlite3_step(prepared) == SQLITE_ROW) {
initializer->params_.version_ = sqlite3_column_int(prepared, 0);
sqlite3_finalize(prepared);
} else {
logger.info(DATASRC_SQLITE_SETUP);
if (prepared != NULL) {
sqlite3_finalize(prepared);
}
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
isc_throw(SQLite3Error,
"Failed to set up schema " << SCHEMA_LIST[i]);
}
}
}
initializer->params_.q_zone_ = prepare(db, q_zone_str);
/* TODO: Yet unneeded statements
initializer->params_.q_record_ = prepare(db, q_record_str);
initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
initializer->params_.q_referral_ = prepare(db, q_referral_str);
initializer->params_.q_any_ = prepare(db, q_any_str);
initializer->params_.q_count_ = prepare(db, q_count_str);
initializer->params_.q_previous_ = prepare(db, q_previous_str);
initializer->params_.q_nsec3_ = prepare(db, q_nsec3_str);
initializer->params_.q_prevnsec3_ = prepare(db, q_prevnsec3_str);
*/
}
}
void
SQLite3Database::open(const std::string& name) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CONNOPEN).arg(name);
if (dbparameters_->db_ != NULL) {
// There shouldn't be a way to trigger this anyway
isc_throw(DataSourceError, "Duplicate SQLite open with " << name);
}
Initializer initializer;
if (sqlite3_open(name.c_str(), &initializer.params_.db_) != 0) {
isc_throw(SQLite3Error, "Cannot open SQLite database file: " << name);
}
checkAndSetupSchema(&initializer);
initializer.move(dbparameters_);
}
SQLite3Database::~SQLite3Database() {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_DROPCONN);
if (dbparameters_->db_ != NULL) {
close();
}
delete dbparameters_;
}
void
SQLite3Database::close(void) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CONNCLOSE);
if (dbparameters_->db_ == NULL) {
isc_throw(DataSourceError,
"SQLite data source is being closed before open");