diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2012-09-24 10:15:50 +0000 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-09-26 13:46:46 +0000 |
commit | 485b97be9f2f2abf5a40923b5fd85f75714a8c02 (patch) | |
tree | ca05cb0ecf3828d909a898c3e5805804a0aff5f8 /dbdimp.c | |
download | perl-dbd-sqlite-tarball-master.tar.gz |
Imported from /srv/lorry/lorry-area/perl-dbd-sqlite-tarball/DBD-SQLite-1.38_01.tar.gz.HEADDBD-SQLite-1.38_01masterbaserock/morph
Diffstat (limited to 'dbdimp.c')
-rw-r--r-- | dbdimp.c | 2721 |
1 files changed, 2721 insertions, 0 deletions
diff --git a/dbdimp.c b/dbdimp.c new file mode 100644 index 0000000..f574da7 --- /dev/null +++ b/dbdimp.c @@ -0,0 +1,2721 @@ +#define PERL_NO_GET_CONTEXT + +#define NEED_newSVpvn_flags +#define NEED_sv_2pvbyte + +#include "SQLiteXS.h" + +DBISTATE_DECLARE; + +#define SvPV_nolen_undef_ok(x) (SvOK(x) ? SvPV_nolen(x) : "undef") + +/*-----------------------------------------------------* + * Debug Macros + *-----------------------------------------------------*/ + +#undef DBD_SQLITE_CROAK_DEBUG + +#ifdef DBD_SQLITE_CROAK_DEBUG + #define croak_if_db_is_null() if (!imp_dbh->db) croak("imp_dbh->db is NULL at line %d in %s", __LINE__, __FILE__) + #define croak_if_stmt_is_null() if (!imp_sth->stmt) croak("imp_sth->stmt is NULL at line %d in %s", __LINE__, __FILE__) +#else + #define croak_if_db_is_null() + #define croak_if_stmt_is_null() +#endif + + +/*-----------------------------------------------------* + * Globals + *-----------------------------------------------------*/ +imp_dbh_t *last_executed_dbh; /* needed by perl_tokenizer + to know if unicode is on/off */ + + +/*-----------------------------------------------------* + * Helper Methods + *-----------------------------------------------------*/ + +#define sqlite_error(h,rc,what) _sqlite_error(aTHX_ __FILE__, __LINE__, h, rc, what) +#define sqlite_trace(h,xxh,level,what) if ( DBIc_TRACE_LEVEL((imp_xxh_t*)xxh) >= level ) _sqlite_trace(aTHX_ __FILE__, __LINE__, h, (imp_xxh_t*)xxh, what) +#define sqlite_exec(h,sql) _sqlite_exec(aTHX_ h, imp_dbh->db, sql) +#define sqlite_open(dbname,db) _sqlite_open(aTHX_ dbh, dbname, db) + +static void +_sqlite_trace(pTHX_ char *file, int line, SV *h, imp_xxh_t *imp_xxh, const char *what) +{ + PerlIO_printf( + DBIc_LOGPIO(imp_xxh), + "sqlite trace: %s at %s line %d\n", what, file, line + ); +} + +static void +_sqlite_error(pTHX_ char *file, int line, SV *h, int rc, const char *what) +{ + D_imp_xxh(h); + + 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 + ); + } +} + +int +_sqlite_exec(pTHX_ SV *h, sqlite3 *db, const char *sql) +{ + int rc; + char *errmsg; + + rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg); + if ( rc != SQLITE_OK ) { + sqlite_error(h, rc, errmsg); + if (errmsg) sqlite3_free(errmsg); + } + return rc; +} + +int +_sqlite_open(pTHX_ SV *dbh, const char *dbname, sqlite3 **db) +{ + int rc; + rc = sqlite3_open(dbname, db); + if ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, sqlite3_errmsg(*db)); + if (*db) sqlite3_close(*db); + } + return rc; +} + +static int +sqlite_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; + } +} + +static int +sqlite_type_from_odbc_type(int type) +{ + switch(type) { + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_BIGINT: + return SQLITE_INTEGER; + case SQL_FLOAT: + case SQL_REAL: + case SQL_DOUBLE: + return SQLITE_FLOAT; + case SQL_BLOB: + return SQLITE_BLOB; + default: + return SQLITE_TEXT; + } +} + +static void +sqlite_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) ) { + if ((UV)(sqlite3_int64)UV_MAX == UV_MAX) + sqlite3_result_int64( context, (sqlite3_int64)SvUV(result)); + else { + s = SvPV(result, len); + sqlite3_result_text( context, s, len, SQLITE_TRANSIENT ); + } + } + else if ( SvIOK(result) ) { +#if defined(USE_64_BIT_INT) + sqlite3_result_int64( context, SvIV(result)); +#else + sqlite3_result_int( context, SvIV(result)); +#endif + } else if ( SvNOK(result) && ( sizeof(NV) == sizeof(double) || SvNVX(result) == (double) SvNVX(result) ) ) { + sqlite3_result_double( context, SvNV(result)); + } else { + s = SvPV(result, len); + sqlite3_result_text( context, s, len, SQLITE_TRANSIENT ); + } +} + +/* + * see also sqlite3IsNumber, sqlite3_int64 type definition, + * applyNumericAffinity, sqlite3Atoi64, etc from sqlite3.c + */ +static int +sqlite_is_number(pTHX_ const char *v, int sql_type) +{ + const char *z = v; + const char *d = v; + int neg; + int digit = 0; + int precision = 0; + bool has_plus = FALSE; + bool maybe_int = TRUE; + char format[10]; + + if (sql_type != SQLITE_NULL) { + while (*z == ' ') { z++; v++; d++; } + } + + if (*z == '-') { neg = 1; z++; d++; } + else if (*z == '+') { neg = 0; z++; d++; has_plus = TRUE; } + else { neg = 0; } + if (!isdigit(*z)) return 0; + while (isdigit(*z)) { digit++; z++; } +#if defined(USE_64_BIT_INT) + if (digit > 19) maybe_int = FALSE; /* too large for i64 */ + if (digit == 19) { + int c; + char tmp[22]; + strncpy(tmp, d, z - d + 1); + c = memcmp(tmp, "922337203685477580", 18); + if (c == 0) { + c = tmp[18] - '7' - neg; + } + if (c > 0) maybe_int = FALSE; + } +#else + if (digit > 10) maybe_int = FALSE; /* too large for i32 */ + if (digit == 10) { + int c; + char tmp[14]; + strncpy(tmp, d, z - d + 1); + c = memcmp(tmp, "214748364", 9); + if (c == 0) { + c = tmp[9] - '7' - neg; + } + if (c > 0) maybe_int = FALSE; + } +#endif + if (*z == '.') { + maybe_int = FALSE; + z++; + if (!isdigit(*z)) return 0; + while (isdigit(*z)) { precision++; z++; } + } + if (*z == 'e' || *z == 'E') { + maybe_int = FALSE; + z++; + if (*z == '+' || *z == '-') { z++; } + if (!isdigit(*z)) return 0; + while (isdigit(*z)) { z++; } + } + if (*z && !isdigit(*z)) return 0; + + if (maybe_int || sql_type == SQLITE_INTEGER) { +#if defined(USE_64_BIT_INT) + #if defined(HAS_ATOLL) + if (strEQ(form((has_plus ? "+%lli" : "%lli"), atoll(v)), v)) return 1; + #else + if (strEQ(form((has_plus ? "+%li" : "%li"), atol(v)), v)) return 1; + #endif +#else + if (strEQ(form((has_plus ? "+%i" : "%i"), atoi(v)), v)) return 1; +#endif + } + if (sql_type != SQLITE_INTEGER) { + sprintf(format, (has_plus ? "+%%.%df" : "%%.%df"), precision); + if (strEQ(form(format, atof(v)), v)) return 2; + } + return 0; +} + +/*-----------------------------------------------------* + * DBD Methods + *-----------------------------------------------------*/ + +void +sqlite_init(dbistate_t *dbistate) +{ + dTHX; + DBISTATE_INIT; /* Initialize the DBI macros */ +} + +int +sqlite_discon_all(SV *drh, imp_drh_t *imp_drh) +{ + dTHX; + return FALSE; /* no way to do this */ +} + +int +sqlite_db_login6(SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *user, char *pass, SV *attr) +{ + dTHX; + int rc; + + sqlite_trace(dbh, imp_dbh, 3, form("login '%s' (version %s)", dbname, sqlite3_version)); + + rc = sqlite_open(dbname, &(imp_dbh->db)); + if ( rc != SQLITE_OK ) { + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + DBIc_IMPSET_on(imp_dbh); + + imp_dbh->unicode = FALSE; + imp_dbh->functions = newAV(); + imp_dbh->aggregates = newAV(); + imp_dbh->collation_needed_callback = newSVsv( &PL_sv_undef ); + imp_dbh->timeout = SQL_TIMEOUT; + imp_dbh->handle_binary_nulls = FALSE; + imp_dbh->allow_multiple_statements = FALSE; + imp_dbh->use_immediate_transaction = TRUE; + imp_dbh->see_if_its_a_number = FALSE; + + sqlite3_busy_timeout(imp_dbh->db, SQL_TIMEOUT); + + sqlite_exec(dbh, "PRAGMA empty_result_callbacks = ON"); + sqlite_exec(dbh, "PRAGMA show_datatypes = ON"); + +#if 0 + /* + ** As of 1.26_06 foreign keys support was enabled by default, + ** but with further discussion, we agreed to follow what + ** sqlite team does, i.e. wait until the team think it + ** reasonable to enable the support by default, as they have + ** larger users and will allocate enough time for people to + ** get used to the foreign keys. However, we should say it loud + ** that sometime in the (near?) future, this feature may break + ** your applications (and it actually broke applications). + ** Let everyone be prepared. + */ + sqlite_exec(dbh, "PRAGMA foreign_keys = ON"); +#endif + + DBIc_ACTIVE_on(imp_dbh); + + return TRUE; +} + +int +sqlite_db_commit(SV *dbh, imp_dbh_t *imp_dbh) +{ + dTHX; + int rc; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to commit on inactive database handle"); + return FALSE; + } + + 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 (DBIc_is(imp_dbh, DBIcf_BegunWork)) { + DBIc_off(imp_dbh, DBIcf_BegunWork); + DBIc_on(imp_dbh, DBIcf_AutoCommit); + } + + croak_if_db_is_null(); + + if (!sqlite3_get_autocommit(imp_dbh->db)) { + sqlite_trace(dbh, imp_dbh, 3, "COMMIT TRAN"); + + rc = sqlite_exec(dbh, "COMMIT TRANSACTION"); + if (rc != SQLITE_OK) { + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + } + + return TRUE; +} + +int +sqlite_db_rollback(SV *dbh, imp_dbh_t *imp_dbh) +{ + dTHX; + int rc; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to rollback on inactive database handle"); + return FALSE; + } + + if (DBIc_is(imp_dbh, DBIcf_BegunWork)) { + DBIc_off(imp_dbh, DBIcf_BegunWork); + DBIc_on(imp_dbh, DBIcf_AutoCommit); + } + + croak_if_db_is_null(); + + if (!sqlite3_get_autocommit(imp_dbh->db)) { + + sqlite_trace(dbh, imp_dbh, 3, "ROLLBACK TRAN"); + + rc = sqlite_exec(dbh, "ROLLBACK TRANSACTION"); + if (rc != SQLITE_OK) { + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + } + + return TRUE; +} + +int +sqlite_db_disconnect(SV *dbh, imp_dbh_t *imp_dbh) +{ + dTHX; + int rc; + sqlite3_stmt *pStmt; + + if (DBIc_is(imp_dbh, DBIcf_AutoCommit) == FALSE) { + sqlite_db_rollback(dbh, imp_dbh); + } + DBIc_ACTIVE_off(imp_dbh); + +#if 0 + /* + ** This cause segfaults when we have virtual tables, as sqlite3 + ** seems to try to finalize the statements for the tables (freed + ** here) while closing. So we need to find other ways to do the + ** right thing. + */ + /* COMPAT: sqlite3_next_stmt is only available for 3006000 or newer */ + while ( (pStmt = sqlite3_next_stmt(imp_dbh->db, 0)) != NULL ) { + sqlite3_finalize(pStmt); + } +#endif + + croak_if_db_is_null(); + + rc = sqlite3_close(imp_dbh->db); + if (rc != SQLITE_OK) { + /* + ** Most probably we still have unfinalized statements. + ** Let's try to close them. + */ + /* COMPAT: sqlite3_next_stmt is only available for 3006000 or newer */ + while ( (pStmt = sqlite3_next_stmt(imp_dbh->db, 0)) != NULL ) { + sqlite3_finalize(pStmt); + } + + rc = sqlite3_close(imp_dbh->db); + if (rc != SQLITE_OK) { + /* + ** We still have problems. probably a backup operation + ** is not finished. We may need to wait for a while if + ** we get SQLITE_BUSY... + */ + sqlite_error(dbh, rc, sqlite3_errmsg(imp_dbh->db)); + } + } + + 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; + + sv_setsv(imp_dbh->collation_needed_callback, &PL_sv_undef); + SvREFCNT_dec(imp_dbh->collation_needed_callback); + imp_dbh->collation_needed_callback = (SV *)NULL; + + return TRUE; +} + +void +sqlite_db_destroy(SV *dbh, imp_dbh_t *imp_dbh) +{ + dTHX; + if (DBIc_ACTIVE(imp_dbh)) { + sqlite_db_disconnect(dbh, imp_dbh); + } + imp_dbh->db = NULL; + + DBIc_IMPSET_off(imp_dbh); +} + +int +sqlite_db_STORE_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv) +{ + dTHX; + char *key = SvPV_nolen(keysv); + int rc; + + croak_if_db_is_null(); + + if (strEQ(key, "AutoCommit")) { + if (SvTRUE(valuesv)) { + /* commit tran? */ + if ( DBIc_ACTIVE(imp_dbh) && (!DBIc_is(imp_dbh, DBIcf_AutoCommit)) && (!sqlite3_get_autocommit(imp_dbh->db)) ) { + sqlite_trace(dbh, imp_dbh, 3, "COMMIT TRAN"); + rc = sqlite_exec(dbh, "COMMIT TRANSACTION"); + if (rc != SQLITE_OK) { + return TRUE; /* XXX: is this correct? */ + } + } + } + DBIc_set(imp_dbh, DBIcf_AutoCommit, SvTRUE(valuesv)); + return TRUE; + } + if (strEQ(key, "sqlite_allow_multiple_statements")) { + imp_dbh->allow_multiple_statements = !(! SvTRUE(valuesv)); + return TRUE; + } + if (strEQ(key, "sqlite_use_immediate_transaction")) { + imp_dbh->use_immediate_transaction = !(! SvTRUE(valuesv)); + return TRUE; + } + if (strEQ(key, "sqlite_see_if_its_a_number")) { + imp_dbh->see_if_its_a_number = !(! SvTRUE(valuesv)); + return TRUE; + } + if (strEQ(key, "sqlite_unicode")) { +#if PERL_UNICODE_DOES_NOT_WORK_WELL + sqlite_trace(dbh, imp_dbh, 3, form("Unicode support is disabled for this version of perl.")); + imp_dbh->unicode = 0; +#else + imp_dbh->unicode = !(! SvTRUE(valuesv)); +#endif + return TRUE; + } + if (strEQ(key, "unicode")) { + if (DBIc_has(imp_dbh, DBIcf_WARN)) + warn("\"unicode\" attribute will be deprecated. Use \"sqlite_unicode\" instead."); +#if PERL_UNICODE_DOES_NOT_WORK_WELL + sqlite_trace(dbh, imp_dbh, 3, form("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 sv_2mortal(newSVpv(sqlite3_version, 0)); + } + if (strEQ(key, "sqlite_allow_multiple_statements")) { + return sv_2mortal(newSViv(imp_dbh->allow_multiple_statements ? 1 : 0)); + } + if (strEQ(key, "sqlite_use_immediate_transaction")) { + return sv_2mortal(newSViv(imp_dbh->use_immediate_transaction ? 1 : 0)); + } + if (strEQ(key, "sqlite_see_if_its_a_number")) { + return sv_2mortal(newSViv(imp_dbh->see_if_its_a_number ? 1 : 0)); + } + if (strEQ(key, "sqlite_unicode")) { +#if PERL_UNICODE_DOES_NOT_WORK_WELL + sqlite_trace(dbh, imp_dbh, 3, "Unicode support is disabled for this version of perl."); + return sv_2mortal(newSViv(0)); +#else + return sv_2mortal(newSViv(imp_dbh->unicode ? 1 : 0)); +#endif + } + if (strEQ(key, "unicode")) { + if (DBIc_has(imp_dbh, DBIcf_WARN)) + warn("\"unicode\" attribute will be deprecated. Use \"sqlite_unicode\" instead."); +#if PERL_UNICODE_DOES_NOT_WORK_WELL + sqlite_trace(dbh, imp_dbh, 3, "Unicode support is disabled for this version of perl."); + return sv_2mortal(newSViv(0)); +#else + return sv_2mortal(newSViv(imp_dbh->unicode ? 1 : 0)); +#endif + } + + return NULL; +} + +SV * +sqlite_db_last_insert_id(SV *dbh, imp_dbh_t *imp_dbh, SV *catalog, SV *schema, SV *table, SV *field, SV *attr) +{ + dTHX; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to get last inserted id on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + return sv_2mortal(newSViv((IV)sqlite3_last_insert_rowid(imp_dbh->db))); +} + +int +sqlite_st_prepare(SV *sth, imp_sth_t *imp_sth, char *statement, SV *attribs) +{ + dTHX; + int rc = 0; + const char *extra; + D_imp_dbh_from_sth; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(sth, -2, "attempt to prepare on inactive database handle"); + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + +#if 0 + if (*statement == '\0') { + sqlite_error(sth, -2, "attempt to prepare empty statement"); + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } +#endif + + sqlite_trace(sth, imp_sth, 3, form("prepare statement: %s", statement)); + imp_sth->nrow = -1; + imp_sth->retval = SQLITE_OK; + imp_sth->params = newAV(); + imp_sth->col_types = newAV(); + + croak_if_db_is_null(); + + /* COMPAT: sqlite3_prepare_v2 is only available for 3003009 or newer */ + rc = sqlite3_prepare_v2(imp_dbh->db, statement, -1, &(imp_sth->stmt), &extra); + if (rc != SQLITE_OK) { + sqlite_error(sth, rc, sqlite3_errmsg(imp_dbh->db)); + if (imp_sth->stmt) { + rc = sqlite3_finalize(imp_sth->stmt); + imp_sth->stmt = NULL; + if (rc != SQLITE_OK) { + sqlite_error(sth, rc, sqlite3_errmsg(imp_dbh->db)); + } + } + return FALSE; /* -> undef in lib/DBD/SQLite.pm */ + } + if (&extra) { + imp_sth->unprepared_statements = extra; + } + else { + imp_sth->unprepared_statements = NULL; + } + + 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; +} + +int +sqlite_st_rows(SV *sth, imp_sth_t *imp_sth) +{ + return imp_sth->nrow; +} + +int +sqlite_st_execute(SV *sth, imp_sth_t *imp_sth) +{ + dTHX; + D_imp_dbh_from_sth; + int rc = 0; + int num_params = DBIc_NUM_PARAMS(imp_sth); + int i; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(sth, -2, "attempt to execute on inactive database handle"); + return -2; /* -> undef in SQLite.xsi */ + } + + if (!imp_sth->stmt) return 0; + + croak_if_db_is_null(); + croak_if_stmt_is_null(); + + last_executed_dbh = imp_dbh; + + /* COMPAT: sqlite3_sql is only available for 3006000 or newer */ + sqlite_trace(sth, imp_sth, 3, form("executing %s", sqlite3_sql(imp_sth->stmt))); + + if (DBIc_ACTIVE(imp_sth)) { + sqlite_trace(sth, imp_sth, 3, "execute still active, reset"); + imp_sth->retval = sqlite3_reset(imp_sth->stmt); + if (imp_sth->retval != SQLITE_OK) { + sqlite_error(sth, imp_sth->retval, sqlite3_errmsg(imp_dbh->db)); + return -2; /* -> undef in SQLite.xsi */ + } + } + + for (i = 0; i < num_params; i++) { + SV **pvalue = av_fetch(imp_sth->params, 2*i, 0); + SV **sql_type_sv = av_fetch(imp_sth->params, 2*i+1, 0); + SV *value = pvalue ? *pvalue : &PL_sv_undef; + int sql_type = sqlite_type_from_odbc_type(sql_type_sv ? SvIV(*sql_type_sv) : SQL_UNKNOWN_TYPE); + + sqlite_trace(sth, imp_sth, 4, form("bind %d type %d as %s", i, sql_type, SvPV_nolen_undef_ok(value))); + + if (!SvOK(value)) { + sqlite_trace(sth, imp_sth, 5, "binding null"); + rc = sqlite3_bind_null(imp_sth->stmt, i+1); + } + else if (sql_type == SQLITE_BLOB) { + STRLEN len; + char * data = SvPVbyte(value, len); + rc = sqlite3_bind_blob(imp_sth->stmt, i+1, data, len, SQLITE_TRANSIENT); + } + else { + STRLEN len; + const char *data; + int numtype = 0; + + if (imp_dbh->unicode) { + sv_utf8_upgrade(value); + } + data = SvPV(value, len); + + /* + * XXX: For backward compatibility, it'd be better to + * accept a value like " 4" as an integer for an integer + * type column (see t/19_bindparam.t), at least when + * we explicitly specify its type. However, we should + * keep spaces when we just guess. + */ + if (imp_dbh->see_if_its_a_number) { + numtype = sqlite_is_number(aTHX_ data, SQLITE_NULL); + } + else if (sql_type == SQLITE_INTEGER || sql_type == SQLITE_FLOAT) { + numtype = sqlite_is_number(aTHX_ data, sql_type); + } + + if (numtype == 1) { +#if defined(USE_64_BIT_INT) + #if defined(HAS_ATOLL) + rc = sqlite3_bind_int64(imp_sth->stmt, i+1, atoll(data)); + #else + rc = sqlite3_bind_int64(imp_sth->stmt, i+1, atol(data)); + #endif +#else + rc = sqlite3_bind_int(imp_sth->stmt, i+1, atoi(data)); +#endif + } + else if (numtype == 2 && sql_type != SQLITE_INTEGER) { + rc = sqlite3_bind_double(imp_sth->stmt, i+1, atof(data)); + } + else { + if (sql_type == SQLITE_INTEGER || sql_type == SQLITE_FLOAT) { + /* + * die on datatype mismatch did more harm than good + * especially when DBIC heavily depends on this + * explicit type specification + */ + if (DBIc_has(imp_dbh, DBIcf_PrintWarn)) + warn( + "datatype mismatch: bind param (%d) %s as %s", + i, SvPV_nolen_undef_ok(value), + (sql_type == SQLITE_INTEGER ? "integer" : "float") + ); + } + rc = sqlite3_bind_text(imp_sth->stmt, i+1, data, len, SQLITE_TRANSIENT); + } + } + + if (rc != SQLITE_OK) { + sqlite_error(sth, rc, sqlite3_errmsg(imp_dbh->db)); + return -4; /* -> undef in SQLite.xsi */ + } + } + + if (sqlite3_get_autocommit(imp_dbh->db)) { + /* COMPAT: sqlite3_sql is only available for 3006000 or newer */ + const char *sql = sqlite3_sql(imp_sth->stmt); + if ((sql[0] == 'B' || sql[0] == 'b') && + (sql[1] == 'E' || sql[1] == 'e') && + (sql[2] == 'G' || sql[2] == 'g') && + (sql[3] == 'I' || sql[3] == 'i') && + (sql[4] == 'N' || sql[4] == 'n')) { + if (DBIc_is(imp_dbh, DBIcf_AutoCommit)) { + DBIc_on(imp_dbh, DBIcf_BegunWork); + DBIc_off(imp_dbh, DBIcf_AutoCommit); + } + } + else if (!DBIc_is(imp_dbh, DBIcf_AutoCommit)) { + sqlite_trace(sth, imp_sth, 3, "BEGIN TRAN"); + if (imp_dbh->use_immediate_transaction) { + rc = sqlite_exec(sth, "BEGIN IMMEDIATE TRANSACTION"); + } else { + rc = sqlite_exec(sth, "BEGIN TRANSACTION"); + } + if (rc != SQLITE_OK) { + return -2; /* -> undef in SQLite.xsi */ + } + } + } + else if (DBIc_is(imp_dbh, DBIcf_BegunWork)) { + /* COMPAT: sqlite3_sql is only available for 3006000 or newer */ + const char *sql = sqlite3_sql(imp_sth->stmt); + if (((sql[0] == 'C' || sql[0] == 'c') && + (sql[1] == 'O' || sql[1] == 'o') && + (sql[2] == 'M' || sql[2] == 'm') && + (sql[3] == 'M' || sql[3] == 'm') && + (sql[4] == 'I' || sql[4] == 'i') && + (sql[5] == 'T' || sql[5] == 't'))) { + DBIc_off(imp_dbh, DBIcf_BegunWork); + DBIc_on(imp_dbh, DBIcf_AutoCommit); + } + else if ( + ((sql[0] == 'R' || sql[0] == 'r') && + (sql[1] == 'O' || sql[1] == 'o') && + (sql[2] == 'L' || sql[2] == 'l') && + (sql[3] == 'L' || sql[3] == 'l') && + (sql[4] == 'B' || sql[4] == 'b') && + (sql[5] == 'A' || sql[5] == 'a') && + (sql[6] == 'C' || sql[6] == 'c') && + (sql[7] == 'K' || sql[7] == 'k'))) { + int l = strlen(sql); + bool is_savepoint = FALSE; + for(i = 8; i < l; i++) { + if (sql[i] == ' ' || sql[i] == '\t') continue; + if (sql[i] == 'T' || sql[i] == 't') { + if ((sql[i+0] == 'T' || sql[i+0] == 't') && + (sql[i+1] == 'R' || sql[i+1] == 'r') && + (sql[i+2] == 'A' || sql[i+2] == 'a') && + (sql[i+3] == 'N' || sql[i+3] == 'n') && + (sql[i+4] == 'S' || sql[i+4] == 's') && + (sql[i+5] == 'A' || sql[i+5] == 'a') && + (sql[i+6] == 'C' || sql[i+6] == 'c') && + (sql[i+7] == 'T' || sql[i+7] == 't') && + (sql[i+8] == 'I' || sql[i+8] == 'i') && + (sql[i+9] == 'O' || sql[i+9] == 'o') && + (sql[i+10] == 'N' || sql[i+10] == 'n')) { + i += 10; continue; + } + else if ( + (sql[i+0] == 'T' || sql[i+0] == 't') && + (sql[i+1] == 'O' || sql[i+1] == 'o') && + (sql[i+2] == ' ' || sql[i+2] == '\t')) { + /* rolling back to a savepoint should not + change AutoCommit status */ + is_savepoint = TRUE; + } + } + break; + } + if (!is_savepoint) { + DBIc_off(imp_dbh, DBIcf_BegunWork); + DBIc_on(imp_dbh, DBIcf_AutoCommit); + } + } + } + + imp_sth->nrow = 0; + + sqlite_trace(sth, imp_sth, 3, form("Execute returned %d cols", 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; + } + sqlite_error(sth, imp_sth->retval, sqlite3_errmsg(imp_dbh->db)); + if (sqlite3_reset(imp_sth->stmt) != SQLITE_OK) { + sqlite_error(sth, imp_sth->retval, 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); + /* 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_sth, 5, form("exec ok - %d rows, %d cols", imp_sth->nrow, DBIc_NUM_FIELDS(imp_sth))); + if (DBIc_is(imp_dbh, DBIcf_AutoCommit) && !sqlite3_get_autocommit(imp_dbh->db)) { + DBIc_on(imp_dbh, DBIcf_BegunWork); + DBIc_off(imp_dbh, DBIcf_AutoCommit); + } + return 0; /* -> '0E0' in SQLite.xsi */ + default: + sqlite_error(sth, imp_sth->retval, sqlite3_errmsg(imp_dbh->db)); + if (sqlite3_reset(imp_sth->stmt) != SQLITE_OK) { + sqlite_error(sth, imp_sth->retval, sqlite3_errmsg(imp_dbh->db)); + } + return -6; /* -> undef in SQLite.xsi */ + } +} + +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; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(sth, -2, "attempt to fetch on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + croak_if_stmt_is_null(); + + sqlite_trace(sth, imp_sth, 6, form("numFields == %d, nrow == %d", 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_error(sth, imp_sth->retval, sqlite3_errmsg(imp_dbh->db)); + sqlite_st_finish(sth, imp_sth); + 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 = sqlite_type_from_odbc_type(SvIV(*sql_type)); + } + } + switch(col_type) { + case SQLITE_INTEGER: + sqlite_trace(sth, imp_sth, 5, form("fetch column %d as integer", i)); +#if defined(USE_64_BIT_INT) + sv_setiv(AvARRAY(av)[i], sqlite3_column_int64(imp_sth->stmt, i)); +#else + val = (char*)sqlite3_column_text(imp_sth->stmt, i); + if (sqlite_is_number(aTHX_ val, SQLITE_NULL) == 1) { + sv_setiv(AvARRAY(av)[i], atoi(val)); + } else { + sv_setpv(AvARRAY(av)[i], val); + SvUTF8_off(AvARRAY(av)[i]); + } +#endif + break; + case SQLITE_FLOAT: + /* fetching as float may lose precision info in the perl world */ + sqlite_trace(sth, imp_sth, 5, form("fetch column %d as float", i)); + sv_setnv(AvARRAY(av)[i], sqlite3_column_double(imp_sth->stmt, i)); + break; + case SQLITE_TEXT: + sqlite_trace(sth, imp_sth, 5, form("fetch column %d as text", i)); + val = (char*)sqlite3_column_text(imp_sth->stmt, i); + + len = sqlite3_column_bytes(imp_sth->stmt, i); + if (chopBlanks) { + while((len > 0) && (val[len-1] == ' ')) { + len--; + } + } + sv_setpvn(AvARRAY(av)[i], val, len); + if (imp_dbh->unicode) { + SvUTF8_on(AvARRAY(av)[i]); + } else { + SvUTF8_off(AvARRAY(av)[i]); + } + break; + case SQLITE_BLOB: + sqlite_trace(sth, imp_sth, 5, form("fetch column %d as blob", i)); + 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: + sqlite_trace(sth, imp_sth, 5, form("fetch column %d as default", i)); + 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_finish3(SV *sth, imp_sth_t *imp_sth, int is_destroy) +{ + dTHX; + + D_imp_dbh_from_sth; + + croak_if_db_is_null(); + croak_if_stmt_is_null(); + + /* warn("finish statement\n"); */ + if (!DBIc_ACTIVE(imp_sth)) + return TRUE; + + DBIc_ACTIVE_off(imp_sth); + + av_clear(imp_sth->col_types); + + if (!DBIc_ACTIVE(imp_dbh)) /* no longer connected */ + return TRUE; + + if (is_destroy) { + return TRUE; + } + + if ((imp_sth->retval = sqlite3_reset(imp_sth->stmt)) != SQLITE_OK) { + sqlite_error(sth, imp_sth->retval, sqlite3_errmsg(imp_dbh->db)); + return FALSE; /* -> &sv_no (or void) in SQLite.xsi */ + } + + return TRUE; +} + +int +sqlite_st_finish(SV *sth, imp_sth_t *imp_sth) +{ + return sqlite_st_finish3(sth, imp_sth, 0); +} + +void +sqlite_st_destroy(SV *sth, imp_sth_t *imp_sth) +{ + dTHX; + int rc; + + D_imp_dbh_from_sth; + + DBIc_ACTIVE_off(imp_sth); + if (DBIc_ACTIVE(imp_dbh)) { + if (imp_sth->stmt) { + /* COMPAT: sqlite3_sql is only available for 3006000 or newer */ + sqlite_trace(sth, imp_sth, 4, form("destroy statement: %s", sqlite3_sql(imp_sth->stmt))); + + croak_if_db_is_null(); + croak_if_stmt_is_null(); + + /* finalize sth when active connection */ + rc = sqlite3_finalize(imp_sth->stmt); + imp_sth->stmt = NULL; + if (rc != SQLITE_OK) { + sqlite_error(sth, rc, sqlite3_errmsg(imp_dbh->db)); + } + } + } + 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_st_STORE_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv) +{ + dTHX; + /* char *key = SvPV_nolen(keysv); */ + return FALSE; +} + +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; + + croak_if_db_is_null(); + croak_if_stmt_is_null(); + + if (strEQ(key, "sqlite_unprepared_statements")) { + return sv_2mortal(newSVpv(imp_sth->unprepared_statements, 0)); + } +/* + 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_noinc((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; */ + SV *sv_fieldname = newSVpv(fieldname, 0); + if (imp_dbh->unicode) + SvUTF8_on(sv_fieldname); + av_store(av, n, sv_fieldname); + } + } + } + else if (strEQ(key, "PRECISION")) { + AV *av = newAV(); + retsv = sv_2mortal(newRV_noinc((SV*)av)); + } + else if (strEQ(key, "TYPE")) { + AV *av = newAV(); + av_extend(av, i); + retsv = sv_2mortal(newRV_noinc((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 = sqlite_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_noinc((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 rc = sqlite3_table_column_metadata(imp_dbh->db, database, tablename, fieldname, &datatype, &collseq, ¬null, &primary, &autoinc); + if (rc != SQLITE_OK) { + sqlite_error(sth, rc, sqlite3_errmsg(imp_dbh->db)); + 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_noinc((SV*)av)); + } + else if (strEQ(key, "NUM_OF_FIELDS")) { + retsv = sv_2mortal(newSViv(i)); + } + else if (strEQ(key, "NUM_OF_PARAMS")) { + retsv = sv_2mortal(newSViv(sqlite3_bind_parameter_count(imp_sth->stmt))); + } + + return retsv; +} + +/* 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; + + croak_if_stmt_is_null(); + + if (is_inout) { + sqlite_error(sth, -2, "InOut bind params not implemented"); + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + + 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) { + sqlite_error(sth, -2, form("Unknown named parameter: %s", paramstring)); + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + pos = 2 * (pos - 1); + } + else { + sqlite_error(sth, -2, "<param> could not be coerced to a C string"); + return FALSE; /* -> &sv_no in SQLite.xsi */ + } + } + else { + pos = 2 * (SvIV(param) - 1); + } + sqlite_trace(sth, imp_sth, 3, form("bind into 0x%p: %"IVdf" => %s (%"IVdf") pos %d", imp_sth->params, SvIV(param), SvPV_nolen_undef_ok(value), sql_type, pos)); + av_store(imp_sth->params, pos, newSVsv(value)); + if (sql_type) { + 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; +} + +/*-----------------------------------------------------* + * Driver Private Methods + *-----------------------------------------------------*/ + +AV * +sqlite_compile_options() +{ + dTHX; + int i = 0; + const char *option; + AV *av = newAV(); + +#if SQLITE_VERSION_NUMBER >= 3006023 +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS + while((option = sqlite3_compileoption_get(i++))) { + av_push(av, newSVpv(option, 0)); + } +#endif +#endif + + return (AV*)sv_2mortal((SV*)av); +} + +#define _stores_status(op, key) \ + if (sqlite3_status(op, &cur, &hi, reset) == SQLITE_OK) { \ + anon = newHV(); \ + hv_stores(anon, "current", newSViv(cur)); \ + hv_stores(anon, "highwater", newSViv(hi)); \ + hv_stores(hv, key, newRV_noinc((SV*)anon)); \ + } + +#define _stores_dbstatus(op, key) \ + if (sqlite3_db_status(imp_dbh->db, op, &cur, &hi, reset) == SQLITE_OK) { \ + anon = newHV(); \ + hv_stores(anon, "current", newSViv(cur)); \ + hv_stores(anon, "highwater", newSViv(hi)); \ + hv_stores(hv, key, newRV_noinc((SV*)anon)); \ + } + +#define _stores_ststatus(op, key) \ + hv_stores(hv, key, newSViv(sqlite3_stmt_status(imp_sth->stmt, op, reset))) + +HV * +_sqlite_status(int reset) +{ + dTHX; + int cur, hi; + HV *hv = newHV(); + HV *anon; + + _stores_status(SQLITE_STATUS_MEMORY_USED, "memory_used"); + _stores_status(SQLITE_STATUS_PAGECACHE_USED, "pagecache_used"); + _stores_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, "pagecache_overflow"); + _stores_status(SQLITE_STATUS_SCRATCH_USED, "scratch_used"); + + _stores_status(SQLITE_STATUS_SCRATCH_OVERFLOW, "scratch_overflow"); + + _stores_status(SQLITE_STATUS_MALLOC_SIZE, "malloc_size"); + _stores_status(SQLITE_STATUS_PARSER_STACK, "parser_stack"); + _stores_status(SQLITE_STATUS_PAGECACHE_SIZE, "pagecache_size"); + _stores_status(SQLITE_STATUS_SCRATCH_SIZE, "scratch_size"); + _stores_status(SQLITE_STATUS_MALLOC_COUNT, "malloc_count"); + _stores_status(SQLITE_STATUS_SCRATCH_OVERFLOW, "scratch_overflow"); + + return hv; +} + +HV * +_sqlite_db_status(pTHX_ SV* dbh, int reset) +{ + D_imp_dbh(dbh); + int cur, hi; + HV *hv = newHV(); + HV *anon; + + _stores_dbstatus(SQLITE_DBSTATUS_LOOKASIDE_USED, "lookaside_used"); + _stores_dbstatus(SQLITE_DBSTATUS_CACHE_USED, "cache_used"); + _stores_dbstatus(SQLITE_DBSTATUS_SCHEMA_USED, "schema_used"); + _stores_dbstatus(SQLITE_DBSTATUS_STMT_USED, "stmt_used"); + _stores_dbstatus(SQLITE_DBSTATUS_LOOKASIDE_HIT, "lookaside_hit"); + _stores_dbstatus(SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, "lookaside_miss_size"); + _stores_dbstatus(SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, "lookaside_miss_full"); + _stores_dbstatus(SQLITE_DBSTATUS_CACHE_HIT, "cache_hit"); + _stores_dbstatus(SQLITE_DBSTATUS_CACHE_MISS, "cache_miss"); + _stores_dbstatus(SQLITE_DBSTATUS_CACHE_WRITE, "cache_write"); + + return hv; +} + +HV * +_sqlite_st_status(pTHX_ SV* sth, int reset) +{ + D_imp_sth(sth); + HV *hv = newHV(); + + _stores_ststatus(SQLITE_STMTSTATUS_FULLSCAN_STEP, "fullscan_step"); + _stores_ststatus(SQLITE_STMTSTATUS_SORT, "sort"); + _stores_ststatus(SQLITE_STMTSTATUS_AUTOINDEX, "autoindex"); + + return hv; +} + +SV * +sqlite_db_filename(pTHX_ SV *dbh) +{ + D_imp_dbh(dbh); + const char *filename; + + croak_if_db_is_null(); + + filename = sqlite3_db_filename(imp_dbh->db, "main"); + return filename ? newSVpv(filename, 0) : &PL_sv_undef; +} + +int +sqlite_db_busy_timeout(pTHX_ SV *dbh, int timeout ) +{ + D_imp_dbh(dbh); + + croak_if_db_is_null(); + + if (timeout) { + imp_dbh->timeout = timeout; + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to set busy timeout on inactive database handle"); + return -2; + } + sqlite3_busy_timeout(imp_dbh->db, timeout); + } + return imp_dbh->timeout; +} + +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; + int type = sqlite3_value_type(value[i]); + sqlite_int64 iv; + + /* warn("func dispatch type: %d, value: %s\n", type, sqlite3_value_text(value[i])); */ + switch(type) { + case SQLITE_INTEGER: + iv = sqlite3_value_int64(value[i]); + if ( iv >= IV_MIN && iv <= IV_MAX ) { + /* ^^^ compile-time constant (= true) when IV == int64 */ + arg = sv_2mortal(newSViv((IV)iv)); + } + else if ( iv >= 0 && iv <= UV_MAX ) { + /* warn("integer overflow, cast to UV"); */ + arg = sv_2mortal(newSVuv((UV)iv)); + } + else { + /* warn("integer overflow, cast to NV"); */ + arg = sv_2mortal(newSVnv((NV)iv)); + } + break; + case SQLITE_FLOAT: + arg = sv_2mortal(newSVnv(sqlite3_value_double(value[i]))); + break; + case SQLITE_TEXT: + len = sqlite3_value_bytes(value[i]); + arg = newSVpvn((const char *)sqlite3_value_text(value[i]), len); + if (is_unicode) { + SvUTF8_on(arg); + } + arg = sv_2mortal(arg); + break; + case SQLITE_BLOB: + len = sqlite3_value_bytes(value[i]); + 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_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_set_result(aTHX_ context, err, 1); + /* Clear the stack */ + for ( i=0; i < count; i++ ) { + POPs; + } + } else { + sqlite_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); +} + +int +sqlite_db_create_function(pTHX_ SV *dbh, const char *name, int argc, SV *func) +{ + D_imp_dbh(dbh); + int rc; + SV *func_sv; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to create function on inactive database handle"); + return FALSE; + } + + /* Copy the function reference */ + func_sv = newSVsv(func); + av_push( imp_dbh->functions, func_sv ); + + croak_if_db_is_null(); + + /* warn("create_function %s with %d args\n", name, argc); */ + rc = 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 ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, form("sqlite_create_function failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + return TRUE; +} + +#ifndef SQLITE_OMIT_LOAD_EXTENSION + +int +sqlite_db_enable_load_extension(pTHX_ SV *dbh, int onoff) +{ + D_imp_dbh(dbh); + int rc; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to enable load extension on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + /* COMPAT: sqlite3_enable_load_extension is only available for 3003006 or newer */ + rc = sqlite3_enable_load_extension( imp_dbh->db, onoff ); + if ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, form("sqlite_enable_load_extension failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + return TRUE; +} + +int +sqlite_db_load_extension(pTHX_ SV *dbh, const char *file, const char *proc) +{ + D_imp_dbh(dbh); + int rc; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to load extension on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + /* COMPAT: sqlite3_load_extension is only available for 3003006 or newer */ + rc = sqlite3_load_extension( imp_dbh->db, file, proc, NULL ); + if ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, form("sqlite_load_extension failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + return TRUE; +} + +#endif + +HV* +sqlite_db_table_column_metadata(pTHX_ SV *dbh, SV *dbname, SV *tablename, SV *columnname) +{ + D_imp_dbh(dbh); + const char *datatype, *collseq; + int notnull, primary, autoinc; + int rc; + HV *metadata = newHV(); + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to fetch table column metadata on inactive database handle"); + return metadata; + } + + croak_if_db_is_null(); + + /* dbname may be NULL but (table|column)name may not be NULL */ + if (!tablename || !SvPOK(tablename)) { + sqlite_error(dbh, -2, "table_column_metadata requires a table name"); + return metadata; + } + if (!columnname || !SvPOK(columnname)) { + sqlite_error(dbh, -2, "table_column_metadata requires a column name"); + return metadata; + } + +#ifdef SQLITE_ENABLE_COLUMN_METADATA + rc = sqlite3_table_column_metadata( + imp_dbh->db, + (dbname && SvPOK(dbname)) ? SvPV_nolen(dbname) : NULL, + SvPV_nolen(tablename), + SvPV_nolen(columnname), + &datatype, &collseq, ¬null, &primary, &autoinc); +#endif + + if (rc == SQLITE_OK) { + hv_stores(metadata, "data_type", datatype ? newSVpv(datatype, 0) : newSV(0)); + hv_stores(metadata, "collation_name", collseq ? newSVpv(collseq, 0) : newSV(0)); + hv_stores(metadata, "not_null", newSViv(notnull)); + hv_stores(metadata, "primary", newSViv(primary)); + hv_stores(metadata, "auto_increment", newSViv(autoinc)); + } + + return metadata; +} + +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; i<count; i++ ) { + POPs; + } + } else { + sqlite_set_result(aTHX_ context, POPs, 0); + } + PUTBACK; + } + + if ( aggr->err ) { + warn( "DBD::SQLite: error in aggregator cannot be reported to SQLite: %s", + SvPV_nolen( aggr->err ) ); + + /* sqlite_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; +} + +int +sqlite_db_create_aggregate(pTHX_ SV *dbh, const char *name, int argc, SV *aggr_pkg) +{ + D_imp_dbh(dbh); + int rc; + SV *aggr_pkg_copy; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to create aggregate on inactive database handle"); + return FALSE; + } + + /* Copy the aggregate reference */ + aggr_pkg_copy = newSVsv(aggr_pkg); + av_push( imp_dbh->aggregates, aggr_pkg_copy ); + + croak_if_db_is_null(); + + rc = 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 ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, form("sqlite_create_aggregate failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + return TRUE; +} + +int +sqlite_db_collation_dispatcher(void *func, int len1, const void *string1, + int len2, const void *string2) +{ + dTHX; + dSP; + int cmp = 0; + int n_retval, i; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs( sv_2mortal( newSVpvn( string1, len1) ) ); + XPUSHs( sv_2mortal( newSVpvn( string2, len2) ) ); + PUTBACK; + n_retval = call_sv(func, G_SCALAR); + SPAGAIN; + if (n_retval != 1) { + warn("collation function returned %d arguments", n_retval); + } + for(i = 0; i < n_retval; i++) { + 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 = 0; + int n_retval, i; + 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(func, G_SCALAR); + SPAGAIN; + if (n_retval != 1) { + warn("collation function returned %d arguments", n_retval); + } + for(i = 0; i < n_retval; i++) { + cmp = POPi; + } + PUTBACK; + FREETMPS; + LEAVE; + + return cmp; +} + +int +sqlite_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); + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to create collation on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + /* Check that this is a proper collation function */ + rv = sqlite_db_collation_dispatcher(func_sv, 2, aa, 2, aa); + if (rv != 0) { + sqlite_trace(dbh, imp_dbh, 3, form("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)) { + sqlite_trace(dbh, imp_dbh, 3, form("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 ) { + sqlite_error(dbh, rv, form("sqlite_create_collation failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + return TRUE; +} + +void +sqlite_db_collation_needed_dispatcher( + void *dbh, + sqlite3* db, /* unused */ + int eTextRep, /* unused */ + const char* collation_name +) +{ + dTHX; + dSP; + + D_imp_dbh(dbh); + + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs( dbh ); + XPUSHs( sv_2mortal( newSVpv( collation_name, 0) ) ); + PUTBACK; + + call_sv( imp_dbh->collation_needed_callback, G_VOID ); + SPAGAIN; + + PUTBACK; + FREETMPS; + LEAVE; +} + +void +sqlite_db_collation_needed(pTHX_ SV *dbh, SV *callback) +{ + D_imp_dbh(dbh); + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to see if collation is needed on inactive database handle"); + return; + } + + croak_if_db_is_null(); + + /* remember the callback within the dbh */ + sv_setsv(imp_dbh->collation_needed_callback, callback); + + /* Register the func within sqlite3 */ + (void) sqlite3_collation_needed( imp_dbh->db, + (void*) (SvOK(callback) ? dbh : NULL), + sqlite_db_collation_needed_dispatcher ); +} + +int +sqlite_db_generic_callback_dispatcher( void *callback ) +{ + dTHX; + dSP; + int n_retval, i; + int retval = 0; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + n_retval = call_sv( callback, G_SCALAR ); + SPAGAIN; + if ( n_retval != 1 ) { + warn( "callback returned %d arguments", n_retval ); + } + for(i = 0; i < n_retval; i++) { + retval = POPi; + } + PUTBACK; + FREETMPS; + LEAVE; + + return retval; +} + +int +sqlite_db_progress_handler(pTHX_ SV *dbh, int n_opcodes, SV *handler) +{ + D_imp_dbh(dbh); + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to set progress handler on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + if (!SvOK(handler)) { + /* 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_generic_callback_dispatcher, + handler_sv ); + } + return TRUE; +} + +SV* +sqlite_db_commit_hook(pTHX_ SV *dbh, SV *hook) +{ + D_imp_dbh(dbh); + void *retval; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to set commit hook on inactive database handle"); + return &PL_sv_undef; + } + + croak_if_db_is_null(); + + if (!SvOK(hook)) { + /* remove previous hook */ + retval = sqlite3_commit_hook( imp_dbh->db, NULL, NULL ); + } + else { + SV *hook_sv = newSVsv( hook ); + + /* Copy the handler ref so that it can be deallocated at disconnect */ + av_push( imp_dbh->functions, hook_sv ); + + /* Register the hook within sqlite3 */ + retval = sqlite3_commit_hook( imp_dbh->db, + sqlite_db_generic_callback_dispatcher, + hook_sv ); + } + + return retval ? newSVsv(retval) : &PL_sv_undef; +} + +SV* +sqlite_db_rollback_hook(pTHX_ SV *dbh, SV *hook) +{ + D_imp_dbh(dbh); + void *retval; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to set rollback hook on inactive database handle"); + return &PL_sv_undef; + } + + croak_if_db_is_null(); + + if (!SvOK(hook)) { + /* remove previous hook */ + retval = sqlite3_rollback_hook( imp_dbh->db, NULL, NULL ); + } + else { + SV *hook_sv = newSVsv( hook ); + + /* Copy the handler ref so that it can be deallocated at disconnect */ + av_push( imp_dbh->functions, hook_sv ); + + /* Register the hook within sqlite3 */ + retval = sqlite3_rollback_hook( imp_dbh->db, + (void(*)(void *)) + sqlite_db_generic_callback_dispatcher, + hook_sv ); + } + + return retval ? newSVsv(retval) : &PL_sv_undef; +} + +void +sqlite_db_update_dispatcher( void *callback, int op, + char const *database, char const *table, + sqlite3_int64 rowid ) +{ + dTHX; + dSP; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + + XPUSHs( sv_2mortal( newSViv( op ) ) ); + XPUSHs( sv_2mortal( newSVpv( database, 0 ) ) ); + XPUSHs( sv_2mortal( newSVpv( table, 0 ) ) ); + XPUSHs( sv_2mortal( newSViv( (IV)rowid ) ) ); + PUTBACK; + + call_sv( callback, G_VOID ); + SPAGAIN; + + PUTBACK; + FREETMPS; + LEAVE; +} + +SV* +sqlite_db_update_hook(pTHX_ SV *dbh, SV *hook) +{ + D_imp_dbh(dbh); + void *retval; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to set update hook on inactive database handle"); + return &PL_sv_undef; + } + + croak_if_db_is_null(); + + if (!SvOK(hook)) { + /* remove previous hook */ + retval = sqlite3_update_hook( imp_dbh->db, NULL, NULL ); + } + else { + SV *hook_sv = newSVsv( hook ); + + /* Copy the handler ref so that it can be deallocated at disconnect */ + av_push( imp_dbh->functions, hook_sv ); + + /* Register the hook within sqlite3 */ + retval = sqlite3_update_hook( imp_dbh->db, + sqlite_db_update_dispatcher, + hook_sv ); + } + + return retval ? newSVsv(retval) : &PL_sv_undef; +} + +int +sqlite_db_authorizer_dispatcher ( + void *authorizer, + int action_code, + const char *details_1, + const char *details_2, + const char *details_3, + const char *details_4 +) +{ + dTHX; + dSP; + int retval = 0; + int n_retval, i; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + + XPUSHs( sv_2mortal ( newSViv ( action_code ) ) ); + + /* these ifs are ugly but without them, perl 5.8 segfaults */ + XPUSHs( sv_2mortal( details_1 ? newSVpv( details_1, 0 ) : &PL_sv_undef ) ); + XPUSHs( sv_2mortal( details_2 ? newSVpv( details_2, 0 ) : &PL_sv_undef ) ); + XPUSHs( sv_2mortal( details_3 ? newSVpv( details_3, 0 ) : &PL_sv_undef ) ); + XPUSHs( sv_2mortal( details_4 ? newSVpv( details_4, 0 ) : &PL_sv_undef ) ); + PUTBACK; + + n_retval = call_sv(authorizer, G_SCALAR); + SPAGAIN; + if ( n_retval != 1 ) { + warn( "callback returned %d arguments", n_retval ); + } + for(i = 0; i < n_retval; i++) { + retval = POPi; + } + + PUTBACK; + FREETMPS; + LEAVE; + + return retval; +} + +int +sqlite_db_set_authorizer(pTHX_ SV *dbh, SV *authorizer) +{ + D_imp_dbh(dbh); + int retval; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to set authorizer on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + if (!SvOK(authorizer)) { + /* remove previous hook */ + retval = sqlite3_set_authorizer( imp_dbh->db, NULL, NULL ); + } + else { + SV *authorizer_sv = newSVsv( authorizer ); + + /* Copy the coderef so that it can be deallocated at disconnect */ + av_push( imp_dbh->functions, authorizer_sv ); + + /* Register the hook within sqlite3 */ + retval = sqlite3_set_authorizer( imp_dbh->db, + sqlite_db_authorizer_dispatcher, + authorizer_sv ); + } + + return retval; +} + +#ifndef SQLITE_OMIT_TRACE +void +sqlite_db_trace_dispatcher(void *callback, const char *sql) +{ + dTHX; + dSP; + int n_retval, i; + int retval = 0; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs( sv_2mortal( newSVpv( sql, 0 ) ) ); + PUTBACK; + + n_retval = call_sv( callback, G_SCALAR ); + SPAGAIN; + if ( n_retval != 1 ) { + warn( "callback returned %d arguments", n_retval ); + } + for(i = 0; i < n_retval; i++) { + retval = POPi; + } + PUTBACK; + FREETMPS; + LEAVE; +} + +int +sqlite_db_trace(pTHX_ SV *dbh, SV *func) +{ + D_imp_dbh(dbh); + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to set trace on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + if (!SvOK(func)) { + /* remove previous callback */ + sqlite3_trace( imp_dbh->db, NULL, NULL ); + } + else { + SV *func_sv = newSVsv(func); + + /* Copy the func ref so that it can be deallocated at disconnect */ + av_push( imp_dbh->functions, func_sv ); + + /* Register the func within sqlite3 */ + sqlite3_trace( imp_dbh->db, + sqlite_db_trace_dispatcher, + func_sv ); + } + return TRUE; +} +#endif + +void +sqlite_db_profile_dispatcher(void *callback, const char *sql, sqlite3_uint64 elapsed) +{ + dTHX; + dSP; + int n_retval, i; + int retval = 0; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs( sv_2mortal( newSVpv( sql, 0 ) ) ); + /* + * The profile callback time is in units of nanoseconds, + * however the current implementation is only capable of + * millisecond resolution so the six least significant digits + * in the time are meaningless. + * (http://sqlite.org/c3ref/profile.html) + */ + XPUSHs( sv_2mortal( newSViv( elapsed / 1000000 ) ) ); + PUTBACK; + + n_retval = call_sv( callback, G_SCALAR ); + SPAGAIN; + if ( n_retval != 1 ) { + warn( "callback returned %d arguments", n_retval ); + } + for(i = 0; i < n_retval; i++) { + retval = POPi; + } + PUTBACK; + FREETMPS; + LEAVE; +} + +int +sqlite_db_profile(pTHX_ SV *dbh, SV *func) +{ + D_imp_dbh(dbh); + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to profile on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + if (!SvOK(func)) { + /* remove previous callback */ + sqlite3_profile( imp_dbh->db, NULL, NULL ); + } + else { + SV *func_sv = newSVsv(func); + + /* Copy the func ref so that it can be deallocated at disconnect */ + av_push( imp_dbh->functions, func_sv ); + + /* Register the func within sqlite3 */ + sqlite3_profile( imp_dbh->db, + sqlite_db_profile_dispatcher, + func_sv ); + } + return TRUE; +} + +/* Accesses the SQLite Online Backup API, and fills the currently loaded + * database from the passed filename. + * Usual usage of this would be when you're operating on the :memory: + * special database connection and want to copy it in from a real db. + */ +int +sqlite_db_backup_from_file(pTHX_ SV *dbh, char *filename) +{ + D_imp_dbh(dbh); + +#if SQLITE_VERSION_NUMBER >= 3006011 + int rc; + sqlite3 *pFrom; + sqlite3_backup *pBackup; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to backup from file on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + rc = sqlite_open(filename, &pFrom); + if ( rc != SQLITE_OK ) { + return FALSE; + } + + /* COMPAT: sqlite3_backup_* are only available for 3006011 or newer */ + pBackup = sqlite3_backup_init(imp_dbh->db, "main", pFrom, "main"); + if (pBackup) { + (void)sqlite3_backup_step(pBackup, -1); + (void)sqlite3_backup_finish(pBackup); + } + rc = sqlite3_errcode(imp_dbh->db); + (void)sqlite3_close(pFrom); + + if ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, form("sqlite_backup_from_file failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + + return TRUE; +#else + sqlite_error(dbh, SQLITE_ERROR, form("backup feature requires SQLite 3.6.11 and newer")); + return FALSE; +#endif +} + +/* Accesses the SQLite Online Backup API, and copies the currently loaded + * database into the passed filename. + * Usual usage of this would be when you're operating on the :memory: + * special database connection, and want to back it up to an on-disk file. + */ +int +sqlite_db_backup_to_file(pTHX_ SV *dbh, char *filename) +{ + D_imp_dbh(dbh); + +#if SQLITE_VERSION_NUMBER >= 3006011 + int rc; + sqlite3 *pTo; + sqlite3_backup *pBackup; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to backup to file on inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + rc = sqlite_open(filename, &pTo); + if ( rc != SQLITE_OK ) { + return FALSE; + } + + /* COMPAT: sqlite3_backup_* are only available for 3006011 or newer */ + pBackup = sqlite3_backup_init(pTo, "main", imp_dbh->db, "main"); + if (pBackup) { + (void)sqlite3_backup_step(pBackup, -1); + (void)sqlite3_backup_finish(pBackup); + } + rc = sqlite3_errcode(pTo); + (void)sqlite3_close(pTo); + + if ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, form("sqlite_backup_to_file failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + + return TRUE; +#else + sqlite_error(dbh, SQLITE_ERROR, form("backup feature requires SQLite 3.6.11 and newer")); + return FALSE; +#endif +} + +typedef struct perl_tokenizer { + sqlite3_tokenizer base; + SV *coderef; /* the perl tokenizer is a coderef that takes + a string and returns a cursor coderef */ +} perl_tokenizer; + +typedef struct perl_tokenizer_cursor { + sqlite3_tokenizer_cursor base; + SV *coderef; /* ref to the closure that returns terms */ + char *pToken; /* storage for a copy of the last token */ + int nTokenAllocated; /* space allocated to pToken buffer */ + + /* members below are only used if the input string is in utf8 */ + const char *pInput; /* input we are tokenizing */ + const char *lastByteOffset; /* offset into pInput */ + int lastCharOffset; /* char offset corresponding to lastByteOffset */ +} perl_tokenizer_cursor; + +/* +** Create a new tokenizer instance. +** Will be called whenever a FTS3 table is created with +** CREATE .. USING fts3( ... , tokenize=perl qualified::function::name) +** where qualified::function::name is a fully qualified perl function +*/ +static int perl_tokenizer_Create( + int argc, const char * const *argv, + sqlite3_tokenizer **ppTokenizer +){ + dTHX; + dSP; + int n_retval; + SV *retval; + perl_tokenizer *t; + + if (!argc) { + return SQLITE_ERROR; + } + + t = (perl_tokenizer *) sqlite3_malloc(sizeof(*t)); + if( t==NULL ) return SQLITE_NOMEM; + memset(t, 0, sizeof(*t)); + + ENTER; + SAVETMPS; + + /* call the qualified::function::name */ + PUSHMARK(SP); + PUTBACK; + n_retval = call_pv(argv[0], G_SCALAR); + SPAGAIN; + + /* store a copy of the returned coderef into the tokenizer structure */ + if (n_retval != 1) { + warn("tokenizer_Create returned %d arguments", n_retval); + } + retval = POPs; + t->coderef = newSVsv(retval); + *ppTokenizer = &t->base; + + PUTBACK; + FREETMPS; + LEAVE; + + return SQLITE_OK; +} + +/* +** Destroy a tokenizer +*/ +static int perl_tokenizer_Destroy(sqlite3_tokenizer *pTokenizer){ + dTHX; + perl_tokenizer *t = (perl_tokenizer *) pTokenizer; + sv_free(t->coderef); + sqlite3_free(t); + return SQLITE_OK; +} + +/* +** Prepare to begin tokenizing a particular string. The input +** string to be tokenized is supposed to be pInput[0..nBytes-1] .. +** except that nBytes passed by fts3 is -1 (don't know why) ! +** This is passed to the tokenizer instance, which then returns a +** closure implementing the cursor (so the cursor is again a coderef). +*/ +static int perl_tokenizer_Open( + sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ + const char *pInput, int nBytes, /* Input buffer */ + sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ +){ + dTHX; + dSP; + U32 flags; + SV *perl_string; + int n_retval; + + perl_tokenizer *t = (perl_tokenizer *)pTokenizer; + + /* allocate and initialize the cursor struct */ + perl_tokenizer_cursor *c; + c = (perl_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); + memset(c, 0, sizeof(*c)); + *ppCursor = &c->base; + + /* flags for creating the Perl SV containing the input string */ + flags = SVs_TEMP; /* will call sv_2mortal */ + + /* special handling if working with utf8 strings */ + if (last_executed_dbh->unicode) { /* global var ... no better way ! */ + + /* data to keep track of byte offsets */ + c->lastByteOffset = c->pInput = pInput; + c->lastCharOffset = 0; + + /* string passed to Perl needs to be flagged as utf8 */ + flags |= SVf_UTF8; + } + + ENTER; + SAVETMPS; + + /* build a Perl copy of the input string */ + if (nBytes < 0) { /* we get -1 from fts3. Don't know why ! */ + nBytes = strlen(pInput); + } + perl_string = newSVpvn_flags(pInput, nBytes, flags); + + /* call the tokenizer coderef */ + PUSHMARK(SP); + XPUSHs(perl_string); + PUTBACK; + n_retval = call_sv(t->coderef, G_SCALAR); + SPAGAIN; + + /* store the cursor coderef returned by the tokenizer */ + if (n_retval != 1) { + warn("tokenizer returned %d arguments", n_retval); + } + c->coderef = newSVsv(POPs); + + PUTBACK; + FREETMPS; + LEAVE; + return SQLITE_OK; +} + +/* +** Close a tokenization cursor previously opened by a call to +** perl_tokenizer_Open() above. +*/ +static int perl_tokenizer_Close(sqlite3_tokenizer_cursor *pCursor){ + perl_tokenizer_cursor *c = (perl_tokenizer_cursor *) pCursor; + + dTHX; + sv_free(c->coderef); + if (c->pToken) sqlite3_free(c->pToken); + sqlite3_free(c); + return SQLITE_OK; +} + +/* +** Extract the next token from a tokenization cursor. The cursor must +** have been opened by a prior call to perl_tokenizer_Open(). +*/ +static int perl_tokenizer_Next( + sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by perl_tokenizer_Open */ + const char **ppToken, /* OUT: *ppToken is the token text */ + int *pnBytes, /* OUT: Number of bytes in token */ + int *piStartOffset, /* OUT: Starting offset of token */ + int *piEndOffset, /* OUT: Ending offset of token */ + int *piPosition /* OUT: Position integer of token */ +){ + perl_tokenizer_cursor *c = (perl_tokenizer_cursor *) pCursor; + int result; + int n_retval; + STRLEN n_a; + char *token; + char *byteOffset; + I32 hop; + + dTHX; + dSP; + + ENTER; + SAVETMPS; + + /* call the cursor */ + PUSHMARK(SP); + PUTBACK; + n_retval = call_sv(c->coderef, G_ARRAY); + SPAGAIN; + + /* if we get back an empty list, there is no more token */ + if (n_retval == 0) { + result = SQLITE_DONE; + } + /* otherwise, get token details from the return list */ + else { + if (n_retval != 5) { + warn("tokenizer cursor returned %d arguments", n_retval); + } + *piPosition = POPi; + *piEndOffset = POPi; + *piStartOffset = POPi; + *pnBytes = POPi; + token = POPpx; + + if (c->pInput) { /* if working with utf8 data */ + + /* recompute *pnBytes in bytes, not in chars */ + *pnBytes = strlen(token); + + /* recompute start/end offsets in bytes, not in chars */ + hop = *piStartOffset - c->lastCharOffset; + byteOffset = utf8_hop((U8*)c->lastByteOffset, hop); + hop = *piEndOffset - *piStartOffset; + *piStartOffset = byteOffset - c->pInput; + byteOffset = utf8_hop(byteOffset, hop); + *piEndOffset = byteOffset - c->pInput; + + /* remember where we are for next round */ + c->lastCharOffset = *piEndOffset, + c->lastByteOffset = byteOffset; + } + + /* make sure we have enough storage for copying the token */ + if (*pnBytes > c->nTokenAllocated ){ + char *pNew; + c->nTokenAllocated = *pnBytes + 20; + pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated); + if( !pNew ) return SQLITE_NOMEM; + c->pToken = pNew; + } + + /* need to copy the token into the C cursor before perl frees that + memory */ + memcpy(c->pToken, token, *pnBytes); + *ppToken = c->pToken; + + result = SQLITE_OK; + } + + PUTBACK; + FREETMPS; + LEAVE; + + return result; +} + +/* +** The set of routines that implement the perl tokenizer +*/ +sqlite3_tokenizer_module perl_tokenizer_Module = { + 0, + perl_tokenizer_Create, + perl_tokenizer_Destroy, + perl_tokenizer_Open, + perl_tokenizer_Close, + perl_tokenizer_Next +}; + +/* +** Register the perl tokenizer with FTS3 +*/ +int sqlite_db_register_fts3_perl_tokenizer(pTHX_ SV *dbh) +{ + D_imp_dbh(dbh); + + int rc; + sqlite3_stmt *pStmt; + const char zSql[] = "SELECT fts3_tokenizer(?, ?)"; + sqlite3_tokenizer_module *p = &perl_tokenizer_Module; + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to register fts3 tokenizer on inactive database handle"); + return FALSE; + } + + rc = sqlite3_prepare_v2(imp_dbh->db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + + sqlite3_bind_text(pStmt, 1, "perl", -1, SQLITE_STATIC); + sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC); + sqlite3_step(pStmt); + + return sqlite3_finalize(pStmt); +} + +/* end */ |