/* -*- 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
