/**
This file holds script bindings for libfossil/th1ish. Build shell.c
with TH1ISH_SHELL_EXTEND and link this file to shell.o, which will
cause shell.c to run this file's init routine
(th1ish_shell_extend()) when the shell is run.
*/
#include <assert.h>
#include <locale.h>
#include "th1ish_amalgamation.h"
#include "fossil-scm/fossil.h"
#include "fossil-scm/fossil-internal.h"
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h> /* F_OK and friends */
#endif
/* Only for debuggering... */
#include <stdio.h>
#define MARKER(pfexp) \
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
printf pfexp; \
} while(0)
static int cb_toss( cwal_callback_args const * args, int code,
char const * fmt, ... ){
int rc;
va_list vargs;
va_start(vargs,fmt);
rc = cwal_exception_setfv( args->engine, code, fmt, vargs );
va_end(vargs);
return rc;
}
static void fsl_finalizer_f_fsl_cx( cwal_engine * e, void * m ){
if(m){
/* MARKER(("Finalizing fsl_cx @%p\n", m)); */
fsl_cx_finalize( (fsl_cx*)m );
}
}
static void fsl_finalizer_f_fsl_db( cwal_engine * e, void * m ){
if(m){
/* MARKER(("Finalizing fsl_db @%p\n", m)); */
fsl_db_close( (fsl_db*)m );
}
}
static void fsl_finalizer_f_fsl_stmt( cwal_engine * e, void * m ){
if(m){
/* MARKER(("Finalizing fsl_stmt @%p\n", m)); */
fsl_stmt_finalize( (fsl_stmt*)m );
}
}
/**
Type IDs for type-safely mapping (void*) to cwal_native
instances. Their types and values are irrelevant - we use only their
addresses.
*/
static const int cwal_type_id_fsl_cx = 0;
static const int cwal_type_id_fsl_db = 0;
static const int cwal_type_id_fsl_stmt = 0;
#define FSL_TYPEID(X) (&cwal_type_id_##X)
/*
** fsl_output_f() impl which forwards to cwal_output().
** state must be a (cwal_engine *).
*/
static int fsl_output_f_cwal_out( void * state,
void const * src,
fsl_size_t n ){
cwal_engine * e = (cwal_engine *)state;
return cwal_output( e, src, (cwal_size_t)n )
? FSL_RC_IO
: 0;
}
/*
** fsl_flush_f() impl which forwards to cwal_output_flush(). state
** must be a (cwal_engine *).
*/
static int fsl_flush_f_cwal_out( void * state ){
cwal_engine * e = (cwal_engine *)state;
return cwal_output_flush(e);
}
static cwal_value * fsl_cx_prototype( th1ish_interp * ie );
static cwal_value * fsl_db_prototype( th1ish_interp * ie );
static cwal_value * fsl_stmt_prototype( th1ish_interp * ie );
/*
** If cx has a property named "db", it is returned,
** else a new Object named "db" is inserted into cx
** and returned. Returns NULL on allocation error.
*/
static cwal_value * fsl_cx_db_prop( fsl_cx * f,
cwal_value * fv,
th1ish_interp * ie,
char createIfNotExists);
/*
** Constructor function for fsl_cx instances.
**
** Script-side usage:
**
** var f = ThisFunc(object{...})
**
**
** The parameter object is optional. The only supported option at the
** moment is the traceSql boolean, which enables or disabled SQL
** tracing output.
**
** On success it returns (via *rv) a new cwal_native which is bound to
** a new fsl_cx instance. The fsl_cx will be initialized such that
** fsl_output() and friends will be redirected through cwal_output(),
** to allow fsl_output()-generated output to take advantage of
** th1ish's output buffering and whatnot.
*/
int cb_fsl_cx_ctor( cwal_callback_args const * args,
cwal_value **rv ){
fsl_cx * f = NULL;
cwal_value * v;
int rc;
fsl_cx_init_opt init = fsl_cx_init_opt_empty;
th1ish_interp * ie = th1ish_args_interp(args);
assert(ie);
init.output.out = fsl_output_f_cwal_out;
init.output.flush = fsl_flush_f_cwal_out;
init.output.state.state = args->engine;
if(args->argc && cwal_props_can(args->argv[0])){
init.config.traceSql =
cwal_value_get_bool(cwal_prop_get(args->argv[0],
"traceSql", 8));
}
rc = fsl_cx_init( &f, &init );
if(rc){
return cb_toss(args, FSL_RC_ERROR,
"Fossil context initialization failed "
"with code #%d (%s).", rc,
fsl_rc_cstr(rc));
}
v = cwal_new_native_value(args->engine, f,
fsl_finalizer_f_fsl_cx,
FSL_TYPEID(fsl_cx));
if(!v){
fsl_cx_finalize( f );
return CWAL_RC_OOM;
}
cwal_value_prototype_set( v, fsl_cx_prototype(ie) );
fsl_cx_db_prop( f, v, ie, 1 ) /* initialize this->db */;
*rv = v;
return 0;
}
/*
** Searches v and its prototype chain for a fsl_cx binding. If found,
** it is returned, else NULL is returned.
*/
static fsl_cx * cwal_value_fsl_cx_part( cwal_engine * e,
cwal_value * v ){
if(!v) return NULL;
else {
cwal_native * nv;
fsl_cx * f;
while(v){
nv = cwal_value_get_native(v);
f = nv
? (fsl_cx *)cwal_native_get( nv, FSL_TYPEID(fsl_cx) )
: NULL;
if(f) return f;
else v = cwal_value_prototype_get(e, v);
} while(v);
return NULL;
}
}
#if 0
/*
** Searches v and its prototype chain for a fsl_db binding. If found,
** it is returned, else NULL is returned.
*/
static fsl_db * fsl_value_db_part( th1ish_interp * ie,
cwal_value * v ){
if(!v) return NULL;
else {
cwal_native * nv;
fsl_db * db;
while(v){
nv = cwal_value_get_native(v);
db = nv
? (fsl_db *)cwal_native_get( nv, FSL_TYPEID(fsl_db) )
: NULL;
if(db) return db;
else v = cwal_value_prototype_get(ie->e, v);
} while(v);
return NULL;
}
}
#endif
/*
** Creates a new cwal_value (cwal_native) wrapper for the given fsl_db
** instance. If addDtor is true then a finalizer is installed for the
** instance, else it is assumed to live native-side and gets no
** destructor installed (in which case we will eventually have a problem
** when such a db is destroyed outside of the script API, unless we
** rewrite these to use weak references instead, but that might require
** one more level of struct indirection).
**
** On success, returns 0 and assigns *rv to the new Db value.
**
** If 0!=db->filename.used then the new value gets a 'name' property
** set to the contents of db->filename.
**
** Maintenance reminder: this routine must return cwal_rc_t codes, not
** fsl_rc_t codes.
**
** TODO? Wrap up cwal_weak_ref of db handle, instead of the db handle
** itself? We only need this if Fossil instances will have their DB
** instances manipulated from outside of script-space.
*/
static int fsl_db_new_native( th1ish_interp * ie,
fsl_cx * f,
fsl_db * db,
char addDtor,
cwal_value **rv,
fsl_db_role_t role ){
cwal_native * n;
cwal_value * nv;
int rc = 0;
char const * fname = NULL;
fsl_size_t nameLen = 0;
assert(ie && db && rv);
n = cwal_new_native(ie->e, db,
addDtor ? fsl_finalizer_f_fsl_db : NULL,
FSL_TYPEID(fsl_db));
if(!n) return CWAL_RC_OOM;
nv = cwal_native_value(n);
if(FSL_DB_ROLE_NONE != role){
/* Fetch the db file's name ... */
fname = fsl_cx_db_file_for_role(f, role, &nameLen);
/* MARKER(("role=%d, name=[%s]\n", role, fname)); */
}
if(!fname && db->filename.used){
fname = fsl_buffer_cstr2(&db->filename, &nameLen);
}
if(nameLen){
cwal_value * fn =
cwal_new_string_value(ie->e, fname, (cwal_size_t)nameLen);
rc = fn
? cwal_prop_set(nv, "filename", 8, fn)
: CWAL_RC_OOM;
if(rc && fn) cwal_value_unref(fn);
}
if(!rc){
*rv = nv;
cwal_value_prototype_set( nv, fsl_db_prototype(ie) );
}else{
/*
Achtung: if addDtor then on error the dtor will be called
here.
*/
cwal_value_unref(nv);
}
return rc;
}
static cwal_value * fsl_cx_db_prop( fsl_cx * f,
cwal_value * fv,
th1ish_interp * ie,
char createIfNotExists){
cwal_value * rv;
fsl_db * db = fsl_cx_db(f);
/* assert(db); */
rv = db ? cwal_prop_get(fv, "db", 2) : NULL;
if(db && !rv && createIfNotExists){
int rc = fsl_db_new_native(ie, f, db, 0, &rv, FSL_DB_ROLE_MAIN);
if(rc) return NULL;
if(rv && cwal_prop_set(fv, "db", 2, rv)){
cwal_value_unref(rv);
rv = NULL;
}
}
return rv;
}
/**
** Internal helper which adds vSelf->db->repo/checkout
** handles. Returns 0 on success. MUST return CWAL_RC_xxx
** codes, NOT FSL_RC_xxx codes.
*/
static int fsl_cx_add_db_handles(fsl_cx * f, cwal_value * vSelf,
th1ish_interp * ie){
cwal_value * dbV = fsl_cx_db_prop(f, vSelf, ie, 1);
cwal_value * d = NULL;
fsl_db * db;
int rc;
if(!dbV) return FSL_RC_OOM;
d = cwal_prop_get(dbV, "repo", 4);
if(!d && (db = fsl_cx_db_repo(f))){
rc = fsl_db_new_native(ie, f, db, 0, &d, FSL_DB_ROLE_REPO);
if(rc) return rc;
rc = cwal_prop_set(dbV, "repo", 4, d);
if(rc){
cwal_value_unref(d);
return rc;
}
}
d = cwal_prop_get(dbV, "checkout", 8);
if(!d && (db = fsl_cx_db_checkout(f))){
rc = fsl_db_new_native(ie, f, db, 0, &d, FSL_DB_ROLE_CHECKOUT);
if(rc) return rc;
rc = cwal_prop_set(dbV, "checkout", 8, d);
if(rc){
cwal_value_unref(d);
return rc;
}
}
return 0;
}
#define THIS_F th1ish_interp * ie = th1ish_args_interp(args); \
cwal_native * nat = cwal_value_native_part(args->engine, args->self); \
cwal_value * natV = nat ? cwal_native_value(nat) : NULL; \
fsl_cx * f = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL; \
assert(ie); \
if(!f){ return th1ish_toss(ie, FSL_RC_TYPE, \
"'this' is not (or is no longer) " \
"a Fossil instance."); } (void)0
static int cb_fsl_repo_open( cwal_callback_args const * args,
cwal_value **rv ){
char const * dbName;
int rc;
THIS_F;
dbName = args->argc
? cwal_value_get_cstr(args->argv[0], NULL)
: NULL;
if(!dbName || !*dbName){
return th1ish_toss(ie, FSL_RC_MISUSE,
"Expecting a non-empty string argument.");
}
rc = fsl_repo_open( f, dbName );
if(rc){
char const * msg = NULL;
fsl_size_t msgLen = 0;
fsl_cx_err_get( f, &msg, &msgLen );
if(msg){
rc = th1ish_toss(ie, rc,
"Opening repo db failed: %.*s",
(int)msgLen, msg);
}else{
rc = th1ish_toss(ie, rc,
"Opening repo db failed with code "
"%d (%s).", rc, fsl_rc_cstr(rc));
}
}
else{
rc = fsl_cx_add_db_handles(f, args->self, ie);
}
return rc;
}
static int cb_fsl_checkout_open( cwal_callback_args const * args,
cwal_value **rv ){
char const * dbName;
cwal_size_t nameLen = 0;
int rc;
THIS_F;
dbName = args->argc
? cwal_value_get_cstr(args->argv[0], &nameLen)
: NULL;
rc = fsl_checkout_open( f, dbName,
nameLen ? (fsl_int_t)nameLen : -1 );
if(rc){
char const * msg = NULL;
fsl_size_t msgLen = 0;
fsl_cx_err_get( f, &msg, &msgLen );
if(msg){
rc = th1ish_toss(ie, rc,
"Opening checkout from [%s] failed: %.*s",
dbName ? dbName : ".",
(int)msgLen, msg);
}else{
rc = th1ish_toss(ie, rc,
"Opening checkout from [%s] failed "
"with code %d (%s).",
dbName ? dbName : ".",
rc, fsl_rc_cstr(rc));
}
}
else{
rc = fsl_cx_add_db_handles(f, args->self, ie);
}
return rc;
}
static int cb_fsl_cx_resolve_uuid( cwal_callback_args const * args,
cwal_value **rv ){
char * uuid = NULL;
char const * sym;
int rc;
THIS_F;
if(!fsl_cx_db_repo(f)){
return cb_toss(args, FSL_RC_MISUSE,
"Fossil does not have an opened repository.");
}
sym = args->argc
? cwal_value_get_cstr(args->argv[0], NULL)
: NULL;
if(!sym || !*sym){
return cb_toss(args, FSL_RC_MISUSE,
"Expecting a non-empty string argument.");
}
/* TODO: add second arg. */
rc = fsl_sym_to_uuid( f, sym, FSL_ATYPE_ANY, &uuid, NULL );
switch(rc){
case 0:
*rv = cwal_new_zstring_value(args->engine, uuid, FSL_UUID_STRLEN)
/* Reminder zstring is only safe here if f and args->engine
are configured for the same allocator.
*/;
if(!*rv){
fsl_free(uuid);
rc = CWAL_RC_OOM;
}
return rc;
case FSL_RC_AMBIGUOUS:
return cb_toss(args, FSL_RC_RANGE,
"Symbol [%s] resolves to more "
"than one UUID.", sym);
case FSL_RC_NOT_FOUND:
return 0;
case FSL_RC_OOM:
return CWAL_RC_OOM;
default:
return cb_toss(args, rc,
"Symbol lookup for [%s] failed "
"with code %d (%s).", sym,
rc, fsl_rc_cstr(rc));
}
}
#if 0
/* recycle this func. changing trace mode at runtime current has
no effect.
*/
static int cb_fsl_cx_trace_sql( cwal_callback_args const * args,
cwal_value **rv ){
THIS_F;
if(args->argc){
f->config.traceSql = cwal_value_get_bool(args->argv[0]);
}
*rv = cwal_new_bool(f->config.traceSql);
return 0;
}
#endif
static void cb_fsl_clear_handles(cwal_value * parent,
char const * dbName){
cwal_engine * e = cwal_value_engine(parent);
cwal_value * v = e ? cwal_prop_get(parent, dbName, 0) : NULL;
assert(e);
if(v){
cwal_native * nat = cwal_value_native_part(e, v);
fsl_db * db = nat ? cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL;
if(db){
cwal_native_clear(nat, 0);
cwal_prop_unset(parent, dbName, 0);
}
}
}
static int cb_fsl_repo_close( cwal_callback_args const * args,
cwal_value **rv ){
cwal_value * dbNs;
int rc;
THIS_F;
dbNs = fsl_cx_db_prop(f, natV, ie, 0);
if(dbNs){
cb_fsl_clear_handles(dbNs, "repo");
}
rc = fsl_repo_close( f );
return rc
? cb_toss(args, rc,
"Closing repo failed with code %d (%s).",
rc, fsl_rc_cstr(rc))
: 0;
}
static int cb_fsl_checkout_close( cwal_callback_args const * args,
cwal_value **rv ){
cwal_value * dbNs;
int rc;
THIS_F;
dbNs = fsl_cx_db_prop(f, natV, ie, 0);
if(dbNs){
cb_fsl_clear_handles(dbNs, "repo");
cb_fsl_clear_handles(dbNs, "checkout");
}
rc = fsl_checkout_close( f );
return rc
? cb_toss(args, rc,
"Closing checkout failed with code %d (%s).",
rc, fsl_rc_cstr(rc))
: 0;
}
static int cb_fsl_cx_finalize( cwal_callback_args const * args,
cwal_value **rv ){
THIS_F;
cwal_native_clear( nat, 1 );
return 0;
}
static int th1ish_fsl_openmode_to_flags(char const * openMode, int startFlags){
int openFlags = startFlags;
for( ; *openMode; ++openMode ){
switch(*openMode){
case 'w': openFlags |= FSL_OPEN_F_RW;
break;
case 'c': openFlags |= FSL_OPEN_F_CREATE;
break;
case 'v': openFlags |= FSL_OPEN_F_SCHEMA_VALIDATE;
break;
default:
break;
}
}
return openFlags;
}
int cb_fsl_db_ctor( cwal_callback_args const * args,
cwal_value **rv ){
cwal_value * v = NULL;
int rc;
th1ish_interp * ie = th1ish_args_interp(args);
char const * fn;
char const * openMode;
cwal_size_t fnLen = 0;
int openFlags = 0;
fsl_db dbA = fsl_db_empty;
fsl_cx * f = cwal_value_fsl_cx_part(args->engine, args->self)
/* It's okay if this is NULL. It will only be set when we
aFossilContext.dbOpen() is called.
*/
;
assert(ie);
fn = args->argc
? cwal_value_get_cstr(args->argv[0], &fnLen)
: NULL;
if(!fn){
return cb_toss(args, FSL_RC_MISUSE,
"Expecting a string (db file name) argument.");
}
openMode = (args->argc>0)
? cwal_value_get_cstr(args->argv[1], NULL)
: NULL;
if(!openMode){
openFlags = SQLITE_OPEN_READWRITE |
SQLITE_OPEN_CREATE;
}else{
openFlags = th1ish_fsl_openmode_to_flags( openMode, openFlags );
}
dbA.f = f;
rc = fsl_db_open( &dbA, fn, openFlags );
if(rc){
if(dbA.error.msg.used){
rc = cb_toss(args, FSL_RC_ERROR,
"Db open failed: code #%d: %s",
dbA.error.code,
(char const *)dbA.error.msg.mem);
}else{
rc = cb_toss(args, FSL_RC_ERROR,
"Db open failed "
"with code #%d (%s).", rc,
fsl_rc_cstr(rc));
}
fsl_db_close(&dbA);
}
else{
fsl_db * dbP = fsl_db_malloc();
if(!dbP){
fsl_db_close(&dbA);
rc = CWAL_RC_OOM;
}
else{
void const * kludge = dbP->allocStamp;
*dbP = dbA /* transfer ownership */;
dbA = fsl_db_empty;
dbP->allocStamp = kludge /* ensure that fsl_db_close() DRTR. */;
rc = fsl_db_new_native(ie, f, dbP, 1, &v, FSL_DB_ROLE_NONE);
if(rc){
fsl_db_close(dbP);
dbP = NULL;
assert(!v);
}
}
}
if(!rc){
assert(v);
*rv = v;
return 0;
}else{
assert(!v);
return rc;
}
}
#define THIS_DB th1ish_interp * ie = th1ish_args_interp(args); \
cwal_native * nat = cwal_value_native_part(args->engine, args->self); \
cwal_value * natV = nat ? cwal_native_value(nat) : NULL; \
fsl_db * db = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL; \
assert(ie); \
if(!db){ return th1ish_toss(ie, FSL_RC_TYPE, \
"'this' is not (or is no longer) " \
"a Db instance."); } (void)0
#define THIS_STMT th1ish_interp * ie = th1ish_args_interp(args); \
cwal_native * nat = cwal_value_native_part(args->engine, args->self); \
cwal_value * natV = nat ? cwal_native_value(nat) : NULL; \
fsl_stmt * stmt = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_stmt) ) : NULL; \
assert(ie); \
if(!stmt){ return th1ish_toss(ie, FSL_RC_TYPE, \
"'this' is not (or is no longer) " \
"a Stmt instance."); } (void)0
static int cb_fsl_db_finalize( cwal_callback_args const * args,
cwal_value **rv ){
THIS_DB;
cwal_native_clear( nat, 1 );
return 0;
}
#if 0
static int cb_fsl_db_filename( cwal_callback_args const * args,
cwal_value **rv ){
char const * fname = NULL;
fsl_size_t nameLen = 0;
THIS_DB;
MARKER(("db role=%d, f=%p\n", db->role, (void const*)db->f));
if(db->f && (FSL_DB_ROLE_NONE!=db->role)){
/* Kludge to get the right db file name for repo/checkout
dbs.
*/
fname = fsl_cx_db_file_for_role(db->f, db->role, &nameLen);
}
if(!fname){
fname = (char const *)db->filename.mem;
nameLen = (cwal_size_t)db->filename.used;
}
*rv = cwal_new_string_value(args->engine, fname, nameLen);
return *rv ? 0 : CWAL_RC_OOM;
}
#endif
static int cb_fsl_stmt_finalize( cwal_callback_args const * args,
cwal_value **rv ){
THIS_STMT;
cwal_native_clear( nat, 1 );
return 0;
}
static int cb_fsl_db_last_insert_id( cwal_callback_args const * args,
cwal_value **rv ){
THIS_DB;
*rv = cwal_new_integer(args->engine,
(cwal_int_t)sqlite3_last_insert_rowid(db->dbh));
return *rv ? 0 : CWAL_RC_OOM;
}
/**
Extracts result column ndx from st and returns a "the closest
approximation" of its type in cwal_value form by assigning it to
*rv. Returns 0 on success. On errror *rv is not modified.
Does not trigger a th1ish/cwal exception on error.
*/
static int th1ish_fsl_stmt_to_value( cwal_engine * e,
fsl_stmt * st,
int ndx,
cwal_value ** rv ){
int vtype = sqlite3_column_type(st->stmt, ndx);
switch( vtype ){
case SQLITE_NULL:
*rv = cwal_value_null();
return 0;
case SQLITE_INTEGER:
*rv = cwal_new_integer( e,
(cwal_int_t)sqlite3_column_int64(st->stmt,ndx) );
break;
case SQLITE_FLOAT:
*rv = cwal_new_double( e,
(cwal_double_t)sqlite3_column_double(st->stmt, ndx) );
break;
case SQLITE_BLOB: {
int slen = 0;
void const * bl = sqlite3_column_blob( st->stmt, ndx );
slen = bl ? sqlite3_column_bytes(st->stmt, ndx) : 0;
#if 1
{
cwal_buffer * buf = cwal_new_buffer(e, slen+1);
int rc;
if(!buf) return CWAL_RC_OOM;
rc = cwal_buffer_append( e, buf, bl, slen );
if(rc){
assert(!*rv);
cwal_value_unref(cwal_buffer_value(buf));
}else{
*rv = cwal_buffer_value(buf);
}
}
#else
/* we'll just hope it's really a string for now. We could/should
use a buffer here. */
*rv = cwal_new_string_value(e, slen ? (char const *)bl : NULL,
(cwal_size_t)slen);
#endif
break;
}
case SQLITE_TEXT: {
int slen = 0;
void const * str = sqlite3_column_text( st->stmt, ndx );
slen = str ? sqlite3_column_bytes(st->stmt, ndx) : 0;
*rv = cwal_new_string_value(e, slen ? str : NULL,
(cwal_size_t) slen);
break;
}
default:
return cwal_exception_setf(e, CWAL_RC_TYPE,
"Unknown db column type (%d).",
vtype);
}
return *rv ? 0 : CWAL_RC_OOM;
}
/**
Extracts all columns from st into a new cwal_object value.
colNames is expected to hold the same number of entries
as st has columns, and in the same order. The entries in
colNames are used as the object's field keys.
Returns NULL on error.
*/
static cwal_value * th1ish_fsl_stmt_row_to_object2( cwal_engine * e,
fsl_stmt * st,
cwal_array const * colNames ){
int colCount;
cwal_value * objV;
cwal_object * obj;
cwal_string * colName;
int i;
int rc;
if( ! st ) return NULL;
colCount = st->colCount;
if( !colCount || (colCount>(int)cwal_array_length_get(colNames)) ) {
return NULL;
}
obj = cwal_new_object(e);
if( ! obj ) return NULL;
objV = cwal_object_value( obj );
for( i = 0; i < colCount; ++i ){
cwal_value * v = NULL;
colName = cwal_value_get_string( cwal_array_get( colNames, i ) );
if( ! colName ) goto error;
rc = th1ish_fsl_stmt_to_value( e, st, i, &v );
if( rc ) goto error;
rc = cwal_object_set_s( obj, colName, v );
if( rc ){
cwal_value_unref(v);
goto error;
}
}
return objV;
error:
cwal_value_unref( objV );
return NULL;
}
/**
Extracts all columns from st into a new cwal_object value,
using st's column names as the keys.
Returns NULL on error.
*/
static cwal_value * th1ish_fsl_stmt_row_to_object( cwal_engine * e,
fsl_stmt * st ){
cwal_value * objV;
cwal_object * obj;
char const * colName;
int i;
int rc;
if( ! st || !st->colCount ) return NULL;
obj = cwal_new_object(e);
if( ! obj ) return NULL;
objV = cwal_object_value(obj);
for( i = 0; i < st->colCount; ++i ){
cwal_value * v = NULL;
colName = sqlite3_column_name(st->stmt, i);
if( ! colName ) goto error;
rc = th1ish_fsl_stmt_to_value( e, st, i, &v );
if( rc ) goto error;
rc = cwal_object_set( obj, colName, 0, v );
if( rc ){
cwal_value_unref(v);
goto error;
}
}
return objV;
error:
cwal_value_unref( objV );
return NULL;
}
/**
Appends all result columns from st to ar.
*/
static int th1ish_fsl_stmt_row_to_array2( cwal_engine * e,
fsl_stmt * st,
cwal_array * ar)
{
int i;
int rc = 0;
for( i = 0; i < st->colCount; ++i ){
cwal_value * v = NULL;
rc = th1ish_fsl_stmt_to_value( e, st, i, &v );
if( rc ) goto end;
rc = cwal_array_append( ar, v );
if( rc ){
cwal_value_unref(v);
goto end;
}
}
end:
return rc;
}
/**
Converts all column values from st into a cwal_array
value. Returns NULL on error, else an array value.
*/
static cwal_value * th1ish_fsl_stmt_row_to_array( cwal_engine * e,
fsl_stmt * st )
{
cwal_array * ar;
int rc = 0;
if( ! st || !st->colCount ) return NULL;
ar = cwal_new_array(e);
if( ! ar ) return NULL;
rc = th1ish_fsl_stmt_row_to_array2( e, st, ar );
if(rc){
cwal_array_unref(ar);
return NULL;
}else{
return cwal_array_value(ar);
}
}
/**
Returns a new cwal_array value containing the result column names
of the given statement . Returns NULL on error, else an array value.
*/
static cwal_value * th1ish_fsl_stmt_col_names( cwal_engine * e,
fsl_stmt * st ){
cwal_value * aryV = NULL;
cwal_array * ary = NULL;
char const * colName = NULL;
int i = 0;
int rc = 0;
cwal_value * newVal = NULL;
assert(st);
if( ! st->colCount ) return NULL;
ary = cwal_new_array(e);
if( ! ary ) return NULL;
rc = cwal_array_reserve(ary, (cwal_size_t)st->colCount);
if(rc) return NULL;
aryV = cwal_array_value(ary);
assert(ary);
for( i = 0; (0==rc) && (i < st->colCount); ++i ){
colName = sqlite3_column_name(st->stmt, i);
if( ! colName ) rc = CWAL_RC_OOM;
else{
newVal = cwal_new_string_value(e, colName,
fsl_strlen(colName));
if( NULL == newVal ){
rc = CWAL_RC_OOM;
}
else{
rc = cwal_array_set( ary, i, newVal );
if( rc ) cwal_value_unref( newVal );
}
}
}
if( 0 == rc ) return aryV;
else{
cwal_value_unref(aryV);
return NULL;
}
}
static int cb_fsl_db_prepare( cwal_callback_args const * args,
cwal_value **rv ){
fsl_stmt st = fsl_stmt_empty;
int rc;
char const * sql;
cwal_size_t sqlLen = 0;
cwal_value * nv = NULL;
cwal_engine * e = args->engine;
fsl_stmt * st2 = NULL;
THIS_DB;
sql = args->argc
? cwal_value_get_cstr(args->argv[0], &sqlLen)
: NULL;
if(!sql || !sqlLen){
return cwal_exception_setf(e,
FSL_RC_MISUSE,
"Expecting a non-empty string "
"argument (SQL).");
}
assert(!st.stmt);
rc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql);
if(rc){
if(db->error.msg.used){
rc = cwal_exception_setf(e, rc,
"%.*s",
(int)db->error.msg.used,
(char const *)db->error.msg.mem);
}else{
rc = cwal_exception_setf(e, FSL_RC_DB,
"Statement preparation failed "
"with code %d (%s).",
rc,
fsl_rc_cstr(rc));
}
assert(!st.stmt);
}else{
st2 = fsl_stmt_malloc();
nv = st2
? cwal_new_native_value(e,
st2, fsl_finalizer_f_fsl_stmt,
FSL_TYPEID(fsl_stmt))
: NULL;
if(!nv){
if(st2) fsl_free(st2);
fsl_stmt_finalize(&st);
rc = CWAL_RC_OOM;
}else{
void const * kludge = st2->allocStamp;
*st2 = st;
st2->allocStamp = kludge;
}
}
end:
if(rc){
if(nv){
cwal_value_unref(nv);
nv = NULL;
}
}
if(nv){ /* Post-processing of now-initialized Stmt... */
cwal_value * v;
assert(!rc);
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(K) CHECKV; rc = cwal_prop_set(nv, K, fsl_strlen(K), v); \
if(rc) goto end
v = cwal_new_string_value(e,
sql, sqlLen);
SET("sql");
cwal_prop_set( nv, "sql", 3, v );
v = cwal_new_integer(e, (cwal_int_t)st2->colCount);
SET("columnCount");
v = cwal_new_integer(e, (cwal_int_t)st2->paramCount);
SET("parameterCount");
if(st2->colCount){
v = th1ish_fsl_stmt_col_names(e, st2);
SET("columnNames");
}
cwal_value_prototype_set(nv, fsl_stmt_prototype(ie));
*rv = nv;
#undef SET
#undef CHECKV
}
return rc;
}
static int cb_fsl_stmt_reset( cwal_callback_args const * args, cwal_value **rv ){
char resetCounter = 0;
int rc;
THIS_STMT;
if(1 < args->argc) resetCounter = cwal_value_get_bool(args->argv[1]);
rc = fsl_stmt_reset2(stmt, resetCounter);
return rc
? cb_toss( args, rc, "Statement reset failed.")
: 0;
}
static int cb_fsl_db_exec_impl( cwal_callback_args const * args,
cwal_value **rv,
char isMulti ){
int rc;
char const * sql;
cwal_size_t sqlLen = 0;
int argIndex = 0;
THIS_DB;
again:
sql = (args->argc>argIndex)
? cwal_value_get_cstr(args->argv[argIndex], &sqlLen)
: NULL;
if(!sql || !sqlLen){
return (argIndex>0)
? 0
: cb_toss(args,
FSL_RC_MISUSE,
"Expecting a non-empty string "
"argument (SQL).");
}
/* MARKER(("SQL:<<<%.*s>>>\n", (int)sqlLen, sql)); */
rc = isMulti
? fsl_db_exec_multi( db, "%.*s", (int)sqlLen, sql )
: fsl_db_exec( db, "%.*s", (int)sqlLen, sql )
;
if(rc){
rc = cb_toss(args, FSL_RC_DB,
"%s failed: %s",
isMulti ? "Multi-exec" : "Exec",
sqlite3_errmsg(db->dbh));
}else if(++argIndex < args->argc){
goto again;
}
return rc;
}
static int cb_fsl_db_exec_multi( cwal_callback_args const * args,
cwal_value **rv ){
return cb_fsl_db_exec_impl( args, rv, 1 );
}
static int cb_fsl_db_exec( cwal_callback_args const * args,
cwal_value **rv ){
return cb_fsl_db_exec_impl( args, rv, 0 );
}
#if 0
static int cb_fsl_stmt_col_count( cwal_callback_args const * args,
cwal_value **rv ){
THIS_STMT;
*rv = cwal_new_integer(args->engine,
(cwal_int_t)stmt->colCount);
return *rv ? 0 : CWAL_RC_OOM;
}
static int cb_fsl_stmt_param_count( cwal_callback_args const * args,
cwal_value **rv ){
THIS_STMT;
*rv = cwal_new_integer(args->engine,
(cwal_int_t)stmt->paramCount);
return *rv ? 0 : CWAL_RC_OOM;
}
#endif
/**
mode: 0==Object, >0==Array, <0==no row data (just boolean
indicator)
*/
static int th1ish_fsl_stmt_step_impl( cwal_callback_args const * args,
cwal_value **rv,
int mode ){
int rc;
int scode;
THIS_STMT;
scode = fsl_stmt_step( stmt );
switch(scode){
case FSL_RC_STEP_DONE:
rc = 0;
*rv = cwal_value_undefined();
break;
case FSL_RC_STEP_ROW:{
if(0==mode){
/* Object mode */
cwal_array const * colNames =
cwal_value_get_array( cwal_prop_get(args->self,
"columnNames", 11) );
*rv = colNames
? th1ish_fsl_stmt_row_to_object2(args->engine,
stmt, colNames)
: th1ish_fsl_stmt_row_to_object(args->engine, stmt);
}else if(mode>0){
/* Array mode */
*rv = th1ish_fsl_stmt_row_to_array(args->engine, stmt);
}else{
/* "Normal" mode */
*rv = cwal_value_true();
}
rc = *rv ? 0 : CWAL_RC_OOM;
break;
}
default:
rc = cb_toss(args, scode,
"Stepping statement failed with "
"code #%d (%s): %s",
scode, fsl_rc_cstr(scode),
sqlite3_errmsg(stmt->db->dbh));
break;
}
return rc;
}
static int cb_fsl_stmt_step_array( cwal_callback_args const * args, cwal_value **rv ){
return th1ish_fsl_stmt_step_impl( args, rv, 1 );
}
static int cb_fsl_stmt_step_object( cwal_callback_args const * args, cwal_value **rv ){
return th1ish_fsl_stmt_step_impl( args, rv, 0 );
}
static int cb_fsl_stmt_step( cwal_callback_args const * args, cwal_value **rv ){
return th1ish_fsl_stmt_step_impl( args, rv, -1 );
}
/**
Tries to bind v to the given parameter column (1-based) of
nv->stmt. Returns 0 on success, triggers a script-side exception
on error.
*/
static int th1ish_fsl_stmt_bind( cwal_engine * e,
fsl_stmt * st,
int ndx,
cwal_value * v ){
int rc;
int const vtype = v ? cwal_value_type_id(v) : CWAL_TYPE_NULL;
if(ndx<1) {
return cwal_exception_setf(e, FSL_RC_RANGE,
"Bind index %d is invalid: indexes are 1-based.",
ndx);
}
else if(ndx > st->paramCount) {
return cwal_exception_setf(e, FSL_RC_RANGE,
"Bind index %d is out of range. Range=(1..%d).",
ndx, st->paramCount);
}
/*MARKER(("Binding %s to column #%u\n", cwal_value_type_name(v), ndx));*/
switch( vtype ){
case CWAL_TYPE_NULL:
case CWAL_TYPE_UNDEF:
rc = fsl_stmt_bind_null(st, ndx);
break;
case CWAL_TYPE_BOOL:
rc = fsl_stmt_bind_int32(st, ndx, cwal_value_get_bool(v));
break;
case CWAL_TYPE_INTEGER:
/* We have no way of knowing which type (32/64-bit) to bind
here, so we'll guess. */
rc = fsl_stmt_bind_int64(st, ndx,
(fsl_int64_t)cwal_value_get_integer(v));
break;
case CWAL_TYPE_DOUBLE:
rc = fsl_stmt_bind_double(st, ndx, (fsl_double_t)cwal_value_get_double(v));
break;
case CWAL_TYPE_BUFFER:
case CWAL_TYPE_STRING: {
/* FIXME: bind using the fsl_stmt_bind_xxx() APIs!!!
string/buffer binding is currently missing.
*/
cwal_size_t slen = 0;
char const * cstr = cwal_value_get_cstr(v, &slen);
if(!cstr){
/* Will only apply to empty buffers (Strings are never
NULL). But it's also possible that a buffer with
length 0 has a non-NULL memory buffer. So we cannot,
without further type inspection, clearly
differentiate between a NULL and empty BLOB
here. This distinction would seem to be (?)
unimportant for this particular use case, so
fixing/improving it can wait.
*/
rc = fsl_stmt_bind_null(st, ndx);
}
#if 1
else if(CWAL_TYPE_BUFFER==vtype){
rc = fsl_stmt_bind_blob(st, ndx, cstr, (int)slen, 1);
}
#endif
else{
rc = fsl_stmt_bind_text(st, ndx, cstr, (int)slen, 1);
}
break;
}
default:
return cwal_exception_setf(e, CWAL_RC_TYPE,
"Unhandled data type (%s) for binding "
"column %d.",
cwal_value_type_name(v), ndx);
}
if(rc){
fsl_size_t msgLen = 0;
char const * msg = NULL;
int const dbRc = fsl_db_err_get(st->db, &msg, &msgLen);
rc = cwal_exception_setf(e, FSL_RC_DB,
"Binding column %d failed with "
"sqlite code #%d: %.*s", ndx,
dbRc, (int)msgLen, msg);
}
return rc;
}
static int th1ish_fsl_stmt_bind_values_a( cwal_engine * e,
fsl_stmt * st,
cwal_array const * src){
cwal_size_t const n = cwal_array_length_get(src);
int i = 0;
int rc = 0;
for( ; !rc && (i < n); ++i ){
rc = th1ish_fsl_stmt_bind( e, st, i+1,
cwal_array_get(src,(cwal_size_t)i));
}
return rc;
}
static int th1ish_fsl_stmt_bind_proxy( cwal_engine * e,
fsl_stmt * st,
int ndx,
cwal_value * bind){
int rc = 0;
if(cwal_value_is_array(bind)){
rc = th1ish_fsl_stmt_bind_values_a(e, st,
cwal_value_get_array(bind));
}else if(cwal_value_is_object(bind)){
rc = cwal_exception_setf(e, FSL_RC_NYI,
"TODO: binding by name");
}else{
rc = th1ish_fsl_stmt_bind( e, st, ndx, bind);
}
return rc;
}
#if 0
static int th1ish_fsl_stmt_bind_from_args( cwal_engine * e,
fsl_stmt *st,
cwal_callback_args const * args,
uint16_t startAtArg ){
int rc = 0;
cwal_value * bind = (args->argc < startAtArg)
? args->argv[startAtArg]
: NULL;
if(!bind) return 0;
if(cwal_value_is_array(bind)){
rc = th1ish_fsl_stmt_bind_values_a(e, st,
cwal_value_get_array(bind));
}else if(cwal_value_is_object(bind)){
MARKER(("TODO: binding by name"));
rc = CWAL_RC_ERROR;
}else if(!cwal_value_is_undef(bind) && (args->argc > startAtArg)){
int i = startAtArg;
for( ; !rc && (i < (int)args->argc); ++i ){
rc = th1ish_fsl_stmt_bind( e, st, i, args->argv[i]);
}
}
return rc;
}
#endif
static int cb_fsl_stmt_bind( cwal_callback_args const * args,
cwal_value **rv ){
cwal_int_t ndx = -999;
THIS_STMT;
if(!args->argc){
return cb_toss(args, CWAL_RC_MISUSE,
"Expecting (Index[,Value]) arguments.");
}
if(cwal_value_is_integer(args->argv[0])){
ndx = cwal_value_get_integer(args->argv[0]);
}
return th1ish_fsl_stmt_bind_proxy(args->engine, stmt, (int)ndx,
(-999==ndx)
? args->argv[0]
: ((args->argc>0)
? args->argv[1]
: NULL));
}
static int cb_fsl_stmt_get( cwal_callback_args const * args, cwal_value **rv ){
int rc;
cwal_int_t ndx;
THIS_STMT;
if(!args->argc || !cwal_value_is_integer(args->argv[0])){
return th1ish_toss(ie, CWAL_RC_MISUSE,
"Expecting Index arguments.");
}
else if(!stmt->colCount){
return th1ish_toss(ie, CWAL_RC_MISUSE,
"This is not a fetch-style statement.");
}
ndx = cwal_value_get_integer(args->argv[0]);
if((ndx<0) || (ndx>=stmt->colCount)){
return cb_toss(args, CWAL_RC_RANGE,
"Column index %d is out of range. "
"Valid range is (%d..%d).", ndx,
0, stmt->colCount-1);
}
rc = th1ish_fsl_stmt_to_value( args->engine, stmt,
(uint16_t)ndx, rv );
if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){
rc = cwal_exception_setf( args->engine, rc,
"Get-by-index failed.");
}
return rc;
}
/**
If !cb, this is a no-op, else if cb is-a Function, it is call()ed,
if it is a String/Buffer, it is eval'd, else an exception is
thrown. self is the 'this' for the call(). Result is placed in
*rv. Returns 0 on success.
*/
static int th1ish_fsl_stmt_each_call_proxy( th1ish_interp * ie,
fsl_stmt * st,
cwal_value * cb,
cwal_value * self,
cwal_value **rv ){
char const * cstr;
cwal_size_t strLen = 0;
if(!cb) return 0;
else if((cstr = cwal_value_get_cstr(cb,&strLen))){
/** TODO: tokenize this only the first time. */
/* Workaround for script source location :/ */
int rc;
/**
Bug Reminder: in stack traces and assertion traces the
script name/location info is wrong in this case (empty and
relative to cstr, respectively) because th1ish doesn't have
a way to map this string back to a source location at this
point. That needs to be fixed/enabled at the th1ish level.
*/
rc = th1ish_eval_string(ie, 0, cstr, strLen, rv);
if(CWAL_RC_RETURN==rc){
rc = 0;
}
return rc;
}else if(cwal_value_is_function(cb)){
cwal_function * f = cwal_value_get_function(cb);
cwal_scope * sc = cwal_scope_current_get(ie->e);
return cwal_function_call_in_scope(sc, f,
self, rv,
0, NULL );
}else {
return cwal_exception_setf(ie->e, CWAL_RC_MISUSE,
"Don't now how to handle callback "
"of type '%s'.",
cwal_value_type_name(cb));
}
}
/**
Script usage:
$db.each object{
sql: "SQL CODE", // required
bind: X, // parameter value or array of values to bind
mode: 0, // 0==rows as objects, else as arrays
callback: string | function, // called for each row
}
Only the 'sql' property is required, and 'bind' must be set
if the SQL contains any binding placeholders.
In the scope of the callback, 'this' will resolve to the current
row data, either in object or array form (depending on the 'mode'
property). If the callback throws, that exception is propagated.
If it returns a literal false (as opposed to another falsy value)
then iteration stops without an error.
In addition, the following scope-level variables are set:
- rowNumber: 0-based number of the row (the iteration count).
- colNames: array of column names for the result set.
Example callbacks:
{
$print rowNumber colNames this
$print this.0 + this.1 // array-mode column access
}
proc(){
$print rowNumber colNames this
$print this.colA + this.colB // object-mode column access
}
Using the string form should be ever so slightly more efficient.
*/
static int cb_fsl_db_each( cwal_callback_args const * args, cwal_value **rv ){
int rc = 0;
cwal_value const * sql;
char const * csql;
cwal_size_t sqlLen = 0;
fsl_stmt st = fsl_stmt_empty;
int scode;
cwal_value * vMode;
int mode;
cwal_value * props;
cwal_value * bind;
cwal_value * callback;
cwal_value * cbSelf = NULL;
cwal_array * cbArray = NULL;
cwal_value * colNamesV = 0;
cwal_array * colNames = 0;
cwal_int_t rowNum;
cwal_engine * e = args->engine;
THIS_DB;
if(!args->argc || !cwal_value_is_object(args->argv[0])){
return cwal_exception_setf(e, CWAL_RC_MISUSE,
"Expecting Object parameter.");
}
props = args->argv[0];
sql = cwal_prop_get(props, "sql", 3 );
csql = sql ? cwal_value_get_cstr(sql, &sqlLen) : NULL;
if(!csql){
return th1ish_toss(ie, CWAL_RC_MISUSE,
"Missing 'sql' string/buffer property.");
}
vMode = cwal_prop_get( props, "mode", 4 );
mode = vMode ? cwal_value_get_integer(vMode) : 1;
bind = cwal_prop_get( props, "bind", 4 );
callback = cwal_prop_get( props, "callback", 8 );
if(callback){
if(!cwal_value_is_function(callback)
&& !cwal_value_is_string(callback)
&& !cwal_value_is_buffer(callback)){
callback = NULL;
}
}
rc = fsl_db_prepare(db, &st, "%.*s", (int)sqlLen, csql);
if(rc){
if(db->error.msg.used){
return cwal_exception_setf(e, FSL_RC_DB,
"%.*s", (int)db->error.msg.used,
(char const *)db->error.msg.mem);
}else{
return cwal_exception_setf(e, FSL_RC_DB,
"Statement preparation failed "
"with code %d (%s).",
rc, fsl_rc_cstr(rc));
}
}
if(bind && !cwal_value_is_undef(bind)){
rc = th1ish_fsl_stmt_bind_proxy( e, &st, 1, bind );
if(rc) goto end;
}
if(st.colCount){
colNamesV = th1ish_fsl_stmt_col_names(e,&st);
if(!colNamesV){
rc = CWAL_RC_OOM;
goto end;
}
rc = th1ish_var_set( ie, 0, "columnNames", 11, colNamesV );
if(rc){
cwal_value_unref(colNamesV);
colNamesV = NULL;
goto end;
}
colNames = cwal_value_get_array(colNamesV);
assert(cwal_array_length_get(colNames) == st.colCount);
}
rc = th1ish_var_set( ie, 0, "columnCount", 11,
cwal_new_integer(e, (cwal_int_t)st.colCount) );
if(rc) goto end;
/*
Step through each row and call the given callback function/string...
*/
for( rowNum = 1;
FSL_RC_STEP_ROW == (scode = fsl_stmt_step( &st ));
++rowNum){
if(callback && !cbSelf){
/** Init callback info if needed */
cbArray = (0==mode)
? NULL
: cwal_new_array(e);
cbSelf = cbArray
? cwal_array_value(cbArray)
: cwal_new_object_value(e);
if(cbSelf){
rc = th1ish_var_set(ie, 0, "this", 4, cbSelf);
if(rc){
cwal_value_unref(cbSelf);
goto end;
}
}
}
if(cbSelf){
/**
Set up this.XXX to each column's value, where
XXX is either the column index (for array mode)
or column name (for object mode).
*/
cwal_value * frv = 0;
int i = 0;
for( ; !rc && (i < st.colCount); ++i ){
cwal_value * cv;
cv = NULL;
rc = th1ish_fsl_stmt_to_value(e, &st, i, &cv);
if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){
rc = cwal_exception_setf(e, rc,
"Conversion from db column to "
"value failed with code "
"%d (%s).",
rc, fsl_rc_cstr(rc));
}
if(cbArray) rc = cwal_array_set( cbArray, i, cv);
else{
cwal_value * key;
assert(colNames);
key = cwal_array_get(colNames, i);
assert(key);
rc = cwal_prop_set_v( cbSelf, key, cv);
}
if(rc) cwal_value_unref(cv);
}
if(rc) goto end;
rc = th1ish_var_set( ie, 0, "rowNumber", 9,
cwal_new_integer(e, rowNum) );
if(rc) goto end;
rc = th1ish_fsl_stmt_each_call_proxy(ie, &st,
callback, cbSelf,
&frv);
if(rc) goto end;
else if(frv == cwal_value_false()/*yes, a ptr comparison*/){
/* If the function returns literal false, stop
looping without an error. */
goto end;
}
}
}
if(FSL_RC_STEP_ERROR==scode){
rc = cwal_exception_setf(e, FSL_RC_DB,
"Stepping cursor failed.");
}
end:
if(st.stmt){
fsl_stmt_finalize( &st );
}
return rc;
}
cwal_value * fsl_db_prototype( th1ish_interp * ie ){
int rc = 0;
cwal_value * proto;
cwal_value * v;
char const * pKey = "Fossil.Db";
proto = th1ish_prototype_get_by_name(ie, pKey);
if(proto) return proto;
/* cwal_value * fv; */
assert(ie && ie->e);
proto = cwal_new_object_value(ie->e);
if(!proto){
rc = CWAL_RC_OOM;
goto end;
}
rc = th1ish_prototype_store( ie, pKey, proto );
if(rc) goto end;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME) \
CHECKV; \
rc = cwal_prop_set( proto, NAME, fsl_strlen(NAME), v ); \
if(rc) goto end
#define FUNC2(NAME,FP) \
v = th1ish_new_function2( ie, FP ); \
SET(NAME)
v = cwal_new_string_value(ie->e, "Db", 2);
SET("__typename");
FUNC2("close", cb_fsl_db_finalize);
FUNC2("each", cb_fsl_db_each);
FUNC2("exec", cb_fsl_db_exec);
FUNC2("execMulti", cb_fsl_db_exec_multi);
/* FUNC2("filename", cb_fsl_db_filename); */
FUNC2("open", cb_fsl_db_ctor);
FUNC2("lastInsertId", cb_fsl_db_last_insert_id);
FUNC2("prepare", cb_fsl_db_prepare);
#undef SET
#undef FUNC2
#undef CHECKV
end:
return rc ? NULL : proto;
}
cwal_value * fsl_stmt_prototype( th1ish_interp * ie ){
int rc = 0;
cwal_value * proto;
cwal_value * v;
char const * pKey = "Fossil.Stmt";
proto = th1ish_prototype_get_by_name(ie, pKey);
if(proto) return proto;
/* cwal_value * fv; */
assert(ie && ie->e);
proto = cwal_new_object_value(ie->e);
if(!proto){
rc = CWAL_RC_OOM;
goto end;
}
rc = th1ish_prototype_store( ie, pKey, proto );
if(rc) goto end;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME) \
CHECKV; \
rc = cwal_prop_set( proto, NAME, fsl_strlen(NAME), v ); \
if(rc) goto end
#define FUNC2(NAME,FP) \
v = th1ish_new_function2( ie, FP ); \
SET(NAME)
v = cwal_new_string_value(ie->e, "Stmt", 4);
SET("__typename");
FUNC2("bind", cb_fsl_stmt_bind);
/* FUNC2("columnCount", cb_fsl_stmt_col_count); */
FUNC2("get", cb_fsl_stmt_get);
/* FUNC2("parameterCount", cb_fsl_stmt_param_count); */
FUNC2("finalize", cb_fsl_stmt_finalize);
FUNC2("reset", cb_fsl_stmt_reset);
FUNC2("step", cb_fsl_stmt_step);
FUNC2("stepArray", cb_fsl_stmt_step_array);
FUNC2("stepObject", cb_fsl_stmt_step_object);
#undef SET
#undef FUNC2
#undef CHECKV
end:
return rc ? NULL : proto;
}
static int cb_fs_file_size( cwal_callback_args const * args,
cwal_value **rv ){
char const * fn;
fsl_int64_t sz;
fn = args->argc
? cwal_value_get_cstr(args->argv[0], NULL)
: NULL;
if(!fn) return cb_toss(args, FSL_RC_MISUSE,
"Expecting non-empty string "
"argument.");
sz = fsl_file_size(fn);
*rv = (sz > (fsl_int64_t)CWAL_INT_T_MAX)
? cwal_new_double(args->engine, (double)sz)
: cwal_new_integer(args->engine, (cwal_int_t)sz)
;
return *rv ? 0 : CWAL_RC_OOM;
}
static int cb_fs_file_mtime( cwal_callback_args const * args,
cwal_value **rv ){
char const * fn;
fsl_time_t sz;
fn = args->argc
? cwal_value_get_cstr(args->argv[0], NULL)
: NULL;
if(!fn) return cb_toss(args, FSL_RC_MISUSE,
"Expecting non-empty string "
"argument.");
sz = fsl_file_mtime(fn);
*rv = (sz > (fsl_time_t)CWAL_INT_T_MAX)
? cwal_new_double(args->engine, (double)sz)
: cwal_new_integer(args->engine, (cwal_int_t)sz)
;
return *rv ? 0 : CWAL_RC_OOM;
}
static int cb_fs_file_isfile( cwal_callback_args const * args,
cwal_value **rv ){
char const * fn;
fn = args->argc
? cwal_value_get_cstr(args->argv[0], NULL)
: NULL;
if(!fn) return cb_toss(args, FSL_RC_MISUSE,
"Expecting non-empty string "
"argument.");
*rv = fsl_file_isfile(fn)
? cwal_value_true()
: cwal_value_false();
return 0;
}
static int cb_fs_file_canonical( cwal_callback_args const * args,
cwal_value **rv ){
int rc;
char const * fn;
fsl_buffer buf = fsl_buffer_empty;
fn = args->argc
? cwal_value_get_cstr(args->argv[0], NULL)
: NULL;
if(!fn) return cb_toss(args, FSL_RC_MISUSE,
"Expecting non-empty string "
"argument.");
rc = fsl_file_canonical_name( fn, &buf, 0 );
if(!rc){
*rv = cwal_string_value(cwal_new_zstring(args->engine,
(char *)buf.mem,
(cwal_size_t)buf.used));
rc = *rv ? 0 : CWAL_RC_OOM;
}
if(rc){
fsl_buffer_reserve(&buf, 0);
if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
}else{
/*transfer ownership of buf.mem to the new z-string*/
assert(*rv);
}
return rc;
}
/*
** Copies fb's state into cb. This is intended to be a temporary
** switch - it is vital that both fb and cb do not get cleaned up
** while they point to this memory, or they will double-free it.
*/
static void fsl_buffer_to_cwal_buffer( fsl_buffer const * fb,
cwal_buffer * cb ){
cb->mem = fb->mem;
cb->capacity = fb->capacity;
cb->used = fb->used;
}
/*
** Reverses the damage done by fsl_buffer_to_cwal_buffer().
*/
static void cwal_buffer_to_fsl_buffer( cwal_buffer const * cb,
fsl_buffer * fb ){
fb->mem = cb->mem;
fb->capacity = cb->capacity;
fb->used = cb->used;
}
/*
** Combined impl for Buffer.compress() and Buffer.uncompress(). If
** doCompress is true, it is the former, else the latter.
*/
static int cb_buffer_press( cwal_callback_args const * args,
cwal_value **rv, char doCompress ){
int rc;
cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
fsl_buffer tmp = fsl_buffer_empty;
if(!buf){
return cb_toss(args, CWAL_RC_TYPE,
"'this' is-not-a Buffer.");
}
cwal_buffer_to_fsl_buffer( buf, &tmp );
rc = doCompress
? fsl_buffer_compress( &tmp, &tmp )
: fsl_buffer_uncompress( &tmp, &tmp );
fsl_buffer_to_cwal_buffer( &tmp, buf );
if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
else if(rc){
rc = cb_toss(args, rc,
"Buffer %s failed with code %d (%s).",
doCompress ? "compression" : "decompression",
rc, fsl_rc_cstr(rc));
}
return rc;
}
static int cb_buffer_compress( cwal_callback_args const * args,
cwal_value **rv ){
return cb_buffer_press(args, rv, 1);
}
static int cb_buffer_uncompress( cwal_callback_args const * args,
cwal_value **rv ){
return cb_buffer_press(args, rv, 0);
}
static int cb_buffer_is_compressed( cwal_callback_args const * args,
cwal_value **rv ){
char rc = 0;
fsl_buffer fb = fsl_buffer_empty;
cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
if(!buf){
return cb_toss(args, CWAL_RC_TYPE,
"'this' is-not-a Buffer.");
}
cwal_buffer_to_fsl_buffer(buf, &fb);
rc = fsl_buffer_is_compressed(&fb);
*rv = cwal_new_bool( rc );
return 0;
}
static int cb_buffer_sha1_self( cwal_callback_args const * args,
cwal_value **rv ){
cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
char * sha;
if(!buf){
return cb_toss(args, CWAL_RC_TYPE,
"'this' is-not-a Buffer.");
}
sha = fsl_sha1sum_cstr( (char const*)buf->mem, (int)buf->used);
if(sha){
assert(sha[0] && sha[39] && !sha[40]);
}else if(buf->used){
return CWAL_RC_OOM /* ??? */;
}
*rv = sha
? cwal_new_zstring_value(args->engine, sha, 40)
: cwal_value_null();
return *rv ? 0 : CWAL_RC_OOM;
}
static int cb_fs_file_access( cwal_callback_args const * args,
cwal_value **rv ){
char const * fn;
char checkWrite;
fn = args->argc
? cwal_value_get_cstr(args->argv[0], NULL)
: NULL;
if(!fn) return cb_toss(args, FSL_RC_MISUSE,
"Expecting non-empty string "
"argument.");
checkWrite = (args->argc>1)
? cwal_value_get_bool(args->argv[1])
: 0;
*rv = fsl_file_access( fn, checkWrite ? W_OK : F_OK )
/*0==OK*/
? cwal_value_false()
: cwal_value_true();
return 0;
}
static int cb_fsl_delta_create( cwal_callback_args const * args,
cwal_value **rv ){
char const * s1 = NULL;
char const * s2 = NULL;
cwal_size_t len1, len2;
fsl_size_t outLen = 0;
cwal_buffer * cb;
int rc;
if(args->argc>1){
s1 = cwal_value_get_cstr(args->argv[0], &len1);
s2 = cwal_value_get_cstr(args->argv[1], &len2);
}
if(!s1 || !s2){
return cb_toss(args, FSL_RC_MISUSE,
"Expecting two non-empty string/buffer "
"arguments.");
}
cb = cwal_new_buffer(args->engine, len2+61);
if(!cb) return CWAL_RC_OOM;
assert(cb->capacity > (len2+60));
rc = fsl_delta_create( (unsigned char const *)s1, (fsl_size_t)len1,
(unsigned char const *)s2, (fsl_size_t)len2,
cb->mem, &outLen);
if(!rc){
/* Resize the buffer to fit. cwal_buffer is missing
this function as of this writing, so we proxy it
through a fsl_buffer.
*/
fsl_buffer rsz = fsl_buffer_empty;
cwal_buffer_to_fsl_buffer(cb, &rsz);
rc = fsl_buffer_resize(&rsz, outLen);
rsz.used = outLen;
fsl_buffer_to_cwal_buffer(&rsz, cb);
assert(0==cb->mem[cb->used]);
}
if(rc){
cwal_value_unref(cwal_buffer_value(cb));
}else{
*rv = cwal_buffer_value(cb);
}
return rc;
}
static int cb_fsl_delta_applied_len( cwal_callback_args const * args,
cwal_value **rv ){
char const * src;
cwal_size_t srcLen;
fsl_size_t appliedLen = 0;
int rc;
src = (args->argc>0)
? cwal_value_get_cstr(args->argv[0], &srcLen)
: NULL;
if(!src){
return cb_toss(args, FSL_RC_MISUSE,
"Expecting one delta string "
"argument.");
}
rc = fsl_delta_applied_size((unsigned char const *)src,
(fsl_size_t)srcLen, &appliedLen);
if(rc){
return cb_toss(args, rc,
"Input does not appear to be a "
"delta. Error #%d (%s).",
rc, fsl_rc_cstr(rc));
}else{
*rv = cwal_new_integer(args->engine,
(cwal_int_t)appliedLen);
return *rv ? 0 : CWAL_RC_OOM;
}
}
static int cb_fsl_delta_apply( cwal_callback_args const * args,
cwal_value **rv ){
char const * s1 = NULL;
char const * s2 = NULL;
char const * src;
char const * delta;
cwal_size_t len1, len2, srcLen, dLen;
fsl_size_t appliedLen = 0;
cwal_buffer * cb;
int rc;
if(args->argc>1){
s1 = cwal_value_get_cstr(args->argv[0], &len1);
s2 = cwal_value_get_cstr(args->argv[1], &len2);
}
if(!s1 || !s2){
return cb_toss(args, FSL_RC_MISUSE,
"Expecting two non-empty string/buffer "
"arguments.");
}
rc = fsl_delta_applied_size((unsigned char const *)s2,
(fsl_size_t)len2, &appliedLen);
if(!rc){
src = s1;
srcLen = len1;
delta = s2;
dLen = len2;
}else{ /* Check if the user perhaps swapped the args. */
rc = fsl_delta_applied_size((unsigned char const *)s1,
(fsl_size_t)len1, &appliedLen);
if(rc){
return cb_toss(args, FSL_RC_MISUSE,
"Expecting a delta string/buffer as one "
"of the first two arguments.");
}
src = s2;
srcLen = len2;
delta = s1;
dLen = len1;
}
cb = cwal_new_buffer(args->engine, appliedLen+1);
if(!cb) return CWAL_RC_OOM;
assert(cb->capacity > appliedLen);
rc = fsl_delta_apply( (unsigned char const *)src, (fsl_size_t)srcLen,
(unsigned char const *)delta, (fsl_size_t)dLen,
cb->mem);
if(!rc){
assert(0==cb->mem[appliedLen]);
if(cb->capacity > (cb->used * 4 / 3)){
fsl_buffer rsz = fsl_buffer_empty;
cwal_buffer_to_fsl_buffer(cb, &rsz);
rc = fsl_buffer_resize(&rsz, appliedLen);
rsz.used = appliedLen;
fsl_buffer_to_cwal_buffer(&rsz, cb);
assert(0==cb->mem[cb->used]);
}
}else{
rc = cb_toss(args, rc,
"Application of delta failed with "
"code #%d (%s).", rc,
fsl_rc_cstr(rc));
}
if(rc){
cwal_value_unref(cwal_buffer_value(cb));
}else{
*rv = cwal_buffer_value(cb);
}
return rc;
}
static int fsl_add_file_funcs( th1ish_interp * ie, cwal_value * ns ){
cwal_value * func;
cwal_value * v;
int rc;
func = cwal_new_object_value(ie->e);
if(!func) return CWAL_RC_OOM;
rc = cwal_prop_set(ns, "file", 4, func);
if(rc){
cwal_value_unref(func);
return rc;
}
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME) \
CHECKV; \
rc = cwal_prop_set( func, NAME, fsl_strlen(NAME), v ); \
if(rc) goto end
#define FUNC2(NAME,FP) \
v = th1ish_new_function2( ie, FP ); \
SET(NAME)
FUNC2("isAccessible", cb_fs_file_access);
FUNC2("size", cb_fs_file_size);
FUNC2("mtime", cb_fs_file_mtime);
FUNC2("isFile", cb_fs_file_isfile);
FUNC2("canonicalName", cb_fs_file_canonical);
#undef SET
#undef FUNC2
#undef CHECKV
end:
return rc;
}
static int fsl_add_rcs( th1ish_interp * ie, cwal_value * ns ){
cwal_value * func;
cwal_value * v;
int rc;
func = cwal_new_object_value(ie->e);
if(!func) return CWAL_RC_OOM;
rc = cwal_prop_set(ns, "rc", 2, func);
if(rc){
cwal_value_unref(func);
return rc;
}
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME) \
CHECKV; \
rc = cwal_prop_set( func, NAME, fsl_strlen(NAME), v ); \
if(rc) goto end
#define RC(N) v = cwal_new_integer(ie->e, FSL_RC_##N); \
SET(#N)
RC(ACCESS);
RC(ALREADY_EXISTS);
RC(BREAK);
RC(CHECKSUM_MISMATCH);
RC(CONSISTENCY);
RC(DB);
RC(DELTA_INVALID_OPERATOR);
RC(DELTA_INVALID_SEPARATOR);
RC(DELTA_INVALID_SIZE);
RC(DELTA_INVALID_TERMINATOR);
RC(ERROR);
RC(IO);
RC(MISUSE);
RC(NOT_A_CHECKOUT);
RC(NOT_A_REPO);
RC(NOT_FOUND);
RC(NYI);
RC(OK);
RC(OOM);
RC(RANGE);
RC(REPO_MISMATCH);
RC(REPO_NEEDS_REBUILD);
RC(REPO_VERSION);
RC(SIZE_MISMATCH);
RC(STEP_DONE);
RC(STEP_ERROR);
RC(STEP_ROW);
RC(TYPE);
RC(TRAILING_COMMA_KLUDGE);
#undef RC
#undef SET
#undef CHECKV
end:
return rc;
}
cwal_value * fsl_cx_prototype( th1ish_interp * ie ){
int rc = 0;
cwal_value * proto;
cwal_value * v;
char const * pKey = "Fossil.Context";
proto = th1ish_prototype_get_by_name(ie, pKey);
if(proto) return proto;
/* cwal_value * fv; */
assert(ie && ie->e);
proto = th1ish_new_function2( ie, cb_fsl_cx_ctor );
if(!proto){
rc = CWAL_RC_OOM;
goto end;
}
rc = th1ish_prototype_store( ie, pKey, proto );
if(rc) goto end;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME) \
CHECKV; \
rc = cwal_prop_set( proto, NAME, fsl_strlen(NAME), v ); \
if(rc) goto end
#define FUNC2(NAME,FP) \
v = th1ish_new_function2( ie, FP ); \
SET(NAME)
v = cwal_new_string_value(ie->e, "FossilContext", 13);
SET("__typename");
FUNC2("finalize", cb_fsl_cx_finalize);
FUNC2("closeCheckout", cb_fsl_checkout_close);
FUNC2("closeRepo", cb_fsl_repo_close);
FUNC2("openRepo", cb_fsl_repo_open);
FUNC2("openCheckout", cb_fsl_checkout_open);
FUNC2("openDb", cb_fsl_db_ctor);
FUNC2("resolveUuid", cb_fsl_cx_resolve_uuid);
/* FUNC2("traceSql", cb_fsl_cx_trace_sql); */
#undef SET
#undef FUNC2
#undef CHECKV
end:
return rc ? NULL : proto;
}
int th1ish_shell_extend(th1ish_interp * ie, int argc, char const * const * argv){
int rc = 0;
cwal_value * top = cwal_scope_properties(ie->topScope);
cwal_value * v;
cwal_value * ns;
cwal_engine * e = th1ish_interp_engine(ie);
setlocale(LC_CTYPE,"");
assert(top);
ns = cwal_new_object_value(e);
if(!ns) return CWAL_RC_OOM;
rc = cwal_prop_set( top, "Fossil", 6, ns );
if(rc){
cwal_value_unref(ns);
return rc;
}
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME) \
CHECKV; \
rc = cwal_prop_set( ns, NAME, fsl_strlen(NAME), v ); \
if(rc) goto end
#define FUNC2(NAME,FP) \
v = th1ish_new_function2( ie, FP ); \
SET(NAME)
FUNC2("deltaCreate", cb_fsl_delta_create);
FUNC2("deltaApply", cb_fsl_delta_apply);
FUNC2("deltaAppliedLength", cb_fsl_delta_applied_len);
v = fsl_cx_prototype(ie);
SET("Context");
v = fsl_db_prototype(ie);
SET("Db");
cwal_prop_set( v, "Stmt", 4, fsl_stmt_prototype(ie) );
SET("Stmt");
rc = fsl_add_file_funcs( ie, ns );
if(!rc) rc = fsl_add_rcs( ie, ns );
if(!rc){
cwal_value * bufProto = th1ish_prototype_buffer(ie);
assert(bufProto);
cwal_prop_set( bufProto, "compress", 8,
th1ish_new_function2(ie, cb_buffer_compress));
cwal_prop_set( bufProto, "isCompressed", 12,
th1ish_new_function2(ie, cb_buffer_is_compressed));
cwal_prop_set( bufProto, "sha1", 4,
th1ish_new_function2(ie, cb_buffer_sha1_self));
cwal_prop_set( bufProto, "uncompress", 10,
th1ish_new_function2(ie, cb_buffer_uncompress));
}
#undef SET
#undef FUNC2
#undef CHECKV
end:
return rc;
}
#undef MARKER
#undef THIS_F
#undef THIS_DB
#undef THIS_STMT
#undef FSL_TYPEID