/* -*- 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 <assert.h>
#include <stddef.h> /* NULL on linux */
#include <time.h> /* 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 && index<stmt->colCount))
? 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; i<argc; i++){
char c = i==argc-1 ? '\n' : ' ';
fsl_outputf(f, "%s%c", sqlite3_value_text(argv[i]), c);
}
}
}
/*
** SQL function to return the number of seconds since 1970. This is
** the same as strftime('%s','now') but is more compact.
*/
static void fsl_db_now_function(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
sqlite3_result_int64(context, (sqlite3_int64)time(0));
}
/*
** Function to return the check-in time for a file.
*/
static void fsl_db_checkin_mtime_function(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
#if 1
sqlite3_result_text(context, "FIXME: missing mtime_of_manifest_file()",
-1, SQLITE_STATIC);
#else
/* TODO: port in v1's descendants.c for mtime_of_manifest_file(). */
fsl_time_t mtime;
int rc = mtime_of_manifest_file(sqlite3_value_int(argv[0]),
sqlite3_value_int(argv[1]), &mtime);
if( rc==0 ){
sqlite3_result_int64(context, (sqlite_int64)mtime);
}
#endif
}
/*
** Implement the user() SQL function. user() takes no arguments and
** returns the user ID of the current user.
*/
static void fsl_db_sql_user(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
#if 0
if( g.zLogin!=0 ){
sqlite3_result_text(context, g.zLogin, -1, SQLITE_STATIC);
}
#else
sqlite3_result_text(context, "FIXME: missing g.zLogin info", -1, SQLITE_STATIC);
#endif
}
fsl_db * fsl_db_malloc(){
fsl_db * rc = (fsl_db *)fsl_malloc(sizeof(fsl_db));
if(rc){
*rc = fsl_db_empty;
rc->allocStamp = &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.nPriorChanges<sqlite3_total_changes(g.db) ){
while( db.nBeforeCommit ){
db.nBeforeCommit--;
sqlite3_exec(g.db, db.azBeforeCommit[db.nBeforeCommit], 0, 0, 0);
sqlite3_free(db.azBeforeCommit[db.nBeforeCommit]);
}
leaf_do_pending_checks();
}
for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
db.doRollback |= db.aHook[i].xHook();
}
while( db.pAllStmt ){
db_finalize(db.pAllStmt);
}
db_multi_exec(db.doRollback ? "ROLLBACK" : "COMMIT");
db.doRollback = 0;
}
#endif
}
int fsl_db_get_int32v( fsl_db * db, fsl_int32_t * rv,
char const * sql, va_list args){
/* Potential fixme: the fsl_db_get_XXX() funcs are 95%
code duplicates. We "could" replace these with a macro
or supermacro, though the latter would be problematic
in the context of an amalgamation build.
*/
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_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