#define PERL_NO_GET_CONTEXT #define NEED_newSVpvn_flags #define NEED_sv_2pvbyte #include "SQLiteXS.h" START_MY_CXT; 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 /*-----------------------------------------------------* * 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, 0, 0) #define sqlite_open2(dbname,db,flags,extended) _sqlite_open(aTHX_ dbh, dbname, db, flags, extended) #define _isspace(c) (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f') #define _skip_whitespaces(sql) \ while ( _isspace(sql[0]) || (sql[0] == '-' && sql[1] == '-')) { \ if ( _isspace(sql[0]) ) { \ while ( _isspace(sql[0]) ) sql++; \ continue; \ } \ else if (sql[0] == '-') { \ while ( sql[0] != 0 && sql[0] != '\n' ) sql++; \ continue; \ } \ } bool _starts_with_begin(const char *sql) { return ( ((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') ) || ( (sql[0] == 'S' || sql[0] == 's') && (sql[1] == 'A' || sql[1] == 'a') && (sql[2] == 'V' || sql[2] == 'v') && (sql[3] == 'E' || sql[3] == 'e') && (sql[4] == 'P' || sql[4] == 'p') && (sql[5] == 'O' || sql[5] == 'o') && (sql[6] == 'I' || sql[6] == 'i') && (sql[7] == 'N' || sql[7] == 'n') && (sql[8] == 'T' || sql[8] == 't') ) ) ? TRUE : FALSE; } /* adopted from sqlite3.c */ #define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) #define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) static int compare2pow63(const char *zNum) { int c = 0; int i; /* 012345678901234567 */ const char *pow63 = "922337203685477580"; for(i = 0; c == 0 && i < 18; i++){ c = (zNum[i] - pow63[i]) * 10; } if(c == 0){ c = zNum[18] - '8'; } return c; } int _sqlite_atoi64(const char *zNum, sqlite3_int64 *pNum) { sqlite3_uint64 u = 0; int neg = 0; int i; int c = 0; const char *zStart; const char *zEnd = zNum + strlen(zNum); while(zNum < zEnd && _isspace(*zNum)) zNum++; if (zNum < zEnd) { if (*zNum == '-') { neg = 1; zNum++; } else if (*zNum == '+') { zNum++; } } zStart = zNum; while(zNum < zEnd && zNum[0] == '0') zNum++; for(i = 0; &zNum[i] < zEnd && (c = zNum[i]) >= '0' && c <= '9'; i++) { u = u * 10 + c - '0'; } if (u > LARGEST_INT64) { *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; } else if (neg) { *pNum = -(sqlite3_int64)u; } else { *pNum = (sqlite3_int64)u; } if ((c != 0 && &zNum[i] < zEnd) || (i == 0 && zStart == zNum) || i > 19) { return 1; } else if (i < 19) { return 0; } else { c = compare2pow63(zNum); if (c < 0) { return 0; } else if (c > 0) { return 1; } else { return neg ? 0 : 2; } } } 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 flags, int extended) { int rc; if (flags) { rc = sqlite3_open_v2(dbname, db, flags, NULL); } else { rc = sqlite3_open(dbname, db); } if ( rc != SQLITE_OK ) { #if SQLITE_VERSION_NUMBER >= 3006005 if (extended) rc = sqlite3_extended_errcode(*db); #endif 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_UNKNOWN_TYPE: return SQLITE_NULL; 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; } } void init_cxt() { dTHX; MY_CXT_INIT; MY_CXT.last_dbh_is_unicode = 0; } SV * stacked_sv_from_sqlite3_value(pTHX_ sqlite3_value *value, int is_unicode) { STRLEN len; sqlite_int64 iv; int type = sqlite3_value_type(value); SV *sv; switch(type) { case SQLITE_INTEGER: iv = sqlite3_value_int64(value); if ( iv >= IV_MIN && iv <= IV_MAX ) { /* ^^^ compile-time constant (= true) when IV == int64 */ return sv_2mortal(newSViv((IV)iv)); } else if ( iv >= 0 && iv <= UV_MAX ) { /* warn("integer overflow, cast to UV"); */ return sv_2mortal(newSVuv((UV)iv)); } else { /* warn("integer overflow, cast to NV"); */ return sv_2mortal(newSVnv((NV)iv)); } case SQLITE_FLOAT: return sv_2mortal(newSVnv(sqlite3_value_double(value))); break; case SQLITE_TEXT: len = sqlite3_value_bytes(value); sv = newSVpvn((const char *)sqlite3_value_text(value), len); if (is_unicode) { SvUTF8_on(sv); } return sv_2mortal(sv); case SQLITE_BLOB: len = sqlite3_value_bytes(value); return sv_2mortal(newSVpvn(sqlite3_value_blob(value), len)); default: return &PL_sv_undef; } } static void sqlite_set_result(pTHX_ sqlite3_context *context, SV *result, int is_error) { STRLEN len; char *s; sqlite3_int64 iv; 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 ( !_sqlite_atoi64(SvPV(result, len), &iv) ) { sqlite3_result_int64( context, iv ); } 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) { sqlite3_int64 iv; 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 (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; } 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 && digit) { if (!_sqlite_atoi64(v, &iv)) return 1; } 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; HV *hv; SV **val; int extended = 0; int flag = 0; int unicode = 0; sqlite_trace(dbh, imp_dbh, 3, form("login '%s' (version %s)", dbname, sqlite3_version)); if (SvROK(attr)) { hv = (HV*)SvRV(attr); if (hv_exists(hv, "sqlite_extended_result_codes", 28)) { val = hv_fetch(hv, "sqlite_extended_result_codes", 28, 0); extended = (val && SvOK(*val)) ? !(!SvTRUE(*val)) : 0; } if (hv_exists(hv, "ReadOnly", 8)) { val = hv_fetch(hv, "ReadOnly", 8, 0); if ((val && SvOK(*val)) ? SvIV(*val) : 0) { flag |= SQLITE_OPEN_READONLY; } } if (hv_exists(hv, "sqlite_open_flags", 17)) { val = hv_fetch(hv, "sqlite_open_flags", 17, 0); flag |= (val && SvOK(*val)) ? SvIV(*val) : 0; if (flag & SQLITE_OPEN_READONLY) { hv_stores(hv, "ReadOnly", newSViv(1)); } } /* sqlite_unicode should be detected earlier, to register default functions correctly */ if (hv_exists(hv, "sqlite_unicode", 14)) { val = hv_fetch(hv, "sqlite_unicode", 14, 0); unicode = (val && SvOK(*val)) ? SvIV(*val) : 0; } else if (hv_exists(hv, "unicode", 7)) { val = hv_fetch(hv, "unicode", 7, 0); unicode = (val && SvOK(*val)) ? SvIV(*val) : 0; } } rc = sqlite_open2(dbname, &(imp_dbh->db), flag, extended); if ( rc != SQLITE_OK ) { return FALSE; /* -> undef in lib/DBD/SQLite.pm */ } DBIc_IMPSET_on(imp_dbh); imp_dbh->unicode = unicode; 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; imp_dbh->extended_result_codes = extended; imp_dbh->stmt_list = NULL; imp_dbh->began_transaction = FALSE; sqlite3_busy_timeout(imp_dbh->db, SQL_TIMEOUT); #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 #if 0 /* ** Enable this to see if you (wrongly) expect an implicit order ** of return values from a SELECT statement without ORDER BY. */ sqlite_exec(dbh, "PRAGMA reverse_unordered_selects = ON"); #endif DBIc_ACTIVE_on(imp_dbh); return TRUE; } int sqlite_db_do_sv(SV *dbh, imp_dbh_t *imp_dbh, SV *sv_statement) { dTHX; int rc = 0; int i; char *statement; if (!DBIc_ACTIVE(imp_dbh)) { sqlite_error(dbh, -2, "attempt to do on inactive database handle"); return -2; /* -> undef in SQLite.xsi */ } /* sqlite3_prepare wants an utf8-encoded SQL statement */ if (imp_dbh->unicode) { sv_utf8_upgrade(sv_statement); } statement = SvPV_nolen(sv_statement); sqlite_trace(dbh, imp_dbh, 3, form("do statement: %s", statement)); croak_if_db_is_null(); if (sqlite3_get_autocommit(imp_dbh->db)) { const char *sql = statement; _skip_whitespaces(sql); if (_starts_with_begin(sql)) { if (DBIc_is(imp_dbh, DBIcf_AutoCommit)) { if (!DBIc_is(imp_dbh, DBIcf_BegunWork)) { imp_dbh->began_transaction = TRUE; DBIc_on(imp_dbh, DBIcf_BegunWork); DBIc_off(imp_dbh, DBIcf_AutoCommit); } } } else if (!DBIc_is(imp_dbh, DBIcf_AutoCommit)) { sqlite_trace(dbh, imp_dbh, 3, "BEGIN TRAN"); if (imp_dbh->use_immediate_transaction) { rc = sqlite_exec(dbh, "BEGIN IMMEDIATE TRANSACTION"); } else { rc = sqlite_exec(dbh, "BEGIN TRANSACTION"); } if (rc != SQLITE_OK) { return -2; /* -> undef in SQLite.xsi */ } } } rc = sqlite_exec(dbh, statement); if (rc != SQLITE_OK) { sqlite_error(dbh, rc, sqlite3_errmsg(imp_dbh->db)); return -2; } if (DBIc_is(imp_dbh, DBIcf_BegunWork) && sqlite3_get_autocommit(imp_dbh->db)) { if (imp_dbh->began_transaction) { DBIc_off(imp_dbh, DBIcf_BegunWork); DBIc_on(imp_dbh, DBIcf_AutoCommit); } } return sqlite3_changes(imp_dbh->db); } 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)) { /* XXX: for rt_52573 imp_dbh->began_transaction = FALSE; */ 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)) { /* XXX: for rt_52573 imp_dbh->began_transaction = FALSE; */ 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; stmt_list_s * s; if (DBIc_is(imp_dbh, DBIcf_AutoCommit) == FALSE) { sqlite_db_rollback(dbh, imp_dbh); } DBIc_ACTIVE_off(imp_dbh); croak_if_db_is_null(); sqlite_trace( dbh, imp_dbh, 1, "Closing DB" ); rc = sqlite3_close( imp_dbh->db ); sqlite_trace( dbh, imp_dbh, 1, form("rc = %d", rc) ); if ( SQLITE_BUSY == rc ) { /* We have unfinalized statements */ /* Only close the statements that were prepared by this module */ while ( (s = imp_dbh->stmt_list) ) { sqlite_trace( dbh, imp_dbh, 1, form("Finalizing statement (%p)", s->stmt) ); sqlite3_finalize( s->stmt ); imp_dbh->stmt_list = s->prev; sqlite3_free( s ); } imp_dbh->stmt_list = NULL; sqlite_trace( dbh, imp_dbh, 1, "Trying to close DB again" ); rc = sqlite3_close( imp_dbh->db ); } if ( SQLITE_OK != rc ) { sqlite_error(dbh, rc, sqlite3_errmsg(imp_dbh->db)); } /* The list should be empty at this point, but if for some unforseen reason it isn't, free remaining nodes here */ while( (s = imp_dbh->stmt_list) ) { imp_dbh->stmt_list = s->prev; sqlite3_free( s ); } 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; 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); } 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 SQLITE_VERSION_NUMBER >= 3007011 if (strEQ(key, "ReadOnly")) { if (SvTRUE(valuesv) && !sqlite3_db_readonly(imp_dbh->db, "main")) { sqlite_error(dbh, 0, "ReadOnly is set but it's only advisory"); } return FALSE; } #endif 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_extended_result_codes")) { imp_dbh->extended_result_codes = !(! SvTRUE(valuesv)); sqlite3_extended_result_codes(imp_dbh->db, imp_dbh->extended_result_codes); 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_extended_result_codes")) { return sv_2mortal(newSViv(imp_dbh->extended_result_codes ? 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(SV *sth, imp_sth_t *imp_sth, SV *sv_statement, SV *attribs) { dTHX; dMY_CXT; int rc = 0; const char *extra; char *statement; stmt_list_s * new_stmt; D_imp_dbh_from_sth; MY_CXT.last_dbh_is_unicode = imp_dbh->unicode; if (!DBIc_ACTIVE(imp_dbh)) { sqlite_error(sth, -2, "attempt to prepare on inactive database handle"); return FALSE; /* -> undef in lib/DBD/SQLite.pm */ } /* sqlite3_prepare wants an utf8-encoded SQL statement */ if (imp_dbh->unicode) { sv_utf8_upgrade(sv_statement); } statement = SvPV_nolen(sv_statement); #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 (imp_dbh->allow_multiple_statements) { imp_sth->unprepared_statements = savepv(extra); } else { if (imp_dbh->allow_multiple_statements) Safefree(imp_sth->unprepared_statements); imp_sth->unprepared_statements = NULL; } /* Add the statement to the front of the list to keep track of statements that might need to be finalized later on disconnect */ new_stmt = (stmt_list_s *) sqlite3_malloc( sizeof(stmt_list_s) ); new_stmt->stmt = imp_sth->stmt; new_stmt->prev = imp_dbh->stmt_list; imp_dbh->stmt_list = new_stmt; 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; sqlite3_int64 iv; 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(); /* 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. * * see_if_its_a_number should be ignored if an explicit * SQL type is set via bind_param(). */ if (sql_type == SQLITE_NULL && 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 && !_sqlite_atoi64(data, &iv)) { rc = sqlite3_bind_int64(imp_sth->stmt, i+1, iv); } 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); _skip_whitespaces(sql); if (_starts_with_begin(sql)) { if (DBIc_is(imp_dbh, DBIcf_AutoCommit)) { if (!DBIc_is(imp_dbh, DBIcf_BegunWork)) { imp_dbh->began_transaction = TRUE; } 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 */ } } } 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 */ } /* transaction ended with commit/rollback/release */ if (DBIc_is(imp_dbh, DBIcf_BegunWork) && sqlite3_get_autocommit(imp_dbh->db)) { if (imp_dbh->began_transaction) { DBIc_off(imp_dbh, DBIcf_BegunWork); DBIc_on(imp_dbh, DBIcf_AutoCommit); } } /* 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)) { /* XXX: for rt_52573 if (DBIc_is(imp_dbh, DBIcf_BegunWork)) { imp_dbh->began_transaction = TRUE; } */ 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; sqlite3_int64 iv; 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)); iv = sqlite3_column_int64(imp_sth->stmt, i); if ( iv >= IV_MIN && iv <= IV_MAX ) { sv_setiv(AvARRAY(av)[i], (IV)iv); } else { val = (char*)sqlite3_column_text(imp_sth->stmt, i); sv_setpv(AvARRAY(av)[i], val); SvUTF8_off(AvARRAY(av)[i]); } 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); val = (char*)sqlite3_column_blob(imp_sth->stmt, i); sv_setpvn(AvARRAY(av)[i], len ? val : "", 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; stmt_list_s * i; stmt_list_s * temp; 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 */ sqlite_trace( sth, imp_sth, 1, form("Finalizing statement: %p", imp_sth->stmt) ); rc = sqlite3_finalize(imp_sth->stmt); if (rc != SQLITE_OK) { sqlite_error(sth, rc, sqlite3_errmsg(imp_dbh->db)); } /* find the statement in the statement list and delete it */ i = imp_dbh->stmt_list; temp = i; while( i ) { if ( i->stmt == imp_sth->stmt ) { if ( temp != i ) temp->prev = i->prev; if ( i == imp_dbh->stmt_list ) imp_dbh->stmt_list = i->prev; sqlite_trace( sth, imp_sth, 1, form("Removing statement from list: %p", imp_sth->stmt) ); sqlite3_free( i ); break; } else { temp = i; i = i->prev; } } imp_sth->stmt = NULL; } } if (imp_dbh->allow_multiple_statements) Safefree(imp_sth->unprepared_statements); 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 (!DBIc_ACTIVE(imp_dbh)) { sqlite_error(sth, -2, "attempt to fetch on inactive database handle"); return FALSE; } 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))); } else if (strEQ(key, "ParamValues")) { HV *hv = newHV(); int num_params = DBIc_NUM_PARAMS(imp_sth); if (num_params) { for (n = 0; n < num_params; n++) { SV **pvalue = av_fetch(imp_sth->params, 2 * n, 0); SV *value = pvalue ? *pvalue : &PL_sv_undef; const char *pname = sqlite3_bind_parameter_name(imp_sth->stmt, n + 1); SV *sv_name = pname ? newSVpv(pname, 0) : newSViv(n + 1); hv_store_ent(hv, sv_name, newSVsv(value), 0); } } retsv = sv_2mortal(newRV_noinc((SV*)hv)); } 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, " 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"); #if SQLITE_VERSION_NUMBER >= 3007001 _stores_status(SQLITE_STATUS_MALLOC_COUNT, "malloc_count"); #endif _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"); #if SQLITE_VERSION_NUMBER >= 3007000 _stores_dbstatus(SQLITE_DBSTATUS_CACHE_USED, "cache_used"); #endif #if SQLITE_VERSION_NUMBER >= 3007001 _stores_dbstatus(SQLITE_DBSTATUS_SCHEMA_USED, "schema_used"); _stores_dbstatus(SQLITE_DBSTATUS_STMT_USED, "stmt_used"); #endif #if SQLITE_VERSION_NUMBER >= 3007005 _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"); #endif #if SQLITE_VERSION_NUMBER >= 3007009 _stores_dbstatus(SQLITE_DBSTATUS_CACHE_HIT, "cache_hit"); _stores_dbstatus(SQLITE_DBSTATUS_CACHE_MISS, "cache_miss"); #endif #if SQLITE_VERSION_NUMBER >= 3007012 _stores_dbstatus(SQLITE_DBSTATUS_CACHE_WRITE, "cache_write"); #endif return hv; } HV * _sqlite_st_status(pTHX_ SV* sth, int reset) { D_imp_sth(sth); HV *hv = newHV(); #if SQLITE_VERSION_NUMBER >= 3006004 _stores_ststatus(SQLITE_STMTSTATUS_FULLSCAN_STEP, "fullscan_step"); _stores_ststatus(SQLITE_STMTSTATUS_SORT, "sort"); #endif #if SQLITE_VERSION_NUMBER >= 3007000 _stores_ststatus(SQLITE_STMTSTATUS_AUTOINDEX, "autoindex"); #endif return hv; } SV * sqlite_db_filename(pTHX_ SV *dbh) { D_imp_dbh(dbh); const char *filename; if (!imp_dbh->db) { return &PL_sv_undef; } croak_if_db_is_null(); #if SQLITE_VERSION_NUMBER >= 3007010 filename = sqlite3_db_filename(imp_dbh->db, "main"); #endif return filename ? newSVpv(filename, 0) : &PL_sv_undef; } int sqlite_db_busy_timeout(pTHX_ SV *dbh, SV *timeout ) { D_imp_dbh(dbh); croak_if_db_is_null(); if (timeout && SvIOK(timeout)) { imp_dbh->timeout = SvIV(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, imp_dbh->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++ ) { XPUSHs(stacked_sv_from_sqlite3_value(aTHX_ value[i], is_unicode)); } 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, int flags) { 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|flags, 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, is_unicode = 0; /* TODO : find out from db handle */ 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++ ) { XPUSHs(stacked_sv_from_sqlite3_value(aTHX_ value[i], is_unicode)); } 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, 0); 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_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, int flags) { 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|flags, 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((IV)( 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 } #include "dbdimp_tokenizer.inc" #include "dbdimp_virtual_table.inc" /* end */