472 lines
11 KiB
C
472 lines
11 KiB
C
/* Bongo Project licensing applies to this file, see COPYING
|
|
* (C) 2006 Novell
|
|
* (C) 2007 Alex Hudson
|
|
*/
|
|
|
|
/** \file
|
|
* SQLite Helper API
|
|
*/
|
|
|
|
/* This code all adapted from the Store, but made generic.
|
|
* Eventually, the store should probably use this
|
|
*/
|
|
|
|
#include <xpl.h>
|
|
#include <msgapi.h>
|
|
#include <sqlite3.h>
|
|
#include <logger.h>
|
|
|
|
MsgSQLHandle *
|
|
MsgSQLOpen(char *path, BongoMemStack *memstack, int locktimeoutms)
|
|
{
|
|
MsgSQLHandle *handle = NULL;
|
|
int create = 0;
|
|
|
|
#if 0
|
|
if (! sqlite3_threadsafe()) {
|
|
Log(LOG_DEBUG, "msgapi: SQLite needs to be compiled thread-safe. Cannot run.");
|
|
abort();
|
|
}
|
|
#endif
|
|
|
|
create = access(path, 0);
|
|
|
|
handle = MemMalloc(sizeof(MsgSQLHandle));
|
|
if (!handle) {
|
|
Log(LOG_ERROR, "sql3: Out of memory opening database \"%s\"", path);
|
|
return NULL;
|
|
}
|
|
memset(handle, 0, sizeof(MsgSQLHandle));
|
|
|
|
if (SQLITE_OK != sqlite3_open(path, &handle->db)) {
|
|
Log(LOG_ERROR, "sql3: Failed to open database \"%s\"", path);
|
|
if (handle->db) {
|
|
sqlite3_close(handle->db);
|
|
}
|
|
MemFree(handle);
|
|
return NULL;
|
|
}
|
|
|
|
Log(LOG_TRACE, "open handle %ld", handle->db);
|
|
|
|
handle->memstack = memstack;
|
|
handle->lockTimeoutMs = locktimeoutms;
|
|
handle->transactionDepth = 0;
|
|
XplMutexInit(handle->transactionLock);
|
|
|
|
if (create) {
|
|
handle->isNew = TRUE;
|
|
} else {
|
|
handle->isNew = FALSE;
|
|
}
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
void
|
|
MsgSQLClose(MsgSQLHandle *handle)
|
|
{
|
|
MsgSQLReset(handle);
|
|
XplMutexDestroy(handle->transactionLock);
|
|
if (SQLITE_BUSY == sqlite3_close(handle->db)) {
|
|
Log(LOG_ERROR, "sql3: couldn't close database");
|
|
}
|
|
Log(LOG_TRACE, "sql3: close handle %ld\n\n", handle->db);
|
|
|
|
MemFree(handle);
|
|
}
|
|
|
|
void
|
|
MsgSQLEndStatement(MsgSQLStatement *_stmt)
|
|
{
|
|
int result;
|
|
if (_stmt && _stmt->stmt) {
|
|
result = sqlite3_reset(_stmt->stmt);
|
|
if (result != SQLITE_OK)
|
|
Log(LOG_ERROR, "sql3: Error ending statement: %d", result);
|
|
Log(LOG_TRACE, "sql3: reset stmt %ld", _stmt->stmt);
|
|
}
|
|
}
|
|
|
|
void
|
|
MsgSQLFinalize(MsgSQLStatement *stmt)
|
|
{
|
|
int result;
|
|
if (stmt->stmt) {
|
|
result = sqlite3_finalize(stmt->stmt);
|
|
if (result != SQLITE_OK)
|
|
Log(LOG_ERROR, "sql3: Error finalizing statement: %d", result);
|
|
Log(LOG_TRACE, "sql3: final stmt %ld", stmt->stmt);
|
|
stmt->stmt = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
MsgSQLReset(MsgSQLHandle *handle)
|
|
{
|
|
MsgSQLStatement *stmt;
|
|
|
|
for (stmt = &handle->stmts.begin;
|
|
stmt <= &handle->stmts.end;
|
|
stmt++)
|
|
{
|
|
MsgSQLFinalize(stmt);
|
|
}
|
|
}
|
|
|
|
BongoMemStack *
|
|
MsgSQLGetMemStack(MsgSQLHandle *handle)
|
|
{
|
|
return handle->memstack;
|
|
}
|
|
|
|
void
|
|
MsgSQLSetMemStack(MsgSQLHandle *handle, BongoMemStack *memstack)
|
|
{
|
|
handle->memstack = memstack;
|
|
}
|
|
|
|
|
|
void
|
|
MsgSQLSetLockTimeout(MsgSQLHandle *handle, int timeoutms)
|
|
{
|
|
handle->lockTimeoutMs = timeoutms;
|
|
}
|
|
|
|
// returns 0 on success, -2 db busy, -1 on error
|
|
int
|
|
MsgSQLBeginTransaction(MsgSQLHandle *handle)
|
|
{
|
|
MsgSQLStatement statement;
|
|
MsgSQLStatement *stmt;
|
|
int result;
|
|
int count = 1 + handle->lockTimeoutMs / MSGSQL_STMT_SLEEP_MS;
|
|
|
|
memset(&statement, 0, sizeof(statement));
|
|
|
|
// acquire the transaction lock to prevent other people doing stuff
|
|
XplMutexLock(handle->transactionLock);
|
|
|
|
/*
|
|
* Store connections move between worker threads. Keeping transaction
|
|
* statements cached on the shared handle made SQLite reject a later BEGIN
|
|
* even though the previous transaction had completed. Transaction control
|
|
* statements are small, so prepare and finalize them within the lock.
|
|
*/
|
|
stmt = MsgSQLPrepare(handle, "BEGIN TRANSACTION;", &statement);
|
|
if (!stmt) {
|
|
Log(LOG_ERROR, "sql3: Unable to begin transaction");
|
|
XplMutexUnlock(handle->transactionLock);
|
|
return -1;
|
|
}
|
|
|
|
do {
|
|
result = sqlite3_step(stmt->stmt);
|
|
Log(LOG_TRACE, "sql3: BEGIN TRAN stmt %ld", stmt->stmt);
|
|
} while (SQLITE_BUSY == result && --count && (XplDelay(MSGSQL_STMT_SLEEP_MS), 1));
|
|
|
|
switch (result) {
|
|
case SQLITE_DONE:
|
|
MsgSQLFinalize(&statement);
|
|
++(handle->transactionDepth);
|
|
return 0;
|
|
case SQLITE_BUSY:
|
|
Log(LOG_ERROR, "sql3: Database remained busy while beginning transaction");
|
|
MsgSQLFinalize(&statement);
|
|
XplMutexUnlock(handle->transactionLock);
|
|
return -2;
|
|
default:
|
|
Log(LOG_ERROR, "sql3: Database error %d : %s", result, sqlite3_errmsg(handle->db));
|
|
MsgSQLFinalize(&statement);
|
|
XplMutexUnlock(handle->transactionLock);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// returns 0 on success, -1 on error
|
|
int
|
|
MsgSQLCommitTransaction(MsgSQLHandle *handle)
|
|
{
|
|
MsgSQLStatement statement;
|
|
MsgSQLStatement *stmt;
|
|
int result;
|
|
int count = 1 + handle->lockTimeoutMs / MSGSQL_STMT_SLEEP_MS;
|
|
|
|
if (handle->transactionDepth <= 0) {
|
|
Log(LOG_ERROR, "sql3: Attempted to commit without an active transaction");
|
|
return -1;
|
|
}
|
|
|
|
memset(&statement, 0, sizeof(statement));
|
|
stmt = MsgSQLPrepare(handle, "END TRANSACTION;", &statement);
|
|
if (!stmt) {
|
|
Log(LOG_ERROR, "sql3: Unable to prepare end of transaction statement");
|
|
return -1;
|
|
}
|
|
|
|
do {
|
|
result = sqlite3_step(stmt->stmt);
|
|
Log(LOG_TRACE, "sql3: COMMIT stmt %ld", stmt->stmt);
|
|
} while (SQLITE_BUSY == result && --count && (XplDelay(MSGSQL_STMT_SLEEP_MS), 1));
|
|
|
|
if (SQLITE_DONE != result) {
|
|
Log(LOG_ERROR, "sql3: Database commit error %d: %s", result, sqlite3_errmsg(handle->db));
|
|
MsgSQLFinalize(&statement);
|
|
return -1;
|
|
}
|
|
|
|
MsgSQLFinalize(&statement);
|
|
--handle->transactionDepth;
|
|
XplMutexUnlock(handle->transactionLock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// returns 0 on success, -1 on error
|
|
int
|
|
MsgSQLAbortTransaction(MsgSQLHandle *handle)
|
|
{
|
|
MsgSQLStatement statement;
|
|
MsgSQLStatement *stmt;
|
|
int result;
|
|
|
|
if (handle->transactionDepth <= 0) {
|
|
Log(LOG_ERROR, "sql3: Attempted to rollback without an active transaction");
|
|
return -1;
|
|
}
|
|
|
|
memset(&statement, 0, sizeof(statement));
|
|
stmt = MsgSQLPrepare(handle, "ROLLBACK TRANSACTION;", &statement);
|
|
if (!stmt) {
|
|
Log(LOG_ERROR, "sql3: Unable to rollback transaction");
|
|
result = SQLITE_ERROR;
|
|
} else {
|
|
result = sqlite3_step(stmt->stmt);
|
|
if (SQLITE_DONE != result) {
|
|
Log(LOG_ERROR, "sql3: Transaction rollback failed: %s", sqlite3_errmsg(handle->db));
|
|
}
|
|
Log(LOG_TRACE, "sql3: ABORT stmt %ld\n", stmt->stmt);
|
|
MsgSQLFinalize(&statement);
|
|
}
|
|
|
|
--handle->transactionDepth;
|
|
XplMutexUnlock(handle->transactionLock);
|
|
|
|
return SQLITE_DONE == result ? 0 : -1;
|
|
}
|
|
|
|
int
|
|
MsgSQLCancelTransactions(MsgSQLHandle *handle)
|
|
{
|
|
while (handle->transactionDepth) {
|
|
if (MsgSQLAbortTransaction(handle)) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
MsgSQLStatement *
|
|
MsgSQLPrepare(MsgSQLHandle *handle, const char *statement, MsgSQLStatement *stmt)
|
|
{
|
|
int ret;
|
|
int count = 1 + handle->lockTimeoutMs / MSGSQL_STMT_SLEEP_MS;
|
|
|
|
Log(LOG_TRACE, "sql3: PREPARE '%s'", statement);
|
|
// stmt->filter = NULL;
|
|
if (stmt == NULL) return NULL; // we need an output
|
|
|
|
if (stmt && stmt->stmt) {
|
|
return stmt;
|
|
}
|
|
stmt->query = statement;
|
|
|
|
while (count--) {
|
|
ret = sqlite3_prepare(handle->db, statement, -1, &stmt->stmt, NULL);
|
|
Log(LOG_TRACE, "sql3: - new statement %ld", stmt->stmt);
|
|
|
|
switch (ret) {
|
|
case SQLITE_OK:
|
|
return stmt;
|
|
case SQLITE_BUSY:
|
|
XplDelay(MSGSQL_STMT_SLEEP_MS);
|
|
continue;
|
|
default:
|
|
Log(LOG_ERROR, "sql3: Prepare statement \"%s\" failed; %s",
|
|
statement, sqlite3_errmsg(handle->db));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
MsgSQLBindNull(MsgSQLStatement *stmt, int var)
|
|
{
|
|
Log(LOG_TRACE, "sql3: - bind null stmt %ld\n", stmt->stmt);
|
|
return sqlite3_bind_null(stmt->stmt, var);
|
|
}
|
|
|
|
int
|
|
MsgSQLBindString(MsgSQLStatement *stmt, int var, const char *str, BOOL nullify)
|
|
{
|
|
Log(LOG_TRACE, "sql3: - bind str stmt %ld", stmt->stmt);
|
|
if (str) {
|
|
return sqlite3_bind_text(stmt->stmt, var, str, strlen(str), SQLITE_STATIC);
|
|
} else {
|
|
if (nullify) {
|
|
return sqlite3_bind_null(stmt->stmt, var);
|
|
} else {
|
|
return sqlite3_bind_text(stmt->stmt, var, "", 0, SQLITE_STATIC);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
MsgSQLBindInt(MsgSQLStatement *stmt, int var, int value)
|
|
{
|
|
Log(LOG_TRACE, "sql3: - bind int stmt %ld", stmt->stmt);
|
|
return sqlite3_bind_int(stmt->stmt, var, value);
|
|
}
|
|
|
|
int
|
|
MsgSQLBindInt64(MsgSQLStatement *stmt, int var, uint64_t value)
|
|
{
|
|
Log(LOG_TRACE, "sql3: - bind in64 stmt %ld", stmt->stmt);
|
|
return sqlite3_bind_int64(stmt->stmt, var, value);
|
|
}
|
|
|
|
int
|
|
MsgSQLExecute(MsgSQLHandle *handle, MsgSQLStatement *_stmt)
|
|
{
|
|
|
|
sqlite3_stmt *stmt = _stmt->stmt;
|
|
int result;
|
|
|
|
result = sqlite3_step(stmt);
|
|
Log(LOG_TRACE, "sql3: EXEC stmt %ld", stmt);
|
|
// FIXME - do I need to reset this??
|
|
if (SQLITE_DONE == result) {
|
|
return 0;
|
|
} else {
|
|
Log(LOG_ERROR, "sql3: Execute failed (%d): %s. Query: %s", result, sqlite3_errmsg(handle->db), _stmt->query);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int
|
|
MsgSQLQuickExecute(MsgSQLHandle *handle, const char *query)
|
|
{
|
|
int result;
|
|
char *error;
|
|
|
|
result = sqlite3_exec(handle->db, query, NULL, NULL, &error);
|
|
Log(LOG_TRACE, "sql3: QUICK EXEC");
|
|
if (result == SQLITE_OK) return 0;
|
|
|
|
Log(LOG_ERROR, "sql3: QE failed: %s", error);
|
|
sqlite3_free(error);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
MsgSQLStatementStep(MsgSQLHandle *handle, MsgSQLStatement *_stmt)
|
|
{
|
|
sqlite3_stmt *stmt = _stmt->stmt;
|
|
int count = 1 + handle->lockTimeoutMs / MSGSQL_STMT_SLEEP_MS;
|
|
int dbg;
|
|
while (count--) {
|
|
Log(LOG_TRACE, "sql3: - f stmt step %ld", stmt);
|
|
switch ((dbg = sqlite3_step(stmt))) {
|
|
case SQLITE_ROW:
|
|
return 1;
|
|
case SQLITE_DONE:
|
|
return 0;
|
|
case SQLITE_BUSY:
|
|
XplDelay(MSGSQL_STMT_SLEEP_MS);
|
|
continue;
|
|
default:
|
|
Log(LOG_ERROR, "sql3: Step error: %s", sqlite3_errmsg(handle->db));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
MsgSQLResults(MsgSQLHandle *handle, MsgSQLStatement *_stmt)
|
|
{
|
|
sqlite3_stmt *stmt = _stmt->stmt;
|
|
int result;
|
|
int count = 1 + handle->lockTimeoutMs / MSGSQL_STMT_SLEEP_MS;
|
|
|
|
while (count--) {
|
|
result = sqlite3_step(stmt);
|
|
Log(LOG_TRACE, "sql3: - f stmt results %ld", stmt);
|
|
switch (result) {
|
|
case SQLITE_DONE:
|
|
return 0; // no more results
|
|
break;
|
|
case SQLITE_ROW:
|
|
return 1;
|
|
break;
|
|
case SQLITE_BUSY:
|
|
XplDelay(MSGSQL_STMT_SLEEP_MS);
|
|
continue;
|
|
default:
|
|
// FIXME
|
|
//XplConsolePrintf("SQL Prepare statement \"%s\" failed; %s\r\n",
|
|
// statement, sqlite3_errmsg(handle->db));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
uint64_t
|
|
MsgSQLLastRowID(MsgSQLHandle *handle)
|
|
{
|
|
return sqlite3_last_insert_rowid(handle->db);
|
|
}
|
|
|
|
int
|
|
MsgSQLResultInt(MsgSQLStatement *_stmt, int column)
|
|
{
|
|
Log(LOG_TRACE, "sql3: - result int %ld", _stmt->stmt);
|
|
return sqlite3_column_int(_stmt->stmt, column);
|
|
}
|
|
|
|
uint64_t
|
|
MsgSQLResultInt64(MsgSQLStatement *_stmt, int column)
|
|
{
|
|
Log(LOG_TRACE, "sql3: - result int64 %ld", _stmt->stmt);
|
|
return (uint64_t) sqlite3_column_int64(_stmt->stmt, column);
|
|
}
|
|
|
|
// FIXME : need a sensible API to detect errors
|
|
int
|
|
MsgSQLResultText(MsgSQLStatement *_stmt, int column, char *result, size_t result_size)
|
|
{
|
|
const char *out = (const char *)sqlite3_column_text(_stmt->stmt, column);
|
|
Log(LOG_TRACE, "sql3: - result text %ld", _stmt->stmt);
|
|
if (out != NULL)
|
|
strncpy(result, out, result_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
MsgSQLResultTextPtr(MsgSQLStatement *_stmt, int column, char **ptr)
|
|
{
|
|
*ptr = NULL;
|
|
|
|
const char *result = (const char *)sqlite3_column_text(_stmt->stmt, column);
|
|
if (result != NULL) *ptr = g_strdup(result);
|
|
|
|
return 0;
|
|
}
|