1411 lines
41 KiB
C
1411 lines
41 KiB
C
#include <sqlite3.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include "pcache.h"
|
|
#include "pdatabase.h"
|
|
#include "pdbg.h"
|
|
#include "plocks.h"
|
|
#include "pnetlibs.h"
|
|
#include "prun.h"
|
|
#include "psettings.h"
|
|
#include "psql.h"
|
|
#include "psys.h"
|
|
|
|
#define SQL_NO_LOCK 0
|
|
#define SQL_READ_LOCK 1
|
|
#define SQL_WRITE_LOCK 2
|
|
|
|
#define PSYNC_TNUMBER 1
|
|
#define PSYNC_TSTRING 2
|
|
#define PSYNC_TREAL 3
|
|
#define PSYNC_TNULL 4
|
|
#define PSYNC_TBOOL 5
|
|
|
|
extern PSYNC_THREAD const char *psync_thread_name;
|
|
|
|
const static char *psync_typenames[] = {"[invalid type]", "[number]", "[string]", "[float]", "[null]", "[bool]"};
|
|
|
|
typedef struct {
|
|
psync_list list;
|
|
psync_transaction_callback_t commit;
|
|
psync_transaction_callback_t rollback;
|
|
void *ptr;
|
|
} tran_callback_t;
|
|
|
|
|
|
int psync_do_run = 1; // FIXME: app state. papp.c?
|
|
PSYNC_THREAD uint32_t psync_error = 0; // FIXME: app state. papp.c?
|
|
|
|
static psync_rwlock_t dblock;
|
|
static sqlite3 *psync_db;
|
|
static pthread_mutex_t cpmutex;
|
|
static int in_transaction = 0;
|
|
static int transaction_failed = 0;
|
|
static psync_list commitcbs;
|
|
|
|
#if IS_DEBUG
|
|
typedef struct {
|
|
psync_list list;
|
|
const char *file;
|
|
const char *thread;
|
|
struct timespec tm;
|
|
unsigned line;
|
|
} rd_lock_data;
|
|
|
|
static PSYNC_THREAD rd_lock_data *rdlock = NULL;
|
|
static PSYNC_THREAD unsigned long rdlockctr = 0;
|
|
static PSYNC_THREAD struct timespec rdlockstart;
|
|
unsigned long lockctr = 0;
|
|
static struct timespec lockstart;
|
|
static const char *wrlockfile = "none";
|
|
static const char *wrlockthread = "";
|
|
static unsigned wrlockline = 0;
|
|
static unsigned wrlocked = 0;
|
|
static pthread_t wrlocker;
|
|
static psync_list rdlocks = PSYNC_LIST_STATIC_INIT(rdlocks);
|
|
static pthread_mutex_t rdmutex = PTHREAD_MUTEX_INITIALIZER;
|
|
#endif
|
|
|
|
static void on_error(void *ptr, int code, const char *msg) {
|
|
pdbg_logf(D_WARNING, "database warning %d: %s", code, msg);
|
|
}
|
|
|
|
static void psync_sql_free_cache(void *ptr) {
|
|
psync_sql_res *res = (psync_sql_res *)ptr;
|
|
sqlite3_finalize(res->stmt);
|
|
#if IS_DEBUG
|
|
memset(res, 0xff, sizeof(psync_sql_res));
|
|
#endif
|
|
free(res);
|
|
}
|
|
|
|
static void psync_sql_res_unlock(psync_sql_res *res) {
|
|
switch (res->locked) {
|
|
case SQL_NO_LOCK:
|
|
break;
|
|
case SQL_READ_LOCK:
|
|
psql_rdunlock();
|
|
break;
|
|
case SQL_WRITE_LOCK:
|
|
psql_unlock();
|
|
break;
|
|
#if IS_DEBUG
|
|
default:
|
|
pdbg_logf(D_ERROR, "unknown value for locked %d", res->locked);
|
|
abort();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void proc_checkpoint() {
|
|
int code;
|
|
psql_lock();
|
|
psql_unlock();
|
|
if (pthread_mutex_trylock(&cpmutex)) {
|
|
pdbg_logf(D_NOTICE, "checkpoint already in progress");
|
|
return;
|
|
}
|
|
pdbg_logf(D_NOTICE, "checkpointing database");
|
|
code = sqlite3_wal_checkpoint(psync_db, NULL);
|
|
while (code == SQLITE_LOCKED) {
|
|
psys_sleep_milliseconds(2);
|
|
code = sqlite3_wal_checkpoint(psync_db, NULL);
|
|
}
|
|
pthread_mutex_unlock(&cpmutex);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_CRITICAL, "sqlite3_wal_checkpoint returned error %d", code);
|
|
else
|
|
pdbg_logf(D_NOTICE, "checkpoint finished");
|
|
}
|
|
|
|
static int wal_hook(void *ptr, sqlite3 *db, const char *name,
|
|
int numpages) {
|
|
if (numpages >= PSYNC_DB_CHECKPOINT_AT_PAGES)
|
|
prun_thread("checkpoint charlie", proc_checkpoint);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
static void commit(int success) {
|
|
tran_callback_t *cb;
|
|
psync_list *l1, *l2;
|
|
psync_list_for_each_safe(l1, l2, &commitcbs) {
|
|
|
|
cb = psync_list_element(l1, tran_callback_t, list);
|
|
if (success)
|
|
cb->commit(cb->ptr);
|
|
else
|
|
cb->rollback(cb->ptr);
|
|
free(cb);
|
|
}
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
|
|
static void record_wrlock(const char *file, unsigned line) {
|
|
if (unlikely(rdlock)) {
|
|
pdbg_logf(D_BUG,
|
|
"trying to get write lock at %s:%u, but read lock is already taken "
|
|
"at %s:%u, aborting",
|
|
file, line, rdlock->file, rdlock->line);
|
|
sendpdbg_logf("trying to get write lock at %s:%u, but read lock is already "
|
|
"taken at %s:%u, aborting",
|
|
file, line, rdlock->file, rdlock->line);
|
|
abort();
|
|
}
|
|
sendpdbg_assert(!wrlocked);
|
|
pdbg_assert(!wrlocked);
|
|
wrlockfile = file;
|
|
wrlockline = line;
|
|
wrlockthread = psync_thread_name;
|
|
wrlocked = 1;
|
|
wrlocker = pthread_self();
|
|
}
|
|
|
|
static void record_wrunlock() {
|
|
sendpdbg_assert(pthread_equal(pthread_self(), wrlocker));
|
|
sendpdbg_assert(wrlocked);
|
|
pdbg_assert(pthread_equal(pthread_self(), wrlocker));
|
|
pdbg_assert(wrlocked);
|
|
wrlocked = 0;
|
|
}
|
|
|
|
static void record_rdlock(const char *file, unsigned line,
|
|
struct timespec *tm) {
|
|
rd_lock_data *lock;
|
|
lock = malloc(sizeof(rd_lock_data));
|
|
lock->file = file;
|
|
lock->thread = psync_thread_name;
|
|
lock->line = line;
|
|
memcpy(&lock->tm, tm, sizeof(struct timespec));
|
|
pthread_mutex_lock(&rdmutex);
|
|
psync_list_add_tail(&rdlocks, &lock->list);
|
|
pthread_mutex_unlock(&rdmutex);
|
|
rdlock = lock;
|
|
}
|
|
|
|
static rd_lock_data *record_rdunlock() {
|
|
rd_lock_data *lock;
|
|
pdbg_assert(rdlock);
|
|
lock = rdlock;
|
|
rdlock = NULL;
|
|
pthread_mutex_lock(&rdmutex);
|
|
psync_list_del(&lock->list);
|
|
pthread_mutex_unlock(&rdmutex);
|
|
return lock;
|
|
}
|
|
|
|
void psql_dump_locks() {
|
|
rd_lock_data *lock;
|
|
char dttime[36];
|
|
if (wrlocked) {
|
|
time_format(lockstart.tv_sec, lockstart.tv_nsec, dttime);
|
|
pdbg_logf(D_ERROR, "write lock taken by thread %s from %s:%u at %s",
|
|
wrlockthread, wrlockfile, wrlockline, dttime);
|
|
sendpdbg_logf("write lock taken by thread %s from %s:%u at %s", wrlockthread,
|
|
wrlockfile, wrlockline, dttime);
|
|
}
|
|
pthread_mutex_lock(&rdmutex);
|
|
psync_list_for_each_element(lock, &rdlocks, rd_lock_data, list) {
|
|
time_format(lock->tm.tv_sec, lock->tm.tv_nsec, dttime);
|
|
pdbg_logf(D_ERROR, "read lock taken by thread %s from %s:%u at %s",
|
|
lock->thread, lock->file, lock->line, dttime);
|
|
sendpdbg_logf("read lock taken by thread %s from %s:%u at %s", lock->thread,
|
|
lock->file, lock->line, dttime);
|
|
}
|
|
pthread_mutex_unlock(&rdmutex);
|
|
}
|
|
|
|
int psql_do_trylock(const char *file, unsigned line) {
|
|
if (plocks_trywrlock(&dblock))
|
|
return -1;
|
|
if (++lockctr == 1) {
|
|
clock_gettime(CLOCK_REALTIME, &lockstart);
|
|
record_wrlock(file, line);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void psql_do_lock(const char *file, unsigned line) {
|
|
if (plocks_trywrlock(&dblock)) {
|
|
struct timespec start, end;
|
|
unsigned long msec;
|
|
clock_gettime(CLOCK_REALTIME, &start);
|
|
memcpy(&end, &start, sizeof(end));
|
|
end.tv_sec += PSYNC_DEBUG_LOCK_TIMEOUT;
|
|
if (plocks_timedwrlock(&dblock, &end)) {
|
|
pdbg_logf(D_BUG, "sql write lock timed out called from %s:%u", file, line);
|
|
sendpdbg_logf("sql write lock timed out called from %s:%u", file, line);
|
|
psql_dump_locks();
|
|
abort();
|
|
}
|
|
clock_gettime(CLOCK_REALTIME, &end);
|
|
msec = (end.tv_sec - start.tv_sec) * 1000 + end.tv_nsec / 1000000 -
|
|
start.tv_nsec / 1000000;
|
|
if (msec >= 1000)
|
|
pdbg_logf(D_ERROR, "waited %lu milliseconds for database write lock", msec);
|
|
else if (msec >= 250)
|
|
pdbg_logf(D_WARNING, "waited %lu milliseconds for database write lock", msec);
|
|
else if (msec >= 5)
|
|
pdbg_logf(D_BUG, "waited %lu milliseconds for database write lock", msec);
|
|
pdbg_assert(lockctr == 0);
|
|
lockctr++;
|
|
memcpy(&lockstart, &end, sizeof(struct timespec));
|
|
record_wrlock(file, line);
|
|
} else if (++lockctr == 1) {
|
|
clock_gettime(CLOCK_REALTIME, &lockstart);
|
|
record_wrlock(file, line);
|
|
}
|
|
}
|
|
|
|
void psql_do_rdlock(const char *file, unsigned line) {
|
|
if (plocks_tryrdlock(&dblock)) {
|
|
struct timespec start, end;
|
|
unsigned long msec;
|
|
clock_gettime(CLOCK_REALTIME, &start);
|
|
memcpy(&end, &start, sizeof(end));
|
|
end.tv_sec += PSYNC_DEBUG_LOCK_TIMEOUT;
|
|
if (plocks_timedrdlock(&dblock, &end)) {
|
|
pdbg_logf(D_BUG, "sql read lock timed out, called from %s:%u", file, line);
|
|
sendpdbg_logf("sql read lock timed out, called from %s:%u", file, line);
|
|
psql_dump_locks();
|
|
abort();
|
|
}
|
|
clock_gettime(CLOCK_REALTIME, &end);
|
|
msec = (end.tv_sec - start.tv_sec) * 1000 + end.tv_nsec / 1000000 -
|
|
start.tv_nsec / 1000000;
|
|
if (msec >= 1000)
|
|
pdbg_logf(D_ERROR, "waited %lu milliseconds for database read lock", msec);
|
|
else if (msec >= 250)
|
|
pdbg_logf(D_WARNING, "waited %lu milliseconds for database read lock", msec);
|
|
else if (msec >= 5)
|
|
pdbg_logf(D_BUG, "waited %lu milliseconds for database read lock", msec);
|
|
rdlockctr++;
|
|
memcpy(&rdlockstart, &end, sizeof(struct timespec));
|
|
record_rdlock(file, line, &rdlockstart);
|
|
} else if (++rdlockctr == 1) {
|
|
clock_gettime(CLOCK_REALTIME, &rdlockstart);
|
|
record_rdlock(file, line, &rdlockstart);
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
int psql_trylock() { return plocks_trywrlock(&dblock); }
|
|
void psql_lock() { plocks_wrlock(&dblock); }
|
|
void psql_rdlock() { plocks_rdlock(&dblock); }
|
|
|
|
#endif
|
|
|
|
int psql_connect(const char *db) {
|
|
static int initmutex = 1;
|
|
pthread_mutexattr_t mattr;
|
|
struct stat st;
|
|
uint64_t dbver;
|
|
int initdbneeded = 0;
|
|
int code;
|
|
|
|
pdbg_assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
|
|
pdbg_assert(!strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID));
|
|
pdbg_assert(!strcmp(sqlite3_libversion(), SQLITE_VERSION));
|
|
pdbg_logf(D_NOTICE, "Using sqlite version %s source %s", sqlite3_libversion(),
|
|
sqlite3_sourceid());
|
|
if (!sqlite3_threadsafe()) {
|
|
pdbg_logf(D_CRITICAL, "sqlite is compiled without thread support");
|
|
return -1;
|
|
}
|
|
if (stat(db, &st) != 0)
|
|
initdbneeded = 1;
|
|
|
|
code = sqlite3_open(db, &psync_db);
|
|
if (likely(code == SQLITE_OK)) {
|
|
if (initmutex) {
|
|
plocks_init(&dblock);
|
|
pthread_mutexattr_init(&mattr);
|
|
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
|
|
pthread_mutex_init(&cpmutex, &mattr);
|
|
pthread_mutexattr_destroy(&mattr);
|
|
initmutex = 0;
|
|
}
|
|
if (IS_DEBUG) {
|
|
sqlite3_config(SQLITE_CONFIG_LOG, on_error, NULL);
|
|
}
|
|
sqlite3_wal_hook(psync_db, wal_hook, NULL);
|
|
psql_statement(PSYNC_DATABASE_CONFIG);
|
|
if (initdbneeded == 1)
|
|
return psql_statement(PSYNC_DATABASE_STRUCTURE);
|
|
else if (psql_statement(
|
|
"DELETE FROM setting WHERE id='justcheckingiflocked'")) {
|
|
pdbg_logf(D_ERROR, "database is locked");
|
|
sqlite3_close(psync_db);
|
|
plocks_destroy(&dblock);
|
|
return -1;
|
|
}
|
|
|
|
dbver =
|
|
psql_cellint("SELECT value FROM setting WHERE id='dbversion'", 0);
|
|
if (dbver < PSYNC_DATABASE_VERSION) {
|
|
uint64_t i;
|
|
pdbg_logf(D_NOTICE, "database version %d detected, upgrading to %d",
|
|
(int)dbver, (int)PSYNC_DATABASE_VERSION);
|
|
for (i = dbver; i < PSYNC_DATABASE_VERSION; i++)
|
|
if (psql_statement(psync_db_upgrade[i])) {
|
|
pdbg_logf(D_ERROR, "error running statement %s on sqlite %s",
|
|
psync_db_upgrade[i], sqlite3_libversion());
|
|
if (IS_DEBUG) {
|
|
psync_error = PERROR_DATABASE_OPEN;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
} else {
|
|
pdbg_logf(D_CRITICAL, "could not open sqlite database %s: %d", db, code);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int psql_close() {
|
|
int code, tries;
|
|
tries = 0;
|
|
while (1) {
|
|
code = sqlite3_close(psync_db);
|
|
if (code == SQLITE_BUSY) {
|
|
pcache_clean();
|
|
tries++;
|
|
if (tries > 100) {
|
|
psys_sleep_milliseconds_fast(tries - 90);
|
|
if (tries > 200) {
|
|
pdbg_logf(D_ERROR, "failed to close database");
|
|
break;
|
|
}
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
psync_db = NULL;
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
pdbg_logf(D_CRITICAL, "error when closing database: %d", code);
|
|
code = sqlite3_close_v2(psync_db);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
pdbg_logf(D_CRITICAL,
|
|
"error when closing database even with sqlite3_close_v2: %d", code);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int psql_reopen(const char *path) {
|
|
sqlite3 *db;
|
|
int code;
|
|
pdbg_logf(D_NOTICE, "reopening database %s", path);
|
|
code = sqlite3_open(path, &db);
|
|
if (likely(code == SQLITE_OK)) {
|
|
code = sqlite3_wal_checkpoint(db, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
pdbg_logf(D_CRITICAL, "sqlite3_wal_checkpoint returned error %d", code);
|
|
sqlite3_close(db);
|
|
return -1;
|
|
}
|
|
code = sqlite3_close(db);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
pdbg_logf(D_CRITICAL, "sqlite3_close returned error %d", code);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
} else {
|
|
pdbg_logf(D_CRITICAL, "could not open sqlite dabase %s: %d", path, code);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void psql_checkpt_lock() {
|
|
pthread_mutex_lock(&cpmutex);
|
|
}
|
|
|
|
void psql_checkpt_unlock() {
|
|
pthread_mutex_unlock(&cpmutex);
|
|
}
|
|
|
|
void psql_unlock() {
|
|
#if IS_DEBUG
|
|
pdbg_assert(lockctr > 0);
|
|
if (--lockctr == 0) {
|
|
struct timespec end;
|
|
unsigned long msec;
|
|
clock_gettime(CLOCK_REALTIME, &end);
|
|
msec = (end.tv_sec - lockstart.tv_sec) * 1000 + end.tv_nsec / 1000000 -
|
|
lockstart.tv_nsec / 1000000;
|
|
if (msec >= 2000)
|
|
pdbg_logf(D_ERROR,
|
|
"held database write lock for %lu milliseconds taken from %s:%u",
|
|
msec, wrlockfile, wrlockline);
|
|
else if (msec >= 500)
|
|
pdbg_logf(D_WARNING,
|
|
"held database write lock for %lu milliseconds taken from %s:%u",
|
|
msec, wrlockfile, wrlockline);
|
|
else if (msec >= 10)
|
|
pdbg_logf(D_BUG,
|
|
"held database write lock for %lu milliseconds taken from %s:%u",
|
|
msec, wrlockfile, wrlockline);
|
|
record_wrunlock();
|
|
plocks_unlock(&dblock);
|
|
} else
|
|
plocks_unlock(&dblock);
|
|
#else
|
|
plocks_unlock(&dblock);
|
|
#endif
|
|
}
|
|
|
|
void psql_list_add(psync_list_builder_t *builder, psync_sql_res *res, psync_list_builder_sql_callback callback) {
|
|
psync_variant_row row;
|
|
while ((row = psql_fetch(res))) {
|
|
if (!builder->last_elements ||
|
|
builder->last_elements->used >= builder->elements_per_list) {
|
|
builder->last_elements = (psync_list_element_list *)malloc(
|
|
offsetof(psync_list_element_list, elements) +
|
|
builder->element_size * builder->elements_per_list);
|
|
psync_list_add_tail(&builder->element_list,
|
|
&builder->last_elements->list);
|
|
builder->last_elements->used = 0;
|
|
}
|
|
builder->current_element =
|
|
builder->last_elements->elements +
|
|
builder->last_elements->used * builder->element_size;
|
|
builder->cstrcnt = psync_list_bulder_push_num(builder);
|
|
*builder->cstrcnt = 0;
|
|
while (callback(builder, builder->current_element, row)) {
|
|
row = psql_fetch(res);
|
|
if (!row)
|
|
break;
|
|
*builder->cstrcnt = 0;
|
|
}
|
|
builder->last_elements->used++;
|
|
builder->cnt++;
|
|
}
|
|
psql_free(res);
|
|
}
|
|
|
|
void psql_rdunlock() {
|
|
#if IS_DEBUG
|
|
if (unlikely(rdlockctr == 0)) {
|
|
psql_unlock();
|
|
return;
|
|
}
|
|
if (--rdlockctr == 0) {
|
|
struct timespec end;
|
|
unsigned long msec;
|
|
rd_lock_data *lock;
|
|
plocks_unlock(&dblock);
|
|
clock_gettime(CLOCK_REALTIME, &end);
|
|
lock = record_rdunlock();
|
|
msec = (end.tv_sec - rdlockstart.tv_sec) * 1000 + end.tv_nsec / 1000000 -
|
|
rdlockstart.tv_nsec / 1000000;
|
|
if (msec >= 2000)
|
|
pdbg_logf(D_ERROR,
|
|
"held database read lock for %lu milliseconds taken at %s:%u", msec,
|
|
lock->file, lock->line);
|
|
else if (msec >= 500)
|
|
pdbg_logf(D_WARNING,
|
|
"held database read lock for %lu milliseconds taken at %s:%u", msec,
|
|
lock->file, lock->line);
|
|
else if (msec >= 20)
|
|
pdbg_logf(D_BUG,
|
|
"held database read lock for %lu milliseconds taken at %s:%u", msec,
|
|
lock->file, lock->line);
|
|
free(lock);
|
|
} else
|
|
plocks_unlock(&dblock);
|
|
#else
|
|
plocks_unlock(&dblock);
|
|
#endif
|
|
}
|
|
|
|
int psql_waiting() {
|
|
return plocks_num_waiters(&dblock) > 0;
|
|
}
|
|
|
|
int psql_rdlocked() {
|
|
return plocks_holding_rdlock(&dblock);
|
|
}
|
|
|
|
int psql_locked() {
|
|
return plocks_holding_lock(&dblock);
|
|
}
|
|
|
|
int psql_tryupgradeLock() {
|
|
#if IS_DEBUG
|
|
if (plocks_holding_wrlock(&dblock))
|
|
return 0;
|
|
pdbg_assert(plocks_holding_rdlock(&dblock));
|
|
if (plocks_towrlock(&dblock))
|
|
return -1;
|
|
else {
|
|
rd_lock_data *lock = record_rdunlock();
|
|
lockctr = rdlockctr;
|
|
rdlockctr = 0;
|
|
pdbg_assert(lockctr == 1);
|
|
lockstart = rdlockstart;
|
|
record_wrlock(lock->file, lock->line);
|
|
free(lock);
|
|
return 0;
|
|
}
|
|
#else
|
|
return plocks_towrlock(&dblock);
|
|
#endif
|
|
}
|
|
|
|
int psql_sync() {
|
|
int code;
|
|
pthread_mutex_lock(&cpmutex);
|
|
code = sqlite3_wal_checkpoint(psync_db, NULL);
|
|
if (unlikely(code == SQLITE_BUSY || code == SQLITE_LOCKED)) {
|
|
psql_lock();
|
|
code = sqlite3_wal_checkpoint(psync_db, NULL);
|
|
psql_unlock();
|
|
}
|
|
pthread_mutex_unlock(&cpmutex);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
pdbg_logf(D_CRITICAL, "sqlite3_wal_checkpoint returned error %d", code);
|
|
return -1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
int psql_commit() {
|
|
pdbg_assert(in_transaction);
|
|
if (likely(!transaction_failed)) {
|
|
psync_sql_res *res = psql_prepare("COMMIT");
|
|
if (likely(!psql_run_free(res))) {
|
|
commit(1);
|
|
in_transaction = 0;
|
|
psql_unlock();
|
|
return 0;
|
|
}
|
|
} else
|
|
pdbg_logf(D_ERROR, "rolling back transaction as some statements failed");
|
|
psql_rollback();
|
|
return -1;
|
|
}
|
|
|
|
int psql_rollback() {
|
|
psync_sql_res *res = psql_prepare("ROLLBACK");
|
|
pdbg_assert(in_transaction);
|
|
psql_run_free(res);
|
|
commit(0);
|
|
in_transaction = 0;
|
|
psql_unlock();
|
|
return 0;
|
|
}
|
|
|
|
void psql_translation_add_cb(psync_transaction_callback_t commit, psync_transaction_callback_t rollback, void *ptr) {
|
|
tran_callback_t *cb;
|
|
pdbg_assert(in_transaction);
|
|
cb = malloc(sizeof(tran_callback_t));
|
|
cb->commit = commit;
|
|
cb->rollback = rollback;
|
|
cb->ptr = ptr;
|
|
psync_list_add_tail(&commitcbs, &cb->list);
|
|
}
|
|
|
|
char *psql_cellstr(const char *sql) {
|
|
sqlite3_stmt *stmt;
|
|
int code;
|
|
psql_rdlock();
|
|
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
psql_rdunlock();
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendtpdbg_logf("error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
return NULL;
|
|
}
|
|
code = sqlite3_step(stmt);
|
|
if (code == SQLITE_ROW) {
|
|
char *ret;
|
|
ret = (char *)sqlite3_column_text(stmt, 0);
|
|
if (ret)
|
|
ret = psync_strdup(ret);
|
|
sqlite3_finalize(stmt);
|
|
psql_rdunlock();
|
|
return ret;
|
|
} else {
|
|
sqlite3_finalize(stmt);
|
|
psql_rdunlock();
|
|
if (unlikely(code != SQLITE_DONE)) {
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendtpdbg_logf("sqlite3_step returned error: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int64_t psql_cellint(const char *sql, int64_t dflt) {
|
|
sqlite3_stmt *stmt;
|
|
int code;
|
|
psql_rdlock();
|
|
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendtpdbg_logf("error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
} else {
|
|
code = sqlite3_step(stmt);
|
|
if (code == SQLITE_ROW)
|
|
dflt = sqlite3_column_int64(stmt, 0);
|
|
else if (unlikely(code != SQLITE_DONE)) {
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendtpdbg_logf("sqlite3_step returned error: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
psql_rdunlock();
|
|
return dflt;
|
|
}
|
|
|
|
char **psql_rowstr(const char *sql) {
|
|
sqlite3_stmt *stmt;
|
|
int code, cnt;
|
|
psql_rdlock();
|
|
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
psql_rdunlock();
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendtpdbg_logf("error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
return NULL;
|
|
}
|
|
cnt = sqlite3_column_count(stmt);
|
|
code = sqlite3_step(stmt);
|
|
if (code == SQLITE_ROW) {
|
|
char **arr, *nstr, *str;
|
|
size_t l, ln;
|
|
VAR_ARRAY(lens, size_t, cnt);
|
|
int i;
|
|
ln = 0;
|
|
for (i = 0; i < cnt; i++) {
|
|
l = sqlite3_column_bytes(stmt, i);
|
|
ln += l;
|
|
lens[i] = l;
|
|
}
|
|
ln += (sizeof(char *) + 1) * cnt;
|
|
arr = (char **)malloc(ln);
|
|
nstr = ((char *)arr) + sizeof(char *) * cnt;
|
|
for (i = 0; i < cnt; i++) {
|
|
str = (char *)sqlite3_column_blob(stmt, i);
|
|
if (str) {
|
|
ln = lens[i];
|
|
memcpy(nstr, str, ln);
|
|
nstr[ln] = 0;
|
|
arr[i] = nstr;
|
|
nstr += ln + 1;
|
|
} else
|
|
arr[i] = NULL;
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
psql_rdunlock();
|
|
return arr;
|
|
} else {
|
|
sqlite3_finalize(stmt);
|
|
psql_rdunlock();
|
|
if (unlikely(code != SQLITE_DONE)) {
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendtpdbg_logf("sqlite3_step returned error: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
psync_variant *psql_row(const char *sql) {
|
|
sqlite3_stmt *stmt;
|
|
int code, cnt;
|
|
psql_rdlock();
|
|
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
psql_rdunlock();
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendtpdbg_logf("error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
return NULL;
|
|
}
|
|
cnt = sqlite3_column_count(stmt);
|
|
code = sqlite3_step(stmt);
|
|
if (code == SQLITE_ROW) {
|
|
psync_variant *arr;
|
|
char *nstr, *str;
|
|
size_t l, ln;
|
|
VAR_ARRAY(lens, size_t, cnt);
|
|
int i, t;
|
|
VAR_ARRAY(types, int, cnt);
|
|
ln = sizeof(psync_variant) * cnt;
|
|
for (i = 0; i < cnt; i++) {
|
|
t = sqlite3_column_type(stmt, i);
|
|
types[i] = t;
|
|
if (t == SQLITE_TEXT || t == SQLITE_BLOB) {
|
|
l = sqlite3_column_bytes(stmt, i);
|
|
ln += l + 1;
|
|
lens[i] = l;
|
|
}
|
|
}
|
|
arr = (psync_variant *)malloc(ln);
|
|
nstr = ((char *)arr) + sizeof(psync_variant) * cnt;
|
|
for (i = 0; i < cnt; i++) {
|
|
t = types[i];
|
|
if (t == SQLITE_INTEGER) {
|
|
arr[i].type = PSYNC_TNUMBER;
|
|
arr[i].snum = sqlite3_column_int64(stmt, i);
|
|
} else if (t == SQLITE_TEXT || t == SQLITE_BLOB) {
|
|
str = (char *)sqlite3_column_blob(stmt, i);
|
|
ln = lens[i];
|
|
memcpy(nstr, str, ln);
|
|
nstr[ln] = 0;
|
|
arr[i].type = PSYNC_TSTRING;
|
|
arr[i].str = nstr;
|
|
nstr += ln + 1;
|
|
} else if (t == SQLITE_FLOAT) {
|
|
arr[i].type = PSYNC_TREAL;
|
|
arr[i].real = sqlite3_column_double(stmt, i);
|
|
} else {
|
|
arr[i].type = PSYNC_TNULL;
|
|
}
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
psql_rdunlock();
|
|
return arr;
|
|
} else {
|
|
sqlite3_finalize(stmt);
|
|
psql_rdunlock();
|
|
if (unlikely(code != SQLITE_DONE)) {
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendtpdbg_logf("sqlite3_step returned error: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int psql_reset(psync_sql_res *res) {
|
|
int code = sqlite3_reset(res->stmt);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
pdbg_logf(D_ERROR, "sqlite3_reset returned error: %s",
|
|
sqlite3_errmsg(psync_db));
|
|
return -1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
int psql_run(psync_sql_res *res) {
|
|
int code = sqlite3_step(res->stmt);
|
|
if (unlikely(code != SQLITE_DONE)) {
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s: %s",
|
|
sqlite3_errmsg(psync_db), res->sql);
|
|
sendtpdbg_logf("sqlite3_step returned error (in_transaction=%d): %s: %s",
|
|
in_transaction, sqlite3_errmsg(psync_db), res->sql);
|
|
transaction_failed = 1;
|
|
if (in_transaction)
|
|
pdbg_logf(D_BUG, "transaction query failed, this may lead to restarting "
|
|
"transaction over and over");
|
|
return -1;
|
|
}
|
|
code = sqlite3_reset(res->stmt);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_ERROR, "sqlite3_reset returned error: %s",
|
|
sqlite3_errmsg(psync_db));
|
|
return 0;
|
|
}
|
|
|
|
int psql_run_free_nocache(psync_sql_res *res) {
|
|
int code = sqlite3_step(res->stmt);
|
|
if (unlikely(code != SQLITE_DONE)) {
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s: %s",
|
|
sqlite3_errmsg(psync_db), res->sql);
|
|
sendtpdbg_logf("sqlite3_step returned error (in_transaction=%d): %s: %s",
|
|
in_transaction, sqlite3_errmsg(psync_db), res->sql);
|
|
code = -1;
|
|
transaction_failed = 1;
|
|
if (in_transaction)
|
|
pdbg_logf(D_BUG, "transaction query failed, this may lead to restarting "
|
|
"transaction over and over");
|
|
} else
|
|
code = 0;
|
|
sqlite3_finalize(res->stmt);
|
|
psync_sql_res_unlock(res);
|
|
free(res);
|
|
return code;
|
|
}
|
|
|
|
int psql_run_free(psync_sql_res *res) {
|
|
int code = sqlite3_step(res->stmt);
|
|
if (unlikely(code != SQLITE_DONE ||
|
|
(code = sqlite3_reset(res->stmt)) != SQLITE_OK)) {
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s: %s",
|
|
sqlite3_errmsg(psync_db), res->sql);
|
|
sendtpdbg_logf("sqlite3_step returned error (in_transaction=%d): %s: %s",
|
|
in_transaction, sqlite3_errmsg(psync_db), res->sql);
|
|
sqlite3_finalize(res->stmt);
|
|
transaction_failed = 1;
|
|
if (in_transaction)
|
|
pdbg_logf(D_BUG, "transaction query failed, this may lead to restarting "
|
|
"transaction over and over");
|
|
psync_sql_res_unlock(res);
|
|
free(res);
|
|
return -1;
|
|
} else {
|
|
psync_sql_res_unlock(res);
|
|
pcache_add(res->sql, res, PSYNC_QUERY_CACHE_SEC, psync_sql_free_cache,
|
|
PSYNC_QUERY_MAX_CNT);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void psql_bind_int(psync_sql_res *res, int n, int64_t val) {
|
|
int code = sqlite3_bind_int64(res->stmt, n, val);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db));
|
|
}
|
|
|
|
void psql_bind_uint(psync_sql_res *res, int n, uint64_t val) {
|
|
int code = sqlite3_bind_int64(res->stmt, n, val);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db));
|
|
}
|
|
|
|
void psql_bind_double(psync_sql_res *res, int n, double val) {
|
|
int code = sqlite3_bind_double(res->stmt, n, val);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db));
|
|
}
|
|
|
|
void psql_bind_str(psync_sql_res *res, int n, const char *str) {
|
|
int code = sqlite3_bind_text(res->stmt, n, str, -1, SQLITE_STATIC);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db));
|
|
}
|
|
|
|
void psql_bind_lstr(psync_sql_res *res, int n, const char *str,
|
|
size_t len) {
|
|
int code = sqlite3_bind_text(res->stmt, n, str, len, SQLITE_STATIC);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db));
|
|
}
|
|
|
|
void psql_bind_blob(psync_sql_res *res, int n, const char *str,
|
|
size_t len) {
|
|
int code = sqlite3_bind_blob(res->stmt, n, str, len, SQLITE_STATIC);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db));
|
|
}
|
|
|
|
void psql_bind_null(psync_sql_res *res, int n) {
|
|
int code = sqlite3_bind_null(res->stmt, n);
|
|
if (unlikely(code != SQLITE_OK))
|
|
pdbg_logf(D_ERROR, "error binding value: %s", sqlite3_errmsg(psync_db));
|
|
}
|
|
|
|
psync_variant_row psql_fetch(psync_sql_res *res) {
|
|
int code, i;
|
|
code = sqlite3_step(res->stmt);
|
|
if (code == SQLITE_ROW) {
|
|
for (i = 0; i < res->column_count; i++) {
|
|
code = sqlite3_column_type(res->stmt, i);
|
|
if (code == SQLITE_INTEGER) {
|
|
res->row[i].type = PSYNC_TNUMBER;
|
|
res->row[i].snum = sqlite3_column_int64(res->stmt, i);
|
|
} else if (code == SQLITE_TEXT || code == SQLITE_BLOB) {
|
|
res->row[i].type = PSYNC_TSTRING;
|
|
res->row[i].length = sqlite3_column_bytes(res->stmt, i);
|
|
res->row[i].str = (char *)sqlite3_column_text(res->stmt, i);
|
|
} else if (code == SQLITE_FLOAT) {
|
|
res->row[i].type = PSYNC_TREAL;
|
|
res->row[i].real = sqlite3_column_double(res->stmt, i);
|
|
} else
|
|
res->row[i].type = PSYNC_TNULL;
|
|
}
|
|
return res->row;
|
|
} else {
|
|
if (unlikely(code != SQLITE_DONE))
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s",
|
|
sqlite3_errmsg(psync_db));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
psync_str_row psql_fetch_str(psync_sql_res *res) {
|
|
int code, i;
|
|
const char **strs;
|
|
code = sqlite3_step(res->stmt);
|
|
if (code == SQLITE_ROW) {
|
|
strs = (const char **)res->row;
|
|
for (i = 0; i < res->column_count; i++)
|
|
strs[i] = (const char *)sqlite3_column_text(res->stmt, i);
|
|
return strs;
|
|
} else {
|
|
if (unlikely(code != SQLITE_DONE))
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s",
|
|
sqlite3_errmsg(psync_db));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
const uint64_t *psql_fetch_int(psync_sql_res *res) {
|
|
int code, i;
|
|
uint64_t *ret;
|
|
code = sqlite3_step(res->stmt);
|
|
if (code == SQLITE_ROW) {
|
|
ret = (uint64_t *)res->row;
|
|
for (i = 0; i < res->column_count; i++)
|
|
ret[i] = sqlite3_column_int64(res->stmt, i);
|
|
return ret;
|
|
} else {
|
|
if (unlikely(code != SQLITE_DONE))
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s",
|
|
sqlite3_errmsg(psync_db));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
psync_full_result_int *psql_fetchall_int(psync_sql_res *res) {
|
|
uint64_t *data;
|
|
psync_full_result_int *ret;
|
|
unsigned long rows, cols, off, i, all;
|
|
int code;
|
|
cols = res->column_count;
|
|
rows = 0;
|
|
off = 0;
|
|
all = 0;
|
|
data = NULL;
|
|
while ((code = sqlite3_step(res->stmt)) == SQLITE_ROW) {
|
|
if (rows >= all) {
|
|
all = 10 + all * 2;
|
|
data = (uint64_t *)realloc(data, sizeof(uint64_t) * cols * all);
|
|
}
|
|
for (i = 0; i < cols; i++)
|
|
data[off + i] = sqlite3_column_int64(res->stmt, i);
|
|
off += cols;
|
|
rows++;
|
|
}
|
|
if (unlikely(code != SQLITE_DONE))
|
|
pdbg_logf(D_ERROR, "sqlite3_step returned error: %s", sqlite3_errmsg(psync_db));
|
|
psql_free(res);
|
|
ret = (psync_full_result_int *)malloc(
|
|
offsetof(psync_full_result_int, data) + sizeof(uint64_t) * off);
|
|
ret->rows = rows;
|
|
ret->cols = cols;
|
|
memcpy(ret->data, data, sizeof(uint64_t) * off);
|
|
free(data);
|
|
return ret;
|
|
}
|
|
|
|
uint32_t psql_affected() {
|
|
return sqlite3_changes(psync_db);
|
|
}
|
|
|
|
uint64_t psql_insertid() {
|
|
return sqlite3_last_insert_rowid(psync_db);
|
|
}
|
|
|
|
static const char *PSYNC_CONST get_type_name(uint32_t t) {
|
|
if (unlikely(t >= ARRAY_SIZE(psync_typenames)))
|
|
t = 0;
|
|
return psync_typenames[t];
|
|
}
|
|
|
|
uint64_t psql_expect_num(const char *file, const char *function,
|
|
int unsigned line, const psync_variant *v) {
|
|
if (D_CRITICAL <= DEBUG_LEVEL)
|
|
pdbg_printf(file, function, line, D_CRITICAL,
|
|
"type error, wanted %s got %s", get_type_name(PSYNC_TNUMBER),
|
|
get_type_name(v->type));
|
|
return 0;
|
|
}
|
|
|
|
const char *psql_expect_str(const char *file, const char *function,
|
|
int unsigned line,
|
|
const psync_variant *v) {
|
|
if (D_CRITICAL <= DEBUG_LEVEL)
|
|
pdbg_printf(file, function, line, D_CRITICAL,
|
|
"type error, wanted %s got %s", get_type_name(PSYNC_TSTRING),
|
|
get_type_name(v->type));
|
|
return "";
|
|
}
|
|
|
|
const char *psql_lstring_expected(const char *file, const char *function,
|
|
int unsigned line, const psync_variant *v,
|
|
size_t *len) {
|
|
if (likely(v->type == PSYNC_TSTRING)) {
|
|
*len = v->length;
|
|
return v->str;
|
|
} else {
|
|
if (D_CRITICAL <= DEBUG_LEVEL)
|
|
pdbg_printf(file, function, line, D_CRITICAL,
|
|
"type error, wanted %s got %s", get_type_name(PSYNC_TSTRING),
|
|
get_type_name(v->type));
|
|
*len = 0;
|
|
return "";
|
|
}
|
|
}
|
|
|
|
double psql_expect_real(const char *file, const char *function,
|
|
int unsigned line, const psync_variant *v) {
|
|
if (D_CRITICAL <= DEBUG_LEVEL)
|
|
pdbg_printf(file, function, line, D_CRITICAL,
|
|
"type error, wanted %s got %s", get_type_name(PSYNC_TREAL),
|
|
get_type_name(v->type));
|
|
return 0.0;
|
|
}
|
|
|
|
void psql_try_free() {
|
|
sqlite3_db_release_memory(psync_db);
|
|
pcache_clean();
|
|
}
|
|
|
|
|
|
#if IS_DEBUG
|
|
int psql_do_statement(const char *sql, const char *file, unsigned line) {
|
|
char *errmsg;
|
|
int code;
|
|
psql_do_lock(file, line);
|
|
#else
|
|
int psql_statement(const char *sql) {
|
|
char *errmsg;
|
|
int code;
|
|
psql_lock();
|
|
#endif
|
|
code = sqlite3_exec(psync_db, sql, NULL, NULL, &errmsg);
|
|
psql_unlock();
|
|
if (likely(code == SQLITE_OK))
|
|
return 0;
|
|
else {
|
|
#if IS_DEBUG
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s called from %s:%u", sql,
|
|
errmsg, file, line);
|
|
#else
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql, errmsg);
|
|
#endif
|
|
sqlite3_free(errmsg);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
int psql_do_start_transaction(const char *file, unsigned line) {
|
|
psync_sql_res *res;
|
|
psql_do_lock(file, line);
|
|
res = psql_do_prepare("BEGIN", file, line);
|
|
#else
|
|
int psql_start() {
|
|
psync_sql_res *res;
|
|
psql_lock();
|
|
res = psql_prepare("BEGIN");
|
|
#endif
|
|
pdbg_assert(!in_transaction);
|
|
if (unlikely(!res || psql_run_free(res)))
|
|
return -1;
|
|
in_transaction = 1;
|
|
transaction_failed = 0;
|
|
psync_list_init(&commitcbs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
psync_sql_res *psql_do_query_nocache(const char *sql, const char *file,
|
|
unsigned line) {
|
|
#else
|
|
psync_sql_res *psql_query_nocache(const char *sql) {
|
|
#endif
|
|
sqlite3_stmt *stmt;
|
|
psync_sql_res *res;
|
|
int code, cnt;
|
|
#if IS_DEBUG
|
|
psql_do_lock(file, line);
|
|
#else
|
|
psql_lock();
|
|
#endif
|
|
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
psql_unlock();
|
|
#if IS_DEBUG
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s called from %s:%u", sql,
|
|
sqlite3_errmsg(psync_db), file, line);
|
|
sendpdbg_logf("error running sql statement: %s: %s called from %s:%u", sql,
|
|
sqlite3_errmsg(psync_db), file, line);
|
|
#else
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendpdbg_logf("error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
#endif
|
|
return NULL;
|
|
}
|
|
cnt = sqlite3_column_count(stmt);
|
|
res = (psync_sql_res *)malloc(sizeof(psync_sql_res) +
|
|
cnt * sizeof(psync_variant));
|
|
res->stmt = stmt;
|
|
res->sql = sql;
|
|
res->column_count = cnt;
|
|
res->locked = SQL_WRITE_LOCK;
|
|
return res;
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
psync_sql_res *psql_do_query(const char *sql, const char *file,
|
|
unsigned line) {
|
|
#else
|
|
psync_sql_res *psql_query(const char *sql) {
|
|
#endif
|
|
psync_sql_res *ret;
|
|
ret = (psync_sql_res *)pcache_get(sql);
|
|
if (ret) {
|
|
// pdbg_logf(D_NOTICE, "got query %s from cache", sql);
|
|
ret->locked = SQL_WRITE_LOCK;
|
|
ret->sql = sql;
|
|
#if IS_DEBUG
|
|
psql_do_lock(file, line);
|
|
#else
|
|
psql_lock();
|
|
#endif
|
|
return ret;
|
|
} else
|
|
#if IS_DEBUG
|
|
return psql_do_query_nocache(sql, file, line);
|
|
#else
|
|
return psql_query_nocache(sql);
|
|
#endif
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
psync_sql_res *psql_do_query_rdlock_nocache(const char *sql,
|
|
const char *file,
|
|
unsigned line) {
|
|
#else
|
|
psync_sql_res *psql_rdlock_nocache(const char *sql) {
|
|
#endif
|
|
sqlite3_stmt *stmt;
|
|
psync_sql_res *res;
|
|
int code, cnt;
|
|
#if IS_DEBUG
|
|
psql_do_rdlock(file, line);
|
|
#else
|
|
psql_rdlock();
|
|
#endif
|
|
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
psql_rdunlock();
|
|
#if IS_DEBUG
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s called from %s:%u", sql,
|
|
sqlite3_errmsg(psync_db), file, line);
|
|
sendpdbg_logf("error running sql statement: %s: %s called from %s:%u", sql,
|
|
sqlite3_errmsg(psync_db), file, line);
|
|
#else
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendpdbg_logf("error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
#endif
|
|
return NULL;
|
|
}
|
|
cnt = sqlite3_column_count(stmt);
|
|
res = (psync_sql_res *)malloc(sizeof(psync_sql_res) +
|
|
cnt * sizeof(psync_variant));
|
|
res->stmt = stmt;
|
|
res->sql = sql;
|
|
res->column_count = cnt;
|
|
res->locked = SQL_READ_LOCK;
|
|
return res;
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
psync_sql_res *psql_do_query_rdlock(const char *sql, const char *file,
|
|
unsigned line) {
|
|
#else
|
|
psync_sql_res *psql_query_rdlock(const char *sql) {
|
|
#endif
|
|
psync_sql_res *ret;
|
|
ret = (psync_sql_res *)pcache_get(sql);
|
|
if (ret) {
|
|
// pdbg_logf(D_NOTICE, "got query %s from cache", sql);
|
|
ret->locked = SQL_READ_LOCK;
|
|
ret->sql = sql;
|
|
#if IS_DEBUG
|
|
psql_do_rdlock(file, line);
|
|
#else
|
|
psql_rdlock();
|
|
#endif
|
|
return ret;
|
|
} else
|
|
#if IS_DEBUG
|
|
return psql_do_query_rdlock_nocache(sql, file, line);
|
|
#else
|
|
return psql_rdlock_nocache(sql);
|
|
#endif
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
psync_sql_res *psql_do_query_nolock_nocache(const char *sql, const char *file, unsigned line) {
|
|
#else
|
|
psync_sql_res *psql_query_nolock_nocache(const char *sql) {
|
|
#endif
|
|
sqlite3_stmt *stmt;
|
|
psync_sql_res *res;
|
|
int code, cnt;
|
|
#if IS_DEBUG
|
|
if (!psql_locked()) {
|
|
pdbg_logf(D_BUG,
|
|
"illegal use of psync_sql_query_nolock, can only be used while "
|
|
"holding lock, invoked from %s:%u, sql: %s",
|
|
file, line, sql);
|
|
sendpdbg_logf("illegal use of psync_sql_query_nolock, can only be used while "
|
|
"holding lock, invoked from %s:%u, sql: %s",
|
|
file, line, sql);
|
|
abort();
|
|
}
|
|
#endif
|
|
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendpdbg_logf("error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
return NULL;
|
|
}
|
|
cnt = sqlite3_column_count(stmt);
|
|
res = (psync_sql_res *)malloc(sizeof(psync_sql_res) +
|
|
cnt * sizeof(psync_variant));
|
|
res->stmt = stmt;
|
|
res->sql = sql;
|
|
res->column_count = cnt;
|
|
res->locked = SQL_NO_LOCK;
|
|
return res;
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
psync_sql_res *psql_do_query_nolock(const char *sql, const char *file, unsigned line) {
|
|
#else
|
|
psync_sql_res *psql_query_nolock(const char *sql) {
|
|
#endif
|
|
psync_sql_res *ret;
|
|
#if IS_DEBUG
|
|
if (!psql_locked()) {
|
|
pdbg_logf(D_BUG,
|
|
"illegal use of psync_sql_query_nolock, can only be used while "
|
|
"holding lock, invoked from %s:%u, sql: %s",
|
|
file, line, sql);
|
|
sendpdbg_logf("illegal use of psync_sql_query_nolock, can only be used while "
|
|
"holding lock, invoked from %s:%u, sql: %s",
|
|
file, line, sql);
|
|
abort();
|
|
}
|
|
#endif
|
|
ret = (psync_sql_res *)pcache_get(sql);
|
|
if (ret) {
|
|
// pdbg_logf(D_NOTICE, "got query %s from cache", sql);
|
|
ret->locked = SQL_NO_LOCK;
|
|
ret->sql = sql;
|
|
return ret;
|
|
} else
|
|
#if IS_DEBUG
|
|
return psql_do_query_nolock_nocache(sql, file, line);
|
|
#else
|
|
return psql_query_nolock_nocache(sql);
|
|
#endif
|
|
}
|
|
|
|
void psql_free(psync_sql_res *res) {
|
|
int code = sqlite3_reset(res->stmt);
|
|
psync_sql_res_unlock(res);
|
|
#if IS_DEBUG
|
|
memset(res->row, 0xff, res->column_count * sizeof(psync_variant));
|
|
#endif
|
|
if (code == SQLITE_OK)
|
|
pcache_add(res->sql, res, PSYNC_QUERY_CACHE_SEC, psync_sql_free_cache,
|
|
PSYNC_QUERY_MAX_CNT);
|
|
else
|
|
psync_sql_free_cache(res);
|
|
}
|
|
|
|
void psql_free_nocache(psync_sql_res *res) {
|
|
sqlite3_finalize(res->stmt);
|
|
psync_sql_res_unlock(res);
|
|
#if IS_DEBUG
|
|
memset(res, 0xff, sizeof(psync_sql_res));
|
|
#endif
|
|
free(res);
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
psync_sql_res *psql_do_prepare_nocache(const char *sql, const char *file, unsigned line) {
|
|
#else
|
|
psync_sql_res *psql_prepare_nocache(const char *sql) {
|
|
#endif
|
|
sqlite3_stmt *stmt;
|
|
psync_sql_res *res;
|
|
int code;
|
|
#if IS_DEBUG
|
|
psql_do_lock(file, line);
|
|
#else
|
|
psql_lock();
|
|
#endif
|
|
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
|
|
if (unlikely(code != SQLITE_OK)) {
|
|
psql_unlock();
|
|
#if IS_DEBUG
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s called from %s:%u", sql,
|
|
sqlite3_errmsg(psync_db), file, line);
|
|
sendpdbg_logf("error running sql statement: %s: %s called from %s:%u", sql,
|
|
sqlite3_errmsg(psync_db), file, line);
|
|
#else
|
|
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
sendpdbg_logf("error running sql statement: %s: %s", sql,
|
|
sqlite3_errmsg(psync_db));
|
|
#endif
|
|
return NULL;
|
|
}
|
|
res = malloc(sizeof(psync_sql_res));
|
|
res->stmt = stmt;
|
|
res->sql = sql;
|
|
#if IS_DEBUG
|
|
res->column_count = 0;
|
|
#endif
|
|
res->locked = SQL_WRITE_LOCK;
|
|
return res;
|
|
}
|
|
|
|
#if IS_DEBUG
|
|
psync_sql_res *psql_do_prepare(const char *sql, const char *file, unsigned line) {
|
|
#else
|
|
psync_sql_res *psql_prepare(const char *sql) {
|
|
#endif
|
|
psync_sql_res *ret;
|
|
ret = pcache_get(sql);
|
|
if (ret) {
|
|
// pdbg_logf(D_NOTICE, "got statement %s from cache", sql);
|
|
ret->locked = SQL_WRITE_LOCK;
|
|
#if IS_DEBUG
|
|
psql_do_lock(file, line);
|
|
#else
|
|
psql_lock();
|
|
#endif
|
|
return ret;
|
|
} else
|
|
#if IS_DEBUG
|
|
return psql_do_prepare_nocache(sql, file, line);
|
|
#else
|
|
return psql_prepare_nocache(sql);
|
|
#endif
|
|
}
|