#include "sqlite++.h"
#include <stdexcept>
#include <security_utilities/cfutilities.h>
#define errSecErrnoBase 100000
#define errSecErrnoLimit 100255
namespace Security {
namespace SQLite3 {
void Error::check(int err)
{
if (err != SQLITE_OK)
throw Error(err);
}
Error::Error(Database &db)
: error(db.errcode()), message(db.errmsg())
{
SECURITY_EXCEPTION_THROW_SQLITE(this, error, (char*)message.c_str());
}
void Error::throwMe(int err)
{
throw Error(err);
}
OSStatus Error::osStatus() const
{
return unixError() + errSecErrnoBase;
}
int Error::unixError() const
{
switch (error) {
case SQLITE_PERM:
case SQLITE_READONLY:
case SQLITE_AUTH:
return EACCES;
case SQLITE_BUSY:
return EAGAIN;
case SQLITE_NOMEM:
return ENOMEM;
case SQLITE_IOERR:
return EIO;
case SQLITE_FULL:
return ENOSPC;
case SQLITE_TOOBIG:
return EFBIG;
case SQLITE_MISMATCH:
case SQLITE_MISUSE:
return EINVAL;
case SQLITE_NOLFS:
return ENOTSUP;
case SQLITE_RANGE:
return EDOM;
default:
return -1;
}
}
Database::Database(const char *path, int flags, bool lenient )
: mMutex(Mutex::recursive)
{
try {
int rc = ::sqlite3_open_v2(path, &mDb, flags, NULL);
if (rc != SQLITE_OK && lenient) { sqlite3_close(mDb); mDb = NULL; return;
}
check(rc);
check(::sqlite3_extended_result_codes(mDb, true));
mOpenFlags = flags;
} catch (...) {
sqlite3_close(mDb); throw;
}
}
Database::~Database()
{
this->close();
}
void Database::close()
{
if (mDb)
check(::sqlite3_close(mDb));
}
int Database::execute(const char *text, bool strict )
{
StLock<Mutex> _(mMutex);
int rc = ::sqlite3_exec(mDb, text, NULL, NULL, NULL);
if (strict)
check(rc);
return rc;
}
void Database::busyDelay(int ms)
{
StLock<Mutex> _(mMutex);
check(::sqlite3_busy_timeout(mDb, ms));
}
void Database::check(int err)
{
if (err)
throw Error(*this);
}
bool Database::empty()
{
return value("select count(*) from sqlite_master;", 0) == 0;
}
int Database::errcode()
{
StLock<Mutex> _(mMutex);
return sqlite3_errcode(mDb);
}
const char *Database::errmsg()
{
StLock<Mutex> _(mMutex);
return sqlite3_errmsg(mDb);
}
bool Database::inTransaction()
{
StLock<Mutex> _(mMutex);
return !::sqlite3_get_autocommit(mDb);
}
int64 Database::lastInsert()
{
StLock<Mutex> _(mMutex);
return ::sqlite3_last_insert_rowid(mDb);
}
int Database::changes()
{
StLock<Mutex> _(mMutex);
return ::sqlite3_changes(mDb);
}
void Database::interrupt()
{
StLock<Mutex> _(mMutex);
::sqlite3_interrupt(mDb);
}
Transaction::Transaction(Database &db, Type type, const char *name)
: database(db), mName(name ? name : "")
{
switch (type) {
case deferred: xactCommand("BEGIN DEFERRED"); break;
case immediate: xactCommand("BEGIN IMMEDIATE"); break;
case exclusive: xactCommand("BEGIN EXCLUSIVE"); break;
}
}
Transaction::~Transaction()
{
if (database.inTransaction())
abort();
}
void Transaction::commit()
{
xactCommand("COMMIT");
}
void Transaction::abort()
{
xactCommand("ROLLBACK");
}
void Transaction::xactCommand(const string &cmd)
{
database.execute(cmd + " TRANSACTION " + mName + ";");
}
Statement::Statement(Database &db, const char *text)
: StLock<Mutex>(db.mMutex), database(db), mStmt(NULL)
{
this->query(text);
}
Statement::Statement(Database &db)
: StLock<Mutex>(db.mMutex), database(db), mStmt(NULL)
{ }
void Statement::query(const char *text)
{
this->close();
const char *tail;
check(::sqlite3_prepare_v2(database.sql(), text, -1, &mStmt, &tail));
if (*tail)
throw std::logic_error("multiple statements");
}
void Statement::close()
{
if (mStmt)
::sqlite3_finalize(mStmt);
mStmt = NULL;
}
Statement::~Statement()
{
this->close();
}
void Statement::unbind()
{
check(::sqlite3_clear_bindings(mStmt));
}
void Statement::reset()
{
check(::sqlite3_reset(mStmt));
}
int Statement::step()
{
return ::sqlite3_step(mStmt);
}
void Statement::execute()
{
switch (int rc = this->step()) {
case SQLITE_DONE:
case SQLITE_OK:
break;
default:
check(rc);
}
}
bool Statement::nextRow()
{
switch (int rc = this->step()) {
case SQLITE_ROW:
return true;
case SQLITE_DONE:
return false;
default:
check(rc);
return false;
}
}
Statement::Binding Statement::bind(const char *name) const
{
if (int ix = ::sqlite3_bind_parameter_index(mStmt, name))
return Binding(*this, ix);
else
throw std::logic_error("unknown parameter name");
}
void Statement::Binding::null()
{
statement.check(::sqlite3_bind_null(statement.sql(), index));
}
void Statement::Binding::operator = (const Value &value)
{
statement.check(::sqlite3_bind_value(statement.sql(), index, value.sql()));
}
void Statement::Binding::operator = (int value)
{
statement.check(::sqlite3_bind_int(statement.sql(), index, value));
}
void Statement::Binding::operator = (sqlite3_int64 value)
{
statement.check(::sqlite3_bind_int64(statement.sql(), index, value));
}
void Statement::Binding::integer(sqlite3_int64 value)
{
statement.check(::sqlite3_bind_int64(statement.sql(), index, value));
}
void Statement::Binding::operator = (double value)
{
statement.check(::sqlite3_bind_double(statement.sql(), index, value));
}
void Statement::Binding::operator = (const char *value)
{
if (value == NULL)
this->null();
else
statement.check(::sqlite3_bind_text(statement.sql(), index,
::strdup(value), -1, ::free));
}
void Statement::Binding::operator = (const std::string &value)
{
statement.check(::sqlite3_bind_text(statement.sql(), index,
::strdup(value.c_str()), -1, ::free));
}
void Statement::Binding::blob(const void *data, size_t length, bool shared )
{
if (data == NULL)
this->null();
else if (shared) {
statement.check(::sqlite3_bind_blob(statement.sql(), index, data, (int)length, NULL));
} else if (void *copy = ::malloc(length)) {
::memcpy(copy, data, length);
statement.check(::sqlite3_bind_blob(statement.sql(), index,
copy, (int)length, ::free));
} else
throw std::bad_alloc();
}
void Statement::Binding::operator = (CFDataRef data)
{
if (data)
this->blob(CFDataGetBytePtr(data), CFDataGetLength(data));
else
this->null();
}
void Statement::Binding::operator = (CFStringRef value)
{
if (value)
*this = cfString(value).c_str();
else
this->null();
}
const char *Statement::Binding::name() const
{
return sqlite3_bind_parameter_name(statement.sql(), index);
}
const char *Statement::Result::name() const
{
return sqlite3_column_name(statement.sql(), index);
}
CFDataRef Statement::Result::data() const
{
switch (this->type()) {
case SQLITE_NULL:
return NULL;
case SQLITE_BLOB:
return makeCFData(this->blob(), this->length());
default:
throw Error(SQLITE_MISMATCH, "Retrieving data() of non-Blob");
}
}
} }