#include #include #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 }