Login
fsl_db.c at [2c40e2033b]
Login

File fsl_db.c artifact 02f146aa56 part of check-in 2c40e2033b


/* -*- 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 houses the db-related v2 APIs.
*/
#include "fossil/fossil.h"
#include "fsl_internal.h"
#include <assert.h>
#include <stddef.h> /* NULL on linux */
#include <time.h> /* time() */

#if defined(_WIN32)
# include <windows.h>
#else
# include <unistd.h> /* F_OK */
#endif

/* Only for debugging */
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)

static int fsl_cx_uplift_db_error( fsl_cx * f, fsl_db * db ){
  assert(f);
  if(!db) db = &f->dbMain;
  fsl_error_move( &db->error, &f->error );
  return f->error.code;
}


/*
** Resets db->error state based on the given code and the current
** error string from the driver.  Returns FSL_RC_DB on success, some
** other non-0 value on error (most likely FSL_RC_OOM while allocating
** the error string).
*/
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_db_filename(fsl_db const * db, fsl_size_t * len){
  if(!db || !db->dbh) return NULL;
  else{
    if(len) *len = db->filename.used;
    return (char const *)db->filename.mem;
  }
}

int fsl_db_close( fsl_db * db ){
  if(!db) return FSL_RC_MISUSE;
  else{
    void const * allocStamp = db->allocStamp;
    fsl_cx * f = db->f;
    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_error_clean(&db->error);
    fsl_buffer_reserve(&db->filename, 0);
    *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;
  }
}

/**
 ** Returns one of f->file{Config,Repo,Ckout}, or f->dbMain.filename,
 ** or NULL.
 */
static fsl_buffer * fsl_cx_file_for_db_role(fsl_cx * f, fsl_db_role_t r){
  fsl_buffer * nameBuf = NULL;
  switch(r){
    case FSL_DB_ROLE_CONFIG:
      nameBuf = &f->fileConfig;
      break;
    case FSL_DB_ROLE_REPO:
      nameBuf = &f->fileRepo;
      break;
    case FSL_DB_ROLE_CHECKOUT:
      nameBuf = &f->fileCkout;
      break;
    case FSL_DB_ROLE_MAIN:
      nameBuf = &f->dbMain.filename;
    case FSL_DB_ROLE_NONE:
    default:
      break;
  }
  return nameBuf;
}

static int fsl_db_attach_role(fsl_cx * f, const char *zDbName, fsl_db_role_t r){
  char const * label = fsl_db_role_label(r);
  /* fsl_db * db = fsl_cx_db_for_role(f, r); */
  fsl_buffer * nameBuf = fsl_cx_file_for_db_role(f, r);
  int rc;
  if(!nameBuf) return FSL_RC_RANGE;
  /* assert(db); */
  assert(label);
  switch(r){
    case FSL_DB_ROLE_CONFIG:
    case FSL_DB_ROLE_REPO:
    case FSL_DB_ROLE_CHECKOUT:
      break;
    case FSL_DB_ROLE_MAIN:
    case FSL_DB_ROLE_NONE:
    default:
      assert(!"cannot happen/not legal");
      return FSL_RC_RANGE;
  }
  rc = fsl_db_attach(&f->dbMain, zDbName, label);
  if(rc){
    fsl_cx_uplift_db_error(f, NULL);
  }else{
    f->dbMain.role |= r;
    nameBuf->used = 0;
    fsl_buffer_append(nameBuf, zDbName, -1);
  }
  return rc;
}


static int fsl_db_detach_role(fsl_cx * f, fsl_db_role_t r){
  if(!f) return FSL_RC_MISUSE;
  else{
    fsl_buffer * fn = fsl_cx_file_for_db_role(f,r);
    if(!fn) return FSL_RC_RANGE;
    fsl_buffer_reserve(fn, 0);
    f->dbMain.role &= ~r;
    return fsl_db_detach( &f->dbMain, fsl_db_role_label(r) );
  }
}

int fsl_cx_repo_close( fsl_cx * f ){
  return fsl_db_detach_role(f, FSL_DB_ROLE_REPO);
}

int fsl_cx_config_close( fsl_cx * f ){
  return fsl_db_detach_role(f, FSL_DB_ROLE_CONFIG);
}

int fsl_cx_checkout_close( fsl_cx * f, char alsoCloseRepo ){
  if(!f) return FSL_RC_MISUSE;
  else if(!f->fileCkout.used) return FSL_RC_NOT_A_CHECKOUT;
  else{
    int rc;
    if(!alsoCloseRepo) fsl_cx_repo_close(f)
      /* Ignore error - it might not be opened. */
      ;
    fsl_buffer_reserve(&f->fileCkout, 0);
    rc = fsl_db_detach_role(f, FSL_DB_ROLE_CHECKOUT);
    return rc;
  }
}

/*
** This file contains the fsl_db_xxx() and fsl_stmt_xxx()
** parts of the API.
*/
int fsl_stmt_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_RC_ALREADY_EXISTS;
  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_reserve(&buf, 0);
    }
    return rc;
  }
}

int fsl_stmt_prepare( fsl_db *db, fsl_stmt * tgt, char const * sql, ... ){
  int rc;
  va_list args;
  va_start(args,sql);
  rc = fsl_stmt_preparev( db, tgt, sql, args );
  va_end(args);
  return rc;
}

int fsl_cx_prepare( fsl_cx *f, fsl_stmt * tgt, char const * sql,
                      ... ){
  if(!f) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,sql);
    rc = fsl_stmt_preparev( &f->dbMain, tgt, sql, args );
    va_end(args);
    return rc;
  }
}

int fsl_cx_preparev( fsl_cx *f, fsl_stmt * tgt, char const * sql,
                       va_list args ){
  return (f && tgt)
    ? fsl_stmt_preparev(&f->dbMain, tgt, sql, args)
    : FSL_RC_MISUSE;
}


int fsl_stmt_finalize( fsl_stmt * stmt ){
  if(!stmt) return FSL_RC_MISUSE;
  else if(!stmt->stmt){
    assert(!stmt->sql.mem);
    return FSL_RC_MISUSE;
  }
  else{
    void const * allocStamp = stmt->allocStamp;
    assert(stmt->stmt);
    assert(stmt->db);
    fsl_buffer_reserve(&stmt->sql, 0);
    --stmt->db->openStatementCount;
    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:
        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 || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc;
    rc = fsl_stmt_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_reset( fsl_stmt * stmt ){
  if(!stmt || !stmt->stmt) return FSL_RC_MISUSE;
  else{
    int const rc = sqlite3_reset(stmt->stmt);
    assert(stmt->db);
    return rc
      ? fsl_err_from_db(stmt->db, rc)
      : 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;
}

#define BIND_PARAM_CHECK \
  if(!stmt || !stmt->stmt || !stmt->db) return FSL_RC_MISUSE; else
int fsl_stmt_bind_null( fsl_stmt * stmt, int ndx ){
  BIND_PARAM_CHECK {
    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_CHECK {
    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_CHECK {
    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_CHECK {
    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_CHECK {
    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;
  }
}

#undef BIND_PARAM_CHECK

#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_text( fsl_stmt * stmt, int ndx, char const **out,
                       fsl_int_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) *outLen = t ? sqlite3_column_bytes(stmt->stmt, ndx) : 0;
    return 0;
  }
}

int fsl_stmt_get_blob( fsl_stmt * stmt, int ndx, void const **out,
                       fsl_int_t * outLen ){
  GET_CHECK {
    void const * t = (out || outLen)
      ? sqlite3_column_blob(stmt->stmt, ndx)
      : NULL;
    if(out) *out = (char const *)t;
    if(outLen) *outLen = t ? sqlite3_column_bytes(stmt->stmt, ndx) : 0;
    return 0;
  }
}

#undef GET_CHECK

static void fsl_db_sql_trace(void *fslCtx, 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_outputf((fsl_cx*)fslCtx,
              "SQL TRACE #%d: %s%s\n", ++counter,
              zSql, (n>0 && zSql[n-1]==';') ? "" : ";");
}

/*
** SQL functions for debugging.
**
** The print() function writes its arguments to fsl_output(), but only
** if the -sqlprint command-line option is turned on.
*/
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);
    }
  }
}

/*
** 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 0
  /* TODO, once we can parse manifests. */
  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
}

/*
** Returns non-0 (true) if the given SQL would result
** in at least one result row, else 0 (false).
*/
char fsl_db_val_exists( fsl_db * db, char const * sql, ...){
  fsl_stmt st = fsl_stmt_empty;
  int rc;
  va_list args;
  if(!db || !sql) return 0;
  va_start(args,sql);
  rc = fsl_stmt_preparev( db, &st, sql, args );
  va_end(args);
  if(rc) return 0;
  else{
    rc = (FSL_RC_STEP_ROW == fsl_stmt_step(&st)) ? 1 : 0;
    fsl_stmt_finalize( &st );
    return rc;
  }
}

/*
** Return TRUE if the schema is out-of-date
*/
int fsl_db_schema_is_outofdate(fsl_db *db){
  return fsl_db_val_exists(db, "SELECT 1 FROM config "
                           "WHERE name='aux-schema' "
                           "AND value<>'%s'",
                           FSL_AUX_SCHEMA);
}


/*
** Returns - if db appears to be a current repository,
** 1 if it appears to have an out of date schema, and
** -1 if it appears to not be a repository.
*/
int fsl_cx_repo_verify_schema(fsl_db * db){
  if(fsl_db_schema_is_outofdate(db)) return 1;
  else return fsl_db_val_exists(db,
                                "SELECT 1 FROM config "
                                "WHERE name='project-code'")
    ? 0 : -1;
}

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;
}


/*
** Analog to v1's db_open_or_attach().
**
** This function is still very much up for reconsideration. i'm not
** terribly happy with the "db roles" here - i'd prefer to have
** them each in their own struct, but understand that the roles may
** play a part in query generation, so i haven't yet ruled
** them out.
**
**
** Opens or attaches the given db file. zDbName is the
** file name of the DB. role is the role of that db
** in the framework (that determines its db name in SQL).
**
** The first time this is called, f->dbMain is set to point
** to fsl_cx_db_for_role(f, role).
**
** If f->dbMain is already set then the db is attached using a
** role-dependent name.
**
** If f->dbMain is not opened then it is opened here and its
** role is set to the given role.
**
** the given db zDbName is the name of a database file.  If f->dbMain is not opened
** then that db is assigned to the given db file, otherwise
** attach attach zDbName using the name zLabel.
**
** If pWasAttached is not NULL then it is set to a true value if the
** db gets attached, else a false value. It is only modified on success.
**
** It is an error to open the same db role more than once, and trying
** to do so results in a FSL_RC_ACCESS error (f's error state is
** updated).
*/
#if 0
static
int fsl_cx_db_main_open_or_attach( fsl_cx * f,
                                   const char *zDbName,
                                   /* const char *zLabel, */
                                   fsl_db_role_t role,
                                   int *pWasAttached){
  int rc;
  int wasAttached = 0;
  fsl_db * db;
  assert(f);
  assert(zDbName && *zDbName);
  assert((FSL_DB_ROLE_CONFIG==role)
         || (FSL_DB_ROLE_CHECKOUT==role)
         || (FSL_DB_ROLE_REPO==role));
  if(!f || !zDbName) return FSL_RC_MISUSE;
  else if((FSL_DB_ROLE_NONE==role) || !*zDbName) return FSL_RC_RANGE;
  db = fsl_cx_db_for_role(f, role);
  assert(db);
  try_again:
  if(!f->dbMain) {
    assert( FSL_DB_ROLE_NONE==db->role );
    assert( NULL==db->dbh );
    f->dbMain = db;
    db->f = f;
    db->role = role;
    rc = fsl_db_open( db, zDbName, FSL_OPEN_F_RW );
    /* g.db = fsl_db_open(zDbName); */
    /* g.zMainDbType = zLabel; */
    /* f->mainDbType = zLabel; */
    /* f->dbMain.role = FSL_DB_ROLE_MAIN; */
  }else{
    assert( FSL_DB_ROLE_NONE!=db->role );
    assert( f == db->f );
    if(NULL == f->dbMain->dbh){
      /* It was closed in the meantime. Re-assign dbMain... */
      MARKER(("Untested: re-assigning f->dbMain after it has been closed.\n"));
      f->dbMain = NULL;
      goto try_again;
    }
    if(role == db->role){
      return fsl_cx_err_set(f, FSL_RC_ACCESS,
                            "Cannot open/attach db role "
                            "#%d (%s) more than once.",
                            role, fsl_db_role_label(role));
    }
    rc = fsl_db_attach_role(f, zDbName,
                            fsl_db_role_label(role) );
    wasAttached = 1;
  }
  if(!rc){
    if(pWasAttached) *pWasAttached = wasAttached;
  }else if(db->error.code){
    /* Uplift db's error state into f so we don't lose it
       when we close the db here...
    */
    fsl_error_move(&db->error, &f->error);
    fsl_db_close(db);
    assert(!f->dbMain->dbh);
    f->dbMain = NULL;
  }
  return rc;
}
#endif

const char *fsl_cx_main_db_name(fsl_cx const * f){
  return (f && f->dbMain.dbh)
    ? fsl_db_role_label(f->dbMain.role)
    : NULL;
}

/*
* If zDbName is a valid local database file, open it and return
** 0.  If it is not a valid local database file, return a non-0
** code.
*/
static int fsl_cx_is_valid_checkout_db(fsl_cx * f, const char *zDbName){
  fsl_int64_t lsize;
  /* char *zVFileDef; */
  int rc;
  lsize = fsl_file_size(zDbName);
  if( lsize < 0  ){
    return FSL_RC_NOT_FOUND /* FSL_RC_ACCESS? */;
  }
  if( lsize%1024!=0 || lsize<4096 ) return FSL_RC_RANGE;
#if 1
  rc = fsl_db_attach_role(f, zDbName, FSL_DB_ROLE_CHECKOUT);
#else
  rc = fsl_cx_db_main_open_or_attach(f, zDbName,
                                     FSL_DB_ROLE_CHECKOUT,
                                     NULL);
#endif
  if(rc){
    fsl_cx_uplift_db_error(f, NULL);
  }
  return rc;
#if 0
  /* Historical bits: no longer needed(?). */

  zVFileDef = fsl_db_text(0, "SELECT sql FROM %s.sqlite_master"
                          " WHERE name=='vfile'",
                          fsl_cx_db_name("localdb")); /* ==> "ckout" */
  if( zVFileDef==0 ) return 0;

  /* If the "isexe" column is missing from the vfile table, then
  ** add it now.   This code added on 2010-03-06.  After all users have
  ** upgraded, this code can be safely deleted.
  */
  if( !fsl_str_glob("* isexe *", zVFileDef) ){
    fsl_db_multi_exec("ALTER TABLE vfile ADD COLUMN isexe BOOLEAN DEFAULT 0");
  }

  /* If "islink"/"isLink" columns are missing from tables, then
  ** add them now.   This code added on 2011-01-17 and 2011-08-27.
  ** After all users have upgraded, this code can be safely deleted.
  */
  if( !fsl_str_glob("* islink *", zVFileDef) ){
    db_multi_exec("ALTER TABLE vfile ADD COLUMN islink BOOLEAN DEFAULT 0");
    if( db_local_table_exists_but_lacks_column("stashfile", "isLink") ){
      db_multi_exec("ALTER TABLE stashfile ADD COLUMN isLink BOOL DEFAULT 0");
    }
    if( db_local_table_exists_but_lacks_column("undo", "isLink") ){
      db_multi_exec("ALTER TABLE undo ADD COLUMN isLink BOOLEAN DEFAULT 0");
    }
    if( db_local_table_exists_but_lacks_column("undo_vfile", "islink") ){
      db_multi_exec("ALTER TABLE undo_vfile ADD COLUMN islink BOOL DEFAULT 0");
    }
  }
#endif
  return 0;
}

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_NOT_FOUND_OK & openFlags)
          && !(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(FSL_OPEN_F_RW & openFlags){
      sOpenFlags |= SQLITE_OPEN_READWRITE;
    }
    if(FSL_OPEN_F_CREATE & openFlags){
      sOpenFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
    }
    if(!sOpenFlags) sOpenFlags = SQLITE_OPEN_READONLY;
    if(0==fsl_strcmp(dbFile,":memory:")){
      sOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
    }
    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_cx_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){
      fsl_cx * f = db->f;
      if( f->config.traceSql ){
        sqlite3_trace(dbh, fsl_db_sql_trace, f);
      }
#if 0
      if(FSL_DB_ROLE_NONE!=db->role){
        rc = fsl_buffer_append(&db->name, fsl_db_role_label(db->role), -1);
        if(rc) goto end;
      }
#endif
      /* 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
      /* fsl_db_close(db); */
    }

  }
#if 1
  {
    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
  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_stmt_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;
  }
}


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_stmt_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_stmt_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_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_stmt_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_stmt_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_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_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_stmt_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_stmt_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_db * fsl_cx_db_config( fsl_cx * f ){
  return (f && f->dbMain.dbh && f->fileConfig.used) ? &f->dbMain : NULL;
}
fsl_db * fsl_cx_db_repo( fsl_cx * f ){
  return (f && f->dbMain.dbh && f->fileRepo.used) ? &f->dbMain : NULL;
}
fsl_db * fsl_cx_db_checkout( fsl_cx * f ){
  return (f && f->dbMain.dbh && f->fileCkout.used) ? &f->dbMain : NULL;
}

fsl_db * fsl_cx_db( fsl_cx * f ){
  return f ? &f->dbMain : NULL;
}


int fsl_cx_config_open( fsl_cx * f, char const * openDbName ){
  int rc = 0;
  char const *zDbName = NULL;
  /* char useAttach = 0 */ /* TODO? Move this into a parameter? Do we need the v1 behaviour? */;
  fsl_buffer dbPath = fsl_buffer_empty;
  if(!f) return FSL_RC_MISUSE;
  else if(f->fileConfig.used){
    fsl_db_detach_role(f, FSL_DB_ROLE_CONFIG);
  }

  if(openDbName && *openDbName){
    zDbName = openDbName;
  }else{
    rc = fsl_find_home_dir( &dbPath, 1 );
    if(!rc){
      char const * dbName =
#if defined(_WIN32) || defined(__CYGWIN__)
        /* . filenames give some window systems problems and many apps problems */
        "_fossil"
#else
        ".fossil"
#endif
        ;
      rc = fsl_buffer_appendf(&dbPath, "/%s", dbName);
      if(rc){
        rc = fsl_cx_err_set(f, rc, "%.*s", (int)dbPath.used, fsl_buffer_cstr(&dbPath));
        fsl_buffer_reserve(&dbPath, 0);
        return rc;
      }
    }
    assert(dbPath.used);
    zDbName = fsl_buffer_cstr(&dbPath);
  }

#if 0
  if( fsl_file_size(zDbName)<1024*3 ){
    /* TODO: port in db init code. Consider doing the
       init AFTER opening it, so that we can pipe the
       input into fsl_db_multi_exec(). The v1 approach
       opens the config db twice for this case.

       We need to move away from v1's habit of holding the
       schema as a large C-string because C does not guaranty
       that unbounded sizes are legal (and fossil regularly
       exceeds that limit).

       http://stackoverflow.com/questions/11488616/why-is-max-length-of-c-string-literal-different-from-max-char
       
       http://msdn.microsoft.com/en-us/library/sx08afx2.aspx

       Says:
       
       "ANSI compatibility requires a compiler to accept up to 509
       characters in a string literal after concatenation."

       Supposedly (see first link) C99 increases that minimum to 4k.

       Instead, we can store the schema in an SQL file and compile it
       in as a static const char[].

       Or... probably simpler... we pack the SQL into

       static const char[][] = { "...", "...", "..." }

       and hope we don't hit the limit for individual chunks, e.g.
       CREATE TABLE parts?
    */
    db_init_database(zDbName, zConfigSchema, (char*)0);
  }
#endif

  
#if defined(_WIN32) || defined(__CYGWIN__)
  if( fsl_file_access(zDbName, W_OK) ){
    rc = fsl_cx_err_set(f, FSL_RC_ACCESS,
                        "Configuration database [%s] "
                        "must be writeable.", zDbName);
    fsl_buffer_reserve(&dbPath,0);
    return rc;
  }
#endif


#if 1
  rc = fsl_db_attach_role(f, zDbName, FSL_DB_ROLE_CONFIG);
#else
  if( useAttach ){
    rc = fsl_db_attach_role(f, zDbName, FSL_DB_ROLE_CONFIG);
  }else{
    rc = fsl_db_open(&f->dbConfig, zDbName, FSL_OPEN_F_RWC );
  }
#endif
  fsl_buffer_reserve(&dbPath,0);
  /* g.zConfigDbName = zDbName; */
  return rc;
}

int fsl_cx_repo_open_db( fsl_cx * f, char const * repoDbFile,
                      int openFlags ){
  if(!f || !repoDbFile || !*repoDbFile) return FSL_RC_MISUSE;
  else if(f->fileRepo.used){
    return fsl_cx_err_set(f, FSL_RC_ACCESS,
                          "Context already has an opened repository.");
  }
  else {
    int rc;
    /* fsl_db * db = &f->dbRepo; */
    /* db->f = f; */
    if(0!=fsl_file_access( repoDbFile, F_OK )){
      return fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "Repository db [%s] not found.",
                            repoDbFile);
    }
    if(!openFlags){
      openFlags = FSL_OPEN_F_RW | FSL_OPEN_F_SCHEMA_VALIDATE;
    }
    /* fsl_cx_err_set(f, 0, NULL); */
    /* rc = fsl_db_open( db, repoDbFile, openFlags ); */
    rc = fsl_db_attach_role(f, repoDbFile, FSL_DB_ROLE_REPO);
#if 0
    if(rc && !f->error.code && db->error.code){
      /* Adopt db's error state as our own. */
      fsl_error_move(&db->error, &f->error);
    }
#endif
    return rc;
  }
}

/**
 ** Tries to open the repository which from which the current checkout
 ** derives.  Returns 0 on success.
 */
static int fsl_cx_repo_open_for_checkout(fsl_cx * f){
  char * repoDb = NULL;
  int rc;
  fsl_buffer nameBuf = fsl_buffer_empty;
  assert(f);
  assert(f->dirCkout.used);
  rc = fsl_db_get_text(&f->dbMain, &repoDb, NULL,
                       "SELECT %B || '/' || value FROM %s.vvar "
                       "WHERE name='repository'",
                       &f->dirCkout,
                       fsl_db_role_label(FSL_DB_ROLE_CHECKOUT));

  if(rc) fsl_cx_uplift_db_error( f, NULL );
  else if(repoDb && *repoDb){
    fsl_file_canonical_name(repoDb, &nameBuf, 0);
    fsl_free(repoDb);
    repoDb = fsl_buffer_str(&nameBuf);
    assert(repoDb);
    rc = fsl_cx_repo_open_db(f, repoDb, 0);
    fsl_buffer_reserve(&nameBuf, 0);
  }
  return rc;
}

int fsl_cx_checkout_open( fsl_cx * f, char const * dirName,
                            fsl_int_t dirNameLen ){
  int rc;
  fsl_int_t dLen = 0, i;
  static const char aDbName[][10] = { "_FOSSIL_", ".fslckout" };
  fsl_buffer * buf = &f->scratch
    /*FIXME: don't use f->scratch here. Fix returns below to
      account for a local buffer. */
    ;
  enum { DbCount = 2 };
  if(fsl_cx_db_checkout(f)) return FSL_RC_ACCESS;
  buf->used = 0;
  if(dirName && (dirNameLen<0)) dirNameLen = fsl_strlen(dirName);
  if(dirName){
    if((0==dirNameLen) || !*dirName) return FSL_RC_RANGE;
    rc = fsl_buffer_append( buf, dirName, dirNameLen );
    if(rc) return fsl_cx_err_set(f, rc, NULL)
      /* Reminder: fsl_error_set() ignores the error string (does not
         allocate) if code==FSL_RC_OOM.
      */
      ;
    dLen = dirNameLen;
  }else{
    char zPwd[2000];
    fsl_size_t pwdLen = 0;
    rc = fsl_getcwd( zPwd, sizeof(zPwd)/sizeof(zPwd[0]), &pwdLen );
    if(rc){
      return fsl_cx_err_set(f, rc,
                            "Could not determine current directory. "
                            "Error code %d (%s).",
                            rc, fsl_rc_cstr(rc));
    }
    if(1 == dLen && '/'==*zPwd) *zPwd = '.'
      /* When in the root directory (or chroot) then change dir name
         name to something we can use.
      */
      ;
    rc = fsl_buffer_append(buf, zPwd, dLen);
    if(rc) return fsl_cx_err_set(f, rc, NULL);
    dLen = (fsl_int_t)pwdLen;
  }
  if(rc) return fsl_cx_err_set(f, rc, NULL);
  assert(buf->used == dLen);
  assert(buf->capacity>buf->used);
  assert(0==buf->mem[dLen]);
  while(dLen>0){
    /*
      Loop over the list in aDbName, appending each one to
      the dir name in the search for something we can use.
    */
    fsl_int_t lenMarker = dLen /* position to
                                  re-set to on each
                                  sub-iteration. */ ;
    for( i = 0; i < DbCount; ++i ){
      buf->used = (fsl_size_t)lenMarker;
      dLen = lenMarker;
      rc = fsl_buffer_appendf( buf, "/%s", aDbName[i]);
      if(rc) return fsl_cx_err_set(f, rc, NULL);
      rc = fsl_cx_is_valid_checkout_db(f, fsl_buffer_cstr(buf));
      if(rc){
        --dLen;
        while( dLen>0 && buf->mem[dLen]!='/' ){ --dLen; }
        while( dLen>0 && buf->mem[dLen-1]=='/' ){ --dLen; }
        if(dLen>lenMarker){
          buf->mem[dLen] = 0;
        }
      }
      else{
        f->dirCkout.used = 0;
        rc = fsl_buffer_append(&f->dirCkout, buf->mem, (fsl_size_t)lenMarker);
        if(rc) return fsl_cx_err_set(f, rc, NULL);
        /*
          TODO:

          // not needed:
          g.zLocalRoot = mprintf("%s/", zPwd);
          // not needed:
          g.localOpen = 1;

          // needed:
          // db_open_config(0);
          // done (more or less):
          db_open_repository(zDbName);
        */
        /* why?
           rc = fsl_cx_config_open(f, NULL);
           seems to me we don't need that at the library
           level.
        */
        rc = fsl_cx_repo_open_for_checkout(f);
        return rc;
      }
    }
  }
  buf->used = 0;
  return FSL_RC_NOT_FOUND;
}


char const * fsl_cx_db_file_for_role(fsl_cx * f,
                                     fsl_db_role_t r,
                                     fsl_size_t * len){
  char const * rc = NULL;
  fsl_buffer const * b = f ? fsl_cx_file_for_db_role(f, r) : NULL;
  if(b) rc = fsl_buffer_cstr2(b, len);
  return rc;  
}

char const * fsl_cx_config_db_file(fsl_cx const * f,
                                   fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->fileConfig.used){
    rc = fsl_buffer_cstr2(&f->fileConfig, len);
  }
  return rc;
}


char const * fsl_cx_repo_db_file(fsl_cx const * f,
                                 fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->fileRepo.used){
    rc = fsl_buffer_cstr2(&f->fileRepo, len);
  }
  return rc;
}

char const * fsl_cx_checkout_db_file(fsl_cx const * f,
                                     fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->fileCkout.used){
    rc = fsl_buffer_cstr2(&f->fileCkout, len);
  }
  return rc;
}

char const * fsl_cx_checkout_dir_name(fsl_cx const * f,
                                      fsl_size_t * len){
  char const * rc = NULL;
  if(f && f->fileCkout.used){
    rc = fsl_buffer_cstr2(&f->dirCkout, len);
  }
  return rc;
}

#undef MARKER