/* Code generated by script */

#include "sq_sqlite3.h"
#include <assert.h>
#include <stdlib.h>
#include "lua-regex.h"

SQ_OPT_STRING_STRLEN();

const SQChar *SQLite3_TAG = "SQLite3";
static const SQChar *SQLite3_Stmt_TAG = "SQLite3Stmt";
static const SQChar *SQLite3_Blob_TAG = "SQLite3Blob";

static const SQChar sqlite3_NULL_Name[] = _SC("sqlite3_NULL");
static const SQChar nullName[] = _SC("Null");

//return error if parameter exists but is not a blob
//return 1 if the parameter exists and is a blob
//return 0 if the parameter doesn't exists and creates a new blob
static SQRESULT getOptionalBlob(HSQUIRRELVM v, SQInteger idx, SQBlob **blob)
{
    if(sq_gettop(v) >= idx) {
        if(SQ_FAILED(sq_getinstanceup(v,3,(SQUserPointer*)blob,(SQUserPointer)SQBlob::SQBlob_TAG)))
            return sq_throwerror(v,_SC("expect a blob as second parameter"));
        if(!(*blob) || !(*blob)->IsValid())
            return sq_throwerror(v,_SC("the blob is invalid"));
    } else {
        *blob = new SQBlob(0, 8192);
        return 0;
    }
    return 1;
}

static SQRESULT sqlite3_NULL_tostring(HSQUIRRELVM v)
{
    sq_pushstring(v, "", 0);
    return 1;
}
SQRegFunction sqlite3_NULL_methods[]=
{
    {_SC("_tostring"),sqlite3_NULL_tostring,1, _SC("u")},
    {0,0}
};

static SQRESULT get_sqlite3_instance(HSQUIRRELVM v, SQInteger idx, sq_sqlite3_sdb **sdb)
{
    SQRESULT _rc_;
    if((_rc_ = sq_getinstanceup(v,idx,(SQUserPointer*)sdb,(void*)SQLite3_TAG)) < 0) return _rc_;
    if(!*sdb) return sq_throwerror(v, _SC("database is closed"));
    return _rc_;
}

static SQRESULT get_sqlite3_stmt_instance(HSQUIRRELVM v, SQInteger idx, sqlite3_stmt **stmt)
{
    SQRESULT _rc_;
    if((_rc_ = sq_getinstanceup(v,idx,(SQUserPointer*)stmt,(void*)SQLite3_Stmt_TAG)) < 0) return _rc_;
    if(!*stmt) return sq_throwerror(v, _SC("statement is closed"));
    return _rc_;
}

static SQRESULT get_sqlite3_blob_instance(HSQUIRRELVM v, SQInteger idx, sqlite3_blob **blob)
{
    SQRESULT _rc_;
    if((_rc_ = sq_getinstanceup(v,idx,(SQUserPointer*)blob,(void*)SQLite3_Blob_TAG)) < 0) return _rc_;
    if(!*blob) return sq_throwerror(v, _SC("blob is closed"));
    return _rc_;
}

//#define push_sqlite3_null(v) sq_getonregistrytable(v, sqlite3_NULL_Name, sizeof(sqlite3_NULL_Name)-1);
//#define push_sqlite3_null(v) sq_pushobject(v, sqlite3_NULL);
//#define push_sqlite3_null(v) sq_getbyname(v, 1, nullName, sizeof(nullName)-1)
//#define push_sqlite3_null(v) sq_pushfromregistrytable(v, nullName, sizeof(nullName)-1)
//#define push_sqlite3_null(v) {sq_pushregistrytable(v);sq_pushstring(v, sqlite3_NULL_Name, sizeof(sqlite3_NULL_Name)-1);sq_get(v, -2);sq_remove(v,-2);}
//the line bellow is the fastest one, any other attempt till now was slower
#define push_sqlite3_null(v) {sq_pushstring(v, nullName, sizeof(nullName)-1);sq_get(v, 1);}

#define GET_sqlite3_INSTANCE_AT_VAR(idx, self) \
	sq_sqlite3_sdb *sdb=NULL; \
	if((_rc_ = get_sqlite3_instance(v,idx,&sdb)) < 0) return _rc_;\
	sqlite3 *self = sdb->db;

#define GET_sqlite3_INSTANCE_AT(idx) GET_sqlite3_INSTANCE_AT_VAR(idx, self)
#define GET_sqlite3_INSTANCE() GET_sqlite3_INSTANCE_AT(1)

//#define GET_sqlite3_INSTANCE() SQ_GET_INSTANCE(v, 1, sqlite3, SQLite3_TAG)
#define GET_sqlite3_stmt_INSTANCE_AT(idx, var_name)  \
	sqlite3_stmt *var_name=NULL; \
	if((_rc_ = get_sqlite3_stmt_instance(v,idx,&var_name)) < 0) return _rc_;
#define GET_sqlite3_stmt_INSTANCE() GET_sqlite3_stmt_INSTANCE_AT(1, self)

#define GET_sqlite3_blob_INSTANCE_AT(idx, var_name)  \
	sqlite3_blob *var_name=NULL; \
	if((_rc_ = get_sqlite3_blob_instance(v,idx,&var_name)) < 0) return _rc_;
#define GET_sqlite3_blob_INSTANCE() GET_sqlite3_blob_INSTANCE_AT(1, self)

enum e_type_result {tr_first_row_first_col, tr_first_row, tr_all_rows, tr_ddml};

#define AS_STRING_ALWAYS 0x01
#define NULL_AS_EMPTY_STR 0x02
#define WITH_COL_NAMES 0x04
#define WITH_BLOB_AS_NULL 0x08
#define WITH_SQ_NULL 0x100

static void sqlite3_stmt_push_string(HSQUIRRELVM v, sqlite3_stmt *stmt, int col, int flags)
{
    const char *value = (const char*) sqlite3_column_text(stmt, col);
    if(value) sq_pushstring(v, value, sqlite3_column_bytes(stmt, col));
    else
    {
        if(flags & AS_STRING_ALWAYS) sq_pushliteral(v, "");
        else push_sqlite3_null(v);
    }
}

static SQRESULT sqlite3_stmt_push_value(HSQUIRRELVM v, sqlite3_stmt *stmt, int col, int flags)
{
    const char *value;
    int col_type = 0;
    if(flags & WITH_BLOB_AS_NULL)
    {
        col_type = sqlite3_column_type(stmt, col);
        if(col_type == SQLITE_BLOB)
        {
            if(flags & AS_STRING_ALWAYS) sq_pushliteral(v, "");
            else push_sqlite3_null(v);
            return 1;
        }
    }
    if(flags & AS_STRING_ALWAYS)
    {
        sqlite3_stmt_push_string(v, stmt, col, flags);
    }
    else
    {
        if(!col_type) col_type = sqlite3_column_type(stmt, col);

        switch (col_type)
        {
        case SQLITE_INTEGER:
        {
            sqlite_int64 i64 = sqlite3_column_int64(stmt, col);
            SQInteger n = (SQInteger)i64;
            if (n == i64)
                sq_pushinteger(v, n);
            else
                sq_pushstring(v, (const char*) sqlite3_column_text(stmt, col), sqlite3_column_bytes(stmt, col));
        }
        break;
        case SQLITE_FLOAT:
            sq_pushfloat(v, sqlite3_column_double(stmt, col));
            break;
        case SQLITE_TEXT:
            sqlite3_stmt_push_string(v, stmt, col, flags);
            break;
        case SQLITE_BLOB:
            value = (const char*) sqlite3_column_blob(stmt, col);
            if(value) sq_pushstring(v, value, sqlite3_column_bytes(stmt, col));
            else push_sqlite3_null(v);
            break;
        case SQLITE_NULL:
            if(flags & WITH_SQ_NULL) sq_pushnull(v);
            else push_sqlite3_null(v);
            break;
        default:
            if(flags & WITH_SQ_NULL) sq_pushnull(v);
            else push_sqlite3_null(v);
            break;
        }
    }
    return 1;
}

static void sqlite3_stmt_row_asArray(HSQUIRRELVM v, sqlite3_stmt *stmt, int flags)
{
    int col_count = sqlite3_column_count(stmt);
    sq_newarray(v, col_count);
    for(int i=0; i<col_count; ++i)
    {
        sqlite3_stmt_push_value(v, stmt, i, flags);
        sq_arrayset(v, -2, i);
    }
}

static void sqlite3_stmt_row_asTable(HSQUIRRELVM v, sqlite3_stmt *stmt, int flags)
{
    int col_count = sqlite3_column_count(stmt);
    sq_newtable(v);
    for(int i=0; i<col_count; ++i)
    {
        sq_pushstring(v, sqlite3_column_name(stmt, i), -1);
        sqlite3_stmt_push_value(v, stmt, i, flags);
        sq_rawset(v, -3);
    }
}

static void sqlite3_stmt_asArrayOfArrays(HSQUIRRELVM v, sqlite3_stmt *stmt, int flags)
{
    sq_newarray(v, 0);
    if(flags & WITH_COL_NAMES)
    {
        int col_count = sqlite3_column_count(stmt);
        sq_newarray(v, col_count);
        for(int i=0; i<col_count; ++i)
        {
            sq_pushstring(v, sqlite3_column_name(stmt, i), -1);
            sq_arrayset(v, -2, i);
        }
        sq_arrayappend(v, -2);
    }
    while(sqlite3_step(stmt) == SQLITE_ROW)
    {
        sqlite3_stmt_row_asArray(v, stmt, flags);
        sq_arrayappend(v, -2);
    }
}

static void sqlite3_stmt_asArrayOfTables(HSQUIRRELVM v, sqlite3_stmt *stmt, int flags)
{
    sq_newarray(v, 0);
    while(sqlite3_step(stmt) == SQLITE_ROW)
    {
        sqlite3_stmt_row_asTable(v, stmt, flags);
        sq_arrayappend(v, -2);
    }
}

static SQRESULT sqlite3_stmt_bind_value(HSQUIRRELVM v, sqlite3_stmt *stmt, int npar, int argn)
{
    int _rc_ = 0;
    SQObjectType ptype = sq_gettype(v, argn);
    switch(ptype)
    {
    case OT_BOOL:
        SQ_GET_BOOL(v, argn, param_bool);
        _rc_ = sqlite3_bind_int(stmt, npar, param_bool == SQTrue ? 1 : 0);
        break;
    case OT_INTEGER:
        SQ_GET_INTEGER(v, argn, param_integer);
        _rc_ = sqlite3_bind_int64(stmt, npar, param_integer);
        break;
    case OT_FLOAT:
        SQ_GET_FLOAT(v, argn, param_float);
        _rc_ = sqlite3_bind_double(stmt, npar, param_float);
        break;
    case OT_STRING:
    {
        SQ_GET_STRING(v, argn, param_string);
        _rc_ = sqlite3_bind_text(stmt, npar, param_string, param_string_size, SQLITE_TRANSIENT);
    }
    break;
    case OT_USERDATA:
        {
            SQUserPointer ttag = 0;
            if((sq_gettypetag(v, argn, &ttag) != SQ_OK) || (ttag != sqlite3_NULL_Name))
                return sq_throwerror(v, "Invalid bind parameter %d", npar);
        }
        //fallthrough
    case OT_NULL:
        _rc_ = sqlite3_bind_null(stmt, npar);
        break;
    default:
        return sq_throwerror(v, "Invalid bind parameter %d", npar);
    }
    if(_rc_ != SQLITE_OK)
    {
        sqlite3 *db = sqlite3_db_handle(stmt);
        return sq_throwerror(v, sqlite3_errmsg(db));
    }
    return SQ_OK;
}

static SQRESULT sq_sqlite3_stmt_bind_vargv(HSQUIRRELVM v, sqlite3 *db, sqlite3_stmt *stmt, int params_start)
{
    SQ_FUNC_VARS(v);

    if(_top_ > params_start)
    {
        int nparams = sqlite3_bind_parameter_count(stmt);
        int nparams_given = _top_-params_start;
        if(nparams != nparams_given)
        {
            return sq_throwerror(v, "expect %d parameters but only %d given", nparams, nparams_given);
        }
        for(int i=1; i <= nparams; ++i)
        {
            int argn = i+params_start;
            _rc_ = sqlite3_stmt_bind_value(v, stmt, i, argn);
            if(_rc_ < 0) return _rc_;
        }
    }
    return SQ_OK;
}

static SQRESULT sq_sqlite3_stmt_prepare_aux(HSQUIRRELVM v, sqlite3 *db, sqlite3_stmt **stmt, int params_start)
{
    SQ_FUNC_VARS_NO_TOP(v);
    SQ_GET_STRING(v, params_start, szSQL);
    if(sqlite3_prepare_v2(db, szSQL, szSQL_size, stmt, 0) != SQLITE_OK)
    {
        return sq_throwerror(v, sqlite3_errmsg(db));
    }

    return sq_sqlite3_stmt_bind_vargv(v, db, *stmt, params_start);
}

static SQRESULT sq_sqlite3_stmt_releasehook(SQUserPointer p, SQInteger size, void */*ep*/)
{
    sqlite3_stmt *stmt = ((sqlite3_stmt *)p);
    if(stmt) sqlite3_finalize(stmt);
    return 0;
}

static SQRESULT sq_sqlite3_stmt_constructor(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    _rc_ = SQ_ERROR;
    GET_sqlite3_INSTANCE_AT(2);
    sqlite3_stmt *stmt = 0;
    if(_top_ > 2)
    {
        _rc_ = sq_sqlite3_stmt_prepare_aux(v, self, &stmt, 3);
    }
    else
    {
        if(sqlite3_prepare_v2(self, "select 'statement not prepared';", -1, &stmt, 0) != SQLITE_OK)
        {
            _rc_ = sq_throwerror(v, sqlite3_errmsg(self));
        }
        else _rc_ = SQ_OK;
    }
    sq_setinstanceup(v, 1, stmt); //replace self for this instance with this new sqlite3_stmt
    sq_setreleasehook(v,1, sq_sqlite3_stmt_releasehook);
    return _rc_;
}

static SQRESULT sq_sqlite3_stmt_get_db(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sqlite3 *db = sqlite3_db_handle(self);
    sq_pushuserpointer(v, db);
    if(sq_getonregistrytable(v) == SQ_OK)
    {
        sq_getweakrefval(v, -1);
    }
    else return sq_throwerror(v, _SC("could not retrive database, maybe it was already closed"));
    return 1;
}

static SQRESULT sq_sqlite3_stmt_stmt_ptr(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sq_pushuserpointer(v, self);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_finalize(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sq_setinstanceup(v, 1, 0); //next calls will fail with "statement is closed"
    //printf("sq_sqlite3_stmt_finalize: %p : %s\n", self, sqlite3_sql(self));
    sq_pushinteger(v, sqlite3_finalize(self));
    return 1;
}

static SQRESULT sq_sqlite3_stmt_prepare(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sqlite3 *db = sqlite3_db_handle(self);

    sqlite3_stmt *stmt = 0;
    _rc_ = sq_sqlite3_stmt_prepare_aux(v, db, &stmt, 2);
    if(stmt)
    {
        sqlite3_finalize(self); //finalize the previous sqlite3_stmt
        sq_setinstanceup(v, 1, stmt); //replace self for this instance with this new sqlite3_stmt
    }
    return _rc_;
}

static SQRESULT sq_sqlite3_stmt_get_sql(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sq_pushstring(v, sqlite3_sql(self), -1);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_bind(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_GET_INTEGER(v, 2, npar);
    return sqlite3_stmt_bind_value(v, self, npar, 3);
}

/**
The number/order of fields should be the same to use this function
it'll save temporary data convertion.
*/
static SQRESULT sq_sqlite3_stmt_bind_stmt0(HSQUIRRELVM v, int doExec)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    GET_sqlite3_stmt_INSTANCE_AT(2, stmt_src);

    int rc = SQLITE_OK, bind_count = sqlite3_bind_parameter_count(self);
    for(int i=0; i < bind_count; ++i)
    {
        rc = sqlite3_bind_value(self, i+1, sqlite3_column_value(stmt_src, i));
        if(rc != SQLITE_OK) break;
    }
    if(doExec && rc == SQLITE_OK)
    {
        rc = sqlite3_step(self);
        sqlite3_reset(self);
    }

    sq_pushinteger(v, rc);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_bind_stmt(HSQUIRRELVM v)
{
    return sq_sqlite3_stmt_bind_stmt0(v, 0);
}

static SQRESULT sq_sqlite3_stmt_bind_exec_stmt(HSQUIRRELVM v)
{
    return sq_sqlite3_stmt_bind_stmt0(v, 1);
}

static SQRESULT sq_sqlite3_stmt_bind_empty_null(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_GET_INTEGER(v, 2, npar);
    if(sq_gettype(v, 3) == OT_STRING && sq_getsize(v, 3) == 0)
    {
        sq_pushnull(v);
        sq_replace(v, 3);
    }
    return sqlite3_stmt_bind_value(v, self, npar, 3);
}

static SQRESULT sq_sqlite3_stmt_bind_blob(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_GET_INTEGER(v, 2, npar);
    SQ_GET_STRING(v, 3, blob);
    if(sqlite3_bind_blob(self, npar, blob, blob_size, SQLITE_TRANSIENT) != SQLITE_OK)
    {
        sqlite3 *db = sqlite3_db_handle(self);
        return sq_throwerror(v, sqlite3_errmsg(db));
    }
    return SQ_OK;
}

static SQRESULT sq_sqlite3_stmt_bind_zeroblob(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_GET_INTEGER(v, 2, npar);
    SQ_GET_INTEGER(v, 3, nsize);
    if(sqlite3_bind_zeroblob(self, npar, nsize))
    {
        sqlite3 *db = sqlite3_db_handle(self);
        return sq_throwerror(v, sqlite3_errmsg(db));
    }
    return SQ_OK;
}

static SQRESULT sq_sqlite3_stmt_bind_values0(HSQUIRRELVM v, int isExec)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_stmt_INSTANCE();

    if (_top_ - 1 != sqlite3_bind_parameter_count(self))
        return sq_throwerror(v,
                             _SC("incorrect number of parameters to bind (%d given, %d to bind)"),
                             _top_ - 1,
                             sqlite3_bind_parameter_count(self));

    for (int n = 2; n <= _top_; ++n)
    {
        if (sqlite3_stmt_bind_value(v, self, n-1, n) != SQ_OK) return SQ_ERROR;
    }
    if(isExec)
    {
        int rc = sqlite3_step(self);
        sqlite3_reset(self);
        sq_pushinteger(v, rc);
        return 1;
    }
    return 0;
}

static SQRESULT sq_sqlite3_stmt_bind_values(HSQUIRRELVM v)
{
    return sq_sqlite3_stmt_bind_values0(v, 0);
}

static SQRESULT sq_sqlite3_stmt_bind_exec(HSQUIRRELVM v)
{
    return sq_sqlite3_stmt_bind_values0(v, 1);
}

static SQRESULT sq_sqlite3_stmt_bind_exec_get_one(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQInteger rc = sq_sqlite3_stmt_bind_values0(v, 0);
    if(rc == SQ_ERROR) return rc;
    rc = sqlite3_step(self);
    if(rc == SQLITE_ROW) sqlite3_stmt_push_value(v, self, 0, 0);
    else sq_pushnull(v);
    sqlite3_reset(self);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_bind_exec_get_first_row(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQInteger rc = sq_sqlite3_stmt_bind_values0(v, 0);
    if(rc == SQ_ERROR) return rc;
    rc = sqlite3_step(self);
    if(rc == SQLITE_ROW) sqlite3_stmt_row_asArray(v, self, NULL_AS_EMPTY_STR|AS_STRING_ALWAYS);
    else sq_pushnull(v);
    sqlite3_reset(self);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_bind_names(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();

    SQObjectType ptype = sq_gettype(v, 2);
    int count = sqlite3_bind_parameter_count(self);
    int result;

    for (int n = 1; n <= count; ++n)
    {
        const char *name = sqlite3_bind_parameter_name(self, n);
        if (name && (name[0] == ':' || name[0] == '$' || name[0] == '@'))
        {
            if(ptype == OT_TABLE)
            {
                sq_pushstring(v, ++name, -1);
                if(sq_get(v, 2) == SQ_OK)
                {
                    result = sqlite3_stmt_bind_value(v, self, n, -1);
                    sq_poptop(v);
                }
                else return sq_throwerror(v, _SC("bind parameter (%s) not found"), name);
            }
            else return sq_throwerror(v, _SC("table expected to bind named parameters"));
        }
        else
        {
            if(ptype == OT_ARRAY)
            {
                sq_pushinteger(v, n);
                if(sq_get(v, 2) == SQ_OK)
                {
                    result = sqlite3_stmt_bind_value(v, self, n, -1);
                    sq_poptop(v);
                }
                else return sq_throwerror(v, _SC("bind parameter (%d) not found"), n);
            }
            else return sq_throwerror(v, _SC("array expected to bind numbered parameters"));
        }

        if (result != SQ_OK) return result;
    }
    return 0;
}

static SQRESULT sq_sqlite3_stmt_bind_parameter_index(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_GET_STRING(v, 2, spar);
    sq_pushinteger(v, sqlite3_bind_parameter_index(self, spar));
    return 1;
}

static SQRESULT sq_sqlite3_stmt_bind_parameter_count(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sq_pushinteger(v, sqlite3_bind_parameter_count(self));
    return 1;
}

static SQRESULT sq_sqlite3_stmt_reset(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sq_pushinteger(v, sqlite3_reset(self));
    return 1;
}

static SQRESULT sq_sqlite3_stmt_step(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sq_pushinteger(v, sqlite3_step(self));
    return 1;
}

static SQRESULT sq_sqlite3_stmt_next_row(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sq_pushbool(v, sqlite3_step(self) == SQLITE_ROW);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_col_count(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    sq_pushinteger(v, sqlite3_column_count(self));
    return 1;
}

static SQRESULT sq_sqlite3_stmt_col_name(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_GET_INTEGER(v, 2, col);
    sq_pushstring(v, sqlite3_column_name(self, col), -1);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_col_type(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_GET_INTEGER(v, 2, col);
    sq_pushinteger(v, sqlite3_column_type(self, col));
    return 1;
}

static const char *get_column_type(sqlite3_stmt *stmt, int col)
{
    const char *column_type;
    int icol_type = sqlite3_column_type(stmt, col);
    switch(icol_type)
    {
        case SQLITE_INTEGER: column_type = "INTEGER"; break;
        case SQLITE_FLOAT: column_type = "FLOAT"; break;
        case SQLITE_BLOB: column_type = "BLOB"; break;
        case SQLITE_TEXT: column_type = "TEXT"; break;
        case SQLITE_NULL: column_type = "NULL"; break;
        default:
            column_type = "unknown";
    }
    return column_type;
}

static const char *get_column_dcltype(sqlite3_stmt *stmt, int col)
{
    const char *column_type = sqlite3_column_decltype(stmt, col);
    if(!column_type) column_type = get_column_type(stmt, col);
    return column_type;
}

static SQRESULT sq_sqlite3_stmt_col_declared_type(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_GET_INTEGER(v, 2, col);
    sq_pushstring(v, get_column_dcltype(self, col), -1);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_colsAsArray(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col_count = sqlite3_column_count(self);
    sq_newarray(v, col_count);
    for(int i=0; i<col_count; ++i)
    {
        sq_pushstring(v, sqlite3_column_name(self, i), -1);
        sq_arrayset(v, -2, i);
    }
    return 1;
}

static SQRESULT sq_sqlite3_stmt_colsTypeAsArray(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col_count = sqlite3_column_count(self);
    sq_newarray(v, col_count);
    for(int i=0; i<col_count; ++i)
    {
        sq_pushstring(v, get_column_type(self, i), -1);
        sq_arrayset(v, -2, i);
    }
    return 1;
}

static SQRESULT sq_sqlite3_stmt_colsDeclTypeAsArray(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col_count = sqlite3_column_count(self);
    sq_newarray(v, col_count);
    for(int i=0; i<col_count; ++i)
    {
        sq_pushstring(v, get_column_dcltype(self, i), -1);
        sq_arrayset(v, -2, i);
    }
    return 1;
}

static SQRESULT sq_sqlite3_stmt_colsNameAsArray(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col_count = sqlite3_column_count(self);
    sq_newarray(v, col_count);
    for(int i=0; i<col_count; ++i)
    {
        sq_pushstring(v, sqlite3_column_name(self, i), -1);
        sq_arrayset(v, -2, i);
    }
    return 1;
}

static SQRESULT sq_sqlite3_stmt_colsAsTable(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col_count = sqlite3_column_count(self);
    sq_newtable(v);
    for(int i=0; i<col_count; ++i)
    {
        sq_pushstring(v, sqlite3_column_name(self, i), -1);
        sq_pushinteger(v, i);
        sq_rawset(v, -3);
    }
    return 1;
}

static int get_col_index(HSQUIRRELVM v, sqlite3_stmt *self)
{
    SQInteger _rc_, col;
    switch(sq_gettype(v, 2))
    {
    case OT_INTEGER:
        sq_getinteger(v, 2, &col);
        break;
    case OT_STRING:
    {
        SQ_GET_STRING(v, 2, col_name);
        if(col_name_size == 0) return sq_throwerror(v, "column name can not be empty");
        col = -1;
        for(int i=0, len=sqlite3_column_count(self); i<len; ++i)
        {
            if(strcasecmp(col_name, sqlite3_column_name(self, i)) == 0)
            {
                col = i;
                break;
            }
        }
        if(col == -1) return sq_throwerror(v, "column name not found \"%s\"", col_name);
    }
    break;
    default:
        return sq_throwerror(v, "wrong parameter expected integer|string");
    }
    return col;
}

static SQRESULT sq_sqlite3_stmt_col(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col = get_col_index(v, self);
    if(col < 0) return col;
    sqlite3_stmt_push_value(v, self, col, 0);

    return 1;
}

static SQRESULT sq_sqlite3_stmt_col_slice(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col = get_col_index(v, self);
    if(col < 0) return col;
    SQ_GET_INTEGER(v, 3, start_pos);
    SQ_GET_INTEGER(v, 4, size);

    if((start_pos < 0) || (size < 0))
        return sq_throwerror(v, _SC("slice start_pos or size can't be negative"));

    const char *value = NULL;
    int col_type = sqlite3_column_type(self, col);

    switch (col_type)
    {
    case SQLITE_TEXT:
        value = (const char*) sqlite3_column_text(self, col);
        break;
    case SQLITE_BLOB:
        value = (const char*) sqlite3_column_blob(self, col);
        break;
    case SQLITE_NULL:
        break;
    default:
        return sq_throwerror(v, _SC("column type do not allow slice %d:%d"), col, col_type);
    }
    if(value)
    {
        int col_size = sqlite3_column_bytes(self, col);
        if(col_size > start_pos)
        {
            int space_left = col_size - start_pos;
            sq_pushstring(v, value + start_pos, (space_left > size) ? size : space_left);
        }
        else value = NULL;
    }
    if(!value) push_sqlite3_null(v);

    return 1;
}

static SQRESULT sq_sqlite3_stmt_asBool(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col = get_col_index(v, self);
    if(col < 0) return col;
    const unsigned char *b = sqlite3_column_text(self, col);
    sq_pushbool(v,  b && *b == '1');

    return 1;
}

static SQRESULT sq_sqlite3_stmt_asInteger(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col = get_col_index(v, self);
    if(col < 0) return col;
    sq_pushinteger(v,  sqlite3_column_int64(self, col));

    return 1;
}

static SQRESULT sq_sqlite3_stmt_asFloat(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col = get_col_index(v, self);
    if(col < 0) return col;
    sq_pushfloat(v,  sqlite3_column_double(self, col));

    return 1;
}

static SQRESULT sq_sqlite3_stmt_StringOrNull(HSQUIRRELVM v, int withNull)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col = get_col_index(v, self);
    if(col < 0) return col;
    sqlite3_stmt_push_string(v, self, col, withNull ? 0 : NULL_AS_EMPTY_STR);

    return 1;
}

static SQRESULT sq_sqlite3_stmt_asString(HSQUIRRELVM v)
{
    return sq_sqlite3_stmt_StringOrNull(v, 0);
}

static SQRESULT sq_sqlite3_stmt_asStringOrNull(HSQUIRRELVM v)
{
    return sq_sqlite3_stmt_StringOrNull(v, 1);
}

static SQRESULT sq_sqlite3_stmt_asTable(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_OPT_INTEGER(v, 2, flags, 0);
    sqlite3_stmt_row_asTable(v, self, flags);

    return 1;
}

static SQRESULT sq_sqlite3_stmt_asArray(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_OPT_INTEGER(v, 2, flags, 0);
    sqlite3_stmt_row_asArray(v, self, flags);

    return 1;
}

static SQRESULT sq_sqlite3_stmt_asArrayOfArrays(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_OPT_INTEGER(v, 2, flags, 0);
    sqlite3_stmt_asArrayOfArrays(v, self, flags);
    sqlite3_reset(self);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_asArrayOfTables(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_OPT_INTEGER(v, 2, flags, 0);
    sqlite3_stmt_asArrayOfTables(v, self, flags);
    sqlite3_reset(self);

    return 1;
}

/*utility functions*/
static void append_escaping_json(SQBlob &json, const char *sz)
{
    if(sz)  //escape string
    {
        int last_idx, idx;
        idx = last_idx = 0;
        for(; sz[idx]; ++idx)
        {
            char c = sz[idx];
            switch(c)
            {
            case '"':
            case '\n':
            case '\r':
            case '\t':
            case '\b':
            case '\f':
            case '\\':
            {
                json.Write(sz+last_idx, idx-last_idx);
                last_idx = idx;
                if(c == '\n')
                {
                    json.Write("\\n", 2);
                    ++last_idx;
                }
                else if(c == '\t')
                {
                    json.Write("\\t", 2);
                    ++last_idx;
                }
                else if(c == '\b')
                {
                    json.Write("\\b", 2);
                    ++last_idx;
                }
                else if(c == '\f')
                {
                    json.Write("\\f", 2);
                    ++last_idx;
                }
                else if(c == '\r')
                {
                    json.Write("\\r", 2);
                    ++last_idx;
                }
                else
                {
                    json.Write("\\", 1);
                }
            }
            }
        }
        //last part
        json.Write(sz+last_idx, idx-last_idx);
    }
}

static SQRESULT sq_sqlite3_stmt_asJsonArray(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_stmt_INSTANCE();
    SQ_OPT_BOOL(v, 2, withMetadata, 0);
    int col_count = sqlite3_column_count(self);
    int i, value_type;
    const char* value;

    SQBlob *json = NULL;
    int has_blob = getOptionalBlob(v, 3, &json);
    if(has_blob == SQ_ERROR) return SQ_ERROR;

    if(withMetadata)
    {
        json->WriteZstr("{\n\"columns\":[\n");
        for(i=0; i < col_count; ++i)
        {
            json->WriteZstr((i == 0 ? "\"" : ",\""));
            const char *column_name = sqlite3_column_name(self, i);
            json->WriteZstr(column_name ? column_name : "unknown");
            json->WriteZstr("\"");
        }

        json->WriteZstr("\n],\n\"column_types\":[\n");
        for(i=0; i < col_count; ++i)
        {
            json->WriteZstr((i == 0 ? "\"" : ",\""));
            json->WriteZstr(get_column_dcltype(self, i));
            json->WriteZstr("\"");
        }

        json->WriteZstr("\n],\n\"rows\":[\n");
    }
    else
    {
        json->WriteZstr("[\n");
    }
    int row_count = 0;
    while(sqlite3_step(self) == SQLITE_ROW)
    {
        json->WriteZstr((row_count++ == 0 ? "[" : ",["));
        for(i=0; i < col_count; ++i)
        {
            json->WriteZstr((i == 0 ? "\"" : ",\""));
            //if get the type after sqlite3_column_text it always returns SQLITE_TEXT
            value_type = sqlite3_column_type(self, i);
            value = (const char*)sqlite3_column_text(self, i);
            if(value)
            {
                switch(value_type)
                {
                case SQLITE_BLOB:
                    break; //no blobs in json
                case SQLITE_TEXT:
                {
                    append_escaping_json(*json, value);
                }
                break;
                default:
                    json->WriteZstr(value);
                }
            }
            json->WriteZstr("\"");
        }
        json->WriteZstr("]\n");
    }
    json->WriteZstr("]");
    if(withMetadata) json->WriteZstr("\n}");

    if(has_blob) return 0;
    sq_pushstring(v, (const SQChar*)json->GetBuf(), json->Len());
    delete json;

    return 1;
}

static SQRESULT sq_sqlite3_stmt_asJsonObject(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col_count = sqlite3_column_count(self);
    int i, value_type, record_count = 0;
    const char* value;

    SQBlob *json = NULL;
    int has_blob = getOptionalBlob(v, 2, &json);
    if(has_blob == SQ_ERROR) return SQ_ERROR;

    json->WriteZstr("{");
    if(sqlite3_step(self) == SQLITE_ROW)
    {
        ++record_count;
        for(i=0; i < col_count; ++i)
        {
            json->WriteZstr((i == 0 ? "\"" : "\",\""));
            append_escaping_json(*json, (const char*)sqlite3_column_name(self, i));
            json->WriteZstr("\":\"");
            //if get the type after sqlite3_column_text it always returns SQLITE_TEXT
            value_type = sqlite3_column_type(self, i);
            value = (const char*)sqlite3_column_text(self, i);
            if(value)
            {
                switch(value_type)
                {
                case SQLITE_BLOB:
                    break; //no blobs in json
                case SQLITE_TEXT:
                {
                    append_escaping_json(*json, value);
                }
                break;
                default:
                    json->WriteZstr(value);
                }
            }
        }
        json->WriteZstr("\"");
    }
    json->WriteZstr("}");

    if(has_blob) return 0;
    if(record_count) sq_pushstring(v, (const SQChar*)json->GetBuf(), json->Len());
    else sq_pushnull(v);
    delete json;

    return 1;
}

#define UNSIGNED(n) n##U
#define IBYTE_MAX UNSIGNED(256)
#define IBYTE1 UNSIGNED(240)
#define IBYTE2 UNSIGNED(2287)
#define IBYTE3 UNSIGNED(67823)
#define IBYTE4 UNSIGNED(16777215)

#define SLEMARK 254
#define SLEEND 255

static SQRESULT sle2array(HSQUIRRELVM v, const unsigned char *p, size_t sle_size, const unsigned char **next)
{
    size_t size, data_len = 0;
    const unsigned char *p_end = p + sle_size;

    if(sle_size == 0)
    {
        *next = 0;
        return 0;
    }

    while((*p != SLEEND) && (p <= p_end)) //data finished now follow digest
    {
        size = *p++;
        if(size > IBYTE1)
        {
            if(size <= 248)
            {
                size = (size-241)*IBYTE_MAX + (*p++) + IBYTE1;
            }
            else if(size == 249)
            {
                const unsigned char *p1 = p++, *p2 = p++;
                size = 2288 + (IBYTE_MAX * (*p1)) + (*p2);
            }
            else if(size == 250)
            {
                const unsigned char *p1 = p++, *p2 = p++, *p3 = p++;
                size = ((*p1)<<16) + ((*p2)<<8) + (*p3);
            }
            else if(size == 251)
            {
                const unsigned char *p1 = p++, *p2 = p++, *p3 = p++, *p4 = p++;
                size = ((*p1)<<24) + ((*p2)<<16) + ((*p3)<<8) + (*p4);
            }
            else if(size == SLEMARK)
            {
                //reserved can be used for multi part data, metadata, digest, ...
                break;
            }
        }

        sq_pushstring(v, (const char*)p, size);
        sq_arrayappend(v, -2);
        p += size;
        data_len += size;
        if(data_len > sle_size)
        {
            //something went wrong here
            return sq_throwerror(v, "We got more data than expected !");
        }
    }
    *next = ++p;
    return 0;
}

static SQRESULT sle2vec(HSQUIRRELVM v, const unsigned char *sle,
                        size_t sle_size,
                        const unsigned char **next)
{
    int rc;
    sq_newarray(v, 0);
    if(sle[0] != '[')
    {
        return sq_throwerror(v, "Invalid encoded vector !");
    }
    if(sle[1] == ']')
    {
        *next = sle+2; //empty array
        return 0;
    }
    if((rc = sle2array(v, sle+1, sle_size-1, next)) < 0) return rc;
    if(!*next || *next[0] != ']')
    {
        return sq_throwerror(v, "Invalid end of vector !");
    }
    (*next)++;
    return 0;
}

static SQRESULT sq_sle2vecOfvec(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    SQBlob *blob = NULL;
    const SQChar *sle, *sle_start;
    SQInteger sle_size;

    SQObjectType ptype = sq_gettype(v, 2);
    switch(ptype)
    {
    case OT_STRING:
        sq_getstr_and_size(v, 2, &sle, &sle_size);
        break;
    case OT_INSTANCE:
        if(SQ_FAILED(sq_getinstanceup(v,2,(SQUserPointer*)&blob,(SQUserPointer)SQBlob::SQBlob_TAG)))
            return sq_throwerror(v,_SC("invalid type tag"));
        if(!blob || !blob->IsValid())
            return sq_throwerror(v,_SC("the blob is invalid"));
        sle = (const SQChar*)blob->GetBuf();
        sle_size = blob->Len();
        break;
    default:
        return sq_throwerror(v,_SC("invalid parameter 1 of type %s"), sq_gettypename(v, 2));
    }
    if(sq_gettype(v, 3) != OT_ARRAY)
        return sq_throwerror(v,_SC("invalid parameter 2 of type %s"), sq_gettypename(v, 3));

    SQ_OPT_INTEGER(v, 4, start, 0);
    if(start < 0) return sq_throwerror(v, _SC("invalid start position (%d)"), start);
    sq_push(v, 3);

    int rc, data_count = 1;

    const unsigned char *next;
    sle_start = sle + start;
    sle_size -= start;
    if(sle_start[0] != '[')
    {
        return sq_throwerror(v, "Invalid encoded vector !");
    }
    if(sle_start[1] == ']')
    {
        //sq_pushinteger(v, start+2);
        return 1; //empty array
    }
    int saved_top = sq_gettop(v);
    if((rc=sle2vec(v, (const unsigned char *)sle_start+1, sle_size-1, &next)) < 0) return rc;
    while(next && *next == '[')
    {
        sq_arrayappend(v, -2);
        data_count++;
        if((rc=sle2vec(v, next, sle_size - (next - (const unsigned char *)sle_start), &next)) < 0) return rc;
    }
    if(sq_gettop(v) != saved_top)
    {
        //last added table left
        sq_arrayappend(v, -2);
        data_count++;
    }
    if(!next || *next != ']')
    {
        return sq_throwerror(v, "Invalid end of vector !");
    }
    sq_pushinteger(v, ((const SQChar*)(++next)) - sle);
    return 1;
}

typedef unsigned char slebuf_t[8];

static int sleSize2buf(slebuf_t sle, size_t size)
{
    size_t next_size;
    if(size <= IBYTE1)
    {
        sle[0] = size;
        return 1;
    }
    else if(size <= IBYTE2)
    {
        next_size = size - IBYTE1;
        sle[0] = next_size/IBYTE_MAX + 241;
        sle[1] = next_size%IBYTE_MAX;
        return 2;
    }
    else if(size <= IBYTE3)
    {
        sle[0] = 249;
        next_size = size - 2288;
        sle[1] = next_size/IBYTE_MAX;
        sle[2] = next_size%IBYTE_MAX;
        return 3;
    }
    else if(size <= IBYTE4)
    {
        sle[0] = 250;
        sle[1] = size >> 16;
        sle[2] = size >> 8;
        sle[3] = size;
        return 4;
    }
    else
    {
        sle[0] = 251;
        sle[1] = size >> 24;
        sle[2] = size >> 16;
        sle[3] = size >> 8;
        sle[4] = size;
        return 5;
    }
    return 0;
}

static SQRESULT sq_get_sle_size(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    slebuf_t slebuf;
    SQ_GET_INTEGER(v, 2, size);
    int slesize = sleSize2buf(slebuf, size);
    if(slesize)
    {
        sq_pushstring(v, (const char *)slebuf, slesize);
        return 1;
    }
    return 0;
}

static int add2sle(SQBlob &sle, const char *str, size_t size)
{
    slebuf_t slebuf;
    int slesize = sleSize2buf(slebuf, size);
    if(slesize) sle.Write((const char *)slebuf, slesize);
    if(size) sle.Write(str, size);
    return size;
}

static SQRESULT sq_add2sle(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    SQ_GET_INSTANCE_VAR(v, 2, SQBlob, blob, SQBlob::SQBlob_TAG);
    SQ_GET_STRING(v, 3, str);
    SQ_OPT_INTEGER(v, 4, size, str_size);
    if(size < 0) return sq_throwerror(v, _SC("invalid size value (%d)"), size);
    sq_pushinteger(v, add2sle(*blob, str, size));
    return 1;
}

static SQRESULT sq_sqlite3_stmt_asSleArray(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col_count = sqlite3_column_count(self);
    int i;
    const char *value;

    SQBlob *sle = NULL;
    int has_blob = getOptionalBlob(v, 2, &sle);
    if(has_blob == SQ_ERROR) return SQ_ERROR;

    sle->WriteZstr("[[");
    for(i=0; i < col_count; ++i)
    {
        value = sqlite3_column_name(self, i);
        add2sle(*sle, value, strlen(value));
    }
    sle->WriteChar(SLEEND);
    sle->WriteChar(']');

    while(sqlite3_step(self) == SQLITE_ROW)
    {
        sle->WriteChar('[');
        for(i=0; i < col_count; ++i)
        {
            switch(sqlite3_column_type(self, i))
            {
            case SQLITE_BLOB:
            {
                add2sle(*sle, (const char*)sqlite3_column_blob(self, i),
                        sqlite3_column_bytes(self, i));
            }
            break;
            default:
                add2sle(*sle, (const char*)sqlite3_column_text(self, i),
                        sqlite3_column_bytes(self, i));
            }
        }
        sle->WriteChar(SLEEND);
        sle->WriteChar(']');
    }
    sle->WriteChar(']');

    if(has_blob) return 0;
    sq_pushstring(v, (const SQChar*)sle->GetBuf(), sle->Len());
    delete sle;
    return 1;
}

static SQRESULT sq_sqlite3_stmt_all_blobs_size(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int total_size = 0, col_count = sqlite3_column_count(self);
    for(int i=0; i < col_count; ++i)
    {
        if(sqlite3_column_type(self, i) == SQLITE_BLOB)
        {
            total_size += sqlite3_column_bytes(self, i);
        }
    }
    sq_pushinteger(v, total_size);
    return 1;
}

static SQRESULT sq_sqlite3_stmt_colsSizeAsArray(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_stmt_INSTANCE();
    int col_count = sqlite3_column_count(self);
    sq_newarray(v, col_count);
    for(int i=0; i<col_count; ++i)
    {
        sq_pushinteger(v, sqlite3_column_bytes(self, i));
        sq_arrayset(v, -2, i);
    }
    return 1;
}

#define _DECL_FUNC(name,nparams,tycheck, isStatic) {_SC(#name),  sq_sqlite3_stmt_##name,nparams,tycheck, isStatic}
static SQRegFunction sq_sqlite3_stmt_methods[] =
{
    _DECL_FUNC(constructor,  -2, _SC("xxs s|n|b|o"), SQFalse),

    _DECL_FUNC(stmt_ptr,  1, _SC("x"), SQFalse),
    _DECL_FUNC(get_db,  1, _SC("x"), SQFalse),
    _DECL_FUNC(finalize,  1, _SC("x"), SQFalse),
    _DECL_FUNC(prepare,  -2, _SC("xs s|n|b|o|u"), SQFalse),
    _DECL_FUNC(get_sql,  1, _SC("x"), SQFalse),
    _DECL_FUNC(bind,  3, _SC("xi s|n|b|o|u"), SQFalse),
    _DECL_FUNC(bind_stmt,  2, _SC("xx"), SQFalse),
    _DECL_FUNC(bind_exec_stmt,  2, _SC("xx"), SQFalse),
    _DECL_FUNC(bind_empty_null,  3, _SC("xi s|n|b|o|u"), SQFalse),
    _DECL_FUNC(bind_blob,  3, _SC("xis|u"), SQFalse),
    _DECL_FUNC(bind_zeroblob,  3, _SC("xii"), SQFalse),
    _DECL_FUNC(bind_values,  -2, _SC("x s|n|b|o|u"), SQFalse),
    _DECL_FUNC(bind_exec,  -2, _SC("x s|n|b|o|u"), SQFalse),
    _DECL_FUNC(bind_exec_get_one,  -2, _SC("x s|n|b|o|u"), SQFalse),
    _DECL_FUNC(bind_exec_get_first_row,  -2, _SC("x s|n|b|o|u"), SQFalse),
    _DECL_FUNC(bind_names,  2, _SC("x t|a"), SQFalse),
    _DECL_FUNC(bind_parameter_index,  2, _SC("xs"), SQFalse),
    _DECL_FUNC(bind_parameter_count,  1, _SC("x"), SQFalse),
    _DECL_FUNC(step,  1, _SC("x"), SQFalse),
    _DECL_FUNC(reset,  1, _SC("x"), SQFalse),
    _DECL_FUNC(next_row,  1, _SC("x"), SQFalse),
    _DECL_FUNC(colsAsArray,  1, _SC("x"), SQFalse),
    _DECL_FUNC(colsAsTable,  1, _SC("x"), SQFalse),
    _DECL_FUNC(colsTypeAsArray,  1, _SC("x"), SQFalse),
    _DECL_FUNC(colsDeclTypeAsArray,  1, _SC("x"), SQFalse),
    _DECL_FUNC(col_count,  1, _SC("x"), SQFalse),
    _DECL_FUNC(col_name,  2, _SC("xi"), SQFalse),
    _DECL_FUNC(colsNameAsArray,  1, _SC("x"), SQFalse),
    _DECL_FUNC(col_type,  2, _SC("xi"), SQFalse),
    _DECL_FUNC(col_declared_type,  2, _SC("xi"), SQFalse),
    _DECL_FUNC(asArray,  -1, _SC("xi"), SQFalse),
    _DECL_FUNC(asTable,  -1, _SC("xi"), SQFalse),
    _DECL_FUNC(asArrayOfArrays,  -1, _SC("xi"), SQFalse),
    _DECL_FUNC(asArrayOfTables,  -1, _SC("xi"), SQFalse),
    _DECL_FUNC(asSleArray,  -1, _SC("xx"), SQFalse),
    //todo manage/skip blobs for json
    _DECL_FUNC(asJsonArray,  -1, _SC("xbx"), SQFalse),
    _DECL_FUNC(asJsonObject,  -1, _SC("xx"), SQFalse),
    _DECL_FUNC(col,  2, _SC("x i|s"), SQFalse),
    _DECL_FUNC(col_slice,  4, _SC("x i|s ii"), SQFalse),
    _DECL_FUNC(asString,  2, _SC("x i|s"), SQFalse),
    _DECL_FUNC(asStringOrNull, 2, _SC("x i|s"), SQFalse),
    _DECL_FUNC(asInteger,  2, _SC("x i|s"), SQFalse),
    _DECL_FUNC(asBool,  2, _SC("x i|s"), SQFalse),
    _DECL_FUNC(asFloat,  2, _SC("x i|s"), SQFalse),
    _DECL_FUNC(all_blobs_size,  1, _SC("x"), SQFalse),
    _DECL_FUNC(colsSizeAsArray,  1, _SC("x"), SQFalse),
    {0,0}
};
#undef _DECL_FUNC

#ifdef SQLITE_ENABLE_SESSION

static const SQChar *SQLite3_Session_TAG = "SQLite3Session";

static SQRESULT get_sqlite3_session_instance(HSQUIRRELVM v, SQInteger idx, sqlite3_session **session)
{
    SQRESULT _rc_;
    if((_rc_ = sq_getinstanceup(v,idx,(SQUserPointer*)session,(void*)SQLite3_Session_TAG)) < 0) return _rc_;
    if(!*session) return sq_throwerror(v, _SC("session is closed"));
    return _rc_;
}

#define GET_sqlite3_session_INSTANCE_AT(idx, var_name)  \
	sqlite3_session *var_name=NULL; \
	if((_rc_ = get_sqlite3_session_instance(v,idx,&var_name)) < 0) return _rc_;
#define GET_sqlite3_session_INSTANCE() GET_sqlite3_session_INSTANCE_AT(1, self)

static SQRESULT sq_sqlite3_session_releasehook(SQUserPointer p, SQInteger size, void */*ep*/)
{
    sqlite3_session *session = ((sqlite3_session *)p);
    if(session) sqlite3session_delete(session);
    return 0;
}

static SQRESULT sq_sqlite3_session_constructor(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    _rc_ = SQ_ERROR;
    GET_sqlite3_INSTANCE_AT(2);
    SQ_OPT_STRING(v, 3, dbname, _SC("main"))
    sqlite3_session *session = 0;
    if(sqlite3session_create(self, dbname, &session) != SQLITE_OK)
    {
        _rc_ = sq_throwerror(v, sqlite3_errmsg(self));
    }
    else _rc_ = SQ_OK;
    sq_setinstanceup(v, 1, session); //replace self for this instance with this new sqlite3_stmt
    sq_setreleasehook(v,1, sq_sqlite3_session_releasehook);
    return _rc_;
}

static SQRESULT sq_sqlite3_session_attach(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_session_INSTANCE();
    SQ_OPT_STRING(v, 2, tables, NULL);
    sq_pushinteger(v, sqlite3session_attach(self, tables));
    return 1;
}

static SQRESULT sq_sqlite3_session_enable(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_INSTANCE();
    SQ_GET_BOOL(v, 2, enable);
    sq_pushinteger(v, sqlite3session_enable(self, enable));
    return 1;
}

static SQRESULT sq_sqlite3_session_isempty(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_INSTANCE();
    sq_pushinteger(v, sqlite3session_isempty(self));
    return 1;
}

static SQRESULT sq_sqlite3_session_xdelete(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_INSTANCE();
    sqlite3session_delete(self);
    sq_setinstanceup(v, 1, 0); //next calls will fail with "database is closed"
    return 0;
}

static SQRESULT sq_sqlite3_session_changeset(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_INSTANCE();
    int nChangeset;     /* OUT: Size of changeset blob in bytes */
    void *pChangeset;   /* OUT: Pointer to changeset blob */
    if(sqlite3session_changeset(self, &nChangeset, &pChangeset) != SQ_OK) return SQ_ERROR;
    sq_pushstring(v, (SQChar*)pChangeset, nChangeset);
    sqlite3_free(pChangeset);
    return 1;
}

static SQRESULT sq_sqlite3_session_invert(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_INSTANCE();
    SQ_GET_STRING(v, 2, changeset);
    int nOutChangeset;     /* OUT: Size of changeset blob in bytes */
    void *pOutChangeset;   /* OUT: Pointer to changeset blob */
    if(sqlite3changeset_invert(changeset_size, changeset, &nOutChangeset, &pOutChangeset) != SQ_OK) return SQ_ERROR;
    sq_pushstring(v, (SQChar*)pOutChangeset, nOutChangeset);
    sqlite3_free(pOutChangeset);
    return 1;
}

static SQRESULT sq_sqlite3_session_concat(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_INSTANCE();
    SQ_GET_STRING(v, 2, changeset1);
    SQ_GET_STRING(v, 3, changeset2);
    int nOutChangeset;     /* OUT: Size of changeset blob in bytes */
    void *pOutChangeset;   /* OUT: Pointer to changeset blob */
    if(sqlite3changeset_concat(changeset1_size, changeset1, changeset2_size, changeset2, &nOutChangeset, &pOutChangeset) != SQ_OK) return SQ_ERROR;
    sq_pushstring(v, (SQChar*)pOutChangeset, nOutChangeset);
    sqlite3_free(pOutChangeset);
    return 1;
}

static SQRESULT sq_sqlite3_session_diff(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_INSTANCE();
    SQ_GET_STRING(v, 2, fromDB);
    SQ_GET_STRING(v, 3, tbl);
    sq_pushinteger(v, sqlite3session_diff(self, fromDB, tbl, NULL));
    return 1;
}

static int xConflict(void *pCtx, int eConflict, sqlite3_changeset_iter *pIter){
  int ret = (int)(ptrdiff_t)pCtx;
  return ret;
}

static SQRESULT sq_sqlite3_session_apply(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_INSTANCE();
    GET_sqlite3_INSTANCE_AT_VAR(2, db);
    SQ_GET_STRING(v, 3, changeset);
    if(sqlite3changeset_apply(db, changeset_size, (void*)changeset, 0, xConflict, (void*)0) != SQ_OK) return SQ_ERROR;
    return 0;
}

#define _DECL_FUNC(name,nparams,tycheck, isStatic) {_SC(#name),  sq_sqlite3_session_##name,nparams,tycheck, isStatic}
static SQRegFunction sq_sqlite3_session_methods[] =
{
    _DECL_FUNC(constructor,  -2, _SC("xxs"), SQFalse),

    _DECL_FUNC(attach,  -1, _SC("xs"), SQFalse),
    _DECL_FUNC(enable,  2, _SC("xb"), SQFalse),
    _DECL_FUNC(isempty,  1, _SC("x"), SQFalse),
    _DECL_FUNC(invert,  2, _SC("xs"), SQFalse),
    _DECL_FUNC(concat,  3, _SC("xss"), SQFalse),
    _DECL_FUNC(diff,  3, _SC("xss"), SQFalse),
    _DECL_FUNC(changeset,  1, _SC("x"), SQFalse),
    _DECL_FUNC(xdelete,  1, _SC("x"), SQFalse),
    _DECL_FUNC(apply,  3, _SC("xxs"), SQFalse),
    {0,0}
};
#undef _DECL_FUNC

static const SQChar *SQLite3_Session_Iterator_TAG = "SQLite3SessionIterator";

static SQRESULT get_sqlite3_session_iterator_instance(HSQUIRRELVM v, SQInteger idx, sqlite3_changeset_iter **ppIter)
{
    SQRESULT _rc_;
    if((_rc_ = sq_getinstanceup(v,idx,(SQUserPointer*)ppIter,(void*)SQLite3_Session_Iterator_TAG)) < 0) return _rc_;
    if(!*ppIter) return sq_throwerror(v, _SC("changeset iterator is closed"));
    return _rc_;
}

#define GET_sqlite3_session_iterator_INSTANCE_AT(idx, var_name)  \
	sqlite3_changeset_iter *var_name=NULL; \
	if((_rc_ = get_sqlite3_session_iterator_instance(v,idx,&var_name)) < 0) return _rc_;
#define GET_sqlite3_session_iterator_INSTANCE() GET_sqlite3_session_iterator_INSTANCE_AT(1, self)

static SQRESULT sq_sqlite3_session_iterator_releasehook(SQUserPointer p, SQInteger size, void */*ep*/)
{
    sqlite3_changeset_iter *pIter = ((sqlite3_changeset_iter *)p);
    if(pIter) sqlite3changeset_finalize(pIter);
    return 0;
}

static SQRESULT sq_sqlite3_session_iterator_constructor(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    _rc_ = SQ_ERROR;
    SQ_GET_STRING(v, 2, changeset)
    sqlite3_changeset_iter *pIter = 0;
    if(sqlite3changeset_start(&pIter, changeset_size, changeset) != SQLITE_OK)
    {
        _rc_ = sq_throwerror(v, _SC("sqlite3_changeset_iter error %d"), _rc_);
    }
    else _rc_ = SQ_OK;
    sq_setinstanceup(v, 1, pIter); //replace self for this instance with this new sqlite3_stmt
    sq_setreleasehook(v,1, sq_sqlite3_session_iterator_releasehook);
    return _rc_;
}

static SQRESULT sq_sqlite3_session_iterator_next(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_iterator_INSTANCE();
    sq_pushinteger(v, sqlite3changeset_next(self));
    return 1;
}

static SQRESULT sq_sqlite3_session_iterator_op(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_iterator_INSTANCE();
    const char *pzTab;             /* OUT: Pointer to table name */
    int nCol;                     /* OUT: Number of columns in table */
    int Op;                       /* OUT: SQLITE_INSERT, DELETE or UPDATE */
    int bIndirect;                /* OUT: True for an 'indirect' change */
    if(sqlite3changeset_op(self, &pzTab, &nCol, &Op, &bIndirect) == SQLITE_OK)
    {
        sq_pushstring(v, _SC("table"), -1);
        sq_pushstring(v, pzTab, -1);
        sq_rawset(v, 3);
        sq_pushstring(v, _SC("ncol"), -1);
        sq_pushinteger(v, nCol);
        sq_rawset(v, 3);
        sq_pushstring(v, _SC("op"), -1);
        sq_pushinteger(v, Op);
        sq_rawset(v, 3);
        sq_pushstring(v, _SC("indirect"), -1);
        sq_pushinteger(v, bIndirect);
        sq_rawset(v, 3);
        sq_pushbool(v, SQTrue);
    }
    sq_pushbool(v, SQFalse);
    return 1;
}

static SQRESULT sq_sqlite3_session_iterator_push_value(HSQUIRRELVM v, int isNew)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_session_iterator_INSTANCE();
    SQ_GET_INTEGER(v, 2, col);
    sqlite3_value *pVal;
    int rc;
    if(isNew) rc = sqlite3changeset_new(self, col, &pVal);
    else rc = sqlite3changeset_old(self, col, &pVal);
    if(rc != SQLITE_OK) return sq_throwerror(v, _SC("sqlite3changeset_(old/new) %d"), rc);
    switch( sqlite3_value_type(pVal) ){
        case SQLITE_FLOAT: {
            sq_pushfloat(v, sqlite3_value_double(pVal));
            break;
        }
        case SQLITE_INTEGER: {
            sq_pushinteger(v, sqlite3_value_int64(pVal));
            break;
        }
        case SQLITE_BLOB:
        case SQLITE_TEXT: {
            sq_pushstring(v, (const SQChar*)sqlite3_value_text(pVal), sqlite3_value_bytes(pVal));
            break;
        }
        default: {
            assert( sqlite3_value_type(pVal)==SQLITE_NULL );
            sq_pushnull(v);
            break;
        }
    }
    return 1;
}

static SQRESULT sq_sqlite3_session_iterator_new(HSQUIRRELVM v)
{
    return sq_sqlite3_session_iterator_push_value(v, 1);
}

static SQRESULT sq_sqlite3_session_iterator_old(HSQUIRRELVM v)
{
    return sq_sqlite3_session_iterator_push_value(v, 0);
}

#define _DECL_FUNC(name,nparams,tycheck, isStatic) {_SC(#name),  sq_sqlite3_session_iterator_##name,nparams,tycheck, isStatic}
static SQRegFunction sq_sqlite3_session_iterator_methods[] =
{
    _DECL_FUNC(constructor, 2, _SC("xs"), SQFalse),

    _DECL_FUNC(next,  1, _SC("x"), SQFalse),
    _DECL_FUNC(op,  2, _SC("xt"), SQFalse),
    _DECL_FUNC(new,  2, _SC("xi"), SQFalse),
    _DECL_FUNC(old,  2, _SC("xi"), SQFalse),
    {0,0}
};
#undef _DECL_FUNC

#endif // SQLITE_ENABLE_SESSION

static SQRESULT sqlite3_exec_fmt(HSQUIRRELVM v, e_type_result type_result, int null_as_empty_str=1)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    //SQ_GET_STRING(v, 2, szSQL);
    sqlite3_stmt *stmt = 0;
    SQObjectType ptype = sq_gettype(v, 2);
    switch(ptype)
    {
    case OT_STRING:
        _rc_ = sq_sqlite3_stmt_prepare_aux(v, self, &stmt, 2);
        if(_rc_ < 0)
        {
            if(stmt) sqlite3_finalize(stmt);
            return _rc_;
        }
        break;
    case OT_INSTANCE:
        _rc_ = get_sqlite3_stmt_instance(v, 2, &stmt);
        if(_rc_ < 0) return _rc_;
        _rc_ = sq_sqlite3_stmt_bind_vargv(v, self, stmt, 2);
        if(_rc_ < 0) return _rc_;
        break;
    }

    int rc = sqlite3_step(stmt);

    if(type_result == tr_ddml)
    {
        if(rc != SQLITE_DONE)
        {
            if(ptype == OT_STRING) sqlite3_finalize(stmt);
            else sqlite3_reset(stmt);

            return sq_throwerror(v, "SQL is not a DDML %d %s", rc, sqlite3_errmsg(self));
        }
        sq_pushinteger(v, sqlite3_changes(sqlite3_db_handle(stmt)));
    }
    else
    {
        if(rc != SQLITE_ROW)
        {
            if(ptype == OT_STRING) sqlite3_finalize(stmt);
            else sqlite3_reset(stmt);
            return sq_throwerror(v, "%d %s", rc, sqlite3_errmsg(self));
        }
        switch(type_result)
        {
        case tr_first_row_first_col:
            sqlite3_stmt_push_value(v, stmt, 0, 0);
            break;
        case tr_first_row:
            sqlite3_stmt_row_asArray(v, stmt, NULL_AS_EMPTY_STR|AS_STRING_ALWAYS);
            break;
        case tr_all_rows:
            sqlite3_stmt_asArrayOfArrays(v, stmt, NULL_AS_EMPTY_STR|AS_STRING_ALWAYS);
            break;
        default:
            if(ptype == OT_STRING) sqlite3_finalize(stmt);
            else sqlite3_reset(stmt);
            return sq_throwerror(v, "Unknown requested return type %d", type_result);
        }
    }
    if(ptype == OT_STRING) sqlite3_finalize(stmt);
    else sqlite3_reset(stmt);
    return 1;
}

//lua_regex
#define FUNC_IS_REGEXP 1
#define FUNC_IS_FIND 2
#define FUNC_IS_FIND_COUNT 3
#define FUNC_IS_MATCH 4

static inline void push_match_capture(sqlite3_context *context, int i, LuaMatchState *ms)
{
    ptrdiff_t len = ms->capture[i].len;
    if(len == CAP_POSITION) sqlite3_result_int(context, ms->capture[i].init - ms->src_init);
    else sqlite3_result_text(context, ms->capture[0].init, ms->capture[0].len, SQLITE_TRANSIENT);
}

static void sqlite3_lua_find_base( sqlite3_context *context, int argc, sqlite3_value **argv, int funcType ) {
    assert(argc == 2);
    LuaMatchState ms;
    int isRegexp = funcType == FUNC_IS_REGEXP;
    const char *pattern = (const char *)sqlite3_value_text(argv[isRegexp ? 0 : 1]);
    const char *subject = (const char *)sqlite3_value_text(argv[isRegexp ? 1 : 0]);
    assert(argc == 2);
	if(!subject || !pattern){
		sqlite3_result_null(context);
		return;
	}
    size_t pattern_len = strlen(pattern);
    size_t subject_len = strlen(subject);

    switch(funcType)
    {
    case FUNC_IS_MATCH:
        {
            ptrdiff_t rc = lua_str_match(&ms, subject, subject_len, pattern, pattern_len, 0, 0, 0, 0);
            if(ms.error) sqlite3_result_error(context, ms.error, strlen(ms.error));
            else if(rc < 0) sqlite3_result_null(context);
            else if(ms.level){
                if(ms.level == 1) push_match_capture(context, 0, &ms);
                else {
                    //no way to differentiate more than one match
                    push_match_capture(context, 0, &ms);
                }
            } else {
                sqlite3_result_text(context, subject + ms.start_pos, ms.end_pos-ms.start_pos+1, SQLITE_TRANSIENT);
            }
        }
        break;
    case FUNC_IS_FIND_COUNT:
        {
            int count = 0;
            ms.end_pos = 0;
            ptrdiff_t idx = -1;
            while((idx = lua_str_find(&ms, subject, subject_len, pattern, pattern_len, ms.end_pos, 0, 0, 0)) >= 0) ++count;
            sqlite3_result_int(context, count);
        }
        break;
    case FUNC_IS_FIND:
    case FUNC_IS_REGEXP:
        {
            ptrdiff_t idx = lua_str_find(&ms, subject, subject_len, pattern, pattern_len, 0, 0, NULL, NULL);
            if(idx >= 0)
            {
                sqlite3_result_int(context, funcType == FUNC_IS_FIND ? idx : 1);
            }
            else
            {
                if(ms.error) sqlite3_result_error(context, ms.error, strlen(ms.error));
                else sqlite3_result_int(context, funcType == FUNC_IS_FIND ? idx : 0);
            }
        }
        break;
    }
}

static void sqlite3_lua_regexp( sqlite3_context *context, int argc, sqlite3_value **argv ) {
    sqlite3_lua_find_base(context, argc, argv, FUNC_IS_REGEXP);
}

static void sqlite3_lua_find( sqlite3_context *context, int argc, sqlite3_value **argv ) {
    sqlite3_lua_find_base(context, argc, argv, FUNC_IS_FIND);
}

static void sqlite3_lua_find_count( sqlite3_context *context, int argc, sqlite3_value **argv ) {
    sqlite3_lua_find_base(context, argc, argv, FUNC_IS_FIND_COUNT);
}

static void sqlite3_lua_match( sqlite3_context *context, int argc, sqlite3_value **argv ) {
    sqlite3_lua_find_base(context, argc, argv, FUNC_IS_MATCH);
}

static void sqlite3_lua_gsub( sqlite3_context *context, int argc, sqlite3_value **argv ) {
	assert(argc == 4);
	lua_char_buffer_st *result;
	const char *error_ptr;
	const char *subject = (const char *)sqlite3_value_text(argv[0]);
	const char *pattern = (const char *)sqlite3_value_text(argv[1]);
	const char *replacement = (const char *)sqlite3_value_text(argv[2]);
	int ntimes = sqlite3_value_int(argv[3]);
	if(ntimes < 0) ntimes = 0;

	if(!subject || !pattern || !replacement){
		sqlite3_result_null(context);
		return;
	}
	result = lua_str_gsub(subject, strlen(subject), pattern, strlen(pattern),
					  replacement, strlen(replacement), ntimes, &error_ptr, NULL, NULL);
	if(result){
		sqlite3_result_text(context, result->buf, result->used, SQLITE_TRANSIENT);
		free(result);
	}
	else sqlite3_result_error(context, error_ptr, strlen(error_ptr));
}

int set_sqlite3_lua_regex_func(sqlite3 *db)
{
    int nErr = sqlite3_create_function(db,  "lua_find", 2, SQLITE_UTF8, 0, sqlite3_lua_find, 0, 0);
	nErr += sqlite3_create_function(db,  "lua_find_count", 2, SQLITE_UTF8, 0, sqlite3_lua_find_count, 0, 0);
	nErr += sqlite3_create_function(db,  "lua_match", 2, SQLITE_UTF8, 0, sqlite3_lua_match, 0, 0);
	nErr += sqlite3_create_function(db,  "lua_gsub", 4, SQLITE_UTF8, 0, sqlite3_lua_gsub, 0, 0);
    nErr += sqlite3_create_function(db,  "lua_regexp", 2, SQLITE_UTF8, 0, sqlite3_lua_regexp, 0, 0);
	return nErr ? SQLITE_ERROR : SQLITE_OK;
}

static SQRESULT sq_sqlite3_close_release(HSQUIRRELVM v, sq_sqlite3_sdb *sdb)
{
    SQRESULT rc = SQ_ERROR;
    //printf("sq_sqlite3_close_release:%p\n", sdb);
    if(sdb && sdb->db)
    {
        sqlite3 *db = sdb->db;
        sdb->db = NULL; //prevent double entry/free, see sq_pushregistrytable(sdb->v) bellow
        /*
        sqlite3_stmt* statement = NULL;
        int count = 0;
        while ((statement = sqlite3_next_stmt(db, NULL)))
        {
            printf("sq_sqlite3_close_release:stmt: %p : %s\n", statement, sqlite3_sql(statement));
            sqlite3_finalize(statement);
            statement = c;
            count++;
        }
        if (count) return sq_throwerror(v, _SC("closing database with %d statements not closed."), count);
        */

        int close_rc = sqlite3_close_v2(db) == SQLITE_OK;
        if(!v || (v && close_rc))
        {
            rc = SQ_OK;

            //remove waekref from registrytable
            //printf("Closing %s\n", sqlite3_db_filename(db, "main"));
            sq_delete_on_registry_table(sdb->v, db);

            if(sdb->func)
            {
                sq_sqlite3_sdb_func *func, *func_next;
                func = sdb->func;
                while (func)
                {
                    func_next = func->next;
                    sq_release(sdb->v, &func->fn_step);
                    sq_release(sdb->v, &func->fn_finalize);
                    sq_release(sdb->v, &func->udata);
                    sq_free(func, sizeof(sq_sqlite3_sdb_func));
                    func = func_next;
                }
                sdb->func = NULL;
            }
            /* 'free' all references */
            sq_release(sdb->v, &sdb->busy_cb);
            sq_release(sdb->v, &sdb->busy_udata);
            sq_release(sdb->v, &sdb->progress_cb);
            sq_release(sdb->v, &sdb->progress_udata);
            sq_release(sdb->v, &sdb->trace_cb);
            sq_release(sdb->v, &sdb->trace_udata);
            sq_release(sdb->v, &sdb->update_hook_cb);
            sq_release(sdb->v, &sdb->update_hook_udata);
            sq_release(sdb->v, &sdb->authorizer_hook_cb);
            sq_release(sdb->v, &sdb->authorizer_hook_udata);
            sq_release(sdb->v, &sdb->null_value);

            sq_free(sdb, sizeof(sq_sqlite3_sdb));
        }
        else
        {
            if(v) return sq_throwerror(v, sqlite3_errmsg(db));
        }
    }
    return rc;
}

static SQRESULT sq_sqlite3_releasehook(SQUserPointer p, SQInteger size, void */*ep*/)
{
    sq_sqlite3_sdb *sdb = ((sq_sqlite3_sdb *)p);
    sq_sqlite3_close_release(0, sdb);
    return 0;
}

static SQRESULT sq_sqlite3_constructor(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    SQ_GET_STRING(v, 2, dbname);
    SQ_OPT_INTEGER(v, 3, flags, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | SQLITE_OPEN_SUBLATIN_NA_LIKE);
    //SQ_OPT_INTEGER(v, 3, flags, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE);
    sqlite3 *db;
    int rc = sqlite3_open_v2(dbname, &db, flags, 0);
    if(rc != SQLITE_OK) return sq_throwerror(v, "Failed to open database ! %d", rc);

    set_sqlite3_lua_regex_func(db);
    sq_sqlite3_sdb *sdb = (sq_sqlite3_sdb *)sq_malloc(sizeof(sq_sqlite3_sdb));
    memset(sdb, 0, sizeof(sq_sqlite3_sdb));
    sdb->db = db;
    sdb->v = v;
    sq_resetobject(&sdb->busy_cb);
    sq_resetobject(&sdb->busy_udata);
    sq_resetobject(&sdb->progress_cb);
    sq_resetobject(&sdb->progress_udata);
    sq_resetobject(&sdb->trace_cb);
    sq_resetobject(&sdb->trace_udata);
    sq_resetobject(&sdb->update_hook_cb);
    sq_resetobject(&sdb->update_hook_udata);
    sq_resetobject(&sdb->authorizer_hook_cb);
    sq_resetobject(&sdb->authorizer_hook_udata);
    sq_resetobject(&sdb->null_value);

    sq_setinstanceup(v, 1, sdb);
    sq_setreleasehook(v,1, sq_sqlite3_releasehook);

    //save a weakref to allow statement return it's db
    sq_pushuserpointer(v, sdb->db);
    sq_weakref(v, 1);
    sq_setonregistrytable(v);
    return 1;
}

static SQRESULT sq_sqlite3_close(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    if(sq_sqlite3_close_release(v, sdb) != SQ_OK) return SQ_ERROR;
    sq_setinstanceup(v, 1, 0); //next calls will fail with "database is closed"
    return 0;
}

static SQRESULT sq_sqlite3_db_ptr(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sq_pushuserpointer(v, self);
    return 1;
}

/* bool IsAutoCommitOn(  ) */
static SQRESULT sq_sqlite3_IsAutoCommitOn(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sq_pushbool(v, sqlite3_get_autocommit(self) ? SQTrue : SQFalse);
    return 1;
}

/* int changes(  ) */
static SQRESULT sq_sqlite3_changes(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sq_pushinteger(v, sqlite3_changes(self));
    return 1;
}

static SQRESULT sq_sqlite3_exec(HSQUIRRELVM v)
{
    return sqlite3_exec_fmt(v, tr_all_rows);
}

/* int exec_one_dml( const char * szSQL, ... ) */
static SQRESULT sq_sqlite3_exec_one_dml(HSQUIRRELVM v)
{
    return sqlite3_exec_fmt(v, tr_ddml);
}

/* int exec_dml( const char * szSQL  ) */
static SQRESULT sq_sqlite3_exec_dml(HSQUIRRELVM v)
{
	SQ_FUNC_VARS_NO_TOP(v);
	GET_sqlite3_INSTANCE();
	SQ_GET_STRING(v, 2, szSQL);
	char *errmsg;
	if(sqlite3_exec(self, szSQL, NULL, NULL, &errmsg) != SQLITE_OK){
		sq_throwerror(v, errmsg);
		sqlite3_free(errmsg);
		return SQ_ERROR;
	}
	sq_pushinteger(v, sqlite3_changes(self));
    return 1;
}

/* bool exec_get_one( std::string & result  , const char * szSQL  ) */
static SQRESULT sq_sqlite3_exec_get_one(HSQUIRRELVM v)
{
    return sqlite3_exec_fmt(v, tr_first_row_first_col);
}

static SQRESULT sq_sqlite3_exec_get_first_row(HSQUIRRELVM v)
{
    return sqlite3_exec_fmt(v, tr_first_row);
}

/* const char * get_db_name(  ) */
static SQRESULT sq_sqlite3_get_db_name(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sq_pushstring(v, sqlite3_db_filename(self, "main"), -1);
    return 1;
}

/* sqlite3_int64 last_row_id(  ) */
static SQRESULT sq_sqlite3_last_row_id(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sq_pushinteger(v, sqlite3_last_insert_rowid(self));
    return 1;
}

/* stmt * prepare( const char * sql  ) */
static SQRESULT sq_sqlite3_prepare(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    //GET_sqlite3_INSTANCE();
    SQ_GET_STRING(v, 2, sql);
    sq_pushstring(v, SQLite3_Stmt_TAG, -1);
    if(sq_getonroottable(v) == SQ_ERROR) return SQ_ERROR;
    sq_pushroottable(v);
    sq_push(v, 1);
    sq_push(v, 2);
    if(sq_call(v, 3, SQTrue, SQFalse) != SQ_OK) return SQ_ERROR;
    return 1;
}

//sqlite3_blob
static SQRESULT sq_sqlite3_blob_releasehook(SQUserPointer p, SQInteger size, void */*ep*/)
{
    sqlite3_blob *self = ((sqlite3_blob *)p);
    if(self) sqlite3_blob_close(self);
    return 0;
}

static SQRESULT sq_sqlite3_blob_close(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_blob_INSTANCE();
    sq_setinstanceup(v, 1, 0); //next calls will fail with "blob is closed"
    sq_pushinteger(v, sqlite3_blob_close(self));
    return 1;
}

static SQRESULT sq_sqlite3_blob_reopen(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_blob_INSTANCE();
    SQ_GET_INTEGER(v, 2, iRow);
    int rc = sqlite3_blob_reopen(self, iRow);
    if(rc != SQLITE_OK) return sq_sqlite3_blob_close(v);
    sq_pushinteger(v, rc);
    return 1;
}

static SQRESULT sq_sqlite3_blob_bytes(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_blob_INSTANCE();
    sq_pushinteger(v, sqlite3_blob_bytes(self));
    return 1;
}

static SQRESULT sq_sqlite3_blob_read(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_blob_INSTANCE();
    SQ_OPT_INTEGER(v, 2, size, 0);
    SQ_OPT_INTEGER(v, 3, offset, 0);
    if(!size) size = sqlite3_blob_bytes(self);
    SQChar *buf = sq_getscratchpad(v, size);
    int rc = sqlite3_blob_read(self, buf, size, offset);
    if(rc == SQLITE_OK) sq_pushstring(v, buf, size);
    else sq_pushnull(v);
    return 1;
}

static SQRESULT sq_sqlite3_blob_write(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_blob_INSTANCE();
    SQ_GET_STRING(v, 2, buf);
    SQ_OPT_INTEGER(v, 3, size, buf_size);
    SQ_OPT_INTEGER(v, 4, offset, 0);
    int rc = sqlite3_blob_write(self, buf, size, offset);
    sq_pushinteger(v, rc);
    return 1;
}

static SQRESULT sq_sqlite3_blob_open(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();
    SQ_GET_STRING(v, 2, zDb);
    SQ_GET_STRING(v, 3, zTable);
    SQ_GET_STRING(v, 4, zColumn);
    SQ_GET_INTEGER(v, 5, iRow);
    SQ_OPT_INTEGER(v, 6, flags, 0);

    sqlite3_blob *pBlob;
    int rc = sqlite3_blob_open(self, zDb, zTable, zColumn, iRow, flags, &pBlob);
    if(rc == SQLITE_OK)
    {
        sq_pushstring(v, SQLite3_Blob_TAG, -1);
        if(sq_getonregistrytable(v) == SQ_ERROR) return SQ_ERROR;
        sq_createinstance(v, -1);
        sq_setinstanceup(v, -1, pBlob);
        sq_setreleasehook(v, -1, sq_sqlite3_blob_releasehook);
    }
    else sq_pushnull(v);
    return 1;
}

#define _DECL_FUNC(name,nparams,tycheck) {_SC(#name),  sq_sqlite3_blob_##name,nparams,tycheck}
static SQRegFunction sq_sqlite3_blob_methods[] =
{
    _DECL_FUNC(close,  1, _SC("x")),
    _DECL_FUNC(reopen,  2, _SC("xi")),
    _DECL_FUNC(bytes,  1, _SC("x")),
    _DECL_FUNC(read,  -1, _SC("xii")),
    _DECL_FUNC(write,  -2, _SC("xsii")),
    {0,0}
};
#undef _DECL_FUNC

/* void set_busy_timeout( int nMillisecs  ) */
static SQRESULT sq_sqlite3_set_busy_timeout(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    //GET_sqlite3_INSTANCE();
    SQ_GET_INTEGER(v, 2, nMillisecs);
    //self->set_busy_timeout(nMillisecs);
    return 0;
}

/* int total_changes(  ) */
static SQRESULT sq_sqlite3_total_changes(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sq_pushinteger(v, sqlite3_total_changes(self));
    return 1;
}

static SQRESULT sq_sqlite3_enable_shared_cache(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    SQ_GET_BOOL(v, 2, bhow);
    sq_pushbool(v, sqlite3_enable_shared_cache(bhow) == SQLITE_OK);
    return 1;
}

static SQRESULT sq_sqlite3_sleep(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    SQ_GET_INTEGER(v, 2, n);
    sqlite3_sleep(n);
    return 0;
}

static SQRESULT sq_sqlite3_version(HSQUIRRELVM v)
{
    sq_pushstring(v, sqlite3_libversion(), -1);
    return 1;
}

static SQRESULT sq_sqlite3_threadsafe(HSQUIRRELVM v)
{
    sq_pushinteger(v, sqlite3_threadsafe());
    return 1;
}

static SQRESULT sq_sqlite3_config_single_thread(HSQUIRRELVM v)
{
    sq_pushinteger(v, sqlite3_config(SQLITE_CONFIG_SINGLETHREAD));
    return 1;
}

static SQRESULT sq_sqlite3_config_multi_thread(HSQUIRRELVM v)
{
    sq_pushinteger(v, sqlite3_config(SQLITE_CONFIG_MULTITHREAD));
    return 1;
}

static SQRESULT sq_sqlite3_config_serialized(HSQUIRRELVM v)
{
    sq_pushinteger(v, sqlite3_config(SQLITE_CONFIG_SERIALIZED));
    return 1;
}

static SQRESULT sq_sqlite3_errcode(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sq_pushinteger(v, sqlite3_errcode(self));
    return 1;
}

static SQRESULT sq_sqlite3_errmsg(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sq_pushstring(v, sqlite3_errmsg(self), -1);
    return 1;
}

#ifndef WIN32
static SQRESULT sq_sqlite3_temp_directory(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    const char *oldtemp = sqlite3_temp_directory;
    sq_pushstring(v, oldtemp, -1);

    if (_top_ > 1)
    {
        SQ_GET_STRING(v, 2, temp);
        if (sqlite3_temp_directory)
        {
            sqlite3_free((char*)sqlite3_temp_directory);
        }
        if (temp && temp_size)
        {
            sqlite3_temp_directory = sqlite3_mprintf("%s", temp);
        }
        else
        {
            sqlite3_temp_directory = NULL;
        }
    }
    return 1;
}
#endif

#ifdef SQLITE_HAS_CODEC

//#include "codecext.h"
/*
** Params: db, sql
** returns: code, compiled length or error message
*/
static SQRESULT sq_sqlite3_key(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    SQ_GET_STRING(v, 2, key);
    sq_pushinteger(v, sqlite3_key_v2(self, 0, key, key_size));
    return 1;
}

/*
** Params: db, sql
** returns: code, compiled length or error message
*/
static SQRESULT sq_sqlite3_rekey(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    SQ_GET_STRING(v, 2, key);
    sq_pushinteger(v, sqlite3_rekey_v2(self, 0, key, key_size));
    return 1;
}

#endif

#if !defined(SQLITE_OMIT_PROGRESS_CALLBACK) || !SQLITE_OMIT_PROGRESS_CALLBACK

/*
** progress handler:
** Params: database, number of opcodes, callback function, userdata
**
** callback function:
** Params: userdata
** returns: 0 to return immediatly and return SQLITE_ABORT, non-zero to continue
*/
static int db_progress_callback(void *udata)
{
    SQInteger result = 1; /* abort by default */
    sq_sqlite3_sdb *sdb = (sq_sqlite3_sdb*)udata;
    HSQUIRRELVM v = sdb->v;
    int top = sq_gettop(v);

    sq_pushobject(v, sdb->progress_cb);
    sq_pushroottable(v); //this
    sq_pushobject(v, sdb->progress_udata);

    /* call squilu function */
    if (sq_call(v, 2, SQTrue, SQFalse) == SQ_OK)
        sq_getinteger(v, -1, &result);

    sq_settop(v, top);
    return result;
}

static SQRESULT sq_sqlite3_progress_handler(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();

    /* clear progress handler */
    sqlite3_progress_handler(self, 0, NULL, NULL);

    sq_release(v, &sdb->progress_cb);
    sq_release(v, &sdb->progress_udata);

    sq_resetobject(&sdb->progress_cb);
    sq_resetobject(&sdb->progress_udata);

    SQObjectType otype = sq_gettype(v, 2);
    if (_top_ > 1 && (otype != OT_NULL))
    {
        SQ_GET_INTEGER(v, 2, nop);
        if(sq_gettype(v, 3) != OT_CLOSURE)
            return sq_throwerror(v, _SC("invalid second parameter expected closure"));

        sq_getstackobj(v, 3, &sdb->progress_cb);
        sq_addref(v, &sdb->progress_cb);
        if(_top_ > 3)
        {
            sq_getstackobj(v, 4, &sdb->progress_udata);
            sq_addref(v, &sdb->progress_udata);
        }

        /* set progress callback */
        sqlite3_progress_handler(self, nop, db_progress_callback, sdb);
    }

    return 0;
}

#else /* #if !defined(SQLITE_OMIT_PROGRESS_CALLBACK) || !SQLITE_OMIT_PROGRESS_CALLBACK */

static SQRESULT sq_sqlite3_progress_handler(HSQUIRRELVM v)
{
    return sq_throwerror(v, _SC("progress callback support disabled at compile time"));
    return 0;
}

#endif /* #if !defined(SQLITE_OMIT_PROGRESS_CALLBACK) || !SQLITE_OMIT_PROGRESS_CALLBACK */

/*
** trace callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, sql
*/
static void db_trace_callback(void *udata, const char *sql)
{
    sq_sqlite3_sdb *sdb = (sq_sqlite3_sdb*)udata;
    HSQUIRRELVM v = sdb->v;
    int top = sq_gettop(v);

    /* setup squirrel callback call */
    sq_pushobject(v, sdb->trace_cb);
    sq_pushroottable(v);
    sq_pushobject(v, sdb->trace_udata);
    sq_pushstring(v, sql, -1); /* traced sql statement */

    /* call squirrel function */
    sq_call(v, 3, SQFalse, SQFalse);
    /* ignore any error generated by this function */

    sq_settop(v, top);
}

static SQRESULT sq_sqlite3_trace(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();

    /* clear trace handler */
    sqlite3_trace(self, NULL, NULL);

    sq_release(v, &sdb->trace_cb);
    sq_release(v, &sdb->trace_udata);

    sq_resetobject(&sdb->trace_cb);
    sq_resetobject(&sdb->trace_udata);

    SQObjectType otype = sq_gettype(v, 2);
    if (_top_ > 1 && (otype != OT_NULL))
    {
        if(sq_gettype(v, 2) != OT_CLOSURE)
            return sq_throwerror(v, _SC("invalid fisrt parameter expected closure"));

        sq_getstackobj(v, 2, &sdb->trace_cb);
        sq_addref(v, &sdb->trace_cb);
        if(_top_ > 2)
        {
            sq_getstackobj(v, 3, &sdb->trace_udata);
            sq_addref(v, &sdb->trace_udata);
        }

        /* set trace callback */
        if(_top_ > 3)
        {
            SQBool b;
            sq_getbool(v, 4, &b);
            sqlite3_trace_v0(self, db_trace_callback, sdb, b == SQTrue);
        }
        else sqlite3_trace(self, db_trace_callback, sdb);
    }

    return 0;
}

/*
** update hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, sql
*/
static void db_update_hook_callback(void *udata, int update_type,
                char const *db_name, char const *table_name, sqlite3_int64 rowid)
{
    sq_sqlite3_sdb *sdb = (sq_sqlite3_sdb*)udata;
    HSQUIRRELVM v = sdb->v;
    int top = sq_gettop(v);

    /* setup squirrel callback call */
    sq_pushobject(v, sdb->update_hook_cb);
    sq_pushroottable(v);
    sq_pushobject(v, sdb->update_hook_udata);
    sq_pushinteger(v, update_type);
    sq_pushstring(v, db_name, -1);
    sq_pushstring(v, table_name, -1);
    sq_pushinteger(v, rowid);

    /* call squirrel function */
    sq_call(v, 6, SQFalse, SQFalse);
    /* ignore any error generated by this function */

    sq_settop(v, top);
}

static SQRESULT sq_sqlite3_update_hook(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();

    /* clear update handler */
    sqlite3_update_hook(self, NULL, NULL);

    sq_release(v, &sdb->update_hook_cb);
    sq_release(v, &sdb->update_hook_udata);

    sq_resetobject(&sdb->update_hook_cb);
    sq_resetobject(&sdb->update_hook_udata);

    SQObjectType otype = sq_gettype(v, 2);
    if (_top_ > 1 && (otype != OT_NULL))
    {
        if(sq_gettype(v, 2) != OT_CLOSURE)
            return sq_throwerror(v, _SC("invalid fisrt parameter expected closure"));

        sq_getstackobj(v, 2, &sdb->update_hook_cb);
        sq_addref(v, &sdb->update_hook_cb);
        if(_top_ > 2)
        {
            sq_getstackobj(v, 3, &sdb->update_hook_udata);
            sq_addref(v, &sdb->update_hook_udata);
        }

        /* set update_hook callback */
        sqlite3_update_hook(self, db_update_hook_callback, sdb);
    }

    return 0;
}

/*
** authorizer hook callback:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, sql
*/
#if 0
/******************************************* 3rd ************ 4th ***********/
#define SQLITE_CREATE_INDEX          1   /* Index Name      Table Name      */
#define SQLITE_CREATE_TABLE          2   /* Table Name      NULL            */
#define SQLITE_CREATE_TEMP_INDEX     3   /* Index Name      Table Name      */
#define SQLITE_CREATE_TEMP_TABLE     4   /* Table Name      NULL            */
#define SQLITE_CREATE_TEMP_TRIGGER   5   /* Trigger Name    Table Name      */
#define SQLITE_CREATE_TEMP_VIEW      6   /* View Name       NULL            */
#define SQLITE_CREATE_TRIGGER        7   /* Trigger Name    Table Name      */
#define SQLITE_CREATE_VIEW           8   /* View Name       NULL            */
#define SQLITE_DELETE                9   /* Table Name      NULL            */
#define SQLITE_DROP_INDEX           10   /* Index Name      Table Name      */
#define SQLITE_DROP_TABLE           11   /* Table Name      NULL            */
#define SQLITE_DROP_TEMP_INDEX      12   /* Index Name      Table Name      */
#define SQLITE_DROP_TEMP_TABLE      13   /* Table Name      NULL            */
#define SQLITE_DROP_TEMP_TRIGGER    14   /* Trigger Name    Table Name      */
#define SQLITE_DROP_TEMP_VIEW       15   /* View Name       NULL            */
#define SQLITE_DROP_TRIGGER         16   /* Trigger Name    Table Name      */
#define SQLITE_DROP_VIEW            17   /* View Name       NULL            */
#define SQLITE_INSERT               18   /* Table Name      NULL            */
#define SQLITE_PRAGMA               19   /* Pragma Name     1st arg or NULL */
#define SQLITE_READ                 20   /* Table Name      Column Name     */
#define SQLITE_SELECT               21   /* NULL            NULL            */
#define SQLITE_TRANSACTION          22   /* Operation       NULL            */
#define SQLITE_UPDATE               23   /* Table Name      Column Name     */
#define SQLITE_ATTACH               24   /* Filename        NULL            */
#define SQLITE_DETACH               25   /* Database Name   NULL            */
#define SQLITE_ALTER_TABLE          26   /* Database Name   Table Name      */
#define SQLITE_REINDEX              27   /* Index Name      NULL            */
#define SQLITE_ANALYZE              28   /* Table Name      NULL            */
#define SQLITE_CREATE_VTABLE        29   /* Table Name      Module Name     */
#define SQLITE_DROP_VTABLE          30   /* Table Name      Module Name     */
#define SQLITE_FUNCTION             31   /* NULL            Function Name   */
#define SQLITE_SAVEPOINT            32   /* Operation       Savepoint Name  */
#define SQLITE_COPY                  0   /* No longer used */
#define SQLITE_RECURSIVE            33   /* NULL            NULL            */
#endif
static int db_authorizer_hook_callback(void *udata, int action_code,
                char const *param3, char const *param4,
                char const *param5, char const *param6)
{
    sq_sqlite3_sdb *sdb = (sq_sqlite3_sdb*)udata;
    HSQUIRRELVM v = sdb->v;
    int top = sq_gettop(v);
    SQInteger result = SQLITE_OK;

    /* setup squirrel callback call */
    sq_pushobject(v, sdb->authorizer_hook_cb);
    sq_pushroottable(v);
    sq_pushobject(v, sdb->authorizer_hook_udata);
    sq_pushinteger(v, action_code);
    sq_pushstring(v, param3, -1);
    sq_pushstring(v, param4, -1);
    sq_pushstring(v, param5, -1);
    sq_pushstring(v, param6, -1);

    /* call squilu function */
    /* ignore any error generated by this function */
    if (sq_call(v, 7, SQTrue, SQFalse) == SQ_OK)
        sq_getinteger(v, -1, &result);

    sq_settop(v, top);
    return result;
}

static SQRESULT sq_sqlite3_set_authorizer(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();

    /* clear trace handler */
    sqlite3_set_authorizer(self, NULL, NULL);

    sq_release(v, &sdb->authorizer_hook_cb);
    sq_release(v, &sdb->authorizer_hook_udata);

    sq_resetobject(&sdb->authorizer_hook_cb);
    sq_resetobject(&sdb->authorizer_hook_udata);

    SQObjectType otype = sq_gettype(v, 2);
    if (_top_ > 1 && (otype != OT_NULL))
    {
        if(sq_gettype(v, 2) != OT_CLOSURE)
            return sq_throwerror(v, _SC("invalid fisrt parameter expected closure"));

        sq_getstackobj(v, 2, &sdb->authorizer_hook_cb);
        sq_addref(v, &sdb->authorizer_hook_cb);
        if(_top_ > 2)
        {
            sq_getstackobj(v, 3, &sdb->authorizer_hook_udata);
            sq_addref(v, &sdb->authorizer_hook_udata);
        }

        /* set authorizer_hook callback */
        sqlite3_set_authorizer(self, db_authorizer_hook_callback, sdb);
    }

    return 0;
}

/*
** busy handler:
** Params: database, callback function, userdata
**
** callback function:
** Params: userdata, number of tries
** returns: 0 to return immediatly and return SQLITE_BUSY, non-zero to try again
*/
static int db_busy_callback(void *udata, int tries)
{
    SQBool retry = SQFalse; /* abort by default */
    sq_sqlite3_sdb *sdb = (sq_sqlite3_sdb*)udata;
    HSQUIRRELVM v = sdb->v;
    int top = sq_gettop(v);

    sq_pushobject(v, sdb->busy_cb);
    sq_pushroottable(v);
    sq_pushobject(v, sdb->busy_udata);
    sq_pushinteger(v, tries);

    /* call squilu function */
    if (sq_call(v, 3, SQTrue, SQFalse) == SQ_OK)
        sq_getbool(v, -1, &retry);

    sq_settop(v, top);
    return retry == SQTrue;
}

static SQRESULT sq_sqlite3_busy_handler(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();

    /* clear busy handler */
    sqlite3_busy_handler(self, NULL, NULL);

    sq_release(v, &sdb->busy_cb);
    sq_release(v, &sdb->busy_udata);

    sq_resetobject(&sdb->busy_cb);
    sq_resetobject(&sdb->busy_udata);

    SQObjectType otype = sq_gettype(v, 2);
    if (_top_ > 1 && (otype != OT_NULL))
    {
        if(sq_gettype(v, 2) != OT_CLOSURE)
            return sq_throwerror(v, _SC("invalid fisrt parameter expected closure"));

        sq_getstackobj(v, 2, &sdb->busy_cb);
        sq_addref(v, &sdb->busy_cb);
        if(_top_ > 2)
        {
            sq_getstackobj(v, 3, &sdb->busy_udata);
            sq_addref(v, &sdb->busy_udata);
        }

        /* set busy callback */
        sqlite3_busy_handler(self, db_busy_callback, sdb);
    }

    return 0;
}

static SQRESULT sq_sqlite3_busy_timeout(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    SQ_GET_INTEGER(v, 2, timeout);

    sqlite3_busy_timeout(self, timeout);

    /* if there was a timeout callback registered, it is now
    ** invalid/useless. free any references we may have */
    sq_release(v, &sdb->busy_cb);
    sq_resetobject(&sdb->busy_cb);
    sq_release(v, &sdb->busy_udata);
    sq_resetobject(&sdb->busy_udata);

    return 0;
}

static SQRESULT sq_sqlite3_interrupt(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    sqlite3_interrupt(self);
    return 0;
}

/*
** =======================================================
** User Defined Functions - Context Methods
** =======================================================
*/
typedef struct
{
    sqlite3_context *ctx;
    HSQOBJECT udata;
    HSQUIRRELVM v;
} sq_sqlite3_context_st;

static const SQChar sq_sqlite3_context_TAG[]  = _SC("sqite3_context");

static SQRESULT sq_sqlite3_context_releasehook(SQUserPointer p, SQInteger size, void */*ep*/)
{
    sq_sqlite3_context_st *ctx = ((sq_sqlite3_context_st *)p);
    /* 'free' all references */
    sq_release(ctx->v, &ctx->udata);
    sq_free(ctx, sizeof(sq_sqlite3_context_st));
    return 0;
}

static SQRESULT sq_sqlite3_context_constructor(HSQUIRRELVM v)
{
    sq_sqlite3_context_st *ctx = (sq_sqlite3_context_st*)sq_malloc(sizeof(sq_sqlite3_context_st));
    ctx->ctx = NULL;
    ctx->v = v;
    sq_resetobject(&ctx->udata);
    sq_setinstanceup(v, 1, ctx);
    sq_setreleasehook(v,1, sq_sqlite3_context_releasehook);
    return 1;
}

#define SQ_SQLITE3_GETCONTEXT(v, idx) \
    sq_sqlite3_context_st *self;\
    if((_rc_ = sq_getinstanceup(v, idx, (void**)&self, (void*)sq_sqlite3_context_TAG)) < 0) return _rc_;

#define GET_sqlite3_context_INSTANCE() SQ_SQLITE3_GETCONTEXT(v,1)

static SQRESULT sq_sqlite3_context__tostring(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    char buff[64];
    snprintf(buff, sizeof(buff), "sqlite function context (%p)", self);
    sq_pushstring(v, buff, -1);
    return 1;
}

static SQRESULT sq_sqlite3_context_check_aggregate(HSQUIRRELVM v, sq_sqlite3_context_st *ctx)
{
    sq_sqlite3_sdb_func *func = (sq_sqlite3_sdb_func*)sqlite3_user_data(ctx->ctx);
    if (sq_isclosure(func->fn_finalize))
    {
        return sq_throwerror(v, "attempt to call aggregate method from scalar function");
    }
    return 1;
}

static SQRESULT sq_sqlite3_context_user_data(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    sq_sqlite3_sdb_func *func = (sq_sqlite3_sdb_func*)sqlite3_user_data(self->ctx);
    sq_pushobject(v, func->udata);
    return 1;
}

static SQRESULT sq_sqlite3_context_aggregate_data(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_context_INSTANCE();
    sq_sqlite3_context_check_aggregate(v, self);
    if(_top_ < 2)
    {
        sq_pushobject(v, self->udata);
    }
    else
    {
        sq_release(v, &self->udata);
        sq_getstackobj(v, 2, &self->udata);
        sq_addref(v, &self->udata);
    }
    return 0;
}

static SQRESULT sq_sqlite3_context_aggregate_count(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    sq_sqlite3_context_check_aggregate(v, self);
    sq_pushinteger(v, sqlite3_aggregate_count(self->ctx));
    return 1;
}

struct sq_sqlite3_context_auxdata_st
{
    HSQUIRRELVM vm;
    HSQOBJECT data;
};

static SQRESULT sq_sqlite3_context_get_auxdata(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_INTEGER(v, 2, argn);
    sq_sqlite3_context_auxdata_st *data = (sq_sqlite3_context_auxdata_st*)sqlite3_get_auxdata(self->ctx, argn);
    if(data)
    {
        sq_pushobject(v, data->data);
    }
    else
    {
        sq_pushnull(v);
    }
    return 1;
}

static void sq_sqlite3_context_free_auxdata(void *aux_data)
{
    sq_sqlite3_context_auxdata_st *data = (sq_sqlite3_context_auxdata_st*)aux_data;
    if(data)
    {
        sq_release(data->vm, &data->data);
        sqlite3_free(data);
    }
}
static SQRESULT sq_sqlite3_context_set_auxdata(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_INTEGER(v, 2, argn);
    sq_sqlite3_context_auxdata_st *data = (sq_sqlite3_context_auxdata_st*)sqlite3_malloc(sizeof(*data));
    if(data)
    {
        data->vm = v;
        sq_resetobject(&data->data);
        sq_getstackobj(v, 3, &data->data);
        sq_addref(v, &data->data);
        sqlite3_set_auxdata(self->ctx, argn, data, sq_sqlite3_context_free_auxdata);
    }
    return 0;
}

static SQRESULT sq_sqlite3_context_result_blob(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_STRING(v, 2, blob);
    sqlite3_result_blob(self->ctx, (const void*)blob, blob_size, SQLITE_TRANSIENT);
    return 0;
}

static SQRESULT sq_sqlite3_context_result_double(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_FLOAT(v, 2, d);
    sqlite3_result_double(self->ctx, d);
    return 0;
}

static SQRESULT sq_sqlite3_context_result_error(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_STRING(v,2, err);
    sqlite3_result_error(self->ctx, err, err_size);
    return 0;
}

static SQRESULT sq_sqlite3_context_result_int(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_INTEGER(v, 2, i);
    sqlite3_result_int(self->ctx, i);
    return 0;
}

static SQRESULT sq_sqlite3_context_result_null(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    sqlite3_result_null(self->ctx);
    return 0;
}

static SQRESULT sq_sqlite3_context_result_text(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_STRING(v, 2, text);
    sqlite3_result_text(self->ctx, text, text_size, SQLITE_TRANSIENT);
    return 0;
}

static SQRESULT sq_sqlite3_context_result_subtype(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_INTEGER(v, 2, subtype);
    sqlite3_result_subtype(self->ctx, subtype);
    return 0;
}
/* no way to recover parameter value after function call
static SQRESULT sq_sqlite3_context_value_subtype(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_context_INSTANCE();
    SQ_GET_INTEGER(v, 2, param_idx);
    sq_pushinteger(v, sqlite3_value_subtype(self->ctx, subtype));
    return 1;
}
*/

#define _DECL_FUNC(name,nparams,tycheck) {_SC(#name),  sq_sqlite3_context_##name,nparams,tycheck}
static SQRegFunction sq_sqlite3_context_methods[] =
{
    _DECL_FUNC(constructor,  1, _SC("x")),

    _DECL_FUNC(user_data,  1, _SC("x")),
    _DECL_FUNC(aggregate_data,  -1, _SC("x.")),
    _DECL_FUNC(aggregate_count,  1, _SC("x")),
    _DECL_FUNC(result_null,  1, _SC("x")),
    _DECL_FUNC(result_double,  2, _SC("xn")),
    _DECL_FUNC(result_int,  2, _SC("xi")),
    _DECL_FUNC(result_text,  2, _SC("xs")),
    _DECL_FUNC(result_blob,  2, _SC("xs")),
    _DECL_FUNC(result_error,  2, _SC("xs")),
    _DECL_FUNC(result_subtype,  2, _SC("xi")),
    //_DECL_FUNC(value_subtype,  2, _SC("xi")),
    _DECL_FUNC(get_auxdata,  2, _SC("xi")),
    _DECL_FUNC(set_auxdata,  3, _SC("xi.")),
    _DECL_FUNC(_tostring,  -1, _SC("x")),
    {0,0}
};
#undef _DECL_FUNC

/*
** callback functions used when calling registered sql functions
*/

static void sqlite3_push_value(HSQUIRRELVM v, sqlite3_value *value)
{
    const char *tmp_value;
    switch (sqlite3_value_type(value))
    {
    case SQLITE_TEXT:
        tmp_value = (const char*) sqlite3_value_text(value);
        if(tmp_value) sq_pushstring(v, tmp_value, sqlite3_value_bytes(value));
        else push_sqlite3_null(v);
        break;

    case SQLITE_INTEGER:
    {
        sqlite_int64 i64 = sqlite3_value_int64(value);
        SQInteger n = (SQInteger)i64;
        if (n == i64)
            sq_pushinteger(v, n);
        else
            sq_pushstring(v, (const char*) sqlite3_value_text(value), sqlite3_value_bytes(value));
    }
    break;

    case SQLITE_FLOAT:
        sq_pushfloat(v, sqlite3_value_double(value));
        break;

    case SQLITE_BLOB:
        tmp_value = (const char*) sqlite3_value_blob(value);
        if(tmp_value) sq_pushstring(v, tmp_value, sqlite3_value_bytes(value));
        else push_sqlite3_null(v);
        break;

    case SQLITE_NULL:
        push_sqlite3_null(v);
        break;

    default:
        /* things done properly (SQLite + SquiLu SQLite)
        ** this should never happen */
        push_sqlite3_null(v);
        break;
    }
}
//#if 0

static SQRESULT new_context_instance(HSQUIRRELVM v, sq_sqlite3_context_st **ctx)
{
    sq_pushregistrytable(v);
    sq_pushstring(v, sq_sqlite3_context_TAG,-1);
    int rc = sq_rawget(v, -2);
    sq_remove(v, -2); //remove registrytable
    sq_pushroottable(v);
    rc = sq_call(v, 1, SQTrue, SQFalse);
    sq_remove(v, -2); //class
    rc = sq_getinstanceup(v, -1, (void**)ctx, (void*)sq_sqlite3_context_TAG);
    return rc;
}
/* scalar function to be called
** callback params: context, values... */
static void db_sql_normal_function(sqlite3_context *context, int argc, sqlite3_value **argv)
{
    sq_sqlite3_sdb_func *func = (sq_sqlite3_sdb_func*)sqlite3_user_data(context);
    HSQUIRRELVM v = func->sdb->v;

    int n;
    sq_sqlite3_context_st *ctx;
    int top = sq_gettop(v);

    /* ensure there is enough space in the stack */
    sq_reservestack(v, argc + 5);

    sq_pushobject(v, func->fn_step);
    sq_pushroottable(v);

    if (!sq_isclosure(func->fn_finalize))
    {
        if(new_context_instance(v, &ctx) != SQ_OK)
            sqlite3_result_error(context, sq_getlasterror_str(v), -1);
    }
    else
    {
        /* reuse context userdata value */
        void *p = sqlite3_aggregate_context(context, 1);
        /* i think it is OK to use assume that using a light user data
        ** as an entry on SquiLu REGISTRY table will be unique */
        sq_pushregistrytable(v);
        sq_pushuserpointer(v, p);
        /* context table */
        if(sq_rawget(v, -2) != SQ_OK)
        {
            /* not yet created? */
            sq_poptop(v); //remove null
            new_context_instance(v, &ctx);
            sq_pushuserpointer(v, p);
            sq_push(v, -2);
            sq_rawset(v, -4); //insert into registrytable
        }
        sq_remove(v, -2); //remove registrytable
    }

    /* push params */
    for (n = 0; n < argc; ++n)
    {
        sqlite3_push_value(v, argv[n]);
    }

    /* set context */
    ctx->ctx = context;

    if (sq_call(v, argc + 2, SQFalse, SQFalse) != SQ_OK)   //2 = roottable + ctx
    {
        sqlite3_result_error(context, sq_getlasterror_str(v), -1);
    }
    sq_settop(v, top);
}

static void db_sql_finalize_function(sqlite3_context *context)
{
    sq_sqlite3_sdb_func *func = (sq_sqlite3_sdb_func*)sqlite3_user_data(context);
    HSQUIRRELVM v = func->sdb->v;
    void *p = sqlite3_aggregate_context(context, 1); /* minimal mem usage */
    sq_sqlite3_context_st *ctx;
    int top = sq_gettop(v);

    sq_pushobject(v, func->fn_finalize);
    sq_pushroottable(v);

    /* i think it is OK to use assume that using a light user data
    ** as an entry on SquiLu REGISTRY table will be unique */
    sq_pushregistrytable(v);
    sq_pushuserpointer(v, p);
    /* remove it from registry but we'll use it last time here */
    /* context table */
    if(sq_deleteslot(v, -2, SQTrue) != SQ_OK)
    {
        /* not yet created? - shouldn't happen in finalize function */
        sq_pop(v, 1);
        new_context_instance(v, &ctx);
        sq_pushuserpointer(v, p);
        sq_push(v, -2);
        sq_rawset(v, -4);
    }
    sq_remove(v, -2); //registrytable

    /* set context */
    ctx->ctx = context;

    if (sq_call(v, 1, SQFalse, SQFalse) != SQ_OK)
    {
        sqlite3_result_error(context, sq_getlasterror_str(v), -1);
    }
    sq_settop(v, top);
}

/*
** Register a normal function
** Params: db, function name, number arguments, [ callback | step, finalize], user data
** Returns: true on sucess
**
** Normal function:
** Params: context, params
**
** Aggregate function:
** Params of step: context, params
** Params of finalize: context
*/
static SQRESULT db_register_function(HSQUIRRELVM v, int aggregate)
{
    SQ_FUNC_VARS(v);
    SQInteger ftype = 0;
    GET_sqlite3_INSTANCE();
    SQ_GET_STRING(v, 2, name);
    SQ_GET_INTEGER(v, 3, nargs);
    if(sq_gettype(v, 4) != OT_CLOSURE)
        return sq_throwerror(v, "invalid parameter 3 expected closure");
    if (aggregate)
    {
        if(sq_gettype(v,5) != OT_CLOSURE)
            return sq_throwerror(v, "invalid parameter 4 expected closure");
    } else {
        sq_optinteger(v, 5, &ftype, 0);
    }

    sq_sqlite3_sdb_func *func;
    /* maybe an alternative way to allocate memory should be used/avoided */
    func = (sq_sqlite3_sdb_func*)sq_malloc(sizeof(sq_sqlite3_sdb_func));
    memset(func, 0, sizeof(sq_sqlite3_sdb_func));
    _rc_ = sqlite3_create_function(
               self, name, nargs, SQLITE_UTF8 | (ftype == SQLITE_DETERMINISTIC ? ftype : 0),
                func,
               aggregate ? NULL : db_sql_normal_function,
               aggregate ? db_sql_normal_function : NULL,
               aggregate ? db_sql_finalize_function : NULL
           );

    if (_rc_ == SQLITE_OK)
    {
        /* save registered function in db function list */
        func->sdb = sdb;
        func->next = sdb->func;
        sdb->func = func;

        /* save the setp/normal function callback */
        sq_resetobject(&func->fn_step);
        sq_getstackobj(v, 4, &func->fn_step);
        sq_addref(v, &func->fn_step);
        /* save the finalize function callback */
        sq_resetobject(&func->fn_finalize);
        if (aggregate)
        {
            sq_getstackobj(v, 5, &func->fn_finalize);
            sq_addref(v, &func->fn_finalize);
        }

        /* save user data */
        sq_resetobject(&func->udata);
        int udata_idx = aggregate ? 6 : 5;
        if(_top_ >= udata_idx)
        {
            sq_getstackobj(v, udata_idx, &func->udata);
            sq_addref(v, &func->udata);
        }
    }
    else
    {
        /* free allocated memory */
        sq_free(func, sizeof(sq_sqlite3_sdb_func));
    }

    sq_pushbool(v, _rc_ == SQLITE_OK ? 1 : 0);
    return 1;
}
//#endif

static SQRESULT sq_sqlite3_create_function(HSQUIRRELVM v)
{
    return db_register_function(v, 0);
}

static SQRESULT sq_sqlite3_create_aggregate(HSQUIRRELVM v)
{
    return db_register_function(v, 1);
}

static SQRESULT sq_sqlite3_backup(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();
    SQ_GET_STRING(v, 2, fname);
    SQ_OPT_STRING(v, 3, dbSrcName, _SC("main"));
    SQ_OPT_STRING(v, 4, dbDestName, _SC("main"));

    sqlite3 *pDest;
    sqlite3_backup *pBackup;

    int rc = sqlite3_open(fname, &pDest);
    if(rc!=SQLITE_OK)
    {
        sqlite3_close(pDest);
        return sq_throwerror(v, _SC("SQLite can't open %s"), fname);
    }

    pBackup = sqlite3_backup_init(pDest, dbDestName, self, dbSrcName);
    if( pBackup==0 ){
        rc = sq_throwerror(v, _SC("Error: %s\n"), sqlite3_errmsg(pDest));
        sqlite3_close(pDest);
    }
    while(  (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
    sqlite3_backup_finish(pBackup);
    if( rc==SQLITE_DONE ){
        rc = SQ_OK;
    }else{
        rc = sq_throwerror(v, _SC("Error: %s\n"), sqlite3_errmsg(pDest));
    }
    sqlite3_close(pDest);

    return rc;
}

static SQRESULT sq_sqlite3_restore(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();
    SQ_GET_STRING(v, 2, fname);
    SQ_OPT_STRING(v, 3, dbSrcName, _SC("main"));
    SQ_OPT_STRING(v, 4, dbDestName, _SC("main"));

    sqlite3 *pSrc;
    sqlite3_backup *pBackup;
    int nTimeout = 0;

    int rc = sqlite3_open(fname, &pSrc);
    if(rc!=SQLITE_OK)
    {
        sqlite3_close(pSrc);
        return sq_throwerror(v, _SC("SQLite can't open %s"), fname);
    }

    pBackup = sqlite3_backup_init(self, dbDestName, pSrc, dbSrcName);
    if( pBackup==0 ){
        sqlite3_close(pSrc);
        return sq_throwerror(v, _SC("Error: %s\n"), sqlite3_errmsg(self));
    }
    while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
          || rc==SQLITE_BUSY  ){
      if( rc==SQLITE_BUSY ){
        if( nTimeout++ >= 3 ) break;
        sqlite3_sleep(100);
      }
    }

    sqlite3_backup_finish(pBackup);
    if( rc==SQLITE_DONE ){
        rc = SQ_OK;
    }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
        rc = sq_throwerror(v, _SC("Error: source database is busy\n"));
    }else{
        rc = sq_throwerror(v, _SC("Error: %s\n"), sqlite3_errmsg(self));
    }
    sqlite3_close(pSrc);

    return rc;
}

#ifndef SQLITE_OMIT_LOAD_EXTENSION
static SQRESULT sq_sqlite3_enable_load_extension(HSQUIRRELVM v)
{
    SQ_FUNC_VARS_NO_TOP(v);
    GET_sqlite3_INSTANCE();
    SQ_GET_BOOL(v, 2, onOff);
    sqlite3_enable_load_extension(self, onOff);
    return 0;
}
#endif

#if 0
/**
The number/order of fields should be the same to use this function
it'll save temporary data convertion.
*/
static SQRESULT sq_sqlite3_exec_loop(HSQUIRRELVM v)
{
    SQ_FUNC_VARS(v);
    GET_sqlite3_INSTANCE();
    SQ_GET_STRING(v, 2, outer_sql);
    SQ_GET_STRING(v, 3, inner1_sql);
    SQ_OPT_STRING(v, 4, inner2_sql, NULL);

    sqlite3_stmt *stmt_outer = NULL, *stmt_inner1 = NULL, *stmt_inner2 = NULL;

    int rc = sqlite3_prepare_v2(self, outer_sql, outer_sql_size, &stmt_outer, NULL);
    if(rc == SQLITE_OK)
    {
        rc = sqlite3_prepare_v2(self, inner1_sql, inner1_sql_size, &stmt_inner1, NULL);
        if(rc == SQLITE_OK)
        {
            if(inner2_sql)
            {
                rc = sqlite3_prepare_v2(self, inner2_sql, inner2_sql_size, &stmt_inner2, NULL);
            }
            if(rc == SQLITE_OK)
            {
                int outer_col_count = sqlite3_column_count(stmt_outer);
                int inner1_param_count = sqlite3_bind_parameter_count(stmt_inner1);
                int inner2_param_count = stmt_inner2 ? sqlite3_bind_parameter_count(stmt_inner2) : -1;
                if( (inner1_param_count <= outer_col_count) && ((inner2_param_count > 0)
                                            && (inner2_param_count <= outer_col_count) ) )
                {
                    while(sqlite3_step(stmt_outer) == SQLITE_ROW)
                    {
                        for(int i=0; i < inner1_param_count; ++i)
                        {
                            rc = sqlite3_bind_value(stmt_inner1, i+1, sqlite3_column_value(stmt_outer, i));
                            if(rc != SQLITE_OK) break;
                        }
                        if(rc == SQLITE_OK)
                        {
                            rc = sqlite3_step(stmt_inner1);
                            sqlite3_reset(stmt_inner1);
                        }
                        if(rc != SQLITE_DONE) break;
                        if((inner2_param_count > 0) && (sqlite3_changes(self) == 0))
                        {
                            for(int i=0; i < inner2_param_count; ++i)
                            {
                                rc = sqlite3_bind_value(stmt_inner2, i+1, sqlite3_column_value(stmt_outer, i));
                                if(rc != SQLITE_OK) break;
                            }
                            if(rc == SQLITE_OK)
                            {
                                rc = sqlite3_step(stmt_inner2);
                                sqlite3_reset(stmt_inner2);
                            }
                            if(rc != SQLITE_DONE) break;
                        }
                    }
                }
                else
                {
                    return sq_throwerror(v, _SC("bind parameter count mismatch expect %d : %d but got outer %d"),
                                    inner1_param_count, inner2_param_count, outer_col_count);
                }
            }
        }
    }

    if(stmt_outer) sqlite3_finalize(stmt_outer);
    if(stmt_inner1) sqlite3_finalize(stmt_inner1);
    if(stmt_inner2) sqlite3_finalize(stmt_inner2);

    sq_pushinteger(v, rc);
    return 1;
}
#endif // 0

#define _DECL_FUNC(name,nparams,tycheck) {_SC(#name),  sq_sqlite3_##name,nparams,tycheck}
static SQRegFunction sq_sqlite3_methods[] =
{
    _DECL_FUNC(constructor,  -2, _SC("xsi")),
    _DECL_FUNC(close,  1, _SC("x")),
    _DECL_FUNC(db_ptr,  1, _SC("x")),

    _DECL_FUNC(IsAutoCommitOn,  1, _SC("x")),
    _DECL_FUNC(version,  1, _SC("x|y")),
    _DECL_FUNC(threadsafe,  1, _SC("x|y")),
    _DECL_FUNC(config_single_thread,  1, _SC("y")),
    _DECL_FUNC(config_multi_thread,  1, _SC("y")),
    _DECL_FUNC(config_serialized,  1, _SC("y")),
    _DECL_FUNC(errcode,  1, _SC("x")),
    _DECL_FUNC(errmsg,  1, _SC("x")),
    _DECL_FUNC(sleep,  1, _SC("x")),
    _DECL_FUNC(interrupt,  1, _SC("x")),
    _DECL_FUNC(progress_handler,  -2, _SC("x i|o c .")),
    _DECL_FUNC(trace,  -2, _SC("x c|o .")),
    _DECL_FUNC(update_hook,  -2, _SC("x c|o .")),
    _DECL_FUNC(set_authorizer,  -2, _SC("x c|o .")),
    _DECL_FUNC(busy_handler,  -2, _SC("x c|o .")),
    _DECL_FUNC(busy_timeout,  2, _SC("xi")),
    _DECL_FUNC(create_function,  -4, _SC("xsic.")),
    _DECL_FUNC(create_aggregate,  -5, _SC("xsicc.")),
#ifndef WIN32
    _DECL_FUNC(temp_directory,  2, _SC("xs")),
#endif
    _DECL_FUNC(enable_shared_cache,  2, _SC("xb")),
    _DECL_FUNC(changes,  1, _SC("x")),
    _DECL_FUNC(exec,  -2, _SC("x s|x")),
    _DECL_FUNC(exec_dml,  2, _SC("xs")),
    _DECL_FUNC(exec_one_dml,  -2, _SC("x s|x")),
    _DECL_FUNC(exec_get_first_row,  -2, _SC("x s|x")),
    _DECL_FUNC(exec_get_one,  -2, _SC("x s|x")),
    //_DECL_FUNC(exec_loop,  -3, _SC("x sss")),
    _DECL_FUNC(get_db_name,  1, _SC("x")),
    _DECL_FUNC(last_row_id,  1, _SC("x")),
    _DECL_FUNC(prepare,  2, _SC("xs")),
    _DECL_FUNC(blob_open, -5, _SC("xsssii")),
    _DECL_FUNC(set_busy_timeout,  -1, _SC("xi")),
    _DECL_FUNC(total_changes,  1, _SC("x")),
    _DECL_FUNC(backup,  -2, _SC("xsss")),
    _DECL_FUNC(restore,  -2, _SC("xsss")),
#ifndef SQLITE_OMIT_LOAD_EXTENSION
    _DECL_FUNC(enable_load_extension, 2, _SC("xb")),
#endif
#ifdef SQLITE_HAS_CODEC
    _DECL_FUNC(key,  2, _SC("xs")),
    _DECL_FUNC(rekey,  2, _SC("xs")),
#endif
    {0,0}
};

#undef _DECL_FUNC

#define INT_CONST(v,num) 	sq_pushstring(v,_SC(#num),-1);sq_pushinteger(v,num);sq_newslot(v,-3,SQTrue);


#ifdef __cplusplus
extern "C" {
#endif

    SQRESULT sqext_register_SQLite3(HSQUIRRELVM v)
    {
        sq_insertfunc(v, _SC("sle2vecOfvec"), sq_sle2vecOfvec, -2, _SC(". s|x a i"), SQTrue);
        sq_insertfunc(v, _SC("get_sle_size"), sq_get_sle_size, -2, _SC(".i"), SQTrue);
        sq_insertfunc(v, _SC("add2sle"), sq_add2sle, -3, _SC(".xsi"), SQTrue);
        sq_pushconsttable(v);
#define INT_SLE_CONST(num) 	sq_pushstring(v,_SC("SLE_" #num),-1);sq_pushinteger(v,num);sq_newslot(v,-3,SQTrue);
        INT_SLE_CONST(IBYTE1);
        INT_SLE_CONST(IBYTE2);
        INT_SLE_CONST(IBYTE3);
        INT_SLE_CONST(IBYTE4);
        INT_SLE_CONST(IBYTE_MAX);
        INT_SLE_CONST(SLEMARK);
        INT_SLE_CONST(SLEEND);

        sq_poptop(v); //remove const table


        HSQOBJECT sqlite3_NULL;
        sq_pushregistrytable(v);
        sq_pushstring(v, sqlite3_NULL_Name,-1);
        sq_newuserdata(v, sizeof(void*));
        sq_settypetag(v, -1, (SQUserPointer)sqlite3_NULL_Name);
        sq_resetobject(&sqlite3_NULL);
        sq_getstackobj(v, -1, &sqlite3_NULL);
        sq_newtable(v);
        sq_insert_reg_funcs(v, sqlite3_NULL_methods);
        sq_setdelegate(v, -2);
        sq_newslot(v,-3,SQTrue);

        sq_pushstring(v,sq_sqlite3_context_TAG,-1);
        sq_newclass(v,SQFalse);
        sq_settypetag(v,-1,(SQUserPointer)sq_sqlite3_context_TAG);
        sq_insert_reg_funcs(v, sq_sqlite3_context_methods);
        sq_newslot(v,-3,SQTrue);

        sq_pushstring(v,SQLite3_Blob_TAG,-1);
        sq_newclass(v,SQFalse);
        sq_settypetag(v,-1,(SQUserPointer)SQLite3_Blob_TAG);
        sq_insert_reg_funcs(v, sq_sqlite3_blob_methods);
        sq_newslot(v,-3,SQTrue);

        sq_poptop(v); //remove registrytable

        sq_pushstring(v,SQLite3_TAG,-1);
        sq_newclass(v,SQFalse);
        sq_settypetag(v,-1,(SQUserPointer)SQLite3_TAG);
        sq_insert_reg_funcs(v, sq_sqlite3_methods);

        INT_CONST(v,SQLITE_OPEN_CREATE);
        INT_CONST(v,SQLITE_OPEN_READWRITE);
        INT_CONST(v,SQLITE_OPEN_SHAREDCACHE);
        INT_CONST(v,SQLITE_OPEN_SUBLATIN_NA_LIKE);

        INT_CONST(v,SQLITE_OK);
        INT_CONST(v,SQLITE_BUSY);
        INT_CONST(v,SQLITE_DONE);
        INT_CONST(v,SQLITE_DETERMINISTIC);
        INT_CONST(v,SQLITE_ERROR);
        INT_CONST(v,SQLITE_INTERRUPT);
        INT_CONST(v,SQLITE_MISUSE);
        INT_CONST(v,SQLITE_ROW);
        INT_CONST(v,SQLITE_LOCKED_SHAREDCACHE);
        INT_CONST(v,SQLITE_BUSY_RECOVERY);
        INT_CONST(v,SQLITE_BUSY_SNAPSHOT);
        INT_CONST(v,SQLITE_CANTOPEN_NOTEMPDIR);
        INT_CONST(v,SQLITE_CANTOPEN_ISDIR);
        INT_CONST(v,SQLITE_CANTOPEN_FULLPATH);
        INT_CONST(v,SQLITE_CANTOPEN_CONVPATH);
        INT_CONST(v,SQLITE_CORRUPT_VTAB);
        INT_CONST(v,SQLITE_READONLY_RECOVERY);
        INT_CONST(v,SQLITE_READONLY_CANTLOCK);
        INT_CONST(v,SQLITE_READONLY_ROLLBACK);
        INT_CONST(v,SQLITE_READONLY_DBMOVED);
        INT_CONST(v,SQLITE_ABORT_ROLLBACK);
        INT_CONST(v,SQLITE_CONSTRAINT_CHECK);
        INT_CONST(v,SQLITE_CONSTRAINT_COMMITHOOK);
        INT_CONST(v,SQLITE_CONSTRAINT_FOREIGNKEY);
        INT_CONST(v,SQLITE_CONSTRAINT_FUNCTION);
        INT_CONST(v,SQLITE_CONSTRAINT_NOTNULL);
        INT_CONST(v,SQLITE_CONSTRAINT_PRIMARYKEY);
        INT_CONST(v,SQLITE_CONSTRAINT_TRIGGER);
        INT_CONST(v,SQLITE_CONSTRAINT_UNIQUE);
        INT_CONST(v,SQLITE_CONSTRAINT_VTAB);
        INT_CONST(v,SQLITE_CONSTRAINT_ROWID);
        INT_CONST(v,SQLITE_NOTICE_RECOVER_WAL);
        INT_CONST(v,SQLITE_NOTICE_RECOVER_ROLLBACK);
        INT_CONST(v,SQLITE_WARNING_AUTOINDEX);

        /*Authorizer codes*/
        INT_CONST(v, SQLITE_IGNORE);
        INT_CONST(v, SQLITE_DENY);
        INT_CONST(v, SQLITE_CREATE_INDEX);
        INT_CONST(v, SQLITE_CREATE_TABLE);
        INT_CONST(v, SQLITE_CREATE_TEMP_INDEX);
        INT_CONST(v, SQLITE_CREATE_TEMP_TABLE);
        INT_CONST(v, SQLITE_CREATE_TEMP_TRIGGER);
        INT_CONST(v, SQLITE_CREATE_TEMP_VIEW);
        INT_CONST(v, SQLITE_CREATE_TRIGGER);
        INT_CONST(v, SQLITE_CREATE_VIEW);
        INT_CONST(v, SQLITE_DELETE);
        INT_CONST(v, SQLITE_DROP_INDEX);
        INT_CONST(v, SQLITE_DROP_TABLE);
        INT_CONST(v, SQLITE_DROP_TEMP_INDEX);
        INT_CONST(v, SQLITE_DROP_TEMP_TABLE);
        INT_CONST(v, SQLITE_DROP_TEMP_TRIGGER);
        INT_CONST(v, SQLITE_DROP_TEMP_VIEW);
        INT_CONST(v, SQLITE_DROP_TRIGGER);
        INT_CONST(v, SQLITE_DROP_VIEW);
        INT_CONST(v, SQLITE_INSERT);
        INT_CONST(v, SQLITE_PRAGMA);
        INT_CONST(v, SQLITE_READ);
        INT_CONST(v, SQLITE_SELECT);
        INT_CONST(v, SQLITE_TRANSACTION);
        INT_CONST(v, SQLITE_UPDATE);
        INT_CONST(v, SQLITE_ATTACH);
        INT_CONST(v, SQLITE_DETACH);
        INT_CONST(v, SQLITE_ALTER_TABLE);
        INT_CONST(v, SQLITE_REINDEX);
        INT_CONST(v, SQLITE_ANALYZE);
        INT_CONST(v, SQLITE_CREATE_VTABLE);
        INT_CONST(v, SQLITE_DROP_VTABLE);
        INT_CONST(v, SQLITE_FUNCTION);
        INT_CONST(v, SQLITE_SAVEPOINT);
        INT_CONST(v, SQLITE_COPY);
        INT_CONST(v, SQLITE_RECURSIVE);

        //push sqlite3_NULL as a member
        sq_pushstring(v, nullName,-1);
        sq_pushobject(v, sqlite3_NULL);
        sq_newslot(v,-3,SQTrue);

        sq_newslot(v,-3,SQTrue);

#ifdef SQLITE_ENABLE_SESSION
        sq_pushstring(v, SQLite3_Session_TAG,-1);
        sq_newclass(v,SQFalse);
        sq_settypetag(v,-1,(SQUserPointer)SQLite3_Session_TAG);
        sq_insert_reg_funcs(v, sq_sqlite3_session_methods);

        sq_newslot(v,-3,SQTrue);

        sq_pushstring(v, SQLite3_Session_Iterator_TAG,-1);
        sq_newclass(v,SQFalse);
        sq_settypetag(v,-1,(SQUserPointer)SQLite3_Session_Iterator_TAG);
        sq_insert_reg_funcs(v, sq_sqlite3_session_iterator_methods);

        sq_newslot(v,-3,SQTrue);
#endif // SQLITE_ENABLE_SESSION

        sq_pushstring(v, SQLite3_Stmt_TAG,-1);
        sq_newclass(v,SQFalse);
        sq_settypetag(v,-1,(SQUserPointer)SQLite3_Stmt_TAG);
        sq_insert_reg_funcs(v, sq_sqlite3_stmt_methods);

        INT_CONST(v,SQLITE_INTEGER);
        INT_CONST(v,SQLITE_FLOAT);
        INT_CONST(v,SQLITE_NULL);
        INT_CONST(v,SQLITE_TEXT);
        INT_CONST(v,SQLITE_BLOB);
        INT_CONST(v,SQLITE_OK);
        INT_CONST(v,SQLITE_DONE);
        INT_CONST(v,SQLITE_ROW);
        INT_CONST(v,AS_STRING_ALWAYS);
        INT_CONST(v,NULL_AS_EMPTY_STR);
        INT_CONST(v,WITH_COL_NAMES);
        INT_CONST(v,WITH_BLOB_AS_NULL);
        INT_CONST(v,WITH_SQ_NULL);

        //push sqlite3_NULL as a member
        sq_pushstring(v, nullName,-1);
        sq_pushobject(v, sqlite3_NULL);
        sq_newslot(v,-3,SQTrue);

        sq_newslot(v,-3,SQTrue);
        return 1;
    }

#ifdef __cplusplus
}
#endif

