/* -*- 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 #include /* NULL on linux */ #include /* time() */ #if defined(_WIN32) # include #else # include /* 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 && indexcolCount)) ? 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'%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