/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ** Copyright (c) 2013 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** This file contains the fsl_db_xxx() and fsl_stmt_xxx() parts of the ** API. */ #include "fossil-scm/fossil.h" #include "fossil-scm/fossil-internal.h" #include #include /* NULL on linux */ #include /* time() and friends */ /* Only for debugging */ #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) /** ** fsl_list_visitor_f() impl which requires that obj be NULL or ** a (fsl_st*), which it passed to fsl_stmt_finalize(). */ static int fsl_list_v_fsl_stmt_finalize(void * obj, void * visitorState ){ if(obj) fsl_stmt_finalize( (fsl_stmt*)obj ); return 0; } void fsl_db_clear_strings(fsl_db * db, char alsoErrorState ){ fsl_buffer_clear(&db->filename); fsl_buffer_clear(&db->name); if(alsoErrorState) fsl_error_clean(&db->error); } int fsl_db_err_get( fsl_db const * db, char const ** msg, fsl_size_t * len ){ return db ? fsl_error_get(&db->error, msg, len) : FSL_RC_MISUSE; } fsl_db * fsl_stmt_db( fsl_stmt * stmt ){ return stmt ? stmt->db : NULL; } /** ** Resets db->error state based on the given code and the current ** error string from the db driver. Returns FSL_RC_DB on success, ** some other non-0 value on error (most likely FSL_RC_OOM while ** allocating the error string - that's the only other error case as ** long as db is opened). Results are undefined if !db or db is not ** opened. */ static int fsl_err_from_db( fsl_db * db, int dbCode ){ int rc; assert(db && db->dbh); db->error.msg.used =0 ; rc = fsl_buffer_appendf(&db->error.msg, "Db error #%d: %s", dbCode, sqlite3_errmsg(db->dbh)); return rc ? rc : FSL_RC_DB; } char const * fsl_stmt_sql( fsl_stmt * stmt, fsl_size_t * len ){ return stmt ? fsl_buffer_cstr2(&stmt->sql, len) : NULL; } char const * fsl_db_filename(fsl_db const * db, fsl_size_t * len){ return db ? fsl_buffer_cstr2(&db->filename, len) : NULL; } fsl_id_t fsl_db_last_insert_id(fsl_db *db){ return (db && db->dbh) ? (fsl_id_t)sqlite3_last_insert_rowid(db->dbh) : -1; } /** ** Cleans up db->beforeCommit and its contents. */ static void fsl_db_cleanup_beforeCommit( fsl_db * db ){ fsl_list_visit( &db->beforeCommit, -1, fsl_list_v_fsl_free, NULL ); fsl_list_reserve(&db->beforeCommit, 0); } fsl_size_t fsl_db_stmt_cache_clear(fsl_db * db){ fsl_size_t rc = 0; if(db && db->stCache.list){ fsl_list cache = db->stCache; rc = cache.used; db->stCache = fsl_list_empty /* to bypass cache removal checks during cleanup */; fsl_list_visit( &cache, -1, fsl_list_v_fsl_stmt_finalize, NULL ); fsl_list_reserve(&cache, 0); } return rc; } int fsl_db_close( fsl_db * db ){ if(!db) return FSL_RC_MISUSE; else{ void const * allocStamp = db->allocStamp; fsl_cx * f = db->f; fsl_db_stmt_cache_clear(db); if(db->f && db->f->dbMain==db){ /* Horrible, horrible dependency, and only necessary if the fsl_cx API gets sloppy or repo/checkout/config DBs are otherwised closed improperly (not via the fsl_cx API). */ assert(0 != db->role); f->dbMain = NULL; } while(db->beginCount>0){ fsl_db_transaction_end(db, 1); } if(0!=db->openStatementCount){ MARKER(("WARNING: %d open statement(s) left on db [%.*s].\n", (int)db->openStatementCount, (int)db->filename.used, db->filename.mem)); } if(db->dbh){ sqlite3_close(db->dbh) /* ignoring result in the style of "destructors may not throw". */; } fsl_db_clear_strings(db, 1); fsl_db_cleanup_beforeCommit(db); *db = fsl_db_empty; if(&fsl_db_empty == allocStamp){ fsl_free( db ); }else{ db->allocStamp = allocStamp; db->f = f; } return 0; } } int fsl_db_attach(fsl_db * db, const char *zDbName, const char *zLabel){ return (db && db->dbh && zDbName && *zDbName && zLabel && *zLabel) ? fsl_db_exec(db, "ATTACH DATABASE %Q AS %s", zDbName, zLabel) : FSL_RC_MISUSE; } int fsl_db_detach(fsl_db * db, const char *zLabel){ return (db && db->dbh && zLabel && *zLabel) ? fsl_db_exec(db, "DETACH DATABASE %s", zLabel) : FSL_RC_MISUSE; } /** ** Returns the db name for the given role. */ const char * fsl_db_role_label(int r){ switch(r){ case FSL_DB_ROLE_CONFIG: return "cfg"; case FSL_DB_ROLE_REPO: return "repo"; case FSL_DB_ROLE_CHECKOUT: return "ckout"; case FSL_DB_ROLE_MAIN: return "main"; case FSL_DB_ROLE_NONE: default: assert(!"cannot happen/not legal"); return NULL; } } char * fsl_db_julian_to_iso8601( fsl_db * db, fsl_double_t j, char msPrecision ){ char * s = NULL; fsl_stmt * st = NULL; if(db && db->dbh && (j>=0.0)){ char const * sql = msPrecision ? "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',?)" : "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S',?)" ; fsl_db_prepare_cached(db, &st, sql); if(st){ fsl_stmt_bind_double( st, 1, j ); if( FSL_RC_STEP_ROW==fsl_stmt_step(st) ){ s = fsl_strdup(fsl_stmt_g_text(st, 0, NULL)); } fsl_stmt_cached_yield(st); } } return s; } int fsl_db_preparev( fsl_db *db, fsl_stmt * tgt, char const * sql, va_list args ){ if(!db || !db->dbh || !tgt || !sql) return FSL_RC_MISUSE; else if(!*sql) return FSL_RC_RANGE; else if(tgt->stmt){ return fsl_error_set(&db->error, FSL_RC_ALREADY_EXISTS, "Error: attempt to re-prepare " "active statement."); } else{ int rc; fsl_buffer buf = fsl_buffer_empty; fsl_stmt_t * liteStmt = NULL; rc = fsl_buffer_appendfv( &buf, sql, args ); if(!rc){ sql = fsl_buffer_cstr(&buf); rc = sqlite3_prepare_v2(db->dbh, sql, (int)buf.used, &liteStmt, 0); if(rc){ rc = fsl_error_set(&db->error, FSL_RC_DB, "Db statement preparation failed. " "Error #%d: %s. SQL: %.*s", rc, sqlite3_errmsg(db->dbh), (int)buf.used, (char const *)buf.mem); } } if(!rc){ ++db->openStatementCount; tgt->stmt = liteStmt; tgt->db = db; tgt->sql = buf /*transfer ownership*/; tgt->colCount = sqlite3_column_count(tgt->stmt); tgt->paramCount = sqlite3_bind_parameter_count(tgt->stmt); }else{ assert(!liteStmt); fsl_buffer_clear(&buf); /* TODO: consider _copying_ the error state to db->f->error if db->f is not NULL. */ } return rc; } } int fsl_db_prepare( fsl_db *db, fsl_stmt * tgt, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_preparev( db, tgt, sql, args ); va_end(args); return rc; } enum fsl_stmt_flags_t { /** ** fsl_stmt::flags bit indicating that fsl_db_preparev_cache() has ** doled out this statement, effectively locking it until ** fsl_stmt_cached_yield() is called to release it. */ FSL_STMT_F_CACHE_HELD = 0x01 }; typedef enum fsl_stmt_flags_t fsl_stmt_flags_t; int fsl_db_preparev_cached( fsl_db * db, fsl_stmt ** rv, char const * sql, va_list args ){ int rc = 0; fsl_buffer buf = fsl_buffer_empty; fsl_stmt * st = NULL; fsl_size_t i; if(!db || !rv || !sql) return FSL_RC_MISUSE; else if(!*sql) return FSL_RC_RANGE; rc = fsl_buffer_appendfv( &buf, sql, args ); if(rc) goto end; for( i = 0; i < db->stCache.used; ++i ){ fsl_stmt * cs = (fsl_stmt*)db->stCache.list[i]; if(!cs) continue; else if(0==fsl_buffer_compare(&buf, &cs->sql)){ if(cs->flags & FSL_STMT_F_CACHE_HELD){ rc = fsl_error_set(&db->error, FSL_RC_ACCESS, "Cached statement is already in use. " "Do not use cached statements if recursion " "involving the statement is possible, and use " "fsl_stmt_cached_yield() to release them " "for further (re)use. SQL=%b", &cs->sql); goto end; } cs->flags |= FSL_STMT_F_CACHE_HELD; *rv = cs; goto end; } } st = fsl_stmt_malloc(); if(!st){ rc = FSL_RC_OOM; goto end; } rc = fsl_db_prepare( db, st, "%b", &buf ); if(!rc){ rc = fsl_list_append( &db->stCache, st ); if(rc){ fsl_stmt_finalize(st); goto end; } } *rv = st; st->flags = FSL_STMT_F_CACHE_HELD; end: fsl_buffer_clear(&buf); return rc; } int fsl_db_prepare_cached( fsl_db * db, fsl_stmt ** st, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_preparev_cached( db, st, sql, args ); va_end(args); return rc; } int fsl_stmt_cached_yield( fsl_stmt * st ){ if(!st || !st->db || !st->stmt) return FSL_RC_MISUSE; else if(!(st->flags & FSL_STMT_F_CACHE_HELD)) { return fsl_error_set(&st->db->error, FSL_RC_MISUSE, "fsl_stmt_cached_yield() passed a statement " "which is not marked as cached. SQL=%b", &st->sql); } fsl_stmt_reset(st); st->flags &= ~FSL_STMT_F_CACHE_HELD; return 0; } int fsl_db_before_commitv( fsl_db * db, char const * sql, va_list args ){ int rc = 0; char * cp = NULL; fsl_buffer buf = fsl_buffer_empty; if(!db || !sql) return FSL_RC_MISUSE; else if(!*sql) return FSL_RC_RANGE; rc = fsl_buffer_appendfv( &buf, sql, args ); if(!rc){ cp = (char *)buf.mem /* transfer ownership */; rc = fsl_list_append(&db->beforeCommit, cp); if(rc) fsl_free(cp); else buf = fsl_buffer_empty; } fsl_buffer_clear(&buf); return rc; } int fsl_db_before_commit( fsl_db *db, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_before_commitv( db, sql, args ); va_end(args); return rc; } int fsl_stmt_finalize( fsl_stmt * stmt ){ if(!stmt) return FSL_RC_MISUSE; else{ void const * allocStamp = stmt->allocStamp; fsl_db * db = stmt->db; if(db){ if(stmt->sql.mem){ /* ^^^ b/c that buffer is set at the same time that openStatementCount is incremented. */ --stmt->db->openStatementCount; } if(allocStamp && db->stCache.used){ /* It _might_ be cached - let's remove it. We use allocStamp as a check here only because most statements allocated on the heap currently come from caching. */ fsl_size_t i; fsl_stmt * s; for( i = 0; i < db->stCache.used; ++i ){ s = (fsl_stmt*)db->stCache.list[i]; if(s == stmt){ for( ; i < db->stCache.used; ++i ){ db->stCache.list[i] = (i < (db->stCache.capacity-1)) ? db->stCache.list[i+1] : NULL; } if(i < (db->stCache.capacity-1)){ db->stCache.list[i] = NULL; } --db->stCache.used; break; } } } } fsl_buffer_clear(&stmt->sql); if(stmt->stmt){ sqlite3_finalize( stmt->stmt ); } *stmt = fsl_stmt_empty; if(&fsl_stmt_empty==allocStamp){ fsl_free(stmt); }else{ stmt->allocStamp = allocStamp; } return 0; } } int fsl_stmt_step( fsl_stmt * stmt ){ if(!stmt || !stmt->stmt) return FSL_RC_MISUSE; else{ int const rc = sqlite3_step(stmt->stmt); assert(stmt->db); switch( rc ){ case SQLITE_ROW: ++stmt->rowCount; return FSL_RC_STEP_ROW; case SQLITE_DONE: return FSL_RC_STEP_DONE; default: fsl_error_set(&stmt->db->error, FSL_RC_STEP_ERROR, "Db error #%d: %s", rc, sqlite3_errmsg(stmt->db->dbh)); return FSL_RC_STEP_ERROR; } } } int fsl_db_eachv( fsl_db * db, fsl_stmt_each_f callback, void * callbackState, char const * sql, va_list args ){ if(!db || !db->dbh || !callback || !sql) return FSL_RC_MISUSE; else if(!*sql) return FSL_RC_RANGE; else{ fsl_stmt st = fsl_stmt_empty; int rc; rc = fsl_db_preparev( db, &st, sql, args ); if(!rc){ rc = fsl_stmt_each( &st, callback, callbackState ); fsl_stmt_finalize( &st ); } return rc; } } int fsl_db_each( fsl_db * db, fsl_stmt_each_f callback, void * callbackState, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_eachv( db, callback, callbackState, sql, args ); va_end(args); return rc; } int fsl_stmt_each( fsl_stmt * stmt, fsl_stmt_each_f callback, void * callbackState ){ if(!stmt || !callback) return FSL_RC_MISUSE; else{ int strc; int rc = 0; char doBreak = 0; while( !doBreak && (FSL_RC_STEP_ROW == (strc=fsl_stmt_step(stmt)))){ rc = callback( stmt, callbackState ); switch(rc){ case 0: continue; case FSL_RC_BREAK: rc = 0; /* fall through */ default: doBreak = 1; break; } } return rc ? rc : ((FSL_RC_STEP_ERROR==strc) ? FSL_RC_DB : 0); } } int fsl_stmt_reset2( fsl_stmt * stmt, char resetRowCounter ){ if(!stmt || !stmt->stmt || !stmt->db) return FSL_RC_MISUSE; else{ int const rc = sqlite3_reset(stmt->stmt); if(resetRowCounter) stmt->rowCount = 0; assert(stmt->db); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_reset( fsl_stmt * stmt ){ return fsl_stmt_reset2(stmt, 0); } int fsl_stmt_col_count( fsl_stmt const * stmt ){ return (!stmt || !stmt->stmt) ? -1 : stmt->colCount ; } char const * fsl_stmt_col_name(fsl_stmt * stmt, int index){ return (stmt && stmt->stmt && (index>=0 && indexcolCount)) ? sqlite3_column_name(stmt->stmt, index) : NULL; } int fsl_stmt_param_count( fsl_stmt const * stmt ){ return (!stmt || !stmt->stmt) ? -1 : stmt->paramCount; } /** ** UNTESTED. ** ** Binds a series of values using a formatting string. ** ** The string may contain the following characters, each of which ** refers to the next argument in the args list: ** ** '-': binds a NULL and expects a NULL placeholder ** in the argument list (for consistency's sake). ** ** 'i': binds an int32 ** ** 'I': binds an int64 ** ** 'R': binds a fsl_id_t ('R' as in 'RID') ** ** 'f': binds a fsl_double_t ** ** 's': binds a (char const *) as a string or NULL. ** ** 'S': binds a (char const *) as a blob or NULL. ** ** 'b': binds a (fsl_buffer const *) as a string or NULL. ** ** 'B': binds a (fsl_buffer const *) as a blob or NULL. ** ** ' ': spaces are allowed for readability and are ignored. */ int fsl_stmt_bind_fmtv( fsl_stmt * st, char const * fmt, va_list args ){ int rc = 0, ndx; char const * pos = fmt; if(!fmt || !(st && st->stmt && st->db && st->db->dbh)) return FSL_RC_MISUSE; else if(!*fmt) return FSL_RC_RANGE; for( ndx = 1; !rc && *pos; ++pos, ++ndx ){ if(ndx > st->paramCount){ rc = fsl_error_set(&st->db->error, FSL_RC_RANGE, "Column index %d is out of bounds.", ndx); break; } switch(*pos){ case ' ': --ndx; continue; case '-': va_arg(args,void const *) /* skip arg */; rc = fsl_stmt_bind_null(st, ndx); break; case 'i': rc = fsl_stmt_bind_int32(st, ndx, va_arg(args,fsl_int32_t)); break; case 'I': rc = fsl_stmt_bind_int64(st, ndx, va_arg(args,fsl_int64_t)); break; case 'R': rc = fsl_stmt_bind_id(st, ndx, va_arg(args,fsl_id_t)); break; case 'f': rc = fsl_stmt_bind_double(st, ndx, va_arg(args,fsl_double_t)); break; case 's':{/* C-string as TEXT or NULL */ char const * s = va_arg(args,char const *); rc = s ? fsl_stmt_bind_text(st, ndx, s, -1, 1) : fsl_stmt_bind_null(st, ndx); break; } case 'S':{ /* C-string as BLOB or NULL */ void const * s = va_arg(args,void const *); rc = s ? fsl_stmt_bind_blob(st, ndx, s, -1, 1) : fsl_stmt_bind_null(st, ndx); break; } case 'b':{ /* fsl_buffer as TEXT or NULL */ fsl_buffer const * b = va_arg(args,fsl_buffer const *); rc = (b && b->mem) ? fsl_stmt_bind_text(st, ndx, (char const *)b->mem, (fsl_int_t)b->used, 1) : fsl_stmt_bind_null(st, ndx); break; } case 'B':{ /* fsl_buffer as BLOB or NULL */ fsl_buffer const * b = va_arg(args,fsl_buffer const *); rc = (b && b->mem) ? fsl_stmt_bind_blob(st, ndx, b->mem, (fsl_int_t)b->used, 1) : fsl_stmt_bind_null(st, ndx); break; } default: rc = fsl_error_set(&st->db->error, FSL_RC_RANGE, "Invalid format character: '%c'", *pos); break; } } return rc; } #define BIND_PARAM_CHECK \ if(!(stmt && stmt->stmt && stmt->db && stmt->db->dbh)) return FSL_RC_MISUSE; else #define BIND_PARAM_CHECK2 BIND_PARAM_CHECK \ if(ndx<1 || ndx>stmt->paramCount) return FSL_RC_RANGE; else int fsl_stmt_bind_null( fsl_stmt * stmt, int ndx ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_null( stmt->stmt, ndx ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_int32( fsl_stmt * stmt, int ndx, fsl_int32_t v ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_int( stmt->stmt, ndx, (int)v ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_int64( fsl_stmt * stmt, int ndx, fsl_int64_t v ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_int64( stmt->stmt, ndx, (sqlite3_int64)v ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_id( fsl_stmt * stmt, int ndx, fsl_id_t v ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_int64( stmt->stmt, ndx, (sqlite3_int64)v ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_double( fsl_stmt * stmt, int ndx, fsl_double_t v ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_double( stmt->stmt, ndx, (double)v ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_blob( fsl_stmt * stmt, int ndx, void const * src, fsl_int_t len, char makeCopy ){ BIND_PARAM_CHECK2 { int rc; if(len<0) len = fsl_strlen((char const *)src); rc = sqlite3_bind_blob( stmt->stmt, ndx, src, len, makeCopy ? SQLITE_TRANSIENT : SQLITE_STATIC ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_text( fsl_stmt * stmt, int ndx, char const * src, fsl_int_t len, char makeCopy ){ BIND_PARAM_CHECK { int rc; if(len<0) len = fsl_strlen((char const *)src); rc = sqlite3_bind_text( stmt->stmt, ndx, src, len, makeCopy ? SQLITE_TRANSIENT : SQLITE_STATIC ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_null_name( fsl_stmt * stmt, char const * param ){ BIND_PARAM_CHECK{ return fsl_stmt_bind_null( stmt, sqlite3_bind_parameter_index( stmt->stmt, param) ); } } int fsl_stmt_bind_int32_name( fsl_stmt * stmt, char const * param, fsl_int32_t v ){ BIND_PARAM_CHECK { return fsl_stmt_bind_int32( stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v); } } int fsl_stmt_bind_int64_name( fsl_stmt * stmt, char const * param, fsl_int64_t v ){ BIND_PARAM_CHECK { return fsl_stmt_bind_int64( stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v); } } int fsl_stmt_bind_id_name( fsl_stmt * stmt, char const * param, fsl_id_t v ){ BIND_PARAM_CHECK { return fsl_stmt_bind_id( stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v); } } int fsl_stmt_bind_double_name( fsl_stmt * stmt, char const * param, fsl_double_t v ){ BIND_PARAM_CHECK { return fsl_stmt_bind_double( stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v); } } int fsl_stmt_bind_text_name( fsl_stmt * stmt, char const * param, char const * v, fsl_int_t n, char makeCopy ){ BIND_PARAM_CHECK { return fsl_stmt_bind_text(stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v, n, makeCopy); } } int fsl_stmt_bind_blob_name( fsl_stmt * stmt, char const * param, void const * v, fsl_int_t len, char makeCopy ){ BIND_PARAM_CHECK { return fsl_stmt_bind_blob(stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v, len, makeCopy); } } #undef BIND_PARAM_CHECK #undef BIND_PARAM_CHECK2 #define GET_CHECK if(!stmt || !stmt->colCount) return FSL_RC_MISUSE; \ else if((ndx<0) || (ndx>=stmt->colCount)) return FSL_RC_RANGE; else int fsl_stmt_get_int32( fsl_stmt * stmt, int ndx, fsl_int32_t * v ){ GET_CHECK { if(v) *v = (fsl_int32_t)sqlite3_column_int(stmt->stmt, ndx); return 0; } } int fsl_stmt_get_int64( fsl_stmt * stmt, int ndx, fsl_int64_t * v ){ GET_CHECK { if(v) *v = (fsl_int64_t)sqlite3_column_int64(stmt->stmt, ndx); return 0; } } int fsl_stmt_get_double( fsl_stmt * stmt, int ndx, fsl_double_t * v ){ GET_CHECK { if(v) *v = (fsl_double_t)sqlite3_column_double(stmt->stmt, ndx); return 0; } } int fsl_stmt_get_id( fsl_stmt * stmt, int ndx, fsl_id_t * v ){ GET_CHECK { if(v) *v = (4==sizeof(fsl_id_t)) ? (fsl_id_t)sqlite3_column_int(stmt->stmt, ndx) : (fsl_id_t)sqlite3_column_int64(stmt->stmt, ndx); return 0; } } int fsl_stmt_get_text( fsl_stmt * stmt, int ndx, char const **out, fsl_size_t * outLen ){ GET_CHECK { unsigned char const * t = (out || outLen) ? sqlite3_column_text(stmt->stmt, ndx) : NULL; if(out) *out = (char const *)t; if(outLen){ int const x = sqlite3_column_bytes(stmt->stmt, ndx); *outLen = (x>0) ? (fsl_size_t)x : 0; } return 0; } } int fsl_stmt_get_blob( fsl_stmt * stmt, int ndx, void const **out, fsl_size_t * outLen ){ GET_CHECK { void const * t = (out || outLen) ? sqlite3_column_blob(stmt->stmt, ndx) : NULL; if(out) *out = t; if(outLen){ if(!t) *outLen = 0; else{ int sz = sqlite3_column_bytes(stmt->stmt, ndx); *outLen = (sz>=0) ? (fsl_size_t)sz : 0; } } return 0; } } #undef GET_CHECK fsl_id_t fsl_stmt_g_id( fsl_stmt * stmt, int index ){ fsl_id_t rv = -1; fsl_stmt_get_id(stmt, index, &rv); return rv; } fsl_int32_t fsl_stmt_g_int32( fsl_stmt * stmt, int index ){ fsl_int32_t rv = 0; fsl_stmt_get_int32(stmt, index, &rv); return rv; } fsl_int64_t fsl_stmt_g_int64( fsl_stmt * stmt, int index ){ fsl_int64_t rv = 0; fsl_stmt_get_int64(stmt, index, &rv); return rv; } fsl_double_t fsl_stmt_g_double( fsl_stmt * stmt, int index ){ fsl_double_t rv = 0; fsl_stmt_get_double(stmt, index, &rv); return rv; } char const * fsl_stmt_g_text( fsl_stmt * stmt, int index, fsl_size_t * outLen ){ char const * rv = NULL; fsl_stmt_get_text(stmt, index, &rv, outLen); return rv; } /** ** Registering this function with sqlite3 requires that ** fslCtx be a fsl_cx instance. This function outputs ** tracing info using fsl_output(fslCtx,...). */ static void fsl_db_sql_trace(void *zFILE, const char *zSql){ int const n = fsl_strlen(zSql); static int counter = 0; /* FIXME: in v1 this uses fossil_trace(), but don't have that functionality here yet. */ fsl_fprintf(zFILE ? (FILE*)zFILE : stdout, "SQL TRACE #%d: %s%s\n", ++counter, zSql, (n>0 && zSql[n-1]==';') ? "" : ";"); } /* ** SQL function for debugging. ** ** The print() function writes its arguments to fsl_output(). */ static void fsl_db_sql_print( sqlite3_context *context, int argc, sqlite3_value **argv ){ fsl_cx * f = (fsl_cx*)sqlite3_user_data(context); assert(f); if( f->config.sqlPrint ){ int i; for(i=0; iallocStamp = &fsl_db_empty; } return rc; } fsl_stmt * fsl_stmt_malloc(){ fsl_stmt * rc = (fsl_stmt *)fsl_malloc(sizeof(fsl_stmt)); if(rc){ *rc = fsl_stmt_empty; rc->allocStamp = &fsl_stmt_empty; } return rc; } /** ** Return true if the schema is out-of-date. db must be an opened ** repo db. */ static char fsl_db_repo_schema_is_outofdate(fsl_db *db){ return fsl_db_exists(db, "SELECT 1 FROM config " "WHERE name='aux-schema' " "AND value<>'%s'", FSL_AUX_SCHEMA); } /* ** Returns 0 if db appears to have a current repository schema, 1 if ** it appears to have an out of date schema, and -1 if it appears to ** not be a repository. */ int fsl_db_repo_verify_schema(fsl_db * db){ if(fsl_db_repo_schema_is_outofdate(db)) return 1; else return fsl_db_exists(db, "SELECT 1 FROM config " "WHERE name='project-code'") ? 0 : -1; } int fsl_db_open( fsl_db * db, char const * dbFile, int openFlags ){ int rc; fsl_dbh_t * dbh = NULL; if(!db || !dbFile || !*dbFile) return FSL_RC_MISUSE; else if(db->dbh) return FSL_RC_MISUSE; else if(!(FSL_OPEN_F_CREATE & openFlags) && fsl_file_access(dbFile, 0)){ /* MARKER(("Error msg: %s\n", (char const *)db->error.msg.mem)); */ return FSL_RC_NOT_FOUND; } else{ int sOpenFlags = 0; if(0==fsl_strcmp(dbFile,":memory:")){ sOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; } else{ if(FSL_OPEN_F_RW & openFlags){ sOpenFlags |= SQLITE_OPEN_READWRITE; } if(FSL_OPEN_F_CREATE & openFlags){ sOpenFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; } else if(!sOpenFlags) sOpenFlags = SQLITE_OPEN_READONLY; } rc = sqlite3_open_v2( dbFile, &dbh, sOpenFlags, NULL ); if(rc){ if(dbh){ /* By some complete coincidence, FSL_RC_DB==SQLITE_CANTOPEN. */ rc = fsl_error_set(&db->error, FSL_RC_DB, "Opening db file [%s] failed with " "sqlite code #%d: %s", dbFile, rc, sqlite3_errmsg(dbh)); }else{ rc = fsl_error_set(&db->error, FSL_RC_DB, "Opening db file [%s] failed with " "sqlite code #%d", dbFile, rc); } /* MARKER(("Error msg: %s\n", (char const *)db->error.msg.mem)); */ goto end; }else{ assert(0==db->filename.used); rc = (':'==*dbFile)/* assume ":memory:" or some such: don't canonicalize it. */ ? fsl_buffer_append(&db->filename, dbFile, -1) : fsl_file_canonical_name(dbFile, &db->filename, 0); if(rc){ assert((FSL_RC_OOM==rc) && "That's the only logical explanation."); goto end; } } db->dbh = dbh; if(FSL_OPEN_F_SCHEMA_VALIDATE & openFlags){ int check; check = fsl_db_repo_verify_schema(db); if(0 != check){ rc = (check<0) ? fsl_error_set(&db->error, FSL_RC_NOT_A_REPO, "DB file [%s] does not appear to be " "a repository.", dbFile) : fsl_error_set(&db->error, FSL_RC_REPO_NEEDS_REBUILD, "DB file [%s] appears to be a fossil " "repsitory, but is out-of-date and needs " "a rebuild.", dbFile) ; assert(rc == db->error.code); goto end; } } if(db->f){ /* Plug in fsl_cx-specific functionality to this one. */ fsl_cx * f = db->f; if( f->config.traceSql ){ fsl_db_sqltrace_enable(db, stdout); } /* This all comes from db.c:db_open()... */ /* FIXME: check result codes here. */ sqlite3_busy_timeout(dbh, 5000 /* historical value */); sqlite3_wal_autocheckpoint(dbh, 1); /* Set to checkpoint frequently */ sqlite3_exec(dbh, "PRAGMA foreign_keys=OFF;", 0, 0, 0); sqlite3_create_function(dbh, "now", 0, SQLITE_ANY, 0, fsl_db_now_function, 0, 0); sqlite3_create_function(dbh, "checkin_mtime", 2, SQLITE_ANY, f, fsl_db_checkin_mtime_function, 0, 0); sqlite3_create_function(dbh, "user", 0, SQLITE_ANY, f, fsl_db_sql_user, 0, 0); sqlite3_create_function(dbh, "print", -1, SQLITE_UTF8, f, fsl_db_sql_print,0,0); #if 0 /* functions registered in v1 by db.c:db_open(). */ /* porting cgi() requires access to the HTTP/CGI layer. i.e. this belongs downstream. */ sqlite3_create_function(dbh, "cgi", 1, SQLITE_ANY, 0, db_sql_cgi, 0, 0); sqlite3_create_function(dbh, "cgi", 2, SQLITE_ANY, 0, db_sql_cgi, 0, 0); /* i[sf]_selected() both require access to the array of file names being considered for commit: g.aCommitFile[] */ sqlite3_create_function(dbh, "is_selected", 1, SQLITE_UTF8, f, file_is_selected,0,0 ); sqlite3_create_function(dbh, "if_selected", 3, SQLITE_UTF8, f, file_is_selected,0,0 ); re_add_sql_func(db) /* Requires the regex bits. */; #endif }/*if(db->f)*/ end: if(rc){ #if 0 assert(db->error.code); if(db->f && !db->f->error.code && db->error.code){ /* VERY ARGUABLE: Adopt db's error state as f's. */ fsl_error_move(&db->error, &db->f->error); } #endif } } #if 0 { fsl_buffer can = fsl_buffer_empty; fsl_file_canonical_name( dbFile, &can, 0 ); /* MARKER(("Canonical name: [%s] => [%s]\n", dbFile, fsl_buffer_cstr(&can))); */ fsl_buffer_reserve(&can, 0); } #endif if(rc){ if(db->dbh){ sqlite3_close(db->dbh); db->dbh = NULL; } }else{ assert(db->dbh); } return rc; } int fsl_db_exec_multiv( fsl_db * db, const char * sql, va_list args){ if(!db || !db->dbh || !sql) return FSL_RC_MISUSE; else{ fsl_buffer buf = fsl_buffer_empty; int rc = 0; char const * z; char const * zEnd = NULL; rc = fsl_buffer_appendfv( &buf, sql, args ); if(rc) return rc; z = fsl_buffer_cstr(&buf); while( (SQLITE_OK==rc) && *z ){ fsl_stmt_t * pStmt = NULL; rc = sqlite3_prepare_v2(db->dbh, z, buf.used, &pStmt, &zEnd); if( SQLITE_OK != rc ) break; if(pStmt){ while( SQLITE_ROW == sqlite3_step(pStmt) ){} rc = sqlite3_finalize(pStmt); if(rc) rc = fsl_err_from_db(db, rc); } buf.used -= (zEnd-z); z = zEnd; } fsl_buffer_reserve(&buf, 0); return rc; } } int fsl_db_exec_multi( fsl_db * db, const char * sql, ...){ if(!db || !db->dbh || !sql) return FSL_RC_MISUSE; else{ int rc; va_list args; va_start(args,sql); rc = fsl_db_exec_multiv( db, sql, args ); va_end(args); return rc; } } int fsl_db_execv( fsl_db * db, const char * sql, va_list args){ if(!db || !db->dbh || !sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; /* rc = fsl_stmt_step( &st ); */ while(FSL_RC_STEP_ROW == (rc=fsl_stmt_step(&st))){} fsl_stmt_finalize(&st); return (FSL_RC_STEP_ERROR==rc) ? FSL_RC_DB : 0; } } int fsl_db_exec( fsl_db * db, const char * sql, ...){ if(!db || !db->dbh || !sql) return FSL_RC_MISUSE; else{ int rc; va_list args; va_start(args,sql); rc = fsl_db_execv( db, sql, args ); va_end(args); return rc; } } /** ** Sets db->priorChanges to sqlite3_total_changes(db->dbh). */ static void fsl_db_reset_change_count(fsl_db * db){ db->priorChanges = sqlite3_total_changes(db->dbh); } int fsl_db_transaction_begin(fsl_db * db){ if(!db || !db->dbh) return FSL_RC_MISUSE; else { int rc = (0==db->beginCount) ? fsl_db_exec(db,"BEGIN TRANSACTION") : 0; if(!rc){ if(1 == ++db->beginCount){ fsl_db_reset_change_count(db); } } return rc; } } int fsl_db_transaction_commit(fsl_db * db){ return (db && db->dbh) ? fsl_db_transaction_end(db, 0) : FSL_RC_MISUSE; } int fsl_db_transaction_rollback(fsl_db * db){ return (db && db->dbh) ? fsl_db_transaction_end(db, 1) : FSL_RC_MISUSE; } int fsl_db_rollback_force( fsl_db * db ){ if(!db || !db->dbh) return FSL_RC_MISUSE; else{ int rc; db->beginCount = 0; fsl_db_cleanup_beforeCommit(db); rc = fsl_db_exec(db, "ROLLBACK"); fsl_db_reset_change_count(db); return rc; } } int fsl_db_transaction_end(fsl_db * db, char doRollback){ int rc = 0; if(!db || !db->dbh) return FSL_RC_MISUSE; else if (db->beginCount<=0) return FSL_RC_RANGE; if(doRollback) db->doRollback = 1 /* ACHTUNG: note that db->dbRollback is set before continuing so that if we return due to a non-0 beginCount that the rollback flag propagates through the transaction's stack. */ ; if(--db->beginCount > 0) return 0; if((0==db->doRollback) && (db->priorChanges < sqlite3_total_changes(db->dbh))){ /* Execute before-commit hooks and leaf checks */ fsl_size_t x = 0; for( ; !rc && (x < db->beforeCommit.used); ++x ){ char const * sql = (char const *)db->beforeCommit.list[x]; /* MARKER(("Running before-commit code: [%s]\n", sql)); */ if(sql) rc = fsl_db_exec_multi( db, "%s", sql ); } if(!rc && db->f && (FSL_DB_ROLE_REPO & db->role)){ /* i don't like this one bit - this is low-level SCM functionality in an otherwise generic routine. Maybe we need fsl_cx_transaction_begin/end() instead. */ rc = fsl_repo_leaf_do_pending_checks(db->f); if(!rc && db->f->cache.toVerify.used){ rc = fsl_repo_verify_at_commit(db->f); }else{ fsl_repo_verify_cancel(db->f); } } db->doRollback = rc ? 1 : 0; } fsl_db_cleanup_beforeCommit(db); fsl_db_reset_change_count(db); rc = fsl_db_exec(db, db->doRollback ? "ROLLBACK" : "COMMIT"); db->doRollback = 0; return rc; #if 0 /* original impl, for reference purposes during testing */ if( g.db==0 ) return; if( db.nBegin<=0 ) return; if( rollbackFlag ) db.doRollback = 1; db.nBegin--; if( db.nBegin==0 ){ int i; if( db.doRollback==0 && db.nPriorChangesdbh || !rv || !sql || !*sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; rc = fsl_stmt_step( &st ); switch(rc){ case FSL_RC_STEP_ROW: *rv = sqlite3_column_int(st.stmt, 0); /* Fall through */ case FSL_RC_STEP_DONE: rc = 0; break; default: assert(FSL_RC_STEP_ERROR==rc); break; } fsl_stmt_finalize(&st); return rc; } } int fsl_db_get_int32( fsl_db * db, fsl_int32_t * rv, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_get_int32v(db, rv, sql, args); va_end(args); return rc; } int fsl_db_get_int64v( fsl_db * db, fsl_int64_t * rv, char const * sql, va_list args){ if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; rc = fsl_stmt_step( &st ); switch(rc){ case FSL_RC_STEP_ROW: *rv = sqlite3_column_int64(st.stmt, 0); /* Fall through */ case FSL_RC_STEP_DONE: rc = 0; break; default: assert(FSL_RC_STEP_ERROR==rc); break; } fsl_stmt_finalize(&st); return rc; } } int fsl_db_get_int64( fsl_db * db, fsl_int64_t * rv, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_get_int64v(db, rv, sql, args); va_end(args); return rc; } int fsl_db_get_idv( fsl_db * db, fsl_id_t * rv, char const * sql, va_list args){ if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; rc = fsl_stmt_step( &st ); switch(rc){ case FSL_RC_STEP_ROW: *rv = (fsl_id_t)sqlite3_column_int64(st.stmt, 0); /* Fall through */ case FSL_RC_STEP_DONE: rc = 0; break; default: assert(FSL_RC_STEP_ERROR==rc); break; } fsl_stmt_finalize(&st); return rc; } } int fsl_db_get_id( fsl_db * db, fsl_id_t * rv, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_get_idv(db, rv, sql, args); va_end(args); return rc; } int fsl_db_get_sizev( fsl_db * db, fsl_size_t * rv, char const * sql, va_list args){ if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; rc = fsl_stmt_step( &st ); switch(rc){ case FSL_RC_STEP_ROW:{ sqlite3_int64 const i = sqlite3_column_int64(st.stmt, 0); if(i<0){ rc = FSL_RC_RANGE; break; } *rv = (fsl_size_t)i; rc = 0; break; } case FSL_RC_STEP_DONE: rc = 0; break; default: assert(FSL_RC_STEP_ERROR==rc); break; } fsl_stmt_finalize(&st); return rc; } } int fsl_db_get_size( fsl_db * db, fsl_size_t * rv, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_get_sizev(db, rv, sql, args); va_end(args); return rc; } int fsl_db_get_doublev( fsl_db * db, fsl_double_t * rv, char const * sql, va_list args){ if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; rc = fsl_stmt_step( &st ); switch(rc){ case FSL_RC_STEP_ROW: *rv = sqlite3_column_double(st.stmt, 0); /* Fall through */ case FSL_RC_STEP_DONE: rc = 0; break; default: assert(FSL_RC_STEP_ERROR==rc); break; } fsl_stmt_finalize(&st); return rc; } } int fsl_db_get_double( fsl_db * db, fsl_double_t * rv, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_get_doublev(db, rv, sql, args); va_end(args); return rc; } int fsl_db_get_textv( fsl_db * db, char ** rv, fsl_size_t *rvLen, char const * sql, va_list args){ if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; rc = fsl_stmt_step( &st ); switch(rc){ case FSL_RC_STEP_ROW:{ char const * str = (char const *)sqlite3_column_text(st.stmt, 0); int const len = sqlite3_column_bytes(st.stmt,0); char * x = fsl_strndup(str, len); if(!x) return FSL_RC_OOM; *rv = x; if(rvLen) *rvLen = (fsl_size_t)len; rc = 0; break; } case FSL_RC_STEP_DONE: rc = 0; break; default: assert(FSL_RC_STEP_ERROR==rc); break; } fsl_stmt_finalize(&st); return rc; } } int fsl_db_get_text( fsl_db * db, char ** rv, fsl_size_t * rvLen, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_get_textv(db, rv, rvLen, sql, args); va_end(args); return rc; } int fsl_db_get_blobv( fsl_db * db, void ** rv, fsl_size_t *rvLen, char const * sql, va_list args){ if(!db || !db->dbh || !rv || !sql || !*sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; rc = fsl_stmt_step( &st ); switch(rc){ case FSL_RC_STEP_ROW:{ char const * str = (char const *)sqlite3_column_blob(st.stmt, 0); int const len = sqlite3_column_bytes(st.stmt,0); char * x = fsl_mprintf("%.*s", len, str); if(!x) return FSL_RC_OOM; *rv = x; if(rvLen) *rvLen = (fsl_size_t)len; rc = 0; break; } case FSL_RC_STEP_DONE: rc = 0; break; default: assert(FSL_RC_STEP_ERROR==rc); break; } fsl_stmt_finalize(&st); return rc; } } int fsl_db_get_blob( fsl_db * db, void ** rv, fsl_size_t * rvLen, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_get_blobv(db, rv, rvLen, sql, args); va_end(args); return rc; } int fsl_db_get_bufferv( fsl_db * db, fsl_buffer * b, char asBlob, char const * sql, va_list args){ if(!db || !db->dbh || !b || !sql || !*sql) return FSL_RC_MISUSE; else{ fsl_stmt st = fsl_stmt_empty; int rc = 0; rc = fsl_db_preparev( db, &st, sql, args ); if(rc) return rc; rc = fsl_stmt_step( &st ); switch(rc){ case FSL_RC_STEP_ROW:{ void const * str = asBlob ? sqlite3_column_blob(st.stmt, 0) : (void const *)sqlite3_column_text(st.stmt, 0); int const len = sqlite3_column_bytes(st.stmt,0); rc = 0; b->used = 0; rc = fsl_buffer_append( b, str, len ); break; } case FSL_RC_STEP_DONE: rc = 0; break; default: assert(FSL_RC_STEP_ERROR==rc); break; } fsl_stmt_finalize(&st); return rc; } } int fsl_db_get_buffer( fsl_db * db, fsl_buffer * b, char asBlob, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_get_bufferv(db, b, asBlob, sql, args); va_end(args); return rc; } fsl_int32_t fsl_db_g_int32( fsl_db * db, fsl_int32_t dflt, char const * sql, ... ){ fsl_int32_t rv = dflt; va_list args; va_start(args,sql); fsl_db_get_int32v(db, &rv, sql, args); va_end(args); return rv; } fsl_int64_t fsl_db_g_int64( fsl_db * db, fsl_int64_t dflt, char const * sql, ... ){ fsl_int64_t rv = dflt; va_list args; va_start(args,sql); fsl_db_get_int64v(db, &rv, sql, args); va_end(args); return rv; } fsl_id_t fsl_db_g_id( fsl_db * db, fsl_id_t dflt, char const * sql, ... ){ fsl_id_t rv = dflt; va_list args; va_start(args,sql); fsl_db_get_idv(db, &rv, sql, args); va_end(args); return rv; } fsl_size_t fsl_db_g_size( fsl_db * db, fsl_size_t dflt, char const * sql, ... ){ fsl_size_t rv = dflt; va_list args; va_start(args,sql); fsl_db_get_sizev(db, &rv, sql, args); va_end(args); return rv; } fsl_double_t fsl_db_g_double( fsl_db * db, fsl_double_t dflt, char const * sql, ... ){ fsl_double_t rv = dflt; va_list args; va_start(args,sql); fsl_db_get_doublev(db, &rv, sql, args); va_end(args); return rv; } char * fsl_db_g_text( fsl_db * db, fsl_size_t * len, char const * sql, ... ){ char * rv = NULL; va_list args; va_start(args,sql); fsl_db_get_textv(db, &rv, len, sql, args); va_end(args); return rv; } void * fsl_db_g_blob( fsl_db * db, fsl_size_t * len, char const * sql, ... ){ void * rv = NULL; va_list args; va_start(args,sql); fsl_db_get_blob(db, &rv, len, sql, args); va_end(args); return rv; } fsl_double_t fsl_db_julian_now(fsl_db * db){ fsl_double_t rc = -1.0; if(db && db->dbh){ fsl_db_get_double( db, &rc, "SELECT julianday('now')"); } return rc; } fsl_double_t fsl_db_string_to_julian(fsl_db * db, char const * str){ fsl_double_t rc = -1.0; if(db && db->dbh){ fsl_db_get_double( db, &rc, "SELECT julianday(%Q)",str); } return rc; } char fsl_db_existsv(fsl_db * db, char const * sql, va_list args ){ if(!db || !db->dbh || !sql) return FSL_RC_MISUSE; else if(!*sql) return FSL_RC_RANGE; else{ fsl_stmt st = fsl_stmt_empty; char rv = 0; if(!fsl_db_preparev(db, &st, sql, args)){ rv = FSL_RC_STEP_ROW==fsl_stmt_step(&st); } fsl_stmt_finalize(&st); return rv; } } char fsl_db_exists(fsl_db * db, char const * sql, ... ){ char rc; va_list args; va_start(args,sql); rc = fsl_db_existsv(db, sql, args); va_end(args); return rc; } /* ** Returns non-0 if the database (which must be open) table identified ** by zTableName has a column named zColName (case-sensitive), else ** returns 0. */ char fsl_db_table_has_column( fsl_db * db, char const *zTableName, char const *zColName ){ fsl_stmt q = fsl_stmt_empty; int rc = 0; char rv = 0; if(!db || !zTableName || !*zTableName || !zColName || !*zColName) return 0; rc = fsl_db_prepare(db, &q, "PRAGMA table_info(%Q)", zTableName ); if(!rc) while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){ /* Columns: (cid, name, type, notnull, dflt_value, pk) */ fsl_size_t colLen = 0; char const * zCol = fsl_stmt_g_text(&q, 1, &colLen); if(0==fsl_strncmp(zColName, zCol, colLen)){ rv = 1; break; } } fsl_stmt_finalize(&q); return rv; } char * fsl_db_random_hex(fsl_db * db, fsl_size_t n){ if(!db || !n) return NULL; else{ fsl_size_t rvLen = 0; char * rv = fsl_db_g_text(db, &rvLen, "SELECT lower(hex(" "randomblob(%"FSL_SIZE_T_PFMT")))", n/2+1); if(rv){ assert(rvLen>=n); rv[n]=0; } return rv; } } int fsl_db_select_slistv( fsl_db * db, fsl_list * tgt, char const * fmt, va_list args ){ if(!db || !tgt || !fmt) return FSL_RC_MISUSE; else if(!*fmt) return FSL_RC_RANGE; else{ int rc; fsl_stmt st = fsl_stmt_empty; fsl_size_t nlen; char const * n; char * cp; rc = fsl_db_preparev(db, &st, fmt, args); while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(&st)) ){ nlen = 0; n = fsl_stmt_g_text(&st, 0, &nlen); cp = fsl_strndup(n, (fsl_int_t)nlen); if(n && !cp) rc = FSL_RC_OOM; else{ rc = fsl_list_append(tgt, cp); if(rc) fsl_free(cp); } } fsl_stmt_finalize(&st); return rc; } } int fsl_db_select_slist( fsl_db * db, fsl_list * tgt, char const * fmt, ... ){ int rc; va_list va; va_start (va,fmt); rc = fsl_db_select_slistv(db, tgt, fmt, va); va_end(va); return rc; } void fsl_db_sqltrace_enable( fsl_db * db, FILE * outStream ){ if(db && db->dbh){ sqlite3_trace(db->dbh, fsl_db_sql_trace, outStream); } } #undef MARKER