Commit 1c834de9 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[1068] Merge commit '1351cb42' into trac1068review1 with fixing conflicts.

Also fixed an unrelated bug in getZone() which left an unfinished statement.
parents dc931833 1351cb42
......@@ -51,6 +51,15 @@ public:
/// The number of fields the columns array passed to getNext should have
static const size_t COLUMN_COUNT = 5;
/// TBD
/// Compliant database should support the following columns:
/// name, rname, ttl, rdtype, sigtype, rdata
/// (even though their internal representation may be different).
static const size_t ADD_COLUMN_COUNT = 6;
/// TBD
static const size_t DEL_PARAM_COUNT = 3;
/**
* \brief Destructor
*
......
......@@ -14,38 +14,109 @@
#include <sqlite3.h>
#include <string>
#include <vector>
#include <boost/foreach.hpp>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/logger.h>
#include <datasrc/data_source.h>
#include <util/filename.h>
#include <boost/lexical_cast.hpp>
using namespace std;
namespace isc {
namespace datasrc {
// The following enum and char* array define the SQL statements commonly
// used in this implementation. Corresponding prepared statements (of
// type sqlite3_stmt*) are maintained in the statements_ array of the
// SQLite3Parameters structure.
enum StatementID {
ZONE = 0,
ANY = 1,
BEGIN = 2,
COMMIT = 3,
ROLLBACK = 4,
DEL_ZONE_RECORDS = 5,
ADD_RECORD = 6,
DEL_RECORD = 7,
ITERATE = 8,
NUM_STATEMENTS = 9
};
const char* const text_statements[NUM_STATEMENTS] = {
// note for ANY and ITERATE: the order of the SELECT values is
// specifically chosen to match the enum values in RecordColumns
"SELECT id FROM zones WHERE name=?1 AND rdclass = ?2", // ZONE
"SELECT rdtype, ttl, sigtype, rdata FROM records " // ANY
"WHERE zone_id=?1 AND name=?2",
"BEGIN", // BEGIN
"COMMIT", // COMMIT
"ROLLBACK", // ROLLBACK
"DELETE FROM records WHERE zone_id=?1", // DEL_ZONE_RECORDS
"INSERT INTO records " // ADD_RECORD
"(zone_id, name, rname, ttl, rdtype, sigtype, rdata) "
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
"DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
"AND rdtype=?3 AND rdata=?4",
"SELECT rdtype, ttl, sigtype, rdata, name FROM records " // ITERATE
"WHERE zone_id = ?1 ORDER BY name, rdtype"
};
struct SQLite3Parameters {
SQLite3Parameters() :
db_(NULL), version_(-1),
q_zone_(NULL), q_any_(NULL)
/*q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
q_prevnsec3_(NULL) */
{}
db_(NULL), version_(-1), updating_zone(false), updated_zone_id(-1)
{
for (int i = 0; i < NUM_STATEMENTS; ++i) {
statements_[i] = NULL;
}
}
sqlite3* db_;
int version_;
sqlite3_stmt* q_zone_;
sqlite3_stmt* q_any_;
/*
TODO: Yet unneeded statements
sqlite3_stmt* q_record_;
sqlite3_stmt* q_addrs_;
sqlite3_stmt* q_referral_;
sqlite3_stmt* q_count_;
sqlite3_stmt* q_previous_;
sqlite3_stmt* q_nsec3_;
sqlite3_stmt* q_prevnsec3_;
*/
sqlite3_stmt* statements_[NUM_STATEMENTS];
bool updating_zone; // whether or not updating the zone
int updated_zone_id; // valid only when updating_zone is true
};
// This is a helper class to encapsulate the code logic of executing
// a specific SQLite3 statement, ensuring the corresponding prepared
// statement is always reset whether the execution is completed successfully
// or it results in an exception.
// Note that an object of this class is intended to be used for "ephemeral"
// statement, which is completed with a single "step" (normally within a
// single call to an SQLite3Database method). In particular, it cannot be
// used for "SELECT" variants, which generally expect multiple matching rows.
class StatementExecuter {
public:
// desc will be used on failure in the what() message of the resulting
// DataSourceError exception.
StatementExecuter(SQLite3Parameters& dbparameters, StatementID stmt_id,
const char* desc) :
dbparameters_(dbparameters), stmt_id_(stmt_id), desc_(desc)
{
sqlite3_clear_bindings(dbparameters_.statements_[stmt_id_]);
}
~StatementExecuter() {
sqlite3_reset(dbparameters_.statements_[stmt_id_]);
}
void exec() {
if (sqlite3_step(dbparameters_.statements_[stmt_id_]) !=
SQLITE_DONE) {
sqlite3_reset(dbparameters_.statements_[stmt_id_]);
isc_throw(DataSourceError, "failed to " << desc_ << ": " <<
sqlite3_errmsg(dbparameters_.db_));
}
}
private:
SQLite3Parameters& dbparameters_;
const StatementID stmt_id_;
const char* const desc_;
};
SQLite3Database::SQLite3Database(const std::string& filename,
......@@ -72,35 +143,10 @@ namespace {
class Initializer {
public:
~Initializer() {
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_);
}
if (params_.q_addrs_ != NULL) {
sqlite3_finalize(params_.q_addrs_);
}
if (params_.q_referral_ != NULL) {
sqlite3_finalize(params_.q_referral_);
for (int i = 0; i < NUM_STATEMENTS; ++i) {
sqlite3_finalize(params_.statements_[i]);
}
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_);
}
......@@ -136,49 +182,6 @@ const char* const SCHEMA_LIST[] = {
NULL
};
const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
// 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";
/* 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_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;
......@@ -213,17 +216,9 @@ 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);
initializer->params_.q_referral_ = prepare(db, q_referral_str);
initializer->params_.q_count_ = prepare(db, q_count_str);
initializer->params_.q_previous_ = prepare(db, q_previous_str);
initializer->params_.q_nsec3_ = prepare(db, q_nsec3_str);
initializer->params_.q_prevnsec3_ = prepare(db, q_prevnsec3_str);
*/
for (int i = 0; i < NUM_STATEMENTS; ++i) {
initializer->params_.statements_[i] = prepare(db, text_statements[i]);
}
}
}
......@@ -262,34 +257,10 @@ SQLite3Database::close(void) {
}
// XXX: sqlite3_finalize() could fail. What should we do in that case?
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;
sqlite3_finalize(dbparameters->q_addrs_);
dbparameters->q_addrs_ = NULL;
sqlite3_finalize(dbparameters->q_referral_);
dbparameters->q_referral_ = NULL;
sqlite3_finalize(dbparameters->q_count_);
dbparameters->q_count_ = NULL;
sqlite3_finalize(dbparameters->q_previous_);
dbparameters->q_previous_ = NULL;
sqlite3_finalize(dbparameters->q_prevnsec3_);
dbparameters->q_prevnsec3_ = NULL;
sqlite3_finalize(dbparameters->q_nsec3_);
dbparameters->q_nsec3_ = NULL;
*/
for (int i = 0; i < NUM_STATEMENTS; ++i) {
sqlite3_finalize(dbparameters_->statements_[i]);
dbparameters_->statements_[i] = NULL;
}
sqlite3_close(dbparameters_->db_);
dbparameters_->db_ = NULL;
......@@ -297,41 +268,43 @@ SQLite3Database::close(void) {
std::pair<bool, int>
SQLite3Database::getZone(const isc::dns::Name& name) const {
return (getZone(name.toText()));
}
std::pair<bool, int>
SQLite3Database::getZone(const string& name) const {
int rc;
sqlite3_stmt* const stmt = dbparameters_->statements_[ZONE];
// Take the statement (simple SELECT id FROM zones WHERE...)
// and prepare it (bind the parameters to it)
sqlite3_reset(dbparameters_->q_zone_);
rc = sqlite3_bind_text(dbparameters_->q_zone_, 1, name.toText().c_str(),
-1, SQLITE_TRANSIENT);
sqlite3_reset(stmt);
rc = sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_STATIC);
if (rc != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not bind " << name <<
" to SQL statement (zone)");
}
rc = sqlite3_bind_text(dbparameters_->q_zone_, 2, class_.c_str(), -1,
SQLITE_STATIC);
rc = sqlite3_bind_text(stmt, 2, class_.c_str(), -1, SQLITE_STATIC);
if (rc != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not bind " << class_ <<
" to SQL statement (zone)");
}
// Get the data there and see if it found anything
rc = sqlite3_step(dbparameters_->q_zone_);
std::pair<bool, int> result;
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
result = std::pair<bool, int>(true,
sqlite3_column_int(dbparameters_->
q_zone_, 0));
return (result);
const int zone_id = sqlite3_column_int(stmt, 0);
sqlite3_reset(stmt);
return (pair<bool, int>(true, zone_id));
} else if (rc == SQLITE_DONE) {
result = std::pair<bool, int>(false, 0);
// Free resources
sqlite3_reset(dbparameters_->q_zone_);
return (result);
sqlite3_reset(stmt);
return (pair<bool, int>(false, 0));
}
sqlite3_reset(stmt);
isc_throw(DataSourceError, "Unexpected failure in sqlite3_step: " <<
sqlite3_errmsg(dbparameters_->db_));
sqlite3_errmsg(dbparameters_->db_));
// Compilers might not realize isc_throw always throws
return (std::pair<bool, int>(false, 0));
}
......@@ -375,7 +348,8 @@ public:
statement_(NULL)
{
// We create the statement now and then just keep getting data from it
statement_ = prepare(database->dbparameters_->db_, q_iterate_str);
statement_ = prepare(database->dbparameters_->db_,
text_statements[ITERATE]);
bindZoneId(id);
}
......@@ -388,7 +362,8 @@ public:
statement_(NULL)
{
// We create the statement now and then just keep getting data from it
statement_ = prepare(database->dbparameters_->db_, q_any_str);
statement_ = prepare(database->dbparameters_->db_,
text_statements[ANY]);
bindZoneId(id);
bindName(name);
}
......@@ -467,5 +442,121 @@ SQLite3Database::getAllRecords(int id) const {
return (IteratorContextPtr(new Context(shared_from_this(), id)));
}
pair<bool, int>
SQLite3Database::startUpdateZone(const string& zone_name, const bool replace) {
if (dbparameters_->updating_zone) {
isc_throw(DataSourceError,
"duplicate zone update on SQLite3 data source");
}
const pair<bool, int> zone_info(getZone(zone_name));
if (!zone_info.first) {
return (zone_info);
}
dbparameters_->updating_zone = true;
dbparameters_->updated_zone_id = zone_info.second;
StatementExecuter(*dbparameters_, BEGIN,
"start an SQLite3 transaction").exec();
if (replace) {
StatementExecuter delzone_exec(*dbparameters_, DEL_ZONE_RECORDS,
"delete zone records");
sqlite3_clear_bindings(dbparameters_->statements_[DEL_ZONE_RECORDS]);
if (sqlite3_bind_int(dbparameters_->statements_[DEL_ZONE_RECORDS],
1, zone_info.second) != SQLITE_OK) {
isc_throw(DataSourceError,
"failed to bind SQLite3 parameter: " <<
sqlite3_errmsg(dbparameters_->db_));
}
delzone_exec.exec();
}
return (zone_info);
}
void
SQLite3Database::commitUpdateZone() {
if (!dbparameters_->updating_zone) {
isc_throw(DataSourceError, "committing zone update on SQLite3 "
"data source without transaction");
}
StatementExecuter(*dbparameters_, COMMIT,
"commit an SQLite3 transaction").exec();
dbparameters_->updating_zone = false;
dbparameters_->updated_zone_id = -1;
}
void
SQLite3Database::rollbackUpdateZone() {
if (!dbparameters_->updating_zone) {
isc_throw(DataSourceError, "rolling back zone update on SQLite3 "
"data source without transaction");
}
StatementExecuter(*dbparameters_, ROLLBACK,
"rollback an SQLite3 transaction").exec();
dbparameters_->updating_zone = false;
dbparameters_->updated_zone_id = -1;
}
namespace {
// Commonly used code sequence for adding/deleting record
void
doUpdate(SQLite3Parameters& dbparams, StatementID stmt_id,
const vector<string>& update_params, const char* exec_desc)
{
sqlite3_stmt* const stmt = dbparams.statements_[stmt_id];
StatementExecuter executer(dbparams, stmt_id, exec_desc);
int param_id = 0;
if (sqlite3_bind_int(stmt, ++param_id, dbparams.updated_zone_id)
!= SQLITE_OK) {
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
sqlite3_errmsg(dbparams.db_));
}
BOOST_FOREACH(const string& column, update_params) {
if (sqlite3_bind_text(stmt, ++param_id, column.c_str(), -1,
SQLITE_TRANSIENT) != SQLITE_OK) {
isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
sqlite3_errmsg(dbparams.db_));
}
}
executer.exec();
}
}
void
SQLite3Database::addRecordToZone(const vector<string>& columns) {
if (!dbparameters_->updating_zone) {
isc_throw(DataSourceError, "adding record to SQLite3 "
"data source without transaction");
}
if (columns.size() != ADD_COLUMN_COUNT) {
isc_throw(DataSourceError, "adding incompatible number of columns "
"to SQLite3 data source: " << columns.size());
}
doUpdate(*dbparameters_, ADD_RECORD, columns, "add record to zone");
}
void
SQLite3Database::deleteRecordInZone(const vector<string>& params) {
if (!dbparameters_->updating_zone) {
isc_throw(DataSourceError, "deleting record in SQLite3 "
"data source without transaction");
}
if (params.size() != DEL_PARAM_COUNT) {
isc_throw(DataSourceError, "incompatible # of parameters for "
"deleting in SQLite3 data source: " << params.size());
}
doUpdate(*dbparameters_, DEL_RECORD, params, "delete record from zone");
}
}
}
......@@ -119,6 +119,37 @@ public:
*/
virtual IteratorContextPtr getAllRecords(int id) const;
/// TBD
/// This cannot be nested.
virtual std::pair<bool, int> startUpdateZone(const std::string& zone_name,
bool replace);
/// TBD
/// Note: we are quite impatient here: it's quite possible that the COMMIT
/// fails due to other process performing SELECT on the same database
/// (consider the case where COMMIT is done by xfrin or dynamic update
/// server while an authoritative server is busy reading the DB).
/// In a future version we should probably need to introduce some retry
/// attempt and/or increase timeout before giving up the COMMIT, even
/// if it still doesn't guarantee 100% success.
virtual void commitUpdateZone();
/// TBD
///
/// In SQLite3 rollback can fail if there's another unfinished statement
/// is performed for the same database structure. Although it's not
/// expected to happen in our expected usage, it's not guaranteed to be
/// prevented at the API level. If it ever happens, this method throws
/// an \c DataSourceError exception. It should be considered a bug of
/// the higher level application program.
virtual void rollbackUpdateZone();
/// TBD
virtual void addRecordToZone(const std::vector<std::string>& columns);
/// TBD
virtual void deleteRecordInZone(const std::vector<std::string>& params);
/// The SQLite3 implementation of this method returns a string starting
/// with a fixed prefix of "sqlite3_" followed by the DB file name
/// removing any path name. For example, for the DB file
......@@ -127,6 +158,11 @@ public:
virtual const std::string& getDBName() const { return (database_name_); }
private:
// same as the public version except it takes name as a string
// (actually this is the intended interface. this should replace the
// current public version).
std::pair<bool, int> getZone(const std::string& name) const;
/// \brief Private database data
boost::scoped_ptr<SQLite3Parameters> dbparameters_;
/// \brief The class for which the queries are done
......@@ -144,4 +180,8 @@ private:
}
}
#endif
#endif // __DATASRC_SQLITE3_CONNECTION_H
// Local Variables:
// mode: c++
// End:
SUBDIRS = . testdata
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
......
......@@ -11,6 +11,9 @@
// 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 <vector>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/data_source.h>
......@@ -20,7 +23,9 @@
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
using namespace std;
using namespace isc::datasrc;
using boost::shared_ptr;
using isc::data::ConstElementPtr;
using isc::data::Element;
using isc::dns::RRClass;
......@@ -273,4 +278,299 @@ TEST_F(SQLite3Access, getRecords) {
"33495 example.com. FAKEFAKEFAKEFAKE", "");
}
//
// Commonly used data for update tests
//
const char* const common_expected_data[] = {
// Test record already stored in the tested sqlite3 DB file.
"foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
"192.0.2.1"
};
const char* const new_data[] = {
// Newly added data commonly used by some of the tests below
"newdata.example.com.", "com.example.newdata.", "3600", "A", "",
"192.0.2.1"
};
const char* const deleted_data[] = {
// Existing data to be removed commonly used by some of the tests below
"foo.bar.example.com.", "A", "192.0.2.1"
};
class SQLite3Update : public SQLite3Access {
protected:
SQLite3Update() {
ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
"/test.sqlite3 "
TEST_DATA_BUILDDIR "/test.sqlite3.copied"));
initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", RRClass::IN());
zone_id = db->getZone(Name("example.com")).second;
another_db.reset(new SQLite3Database(
TEST_DATA_BUILDDIR "/test.sqlite3.copied",