diff --git a/dbdimp.c b/dbdimp.c index b5b6a39..f7416ae 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -1,1255 +1,1383 @@ -/* $Id: dbdimp.c,v 1.64 2006/09/18 18:54:05 matt Exp $ */ - -#include "SQLiteXS.h" - -DBISTATE_DECLARE; - -#ifndef SvPV_nolen -#define SvPV_nolen(x) SvPV(x,PL_na) -#endif - -#define SvPV_nolen_undef_ok(x) (SvOK(x) ? SvPV_nolen(x) : "undef") - -#ifndef call_method -#define call_method(x,y) perl_call_method(x,y) -#endif - -#ifndef call_sv -#define call_sv(x,y) perl_call_sv(x,y) -#endif - -#define sqlite_error(h,xxh,rc,what) _sqlite_error(__FILE__, __LINE__, h, xxh, rc, what) -#if defined(__GNUC__) && (__GNUC__ > 2) -# define sqlite_trace(level,fmt...) _sqlite_tracef(__FILE__, __LINE__, level, fmt) -#else -# define sqlite_trace _sqlite_tracef_noline -#endif - -void -sqlite_init(dbistate_t *dbistate) -{ - dTHR; - DBIS = dbistate; -} - -static void -_sqlite_error(char *file, int line, SV *h, imp_xxh_t *imp_xxh, int rc, char *what) -{ - dTHR; - - SV *errstr = DBIc_ERRSTR(imp_xxh); - sv_setiv(DBIc_ERR(imp_xxh), (IV)rc); - sv_setpv(errstr, what); - /* #7753: DBD::SQLite error shouldn't include extraneous info */ - /* sv_catpvf(errstr, "(%d) at %s line %d", rc, file, line); */ - - if ( DBIS->debug >= 3 ) { - PerlIO_printf(DBILOGFP, "sqlite error %d recorded: %s at %s line %d\n", - rc, what, file, line); - } -} - -static void -_sqlite_tracef(char *file, int line, int level, const char *fmt, ...) -{ - dTHR; - - va_list ap; - if (DBIS->debug >= level) { - char format[8192]; - sqlite3_snprintf(8191, format, "sqlite trace: %s at %s line %d\n", fmt, file, line); - va_start(ap, fmt); - PerlIO_vprintf(DBILOGFP, format, ap); - va_end(ap); - } -} - -static void -_sqlite_tracef_noline(int level, const char *fmt, ...) -{ - dTHR; - - va_list ap; - if (DBIS->debug >= level) { - char format[8192]; - sqlite3_snprintf(8191, format, "sqlite trace: %s\n", fmt); - va_start(ap, fmt); - PerlIO_vprintf(DBILOGFP, format, ap); - va_end(ap); - } -} - -int -sqlite_db_login(SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *user, char *pass) -{ - dTHR; - int retval; - char *errmsg = NULL; - - if (DBIS->debug >= 3) { - PerlIO_printf(DBILOGFP, " login '%s' (version %s)\n", - dbname, sqlite3_version); - } - - if (sqlite3_open(dbname, &(imp_dbh->db)) != SQLITE_OK) { - sqlite_error(dbh, (imp_xxh_t*)imp_dbh, 1, (char*)sqlite3_errmsg(imp_dbh->db)); - return FALSE; - } - DBIc_IMPSET_on(imp_dbh); - - imp_dbh->in_tran = FALSE; - imp_dbh->unicode = FALSE; - imp_dbh->functions = newAV(); - imp_dbh->aggregates = newAV(); - imp_dbh->timeout = SQL_TIMEOUT; - - imp_dbh->handle_binary_nulls = FALSE; - - sqlite3_busy_timeout(imp_dbh->db, SQL_TIMEOUT); - - if ((retval = sqlite3_exec(imp_dbh->db, "PRAGMA empty_result_callbacks = ON", - NULL, NULL, &errmsg)) - != SQLITE_OK) - { - /* warn("failed to set pragma: %s\n", errmsg); */ - sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); - return FALSE; - } - - if ((retval = sqlite3_exec(imp_dbh->db, "PRAGMA show_datatypes = ON", - NULL, NULL, &errmsg)) - != SQLITE_OK) - { - /* warn("failed to set pragma: %s\n", errmsg); */ - sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); - return FALSE; - } - - DBIc_ACTIVE_on(imp_dbh); - - return TRUE; -} - -int -dbd_set_sqlite3_busy_timeout ( SV *dbh, int timeout ) -{ - D_imp_dbh(dbh); - if (timeout) { - imp_dbh->timeout = timeout; - sqlite3_busy_timeout(imp_dbh->db, timeout); - } - return imp_dbh->timeout; -} - -int -sqlite_db_disconnect (SV *dbh, imp_dbh_t *imp_dbh) -{ - dTHR; - sqlite3_stmt *pStmt; - DBIc_ACTIVE_off(imp_dbh); - - if (DBIc_is(imp_dbh, DBIcf_AutoCommit) == FALSE) { - sqlite_db_rollback(dbh, imp_dbh); - } - - while ( (pStmt = sqlite3_next_stmt(imp_dbh->db, 0))!=0 ) { - sqlite3_finalize(pStmt); - } - - if (sqlite3_close(imp_dbh->db) == SQLITE_BUSY) { - /* active statements! */ - warn("closing dbh with active statement handles"); - } - imp_dbh->db = NULL; - - av_undef(imp_dbh->functions); - SvREFCNT_dec(imp_dbh->functions); - imp_dbh->functions = (AV *)NULL; - - av_undef(imp_dbh->aggregates); - SvREFCNT_dec(imp_dbh->aggregates); - imp_dbh->aggregates = (AV *)NULL; - - return TRUE; -} - -void -sqlite_db_destroy (SV *dbh, imp_dbh_t *imp_dbh) -{ - dTHR; - if (DBIc_ACTIVE(imp_dbh)) { - sqlite_db_disconnect(dbh, imp_dbh); - } - DBIc_IMPSET_off(imp_dbh); -} - -int -sqlite_db_rollback(SV *dbh, imp_dbh_t *imp_dbh) -{ - dTHR; - int retval; - char *errmsg; - - if (imp_dbh->in_tran) { - sqlite_trace(2, "ROLLBACK TRAN"); - if ((retval = sqlite3_exec(imp_dbh->db, "ROLLBACK TRANSACTION", - NULL, NULL, &errmsg)) - != SQLITE_OK) - { - sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); - return FALSE; - } - imp_dbh->in_tran = FALSE; - } - - return TRUE; -} - -int -sqlite_db_commit(SV *dbh, imp_dbh_t *imp_dbh) -{ - dTHR; - int retval; - char *errmsg; - - if (DBIc_is(imp_dbh, DBIcf_AutoCommit)) { - warn("commit ineffective with AutoCommit"); - return TRUE; - } - - if (imp_dbh->in_tran) { - sqlite_trace(2, "COMMIT TRAN"); - if ((retval = sqlite3_exec(imp_dbh->db, "COMMIT TRANSACTION", - NULL, NULL, &errmsg)) - != SQLITE_OK) - { - sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); - return FALSE; - } - imp_dbh->in_tran = FALSE; - } - return TRUE; -} - -int -sqlite_discon_all(SV *drh, imp_drh_t *imp_drh) -{ - dTHR; - return FALSE; /* no way to do this */ -} - -SV * -sqlite_db_last_insert_id(SV *dbh, imp_dbh_t *imp_dbh, SV *catalog, SV *schema, SV *table, SV *field, SV *attr) -{ - return newSViv(sqlite3_last_insert_rowid(imp_dbh->db)); -} - -int -sqlite_st_prepare (SV *sth, imp_sth_t *imp_sth, - char *statement, SV *attribs) -{ - dTHR; - D_imp_dbh_from_sth; - const char *extra; - int retval = 0; - - if (!DBIc_ACTIVE(imp_dbh)) { - sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, "attempt to prepare on inactive database handle"); - return FALSE; - } - - if (strlen(statement) < 1) { - sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, "attempt to prepare empty statement"); - return FALSE; - } - - sqlite_trace(2, "prepare statement: %s", statement); - imp_sth->nrow = -1; - imp_sth->retval = SQLITE_OK; - imp_sth->params = newAV(); - imp_sth->col_types = newAV(); - Newz(0, imp_sth->statement, strlen(statement)+1, char); - - if ((retval = sqlite3_prepare_v2(imp_dbh->db, statement, -1, &(imp_sth->stmt), &extra)) - != SQLITE_OK) - { - if (imp_sth->stmt) { - sqlite3_finalize(imp_sth->stmt); - } - sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, (char*)sqlite3_errmsg(imp_dbh->db)); - return FALSE; - } - - /* store the query for later re-use if required */ - Copy(statement, imp_sth->statement, strlen(statement)+1, char); - - DBIc_NUM_PARAMS(imp_sth) = sqlite3_bind_parameter_count(imp_sth->stmt); - DBIc_NUM_FIELDS(imp_sth) = sqlite3_column_count(imp_sth->stmt); - DBIc_IMPSET_on(imp_sth); - - return TRUE; -} - -char * -sqlite_quote(imp_dbh_t *imp_dbh, SV *val) -{ - STRLEN len; - char *cval = SvPV(val, len); - SV *ret = sv_2mortal(NEWSV(0, SvCUR(val) + 2)); - sv_setpvn(ret, "", 0); - - while (len) { - switch (*cval) { - case '\'': - sv_catpvn(ret, "''", 2); - break; - default: - sv_catpvn(ret, cval, 1); - } - *cval++; len--; - } - return SvPV_nolen(ret); -} - -void -sqlite_st_reset (SV *sth) -{ - D_imp_sth(sth); - if (DBIc_IMPSET(imp_sth)) - sqlite3_reset(imp_sth->stmt); -} - -int -sqlite_st_execute (SV *sth, imp_sth_t *imp_sth) -{ - dTHR; - D_imp_dbh_from_sth; - char *errmsg; - int num_params = DBIc_NUM_PARAMS(imp_sth); - int i; - int retval; - - sqlite_trace(3, "execute"); - - /* warn("execute\n"); */ - - if (!DBIc_ACTIVE(imp_dbh)) { - sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, "attempt to execute on inactive database handle"); - return FALSE; - } - - if (DBIc_ACTIVE(imp_sth)) { - sqlite_trace(3, "execute still active, reset"); - if ((imp_sth->retval = sqlite3_reset(imp_sth->stmt)) != SQLITE_OK) { - char *errmsg = (char*)sqlite3_errmsg(imp_dbh->db); - sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, errmsg); - return FALSE; - } - } - - for (i = 0; i < num_params; i++) { - SV *value = av_shift(imp_sth->params); - SV *sql_type_sv = av_shift(imp_sth->params); - int sql_type = (int)SvIV(sql_type_sv); - - sqlite_trace(4, "params left in 0x%p: %d", imp_sth->params, 1+av_len(imp_sth->params)); - sqlite_trace(4, "bind %d type %d as %s", i, sql_type, SvPV_nolen_undef_ok(value)); - - if (!SvOK(value)) { - sqlite_trace(5, "binding null"); - retval = sqlite3_bind_null(imp_sth->stmt, i+1); - } - else if (sql_type >= SQL_NUMERIC && sql_type <= SQL_SMALLINT) { -#if defined(USE_64_BIT_INT) - retval = sqlite3_bind_int64(imp_sth->stmt, i+1, SvIV(value)); -#else - retval = sqlite3_bind_int(imp_sth->stmt, i+1, SvIV(value)); -#endif - } - else if (sql_type >= SQL_FLOAT && sql_type <= SQL_DOUBLE) { - retval = sqlite3_bind_double(imp_sth->stmt, i+1, SvNV(value)); - } - else if (sql_type == SQL_BLOB) { - STRLEN len; - char * data = SvPV(value, len); - retval = sqlite3_bind_blob(imp_sth->stmt, i+1, data, (int)len, SQLITE_TRANSIENT); - } - else { - STRLEN len; - char * data = SvPV(value, len); - retval = sqlite3_bind_text(imp_sth->stmt, i+1, data, (int)len, SQLITE_TRANSIENT); - } - - if (value) { - SvREFCNT_dec(value); - } - SvREFCNT_dec(sql_type_sv); - if (retval != SQLITE_OK) { - sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, (char*)sqlite3_errmsg(imp_dbh->db)); - return -4; - } - } - - if ( (!DBIc_is(imp_dbh, DBIcf_AutoCommit)) && (!imp_dbh->in_tran) ) { - sqlite_trace(2, "BEGIN TRAN"); - if ((retval = sqlite3_exec(imp_dbh->db, "BEGIN TRANSACTION", - NULL, NULL, &errmsg)) - != SQLITE_OK) - { - sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, errmsg); - return -2; - } - imp_dbh->in_tran = TRUE; - } - - imp_sth->nrow = 0; - - sqlite_trace(3, "Execute returned %d cols\n", DBIc_NUM_FIELDS(imp_sth)); - if (DBIc_NUM_FIELDS(imp_sth) == 0) { - while ((imp_sth->retval = sqlite3_step(imp_sth->stmt)) != SQLITE_DONE) { - if (imp_sth->retval == SQLITE_ROW) { - continue; - } - sqlite3_reset(imp_sth->stmt); - sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, (char*)sqlite3_errmsg(imp_dbh->db)); - return -5; - } - /* warn("Finalize\n"); */ - sqlite3_reset(imp_sth->stmt); - imp_sth->nrow = sqlite3_changes(imp_dbh->db); - /* DBIc_ACTIVE_on(imp_sth); */ - /* warn("Total changes: %d\n", sqlite3_total_changes(imp_dbh->db)); */ - /* warn("Nrow: %d\n", imp_sth->nrow); */ - return imp_sth->nrow; - } - - imp_sth->retval = sqlite3_step(imp_sth->stmt); - switch (imp_sth->retval) { - case SQLITE_ROW: - case SQLITE_DONE: DBIc_ACTIVE_on(imp_sth); - sqlite_trace(5, "exec ok - %d rows, %d cols\n", imp_sth->nrow, DBIc_NUM_FIELDS(imp_sth)); - return 0; - default: sqlite3_reset(imp_sth->stmt); - imp_sth->stmt = NULL; - sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, (char*)sqlite3_errmsg(imp_dbh->db)); - return -6; - } -} - -int -sqlite_st_rows (SV *sth, imp_sth_t *imp_sth) -{ - return imp_sth->nrow; -} - -/* bind parameter - * NB: We store the params instead of bind immediately because - * we might need to re-create the imp_sth->stmt (see top of execute() function) - * and so we can't lose these params - */ -int -sqlite_bind_ph (SV *sth, imp_sth_t *imp_sth, - SV *param, SV *value, IV sql_type, SV *attribs, - int is_inout, IV maxlen) -{ - int pos; - if (!looks_like_number(param)) { - STRLEN len; - char *paramstring; - paramstring = SvPV(param, len); - if(paramstring[len] == 0 && strlen(paramstring) == len) { - pos = sqlite3_bind_parameter_index(imp_sth->stmt, paramstring); - if (pos==0) - croak("Unknown named parameter: %s", paramstring); - pos = 2 * (pos - 1); - } - else { - croak(" could not be coerced to a C string"); - } - } - else { - if (is_inout) { - croak("InOut bind params not implemented"); - } - } - pos = (int)( 2 * (SvIV(param) - 1) ); - sqlite_trace(3, "bind into 0x%p: %d => %s (%d) pos %d\n", - imp_sth->params, SvIV(param), SvPV_nolen_undef_ok(value), sql_type, pos); - av_store(imp_sth->params, pos, SvREFCNT_inc(value)); - av_store(imp_sth->params, pos+1, newSViv(sql_type)); - - return TRUE; -} - -int -sqlite_bind_col(SV *sth, imp_sth_t *imp_sth, SV *col, SV *ref, IV sql_type, SV *attribs) -{ - /* store the type */ - av_store(imp_sth->col_types, (I32)SvIV(col)-1, newSViv(sql_type)); - - /* Allow default implementation to continue */ - return 1; -} - -AV * -sqlite_st_fetch (SV *sth, imp_sth_t *imp_sth) -{ - AV *av; - D_imp_dbh_from_sth; - int numFields = DBIc_NUM_FIELDS(imp_sth); - int chopBlanks = DBIc_is(imp_sth, DBIcf_ChopBlanks); - int i; - - sqlite_trace(6, "numFields == %d, nrow == %d\n", numFields, imp_sth->nrow); - - if (!DBIc_ACTIVE(imp_sth)) { - return Nullav; - } - - if (imp_sth->retval == SQLITE_DONE) { - sqlite_st_finish(sth, imp_sth); - return Nullav; - } - - if (imp_sth->retval != SQLITE_ROW) { - /* error */ - sqlite_st_finish(sth, imp_sth); - sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, (char*)sqlite3_errmsg(imp_dbh->db)); - return Nullav; - } - - imp_sth->nrow++; - - av = DBIS->get_fbav(imp_sth); - for (i = 0; i < numFields; i++) { - int len; - char * val; - int col_type = sqlite3_column_type(imp_sth->stmt, i); - SV **sql_type = av_fetch(imp_sth->col_types, i, 0); - if (sql_type && SvOK(*sql_type)) { - if (SvIV(*sql_type)) { - col_type = (int)SvIV(*sql_type); - } - } - switch(col_type) { - case SQLITE_INTEGER: -#if defined(USE_64_BIT_INT) - sv_setiv(AvARRAY(av)[i], sqlite3_column_int64(imp_sth->stmt, i)); -#else - sv_setnv(AvARRAY(av)[i], (double)sqlite3_column_int64(imp_sth->stmt, i)); -#endif - break; - case SQLITE_FLOAT: - sv_setnv(AvARRAY(av)[i], sqlite3_column_double(imp_sth->stmt, i)); - break; - case SQLITE_TEXT: - val = (char*)sqlite3_column_text(imp_sth->stmt, i); - len = sqlite3_column_bytes(imp_sth->stmt, i); - if (chopBlanks) { - val = savepv(val); - while((len > 0) && (val[len-1] == ' ')) { - len--; - } - val[len] = '\0'; - } - sv_setpvn(AvARRAY(av)[i], val, len); - if (imp_dbh->unicode) { - SvUTF8_on(AvARRAY(av)[i]); - } else { - SvUTF8_off(AvARRAY(av)[i]); - } - if (chopBlanks) Safefree(val); - break; - case SQLITE_BLOB: - len = sqlite3_column_bytes(imp_sth->stmt, i); - sv_setpvn(AvARRAY(av)[i], sqlite3_column_blob(imp_sth->stmt, i), len); - SvUTF8_off(AvARRAY(av)[i]); - break; - default: - sv_setsv(AvARRAY(av)[i], &PL_sv_undef); - SvUTF8_off(AvARRAY(av)[i]); - break; - } - SvSETMAGIC(AvARRAY(av)[i]); - } - - imp_sth->retval = sqlite3_step(imp_sth->stmt); - - return av; -} - -int -sqlite_st_finish (SV *sth, imp_sth_t *imp_sth) -{ - return sqlite_st_finish3(sth, imp_sth, 0); -} - -int -sqlite_st_finish3 (SV *sth, imp_sth_t *imp_sth, int is_destroy) -{ - D_imp_dbh_from_sth; - - /* warn("finish statement\n"); */ - if (!DBIc_ACTIVE(imp_sth)) - return 1; - - DBIc_ACTIVE_off(imp_sth); - - av_clear(imp_sth->col_types); - - if (!DBIc_ACTIVE(imp_dbh)) /* no longer connected */ - return 1; - - if (is_destroy) { - return TRUE; - } - - if ((imp_sth->retval = sqlite3_reset(imp_sth->stmt)) != SQLITE_OK) { - char *errmsg = (char*)sqlite3_errmsg(imp_dbh->db); - /* warn("finalize failed! %s\n", errmsg); */ - sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, errmsg); - return FALSE; - } - - return TRUE; -} - -void -sqlite_st_destroy (SV *sth, imp_sth_t *imp_sth) -{ - D_imp_dbh_from_sth; - /* warn("destroy statement: %s\n", imp_sth->statement); */ - DBIc_ACTIVE_off(imp_sth); - if (DBIc_ACTIVE(imp_dbh)) { - /* finalize sth when active connection */ - sqlite3_finalize(imp_sth->stmt); - } - Safefree(imp_sth->statement); - SvREFCNT_dec((SV*)imp_sth->params); - SvREFCNT_dec((SV*)imp_sth->col_types); - DBIc_IMPSET_off(imp_sth); -} - -int -sqlite_st_blob_read (SV *sth, imp_sth_t *imp_sth, - int field, long offset, long len, SV *destrv, long destoffset) -{ - return 0; -} - -int -sqlite_db_STORE_attrib (SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv) -{ - dTHR; - char *key = SvPV_nolen(keysv); - char *errmsg; - int retval; - - if (strEQ(key, "AutoCommit")) { - if (SvTRUE(valuesv)) { - /* commit tran? */ - if ( (!DBIc_is(imp_dbh, DBIcf_AutoCommit)) && (imp_dbh->in_tran) ) { - sqlite_trace(2, "COMMIT TRAN"); - if ((retval = sqlite3_exec(imp_dbh->db, "COMMIT TRANSACTION", - NULL, NULL, &errmsg)) - != SQLITE_OK) - { - sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); - return TRUE; - } - imp_dbh->in_tran = FALSE; - } - } - DBIc_set(imp_dbh, DBIcf_AutoCommit, SvTRUE(valuesv)); - return TRUE; - } - if (strEQ(key, "unicode")) { - imp_dbh->unicode = !(! SvTRUE(valuesv)); - return TRUE; - } - return FALSE; -} - -SV * -sqlite_db_FETCH_attrib (SV *dbh, imp_dbh_t *imp_dbh, SV *keysv) -{ - dTHR; - char *key = SvPV_nolen(keysv); - - if (strEQ(key, "sqlite_version")) { - return newSVpv(sqlite3_version,0); - } - if (strEQ(key, "unicode")) { - return newSViv(imp_dbh->unicode ? 1 : 0); - } - - return NULL; -} - -int -sqlite_st_STORE_attrib (SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv) -{ - char *key = SvPV_nolen(keysv); - return FALSE; -} - -int -type_to_odbc_type (int type) -{ - switch(type) { - case SQLITE_INTEGER: return SQL_INTEGER; - case SQLITE_FLOAT: return SQL_DOUBLE; - case SQLITE_TEXT: return SQL_VARCHAR; - case SQLITE_BLOB: return SQL_BLOB; - case SQLITE_NULL: return SQL_UNKNOWN_TYPE; - default: return SQL_UNKNOWN_TYPE; - } -} - -SV * -sqlite_st_FETCH_attrib (SV *sth, imp_sth_t *imp_sth, SV *keysv) -{ - char *key = SvPV_nolen(keysv); - SV *retsv = NULL; - int i,n; - - if (!DBIc_ACTIVE(imp_sth)) { - return NULL; - } - - /* warn("fetch: %s\n", key); */ - - i = DBIc_NUM_FIELDS(imp_sth); - - if (strEQ(key, "NAME")) { - AV *av = newAV(); -/* warn("Fetch NAME fields: %d\n", i); */ - av_extend(av, i); - retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); - for (n = 0; n < i; n++) { -/* warn("Fetch col name %d\n", n); */ - const char *fieldname = sqlite3_column_name(imp_sth->stmt, n); - if (fieldname) { - /* warn("Name [%d]: %s\n", n, fieldname); */ - /* char *dot = instr(fieldname, "."); */ - /* if (dot) drop table name from field name */ - /* fieldname = ++dot; */ - av_store(av, n, newSVpv(fieldname, 0)); - } - } - } - else if (strEQ(key, "PRECISION")) { - AV *av = newAV(); - retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); - } - else if (strEQ(key, "TYPE")) { - AV *av = newAV(); - av_extend(av, i); - retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); - for (n = 0; n < i; n++) { - const char *fieldtype = sqlite3_column_decltype(imp_sth->stmt, n); - int type = sqlite3_column_type(imp_sth->stmt, n); - /* warn("got type: %d = %s\n", type, fieldtype); */ - type = type_to_odbc_type(type); - /* av_store(av, n, newSViv(type)); */ - if (fieldtype) - av_store(av, n, newSVpv(fieldtype, 0)); - else - av_store(av, n, newSVpv("VARCHAR", 0)); - } - } - else if (strEQ(key, "NULLABLE")) { - AV *av = newAV(); - retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); - } - else if (strEQ(key, "SCALE")) { - AV *av = newAV(); - retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); - } - else if (strEQ(key, "NUM_OF_FIELDS")) { - retsv = sv_2mortal(newSViv(i)); - } - - return retsv; -} - -static void -sqlite_db_set_result(sqlite3_context *context, SV *result, int is_error ) -{ - STRLEN len; - char *s; - - if ( is_error ) { - s = (char*)SvPV(result, len); - sqlite3_result_error( context, s, (int)len ); - return; - } - -/* warn("result: %s\n", SvPV_nolen(result)); */ - if ( !SvOK(result) ) { - sqlite3_result_null( context ); - } else if( SvIOK_UV(result) ) { - s = SvPV(result, len); - sqlite3_result_text( context, s, (int)len, SQLITE_TRANSIENT ); - } - else if ( SvIOK(result) ) { - sqlite3_result_int( context, (int)SvIV(result)); - } else if ( !is_error && SvIOK(result) ) { - sqlite3_result_double( context, SvNV(result)); - } else { - s = SvPV(result, len); - sqlite3_result_text( context, s, (int)len, SQLITE_TRANSIENT ); - } -} - -static void -sqlite_db_func_dispatcher(sqlite3_context *context, int argc, sqlite3_value **value) -{ - dSP; - int count; - int i; - SV *func; - - func = sqlite3_user_data(context); - - ENTER; - SAVETMPS; - - PUSHMARK(SP); - for ( i=0; i < argc; i++ ) { - SV *arg; - STRLEN len = sqlite3_value_bytes(value[i]); - int type = sqlite3_value_type(value[i]); - - /* warn("func dispatch type: %d, value: %s\n", type, sqlite3_value_text(value[i])); */ - switch(type) { - case SQLITE_INTEGER: - arg = sv_2mortal(newSViv(sqlite3_value_int(value[i]))); - break; - case SQLITE_FLOAT: - arg = sv_2mortal(newSVnv(sqlite3_value_double(value[i]))); - break; - case SQLITE_TEXT: - arg = sv_2mortal(newSVpvn((const char *)sqlite3_value_text(value[i]), len)); - break; - case SQLITE_BLOB: - arg = sv_2mortal(newSVpvn(sqlite3_value_blob(value[i]), len)); - break; - default: - arg = &PL_sv_undef; - } - - XPUSHs(arg); - } - PUTBACK; - - count = call_sv(func, G_SCALAR|G_EVAL); - - SPAGAIN; - - /* Check for an error */ - if (SvTRUE(ERRSV) ) { - sqlite_db_set_result( context, ERRSV, 1); - POPs; - } else if ( count != 1 ) { - SV *err = sv_2mortal(newSVpvf( "function should return 1 argument, got %d", - count )); - - sqlite_db_set_result( context, err, 1); - /* Clear the stack */ - for ( i=0; i < count; i++ ) { - POPs; - } - } else { - sqlite_db_set_result( context, POPs, 0 ); - } - - PUTBACK; - - FREETMPS; - LEAVE; -} - -void -sqlite3_db_create_function( SV *dbh, const char *name, int argc, SV *func ) -{ - D_imp_dbh(dbh); - int rv; - - /* Copy the function reference */ - SV *func_sv = newSVsv(func); - av_push( imp_dbh->functions, func_sv ); - - /* warn("create_function %s with %d args\n", name, argc); */ - rv = sqlite3_create_function( imp_dbh->db, name, argc, SQLITE_UTF8, - func_sv, - sqlite_db_func_dispatcher, NULL, NULL ); - if ( rv != SQLITE_OK ) - { - croak( "sqlite_create_function failed with error %s", - sqlite3_errmsg(imp_dbh->db) ); - } -} - -typedef struct aggrInfo aggrInfo; -struct aggrInfo { - SV *aggr_inst; - SV *err; - int inited; -}; - -static void -sqlite_db_aggr_new_dispatcher( sqlite3_context *context, aggrInfo *aggr_info ) -{ - dSP; - SV *pkg = NULL; - int count = 0; - - aggr_info->err = NULL; - aggr_info->aggr_inst = NULL; - - pkg = sqlite3_user_data(context); - if ( !pkg ) - return; - - ENTER; - SAVETMPS; - - PUSHMARK(SP); - XPUSHs( sv_2mortal( newSVsv(pkg) ) ); - PUTBACK; - - count = call_method ("new", G_EVAL|G_SCALAR); - SPAGAIN; - - aggr_info->inited = 1; - - if ( SvTRUE( ERRSV ) ) { - aggr_info->err = newSVpvf ("error during aggregator's new(): %s", - SvPV_nolen (ERRSV)); - POPs; - } else if ( count != 1 ) { - int i; - - aggr_info->err = newSVpvf( "new() should return one value, got %d", - count ); - /* Clear the stack */ - for ( i=0; i < count; i++ ) { - POPs; - } - } else { - SV *aggr = POPs; - if ( SvROK(aggr) ) { - aggr_info->aggr_inst = newSVsv(aggr); - } else{ - aggr_info->err = newSVpvf( "new() should return a blessed reference" ); - } - } - - PUTBACK; - - FREETMPS; - LEAVE; - - return; -} - -static void -sqlite_db_aggr_step_dispatcher (sqlite3_context *context, - int argc, sqlite3_value **value) -{ - dSP; - int i; - aggrInfo *aggr; - - aggr = sqlite3_aggregate_context (context, sizeof (aggrInfo)); - if ( !aggr ) - return; - - ENTER; - SAVETMPS; - - /* initialize on first step */ - if ( !aggr->inited ) { - sqlite_db_aggr_new_dispatcher( context, aggr ); - } - - if ( aggr->err || !aggr->aggr_inst ) - goto cleanup; - - PUSHMARK(SP); - XPUSHs( sv_2mortal( newSVsv( aggr->aggr_inst ) )); - for ( i=0; i < argc; i++ ) { - SV *arg; - int len = sqlite3_value_bytes(value[i]); - int type = sqlite3_value_type(value[i]); - - switch(type) { - case SQLITE_INTEGER: - arg = sv_2mortal(newSViv(sqlite3_value_int(value[i]))); - break; - case SQLITE_FLOAT: - arg = sv_2mortal(newSVnv(sqlite3_value_double(value[i]))); - break; - case SQLITE_TEXT: - arg = sv_2mortal(newSVpvn((const char *)sqlite3_value_text(value[i]), len)); - break; - case SQLITE_BLOB: - arg = sv_2mortal(newSVpvn(sqlite3_value_blob(value[i]), len)); - break; - default: - arg = &PL_sv_undef; - } - - XPUSHs(arg); - } - PUTBACK; - - call_method ("step", G_SCALAR|G_EVAL|G_DISCARD); - - /* Check for an error */ - if (SvTRUE(ERRSV) ) { - aggr->err = newSVpvf( "error during aggregator's step(): %s", - SvPV_nolen(ERRSV)); - POPs; - } - - cleanup: - FREETMPS; - LEAVE; -} - -static void -sqlite_db_aggr_finalize_dispatcher( sqlite3_context *context ) -{ - dSP; - aggrInfo *aggr, myAggr; - int count = 0; - - aggr = sqlite3_aggregate_context (context, sizeof (aggrInfo)); - - ENTER; - SAVETMPS; - - if ( !aggr ) { - /* SQLite seems to refuse to create a context structure - from finalize() */ - aggr = &myAggr; - aggr->aggr_inst = NULL; - aggr->err = NULL; - sqlite_db_aggr_new_dispatcher (context, aggr); - } - - if ( ! aggr->err && aggr->aggr_inst ) { - PUSHMARK(SP); - XPUSHs( sv_2mortal( newSVsv( aggr->aggr_inst )) ); - PUTBACK; - - count = call_method( "finalize", G_SCALAR|G_EVAL ); - SPAGAIN; - - if ( SvTRUE(ERRSV) ) { - aggr->err = newSVpvf ("error during aggregator's finalize(): %s", - SvPV_nolen(ERRSV) ) ; - POPs; - } else if ( count != 1 ) { - int i; - aggr->err = newSVpvf( "finalize() should return 1 value, got %d", - count ); - /* Clear the stack */ - for ( i=0; ierr ) { - warn( "DBD::SQLite: error in aggregator cannot be reported to SQLite: %s", SvPV_nolen( aggr->err ) ); - - /* sqlite_db_set_result( context, aggr->err, 1 ); */ - SvREFCNT_dec( aggr->err ); - aggr->err = NULL; - } - - if ( aggr->aggr_inst ) { - SvREFCNT_dec( aggr->aggr_inst ); - aggr->aggr_inst = NULL; - } - - FREETMPS; - LEAVE; -} - -void -sqlite3_db_create_aggregate( SV *dbh, const char *name, int argc, SV *aggr_pkg ) -{ - D_imp_dbh(dbh); - int rv; - - /* Copy the aggregate reference */ - SV *aggr_pkg_copy = newSVsv(aggr_pkg); - av_push( imp_dbh->aggregates, aggr_pkg_copy ); - - rv = sqlite3_create_function( imp_dbh->db, name, argc, SQLITE_UTF8, - aggr_pkg_copy, - NULL, - sqlite_db_aggr_step_dispatcher, - sqlite_db_aggr_finalize_dispatcher - ); - - if ( rv != SQLITE_OK ) - { - croak( "sqlite_create_aggregate failed with error %s", - sqlite3_errmsg(imp_dbh->db) ); - } -} - - -int sqlite_db_collation_dispatcher(void *func, int len1, const void *string1, - int len2, const void *string2) -{ - dSP; - int cmp; - int n_retval; - - ENTER; - SAVETMPS; - PUSHMARK(SP); - XPUSHs( sv_2mortal ( newSVpvn( string1, len1) ) ); - XPUSHs( sv_2mortal ( newSVpvn( string2, len2) ) ); - PUTBACK; - n_retval = call_sv((void*)func, G_SCALAR); - if (n_retval != 1) { - croak("collation function returned %d arguments", n_retval); - } - SPAGAIN; - cmp = (int)POPi; - PUTBACK; - FREETMPS; - LEAVE; - - return cmp; -} - -int sqlite_db_collation_dispatcher_utf8( - void *func, int len1, const void *string1, - int len2, const void *string2) -{ - dSP; - int cmp; - int n_retval; - SV *sv1, *sv2; - - ENTER; - SAVETMPS; - PUSHMARK(SP); - sv1 = newSVpvn( string1, len1); - SvUTF8_on(sv1); - sv2 = newSVpvn( string2, len2); - SvUTF8_on(sv2); - XPUSHs( sv_2mortal ( sv1 ) ); - XPUSHs( sv_2mortal ( sv2 ) ); - PUTBACK; - n_retval = call_sv((void*)func, G_SCALAR); - if (n_retval != 1) { - croak("collation function returned %d arguments", n_retval); - } - SPAGAIN; - cmp = (int)POPi; - PUTBACK; - FREETMPS; - LEAVE; - - return cmp; -} - - -void -sqlite3_db_create_collation( SV *dbh, const char *name, SV *func ) -{ - D_imp_dbh(dbh); - int rv, rv2; - void *aa = "aa"; - void *zz = "zz"; - - SV *func_sv = newSVsv(func); - - /* Check that this is a proper collation function */ - rv = sqlite_db_collation_dispatcher(func_sv, 2, aa, 2, aa); - if (rv != 0) { - warn("improper collation function: %s(aa, aa) returns %d!", name, rv); - } - rv = sqlite_db_collation_dispatcher(func_sv, 2, aa, 2, zz); - rv2 = sqlite_db_collation_dispatcher(func_sv, 2, zz, 2, aa); - if (rv2 != (rv * -1)) { - warn("improper collation function: '%s' is not symmetric", name); - } - - /* Copy the func reference so that it can be deallocated at disconnect */ - av_push( imp_dbh->functions, func_sv ); - - /* Register the func within sqlite3 */ - rv = sqlite3_create_collation( - imp_dbh->db, name, SQLITE_UTF8, - func_sv, - imp_dbh->unicode ? sqlite_db_collation_dispatcher_utf8 - : sqlite_db_collation_dispatcher - ); - - if ( rv != SQLITE_OK ) - { - croak( "sqlite_create_collation failed with error %s", - sqlite3_errmsg(imp_dbh->db) ); - } -} - - -int sqlite_db_progress_handler_dispatcher( void *handler ) -{ - dSP; - int n_retval; - int retval; - - PUSHMARK(SP); - n_retval = call_sv( handler, G_SCALAR ); - if ( n_retval != 1 ) { - croak( "progress_handler returned %d arguments", n_retval ); - } - SPAGAIN; - retval = (int)POPi; - PUTBACK; - - return retval; -} - - - -void -sqlite3_db_progress_handler( SV *dbh, int n_opcodes, SV *handler ) -{ - D_imp_dbh(dbh); - - if (handler == &PL_sv_undef) { - /* remove previous handler */ - sqlite3_progress_handler( imp_dbh->db, 0, NULL, NULL); - } - else { - SV *handler_sv = newSVsv(handler); - - /* Copy the handler ref so that it can be deallocated at disconnect */ - av_push( imp_dbh->functions, handler_sv ); - - /* Register the func within sqlite3 */ - sqlite3_progress_handler( imp_dbh->db, n_opcodes, - sqlite_db_progress_handler_dispatcher, - handler_sv ); - } -} - - - -/* end */ ->>>>>>> 64bit-warnings:dbdimp.c +#define PERL_NO_GET_CONTEXT + +#include "SQLiteXS.h" + +DBISTATE_DECLARE; + +#ifndef SvPV_nolen +#define SvPV_nolen(x) SvPV(x,PL_na) +#endif + +#define SvPV_nolen_undef_ok(x) (SvOK(x) ? SvPV_nolen(x) : "undef") + +#ifndef call_method +#define call_method(x,y) perl_call_method(x,y) +#endif + +#ifndef call_sv +#define call_sv(x,y) perl_call_sv(x,y) +#endif + +#define sqlite_error(h,xxh,rc,what) _sqlite_error(aTHX_ __FILE__, __LINE__, h, xxh, rc, what) + +/* XXX: is there any good way to use pTHX_/aTHX_ here like above? */ +#if defined(__GNUC__) && (__GNUC__ > 2) +# define sqlite_trace(h,xxh,level,fmt...) _sqlite_tracef(__FILE__, __LINE__, h, xxh, level, fmt) +#else +# define sqlite_trace _sqlite_tracef_noline +#endif + +void +sqlite_init(dbistate_t *dbistate) +{ + dTHX; + DBISTATE_INIT; /* Initialize the DBI macros */ +} + +static void +_sqlite_error(pTHX_ char *file, int line, SV *h, imp_xxh_t *imp_xxh, int rc, char *what) +{ + DBIh_SET_ERR_CHAR(h, imp_xxh, Nullch, rc, what, Nullch, Nullch); + + /* #7753: DBD::SQLite error shouldn't include extraneous info */ + /* sv_catpvf(errstr, "(%d) at %s line %d", rc, file, line); */ + if ( DBIc_TRACE_LEVEL(imp_xxh) >= 3 ) { + PerlIO_printf( + DBIc_LOGPIO(imp_xxh), + "sqlite error %d recorded: %s at %s line %d\n", + rc, what, file, line + ); + } +} + +static void +_sqlite_tracef(char *file, int line, SV *h, imp_xxh_t *imp_xxh, int level, const char *fmt, ...) +{ + dTHX; + + va_list ap; + if ( DBIc_TRACE_LEVEL(imp_xxh) >= level ) { + char format[8192]; + sqlite3_snprintf(8191, format, "sqlite trace: %s at %s line %d\n", fmt, file, line); + va_start(ap, fmt); + PerlIO_vprintf(DBIc_LOGPIO(imp_xxh), format, ap); + va_end(ap); + } +} + +static void +_sqlite_tracef_noline(SV *h, imp_xxh_t *imp_xxh, int level, const char *fmt, ...) +{ + dTHX; + + va_list ap; + if ( DBIc_TRACE_LEVEL(imp_xxh) >= level ) { + char format[8192]; + sqlite3_snprintf(8191, format, "sqlite trace: %s\n", fmt); + va_start(ap, fmt); + PerlIO_vprintf(DBIc_LOGPIO(imp_xxh), format, ap); + va_end(ap); + } +} + +int +sqlite_db_login(SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *user, char *pass) +{ + dTHX; + int retval; + char *errmsg = NULL; + + if ( DBIc_TRACE_LEVEL(imp_dbh) >= 3 ) { + PerlIO_printf(DBILOGFP, " login '%s' (version %s)\n", + dbname, sqlite3_version); + } + + if ((retval = sqlite3_open(dbname, &(imp_dbh->db))) != SQLITE_OK ) { + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, (char*)sqlite3_errmsg(imp_dbh->db)); + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + DBIc_IMPSET_on(imp_dbh); + + imp_dbh->in_tran = FALSE; + imp_dbh->unicode = FALSE; + imp_dbh->functions = newAV(); + imp_dbh->aggregates = newAV(); + imp_dbh->timeout = SQL_TIMEOUT; + imp_dbh->handle_binary_nulls = FALSE; + + sqlite3_busy_timeout(imp_dbh->db, SQL_TIMEOUT); + + if ((retval = sqlite3_exec(imp_dbh->db, "PRAGMA empty_result_callbacks = ON", + NULL, NULL, &errmsg)) + != SQLITE_OK) + { + /* warn("failed to set pragma: %s\n", errmsg); */ + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + + if ((retval = sqlite3_exec(imp_dbh->db, "PRAGMA show_datatypes = ON", + NULL, NULL, &errmsg)) + != SQLITE_OK) + { + /* warn("failed to set pragma: %s\n", errmsg); */ + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + + DBIc_ACTIVE_on(imp_dbh); + +/* + if ( DBIc_WARN(imp_dbh) ) { + warn("DBIc_WARN is on"); + } + else { + warn("DBIc_WARN if off"); + } + if ( DBIc_is(imp_dbh, DBIcf_PrintWarn) ) { + warn("DBIcf_PrintWarn is on"); + } +*/ + + return TRUE; +} + +int +dbd_set_sqlite3_busy_timeout (pTHX_ SV *dbh, int timeout ) +{ + D_imp_dbh(dbh); + if (timeout) { + imp_dbh->timeout = timeout; + sqlite3_busy_timeout(imp_dbh->db, timeout); + } + return imp_dbh->timeout; +} + +int +sqlite_db_disconnect (SV *dbh, imp_dbh_t *imp_dbh) +{ + dTHX; + sqlite3_stmt *pStmt; + DBIc_ACTIVE_off(imp_dbh); + + if (DBIc_is(imp_dbh, DBIcf_AutoCommit) == FALSE) { + sqlite_db_rollback(dbh, imp_dbh); + } + + while ( (pStmt = sqlite3_next_stmt(imp_dbh->db, 0))!=0 ) { + sqlite3_finalize(pStmt); + } + + if (sqlite3_close(imp_dbh->db) == SQLITE_BUSY) { + /* active statements! */ + warn("closing dbh with active statement handles"); + } + imp_dbh->db = NULL; + + av_undef(imp_dbh->functions); + SvREFCNT_dec(imp_dbh->functions); + imp_dbh->functions = (AV *)NULL; + + av_undef(imp_dbh->aggregates); + SvREFCNT_dec(imp_dbh->aggregates); + imp_dbh->aggregates = (AV *)NULL; + + return TRUE; +} + +void +sqlite_db_destroy (SV *dbh, imp_dbh_t *imp_dbh) +{ + dTHX; + if (DBIc_ACTIVE(imp_dbh)) { + /* warn("DBIc_ACTIVE is on"); */ + sqlite_db_disconnect(dbh, imp_dbh); +/* + } else { + warn("DBIc_ACTIVE is off"); +*/ + } + DBIc_IMPSET_off(imp_dbh); +} + +int +sqlite_db_rollback(SV *dbh, imp_dbh_t *imp_dbh) +{ + dTHX; + int retval; + char *errmsg; + + if (imp_dbh->in_tran) { + sqlite_trace(dbh, (imp_xxh_t*)imp_dbh, 2, "ROLLBACK TRAN"); + if ((retval = sqlite3_exec(imp_dbh->db, "ROLLBACK TRANSACTION", + NULL, NULL, &errmsg)) + != SQLITE_OK) + { + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + imp_dbh->in_tran = FALSE; + } + + return TRUE; +} + +int +sqlite_db_commit(SV *dbh, imp_dbh_t *imp_dbh) +{ + dTHX; + int retval; + char *errmsg; + + if (DBIc_is(imp_dbh, DBIcf_AutoCommit)) { + /* We don't need to warn, because the DBI layer will do it for us */ + return TRUE; + } + + if (imp_dbh->in_tran) { + sqlite_trace(dbh, (imp_xxh_t*)imp_dbh, 2, "COMMIT TRAN"); + if ((retval = sqlite3_exec(imp_dbh->db, "COMMIT TRANSACTION", + NULL, NULL, &errmsg)) + != SQLITE_OK) + { + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + imp_dbh->in_tran = FALSE; + } + return TRUE; +} + +int +sqlite_discon_all(SV *drh, imp_drh_t *imp_drh) +{ + dTHX; + return FALSE; /* no way to do this */ +} + +SV * +sqlite_db_last_insert_id(SV *dbh, imp_dbh_t *imp_dbh, SV *catalog, SV *schema, SV *table, SV *field, SV *attr) +{ + dTHX; + return newSViv(sqlite3_last_insert_rowid(imp_dbh->db)); +} + +int +sqlite_st_prepare (SV *sth, imp_sth_t *imp_sth, + char *statement, SV *attribs) +{ + dTHX; + D_imp_dbh_from_sth; + const char *extra; + int retval = 0; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(sth, (imp_xxh_t*)imp_sth, -2, "attempt to prepare on inactive database handle"); + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + + if (strlen(statement) < 1) { + sqlite_error(sth, (imp_xxh_t*)imp_sth, -2, "attempt to prepare empty statement"); + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 2, "prepare statement: %s", statement); + imp_sth->nrow = -1; + imp_sth->retval = SQLITE_OK; + imp_sth->params = newAV(); + imp_sth->col_types = newAV(); + Newz(0, imp_sth->statement, strlen(statement)+1, char); + + if ((retval = sqlite3_prepare_v2(imp_dbh->db, statement, -1, &(imp_sth->stmt), &extra)) + != SQLITE_OK) + { + if (imp_sth->stmt) { + sqlite3_finalize(imp_sth->stmt); + } + sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, (char*)sqlite3_errmsg(imp_dbh->db)); + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + + /* store the query for later re-use if required */ + Copy(statement, imp_sth->statement, strlen(statement)+1, char); + + DBIc_NUM_PARAMS(imp_sth) = sqlite3_bind_parameter_count(imp_sth->stmt); + DBIc_NUM_FIELDS(imp_sth) = sqlite3_column_count(imp_sth->stmt); + DBIc_IMPSET_on(imp_sth); + + return TRUE; +} + +char * +sqlite_quote(imp_dbh_t *imp_dbh, SV *val) +{ + dTHX; + STRLEN len; + char *cval = SvPV(val, len); + SV *ret = sv_2mortal(NEWSV(0, SvCUR(val) + 2)); + sv_setpvn(ret, "", 0); + + while (len) { + switch (*cval) { + case '\'': + sv_catpvn(ret, "''", 2); + break; + default: + sv_catpvn(ret, cval, 1); + } + *cval++; len--; + } + return SvPV_nolen(ret); +} + +void +sqlite_st_reset (pTHX_ SV *sth) +{ + D_imp_sth(sth); + if (DBIc_IMPSET(imp_sth)) + sqlite3_reset(imp_sth->stmt); +} + +int +sqlite_st_execute (SV *sth, imp_sth_t *imp_sth) +{ + dTHX; + D_imp_dbh_from_sth; + char *errmsg; + int num_params = DBIc_NUM_PARAMS(imp_sth); + int i; + int retval = 0; + + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 3, "execute"); + + /* warn("execute\n"); */ + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(sth, (imp_xxh_t*)imp_sth, -2, "attempt to execute on inactive database handle"); + return -2; /* -> undef in SQLite.xsi */ + } + + if (DBIc_ACTIVE(imp_sth)) { + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 3, "execute still active, reset"); + if ((imp_sth->retval = sqlite3_reset(imp_sth->stmt)) != SQLITE_OK) { + char *errmsg = (char*)sqlite3_errmsg(imp_dbh->db); + sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, errmsg); + return -2; /* -> undef in SQLite.xsi */ + } + } + + for (i = 0; i < num_params; i++) { + SV *value = av_shift(imp_sth->params); + SV *sql_type_sv = av_shift(imp_sth->params); + int sql_type = SvIV(sql_type_sv); + + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 4, "params left in 0x%p: %d", imp_sth->params, 1+av_len(imp_sth->params)); + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 4, "bind %d type %d as %s", i, sql_type, SvPV_nolen_undef_ok(value)); + + if (!SvOK(value)) { + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 5, "binding null"); + retval = sqlite3_bind_null(imp_sth->stmt, i+1); + } + else if (sql_type >= SQL_NUMERIC && sql_type <= SQL_SMALLINT) { +#if defined(USE_64_BIT_INT) + retval = sqlite3_bind_int64(imp_sth->stmt, i+1, SvIV(value)); +#else + retval = sqlite3_bind_int(imp_sth->stmt, i+1, SvIV(value)); +#endif + } + else if (sql_type >= SQL_FLOAT && sql_type <= SQL_DOUBLE) { + retval = sqlite3_bind_double(imp_sth->stmt, i+1, SvNV(value)); + } + else if (sql_type == SQL_BLOB) { + STRLEN len; + char * data = SvPV(value, len); + retval = sqlite3_bind_blob(imp_sth->stmt, i+1, data, len, SQLITE_TRANSIENT); + } + else { +#if 0 + /* stop guessing until we figure out better way to do this */ + const int numtype = looks_like_number(value); + if ((numtype & (IS_NUMBER_IN_UV|IS_NUMBER_NOT_INT)) == IS_NUMBER_IN_UV) { +#if defined(USE_64_BIT_INT) + retval = sqlite3_bind_int64(imp_sth->stmt, i+1, SvIV(value)); +#else + retval = sqlite3_bind_int(imp_sth->stmt, i+1, SvIV(value)); +#endif + } + else if ((numtype & (IS_NUMBER_NOT_INT|IS_NUMBER_INFINITY|IS_NUMBER_NAN)) == IS_NUMBER_NOT_INT) { + retval = sqlite3_bind_double(imp_sth->stmt, i+1, SvNV(value)); + } + else { +#endif + STRLEN len; + char *data; + if (imp_dbh->unicode) { + sv_utf8_upgrade(value); + } + data = SvPV(value, len); + retval = sqlite3_bind_text(imp_sth->stmt, i+1, data, len, SQLITE_TRANSIENT); +#if 0 + } +#endif + } + + if (value) { + SvREFCNT_dec(value); + } + SvREFCNT_dec(sql_type_sv); + if (retval != SQLITE_OK) { + sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, (char*)sqlite3_errmsg(imp_dbh->db)); + return -4; /* -> undef in SQLite.xsi */ + } + } + + if ( (!DBIc_is(imp_dbh, DBIcf_AutoCommit)) && (!imp_dbh->in_tran) ) { + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 2, "BEGIN TRAN"); + if ((retval = sqlite3_exec(imp_dbh->db, "BEGIN TRANSACTION", + NULL, NULL, &errmsg)) + != SQLITE_OK) + { + sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, errmsg); + return -2; /* -> undef in SQLite.xsi */ + } + imp_dbh->in_tran = TRUE; + } + + imp_sth->nrow = 0; + + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 3, "Execute returned %d cols\n", DBIc_NUM_FIELDS(imp_sth)); + if (DBIc_NUM_FIELDS(imp_sth) == 0) { + while ((imp_sth->retval = sqlite3_step(imp_sth->stmt)) != SQLITE_DONE) { + if (imp_sth->retval == SQLITE_ROW) { + continue; + } + sqlite3_reset(imp_sth->stmt); + sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, (char*)sqlite3_errmsg(imp_dbh->db)); + return -5; /* -> undef in SQLite.xsi */ + } + /* warn("Finalize\n"); */ + sqlite3_reset(imp_sth->stmt); + imp_sth->nrow = sqlite3_changes(imp_dbh->db); + /* DBIc_ACTIVE_on(imp_sth); */ + /* warn("Total changes: %d\n", sqlite3_total_changes(imp_dbh->db)); */ + /* warn("Nrow: %d\n", imp_sth->nrow); */ + return imp_sth->nrow; + } + + imp_sth->retval = sqlite3_step(imp_sth->stmt); + switch (imp_sth->retval) { + case SQLITE_ROW: + case SQLITE_DONE: DBIc_ACTIVE_on(imp_sth); + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 5, "exec ok - %d rows, %d cols\n", imp_sth->nrow, DBIc_NUM_FIELDS(imp_sth)); + return 0; /* -> '0E0' in SQLite.xsi */ + default: sqlite3_reset(imp_sth->stmt); + imp_sth->stmt = NULL; + sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, (char*)sqlite3_errmsg(imp_dbh->db)); + return -6; /* -> undef in SQLite.xsi */ + } +} + +int +sqlite_st_rows (SV *sth, imp_sth_t *imp_sth) +{ + return imp_sth->nrow; +} + +/* bind parameter + * NB: We store the params instead of bind immediately because + * we might need to re-create the imp_sth->stmt (see top of execute() function) + * and so we can't lose these params + */ +int +sqlite_bind_ph (SV *sth, imp_sth_t *imp_sth, + SV *param, SV *value, IV sql_type, SV *attribs, + int is_inout, IV maxlen) +{ + dTHX; + int pos; + if (!looks_like_number(param)) { + STRLEN len; + char *paramstring; + paramstring = SvPV(param, len); + if(paramstring[len] == 0 && strlen(paramstring) == len) { + pos = sqlite3_bind_parameter_index(imp_sth->stmt, paramstring); + if (pos==0) { + char errmsg[8192]; + sqlite3_snprintf(8191, errmsg, "Unknown named parameter: %s", paramstring); + sqlite_error(sth, (imp_xxh_t*)imp_sth, -2, errmsg); + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + pos = 2 * (pos - 1); + } + else { + sqlite_error(sth, (imp_xxh_t*)imp_sth, -2, " could not be coerced to a C string"); + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + } + else { + if (is_inout) { + sqlite_error(sth, (imp_xxh_t*)imp_sth, -2, "InOut bind params not implemented"); + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + } + pos = 2 * (SvIV(param) - 1); + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 3, "bind into 0x%p: %d => %s (%d) pos %d\n", + imp_sth->params, SvIV(param), SvPV_nolen_undef_ok(value), sql_type, pos); + av_store(imp_sth->params, pos, SvREFCNT_inc(value)); + av_store(imp_sth->params, pos+1, newSViv(sql_type)); + + return TRUE; +} + +int +sqlite_bind_col(SV *sth, imp_sth_t *imp_sth, SV *col, SV *ref, IV sql_type, SV *attribs) +{ + dTHX; + + /* store the type */ + av_store(imp_sth->col_types, SvIV(col)-1, newSViv(sql_type)); + + /* Allow default implementation to continue */ + return 1; +} + +AV * +sqlite_st_fetch (SV *sth, imp_sth_t *imp_sth) +{ + dTHX; + + AV *av; + D_imp_dbh_from_sth; + int numFields = DBIc_NUM_FIELDS(imp_sth); + int chopBlanks = DBIc_is(imp_sth, DBIcf_ChopBlanks); + int i; + + sqlite_trace(sth, (imp_xxh_t*)imp_sth, 6, "numFields == %d, nrow == %d\n", numFields, imp_sth->nrow); + + if (!DBIc_ACTIVE(imp_sth)) { + return Nullav; + } + + if (imp_sth->retval == SQLITE_DONE) { + sqlite_st_finish(sth, imp_sth); + return Nullav; + } + + if (imp_sth->retval != SQLITE_ROW) { + /* error */ + sqlite_st_finish(sth, imp_sth); + sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, (char*)sqlite3_errmsg(imp_dbh->db)); + return Nullav; /* -> undef in SQLite.xsi */ + } + + imp_sth->nrow++; + + av = DBIc_DBISTATE((imp_xxh_t *)imp_sth)->get_fbav(imp_sth); + for (i = 0; i < numFields; i++) { + int len; + char * val; + int col_type = sqlite3_column_type(imp_sth->stmt, i); + SV **sql_type = av_fetch(imp_sth->col_types, i, 0); + if (sql_type && SvOK(*sql_type)) { + if (SvIV(*sql_type)) { + col_type = SvIV(*sql_type); + } + } + switch(col_type) { + case SQLITE_INTEGER: +#if defined(USE_64_BIT_INT) + sv_setiv(AvARRAY(av)[i], sqlite3_column_int64(imp_sth->stmt, i)); +#else + sv_setnv(AvARRAY(av)[i], (double)sqlite3_column_int64(imp_sth->stmt, i)); +#endif + break; + case SQLITE_FLOAT: + sv_setnv(AvARRAY(av)[i], sqlite3_column_double(imp_sth->stmt, i)); + break; + case SQLITE_TEXT: + val = (char*)sqlite3_column_text(imp_sth->stmt, i); + len = sqlite3_column_bytes(imp_sth->stmt, i); + if (chopBlanks) { + val = savepv(val); + while((len > 0) && (val[len-1] == ' ')) { + len--; + } + val[len] = '\0'; + } + sv_setpvn(AvARRAY(av)[i], val, len); + if (imp_dbh->unicode) { + SvUTF8_on(AvARRAY(av)[i]); + } else { + SvUTF8_off(AvARRAY(av)[i]); + } + if (chopBlanks) Safefree(val); + break; + case SQLITE_BLOB: + len = sqlite3_column_bytes(imp_sth->stmt, i); + sv_setpvn(AvARRAY(av)[i], sqlite3_column_blob(imp_sth->stmt, i), len); + SvUTF8_off(AvARRAY(av)[i]); + break; + default: + sv_setsv(AvARRAY(av)[i], &PL_sv_undef); + SvUTF8_off(AvARRAY(av)[i]); + break; + } + SvSETMAGIC(AvARRAY(av)[i]); + } + + imp_sth->retval = sqlite3_step(imp_sth->stmt); + + return av; +} + +int +sqlite_st_finish (SV *sth, imp_sth_t *imp_sth) +{ + return sqlite_st_finish3(sth, imp_sth, 0); +} + +int +sqlite_st_finish3 (SV *sth, imp_sth_t *imp_sth, int is_destroy) +{ + dTHX; + + D_imp_dbh_from_sth; + + /* warn("finish statement\n"); */ + if (!DBIc_ACTIVE(imp_sth)) + return 1; + + DBIc_ACTIVE_off(imp_sth); + + av_clear(imp_sth->col_types); + + if (!DBIc_ACTIVE(imp_dbh)) /* no longer connected */ + return 1; + + if (is_destroy) { + return TRUE; + } + + if ((imp_sth->retval = sqlite3_reset(imp_sth->stmt)) != SQLITE_OK) { + char *errmsg = (char*)sqlite3_errmsg(imp_dbh->db); + /* warn("finalize failed! %s\n", errmsg); */ + sqlite_error(sth, (imp_xxh_t*)imp_sth, imp_sth->retval, errmsg); + return FALSE; /* -> &sv_no (or void) in SQLite.xsi */ + } + + return TRUE; +} + +void +sqlite_st_destroy (SV *sth, imp_sth_t *imp_sth) +{ + dTHX; + + D_imp_dbh_from_sth; + /* warn("destroy statement: %s\n", imp_sth->statement); */ + DBIc_ACTIVE_off(imp_sth); + if (DBIc_ACTIVE(imp_dbh)) { + /* finalize sth when active connection */ + sqlite3_finalize(imp_sth->stmt); + } + Safefree(imp_sth->statement); + SvREFCNT_dec((SV*)imp_sth->params); + SvREFCNT_dec((SV*)imp_sth->col_types); + DBIc_IMPSET_off(imp_sth); +} + +int +sqlite_st_blob_read (SV *sth, imp_sth_t *imp_sth, + int field, long offset, long len, SV *destrv, long destoffset) +{ + return 0; +} + +int +sqlite_db_STORE_attrib (SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv) +{ + dTHX; + char *key = SvPV_nolen(keysv); + char *errmsg; + int retval; + + if (strEQ(key, "AutoCommit")) { + if (SvTRUE(valuesv)) { + /* commit tran? */ + if ( (!DBIc_is(imp_dbh, DBIcf_AutoCommit)) && (imp_dbh->in_tran) ) { + sqlite_trace(dbh, (imp_xxh_t*)imp_dbh, 2, "COMMIT TRAN"); + if ((retval = sqlite3_exec(imp_dbh->db, "COMMIT TRANSACTION", + NULL, NULL, &errmsg)) + != SQLITE_OK) + { + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); + return TRUE; /* XXX: is this correct? */ + } + imp_dbh->in_tran = FALSE; + } + } + DBIc_set(imp_dbh, DBIcf_AutoCommit, SvTRUE(valuesv)); + return TRUE; + } + if (strEQ(key, "unicode")) { +#if (PERL_REVISION <= 5) && ((PERL_VERSION < 8) || (PERL_VERSION == 8 && PERL_SUBVERSION < 5)) + sqlite_trace(dbh, (imp_xxh_t*)imp_dbh, 2, "Unicode support is disabled for this version of perl."); + imp_dbh->unicode = 0; +#else + imp_dbh->unicode = !(! SvTRUE(valuesv)); +#endif + return TRUE; + } + return FALSE; +} + +SV * +sqlite_db_FETCH_attrib (SV *dbh, imp_dbh_t *imp_dbh, SV *keysv) +{ + dTHX; + char *key = SvPV_nolen(keysv); + + if (strEQ(key, "sqlite_version")) { + return newSVpv(sqlite3_version,0); + } + if (strEQ(key, "unicode")) { +#if (PERL_REVISION <= 5) && ((PERL_VERSION < 8) || (PERL_VERSION == 8 && PERL_SUBVERSION < 5)) + sqlite_trace(dbh, (imp_xxh_t*)imp_dbh, 2, "Unicode support is disabled for this version of perl."); + return newSViv(0); +#else + return newSViv(imp_dbh->unicode ? 1 : 0); +#endif + } + + return NULL; +} + +int +sqlite_st_STORE_attrib (SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv) +{ + dTHX; + char *key = SvPV_nolen(keysv); + return FALSE; +} + +int +type_to_odbc_type (int type) +{ + switch(type) { + case SQLITE_INTEGER: return SQL_INTEGER; + case SQLITE_FLOAT: return SQL_DOUBLE; + case SQLITE_TEXT: return SQL_VARCHAR; + case SQLITE_BLOB: return SQL_BLOB; + case SQLITE_NULL: return SQL_UNKNOWN_TYPE; + default: return SQL_UNKNOWN_TYPE; + } +} + +SV * +sqlite_st_FETCH_attrib (SV *sth, imp_sth_t *imp_sth, SV *keysv) +{ + dTHX; + D_imp_dbh_from_sth; + char *key = SvPV_nolen(keysv); + SV *retsv = NULL; + int i,n; + + if (!DBIc_ACTIVE(imp_sth)) { + return NULL; + } + + /* warn("fetch: %s\n", key); */ + + i = DBIc_NUM_FIELDS(imp_sth); + + if (strEQ(key, "NAME")) { + AV *av = newAV(); + /* warn("Fetch NAME fields: %d\n", i); */ + av_extend(av, i); + retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); + for (n = 0; n < i; n++) { + /* warn("Fetch col name %d\n", n); */ + const char *fieldname = sqlite3_column_name(imp_sth->stmt, n); + if (fieldname) { + /* warn("Name [%d]: %s\n", n, fieldname); */ + /* char *dot = instr(fieldname, "."); */ + /* if (dot) drop table name from field name */ + /* fieldname = ++dot; */ + av_store(av, n, newSVpv(fieldname, 0)); + } + } + } + else if (strEQ(key, "PRECISION")) { + AV *av = newAV(); + retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); + } + else if (strEQ(key, "TYPE")) { + AV *av = newAV(); + av_extend(av, i); + retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); + for (n = 0; n < i; n++) { + const char *fieldtype = sqlite3_column_decltype(imp_sth->stmt, n); + int type = sqlite3_column_type(imp_sth->stmt, n); + /* warn("got type: %d = %s\n", type, fieldtype); */ + type = type_to_odbc_type(type); + /* av_store(av, n, newSViv(type)); */ + if (fieldtype) + av_store(av, n, newSVpv(fieldtype, 0)); + else + av_store(av, n, newSVpv("VARCHAR", 0)); + } + } + else if (strEQ(key, "NULLABLE")) { + AV *av = newAV(); + av_extend(av, i); + retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); +#if defined(SQLITE_ENABLE_COLUMN_METADATA) + for (n = 0; n < i; n++) { + const char *database = sqlite3_column_database_name(imp_sth->stmt, n); + const char *tablename = sqlite3_column_table_name(imp_sth->stmt, n); + const char *fieldname = sqlite3_column_name(imp_sth->stmt, n); + const char *datatype, *collseq; + int notnull, primary, autoinc; + int retval = sqlite3_table_column_metadata(imp_dbh->db, database, tablename, fieldname, &datatype, &collseq, ¬null, &primary, &autoinc); + if (retval != SQLITE_OK) { + char *errmsg = (char*)sqlite3_errmsg(imp_dbh->db); + sqlite_error(sth, (imp_xxh_t*)imp_sth, retval, errmsg); + av_store(av, n, newSViv(2)); /* SQL_NULLABLE_UNKNOWN */ + } + else { + av_store(av, n, newSViv(!notnull)); + } + } +#endif + } + else if (strEQ(key, "SCALE")) { + AV *av = newAV(); + retsv = sv_2mortal(newRV(sv_2mortal((SV*)av))); + } + else if (strEQ(key, "NUM_OF_FIELDS")) { + retsv = sv_2mortal(newSViv(i)); + } + + return retsv; +} + +static void +sqlite_db_set_result(pTHX_ sqlite3_context *context, SV *result, int is_error ) +{ + STRLEN len; + char *s; + + if ( is_error ) { + s = SvPV(result, len); + sqlite3_result_error( context, s, len ); + return; + } + + /* warn("result: %s\n", SvPV_nolen(result)); */ + if ( !SvOK(result) ) { + sqlite3_result_null( context ); + } else if( SvIOK_UV(result) ) { + s = SvPV(result, len); + sqlite3_result_text( context, s, len, SQLITE_TRANSIENT ); + } + else if ( SvIOK(result) ) { + sqlite3_result_int( context, SvIV(result)); + } else if ( !is_error && SvIOK(result) ) { + sqlite3_result_double( context, SvNV(result)); + } else { + s = SvPV(result, len); + sqlite3_result_text( context, s, len, SQLITE_TRANSIENT ); + } +} + +static void +sqlite_db_func_dispatcher(int is_unicode, sqlite3_context *context, int argc, sqlite3_value **value) +{ + dTHX; + dSP; + int count; + int i; + SV *func; + + func = sqlite3_user_data(context); + + ENTER; + SAVETMPS; + + PUSHMARK(SP); + for ( i=0; i < argc; i++ ) { + SV *arg; + STRLEN len = sqlite3_value_bytes(value[i]); + int type = sqlite3_value_type(value[i]); + + /* warn("func dispatch type: %d, value: %s\n", type, sqlite3_value_text(value[i])); */ + switch(type) { + case SQLITE_INTEGER: + arg = sv_2mortal(newSViv(sqlite3_value_int(value[i]))); + break; + case SQLITE_FLOAT: + arg = sv_2mortal(newSVnv(sqlite3_value_double(value[i]))); + break; + case SQLITE_TEXT: + arg = newSVpvn((const char *)sqlite3_value_text(value[i]), len); + if (is_unicode) { + SvUTF8_on(arg); + } + arg = sv_2mortal(arg); + break; + case SQLITE_BLOB: + arg = sv_2mortal(newSVpvn(sqlite3_value_blob(value[i]), len)); + break; + default: + arg = &PL_sv_undef; + } + + XPUSHs(arg); + } + PUTBACK; + + count = call_sv(func, G_SCALAR|G_EVAL); + + SPAGAIN; + + /* Check for an error */ + if (SvTRUE(ERRSV) ) { + sqlite_db_set_result(aTHX_ context, ERRSV, 1); + POPs; + } else if ( count != 1 ) { + SV *err = sv_2mortal(newSVpvf( "function should return 1 argument, got %d", + count )); + + sqlite_db_set_result(aTHX_ context, err, 1); + /* Clear the stack */ + for ( i=0; i < count; i++ ) { + POPs; + } + } else { + sqlite_db_set_result(aTHX_ context, POPs, 0 ); + } + + PUTBACK; + + FREETMPS; + LEAVE; +} + + +static void +sqlite_db_func_dispatcher_unicode(sqlite3_context *context, int argc, sqlite3_value **value) +{ + sqlite_db_func_dispatcher(1, context, argc, value); +} + +static void +sqlite_db_func_dispatcher_no_unicode(sqlite3_context *context, int argc, sqlite3_value **value) +{ + sqlite_db_func_dispatcher(0, context, argc, value); +} + +void +sqlite3_db_create_function(pTHX_ SV *dbh, const char *name, int argc, SV *func ) +{ + D_imp_dbh(dbh); + int retval; + + /* Copy the function reference */ + SV *func_sv = newSVsv(func); + av_push( imp_dbh->functions, func_sv ); + + /* warn("create_function %s with %d args\n", name, argc); */ + retval = sqlite3_create_function( imp_dbh->db, name, argc, SQLITE_UTF8, + func_sv, + imp_dbh->unicode ? sqlite_db_func_dispatcher_unicode + : sqlite_db_func_dispatcher_no_unicode, + NULL, NULL ); + if ( retval != SQLITE_OK ) + { + char errmsg[8192]; + sqlite3_snprintf(8191, errmsg, "sqlite_create_function failed with error %s", sqlite3_errmsg(imp_dbh->db)); + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); + } +} + +void +sqlite3_db_enable_load_extension(pTHX_ SV *dbh, int onoff ) +{ + D_imp_dbh(dbh); + int retval; + + retval = sqlite3_enable_load_extension( imp_dbh->db, onoff ); + if ( retval != SQLITE_OK ) + { + char errmsg[8192]; + sqlite3_snprintf(8191, errmsg, "sqlite_enable_load_extension failed with error %s", sqlite3_errmsg(imp_dbh->db)); + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); + } +} + +typedef struct aggrInfo aggrInfo; +struct aggrInfo { + SV *aggr_inst; + SV *err; + int inited; +}; + +static void +sqlite_db_aggr_new_dispatcher(pTHX_ sqlite3_context *context, aggrInfo *aggr_info ) +{ + dSP; + SV *pkg = NULL; + int count = 0; + + aggr_info->err = NULL; + aggr_info->aggr_inst = NULL; + + pkg = sqlite3_user_data(context); + if ( !pkg ) + return; + + ENTER; + SAVETMPS; + + PUSHMARK(SP); + XPUSHs( sv_2mortal( newSVsv(pkg) ) ); + PUTBACK; + + count = call_method ("new", G_EVAL|G_SCALAR); + SPAGAIN; + + aggr_info->inited = 1; + + if ( SvTRUE( ERRSV ) ) { + aggr_info->err = newSVpvf ("error during aggregator's new(): %s", + SvPV_nolen (ERRSV)); + POPs; + } else if ( count != 1 ) { + int i; + + aggr_info->err = newSVpvf( "new() should return one value, got %d", + count ); + /* Clear the stack */ + for ( i=0; i < count; i++ ) { + POPs; + } + } else { + SV *aggr = POPs; + if ( SvROK(aggr) ) { + aggr_info->aggr_inst = newSVsv(aggr); + } else{ + aggr_info->err = newSVpvf( "new() should return a blessed reference" ); + } + } + + PUTBACK; + + FREETMPS; + LEAVE; + + return; +} + +static void +sqlite_db_aggr_step_dispatcher (sqlite3_context *context, + int argc, sqlite3_value **value) +{ + dTHX; + dSP; + int i; + aggrInfo *aggr; + + aggr = sqlite3_aggregate_context (context, sizeof (aggrInfo)); + if ( !aggr ) + return; + + ENTER; + SAVETMPS; + + /* initialize on first step */ + if ( !aggr->inited ) { + sqlite_db_aggr_new_dispatcher(aTHX_ context, aggr ); + } + + if ( aggr->err || !aggr->aggr_inst ) + goto cleanup; + + PUSHMARK(SP); + XPUSHs( sv_2mortal( newSVsv( aggr->aggr_inst ) )); + for ( i=0; i < argc; i++ ) { + SV *arg; + int len = sqlite3_value_bytes(value[i]); + int type = sqlite3_value_type(value[i]); + + switch(type) { + case SQLITE_INTEGER: + arg = sv_2mortal(newSViv(sqlite3_value_int(value[i]))); + break; + case SQLITE_FLOAT: + arg = sv_2mortal(newSVnv(sqlite3_value_double(value[i]))); + break; + case SQLITE_TEXT: + arg = sv_2mortal(newSVpvn((const char *)sqlite3_value_text(value[i]), len)); + break; + case SQLITE_BLOB: + arg = sv_2mortal(newSVpvn(sqlite3_value_blob(value[i]), len)); + break; + default: + arg = &PL_sv_undef; + } + + XPUSHs(arg); + } + PUTBACK; + + call_method ("step", G_SCALAR|G_EVAL|G_DISCARD); + + /* Check for an error */ + if (SvTRUE(ERRSV) ) { + aggr->err = newSVpvf( "error during aggregator's step(): %s", + SvPV_nolen(ERRSV)); + POPs; + } + + cleanup: + FREETMPS; + LEAVE; +} + +static void +sqlite_db_aggr_finalize_dispatcher( sqlite3_context *context ) +{ + dTHX; + dSP; + aggrInfo *aggr, myAggr; + int count = 0; + + aggr = sqlite3_aggregate_context (context, sizeof (aggrInfo)); + + ENTER; + SAVETMPS; + + if ( !aggr ) { + /* SQLite seems to refuse to create a context structure + from finalize() */ + aggr = &myAggr; + aggr->aggr_inst = NULL; + aggr->err = NULL; + sqlite_db_aggr_new_dispatcher(aTHX_ context, aggr); + } + + if ( ! aggr->err && aggr->aggr_inst ) { + PUSHMARK(SP); + XPUSHs( sv_2mortal( newSVsv( aggr->aggr_inst )) ); + PUTBACK; + + count = call_method( "finalize", G_SCALAR|G_EVAL ); + SPAGAIN; + + if ( SvTRUE(ERRSV) ) { + aggr->err = newSVpvf ("error during aggregator's finalize(): %s", + SvPV_nolen(ERRSV) ) ; + POPs; + } else if ( count != 1 ) { + int i; + aggr->err = newSVpvf( "finalize() should return 1 value, got %d", + count ); + /* Clear the stack */ + for ( i=0; ierr ) { + warn( "DBD::SQLite: error in aggregator cannot be reported to SQLite: %s", + SvPV_nolen( aggr->err ) ); + + /* sqlite_db_set_result(aTHX_ context, aggr->err, 1 ); */ + SvREFCNT_dec( aggr->err ); + aggr->err = NULL; + } + + if ( aggr->aggr_inst ) { + SvREFCNT_dec( aggr->aggr_inst ); + aggr->aggr_inst = NULL; + } + + FREETMPS; + LEAVE; +} + +void +sqlite3_db_create_aggregate(pTHX_ SV *dbh, const char *name, int argc, SV *aggr_pkg ) +{ + D_imp_dbh(dbh); + int retval; + + /* Copy the aggregate reference */ + SV *aggr_pkg_copy = newSVsv(aggr_pkg); + av_push( imp_dbh->aggregates, aggr_pkg_copy ); + + retval = sqlite3_create_function( imp_dbh->db, name, argc, SQLITE_UTF8, + aggr_pkg_copy, + NULL, + sqlite_db_aggr_step_dispatcher, + sqlite_db_aggr_finalize_dispatcher + ); + + if ( retval != SQLITE_OK ) + { + char errmsg[8192]; + sqlite3_snprintf(8191, errmsg, "sqlite_create_aggregate failed with error %s", sqlite3_errmsg(imp_dbh->db)); + sqlite_error(dbh, (imp_xxh_t*)imp_dbh, retval, errmsg); + } +} + + +int sqlite_db_collation_dispatcher(void *func, int len1, const void *string1, + int len2, const void *string2) +{ + dTHX; + dSP; + int cmp; + int n_retval; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs( sv_2mortal ( newSVpvn( string1, len1) ) ); + XPUSHs( sv_2mortal ( newSVpvn( string2, len2) ) ); + PUTBACK; + n_retval = call_sv((void*)func, G_SCALAR); + if (n_retval != 1) { + croak("collation function returned %d arguments", n_retval); + } + SPAGAIN; + cmp = POPi; + PUTBACK; + FREETMPS; + LEAVE; + + return cmp; +} + +int sqlite_db_collation_dispatcher_utf8( + void *func, int len1, const void *string1, + int len2, const void *string2) +{ + dTHX; + dSP; + int cmp; + int n_retval; + SV *sv1, *sv2; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + sv1 = newSVpvn( string1, len1); + SvUTF8_on(sv1); + sv2 = newSVpvn( string2, len2); + SvUTF8_on(sv2); + XPUSHs( sv_2mortal ( sv1 ) ); + XPUSHs( sv_2mortal ( sv2 ) ); + PUTBACK; + n_retval = call_sv((void*)func, G_SCALAR); + if (n_retval != 1) { + croak("collation function returned %d arguments", n_retval); + } + SPAGAIN; + cmp = POPi; + PUTBACK; + FREETMPS; + LEAVE; + + return cmp; +} + + +void +sqlite3_db_create_collation(pTHX_ SV *dbh, const char *name, SV *func ) +{ + D_imp_dbh(dbh); + int rv, rv2; + void *aa = "aa"; + void *zz = "zz"; + + SV *func_sv = newSVsv(func); + + /* Check that this is a proper collation function */ + rv = sqlite_db_collation_dispatcher(func_sv, 2, aa, 2, aa); + if (rv != 0) { + warn("improper collation function: %s(aa, aa) returns %d!", name, rv); + } + rv = sqlite_db_collation_dispatcher(func_sv, 2, aa, 2, zz); + rv2 = sqlite_db_collation_dispatcher(func_sv, 2, zz, 2, aa); + if (rv2 != (rv * -1)) { + warn("improper collation function: '%s' is not symmetric", name); + } + + /* Copy the func reference so that it can be deallocated at disconnect */ + av_push( imp_dbh->functions, func_sv ); + + /* Register the func within sqlite3 */ + rv = sqlite3_create_collation( + imp_dbh->db, name, SQLITE_UTF8, + func_sv, + imp_dbh->unicode ? sqlite_db_collation_dispatcher_utf8 + : sqlite_db_collation_dispatcher + ); + + if ( rv != SQLITE_OK ) + { + char errmsg[8192]; + sqlite3_snprintf(8191, errmsg, "sqlite_create_collation failed with error %s", sqlite3_errmsg(imp_dbh->db)); + } +} + + +int sqlite_db_progress_handler_dispatcher( void *handler ) +{ + dTHX; + dSP; + int n_retval; + int retval; + + PUSHMARK(SP); + n_retval = call_sv( handler, G_SCALAR ); + if ( n_retval != 1 ) { + croak( "progress_handler returned %d arguments", n_retval ); + } + SPAGAIN; + retval = POPi; + PUTBACK; + + return retval; +} + + + +void +sqlite3_db_progress_handler(pTHX_ SV *dbh, int n_opcodes, SV *handler ) +{ + D_imp_dbh(dbh); + + if (handler == &PL_sv_undef) { + /* remove previous handler */ + sqlite3_progress_handler( imp_dbh->db, 0, NULL, NULL); + } + else { + SV *handler_sv = newSVsv(handler); + + /* Copy the handler ref so that it can be deallocated at disconnect */ + av_push( imp_dbh->functions, handler_sv ); + + /* Register the func within sqlite3 */ + sqlite3_progress_handler( imp_dbh->db, n_opcodes, + sqlite_db_progress_handler_dispatcher, + handler_sv ); + } +} + +/* end */