/* vim: set noai ts=4 et sw=4: */ /* Copyright (c) 2010, 2011 Popa Marius Adrian Copyright (c) 2011, 2012, 2013 Damyan Ivanov Copyright (c) 2010 Mike Pomraning Copyright (c) 1999-2008 Edwin Pratomo Portions Copyright (c) 2001-2005 Daniel Ritz You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file. */ #include "Firebird.h" #include #ifndef _MSC_VER #include #endif DBISTATE_DECLARE; #define ERRBUFSIZE 255 #define IB_SQLtimeformat(xxh, format, sv) \ do { \ STRLEN len; \ char *frmt = NULL; \ char *buf = SvPV(sv, len); \ if (len < 2 || len > 30) break; \ Newx(frmt, len + 1, char); \ strcpy(frmt, buf); \ if (format) Safefree(format); \ format = frmt; \ } while (0) #define IB_alloc_sqlda(sqlda, n) \ do { \ short len = n; \ char *tmp; \ if (sqlda) \ { \ Safefree(sqlda); \ sqlda = NULL; \ } \ Newxz(tmp, XSQLDA_LENGTH(len), char); \ sqlda = (XSQLDA*)tmp; \ sqlda->sqln = len; \ sqlda->version = SQLDA_OK_VERSION; \ } while (0) #ifndef is_ascii_string #warning "Using built-in implementation of is_ascii_string." #warning "Upgrading perl to 5.12 is suggested." // for perl before 5.12.0 RC1 // taken straight from the perl source bool is_ascii_string(const U8 *s, STRLEN len) { const U8* const send = s + (len ? len : strlen((const char *)s)); const U8* x = s; for (; x < send; ++x) { if (!UTF8_IS_INVARIANT(*x)) break; } return x == send; } #endif int create_cursor_name(SV *sth, imp_sth_t *imp_sth) { ISC_STATUS status[ISC_STATUS_LENGTH]; #define CURSOR_NAME_LEN 22 Newxz(imp_sth->cursor_name, CURSOR_NAME_LEN, char); snprintf(imp_sth->cursor_name, CURSOR_NAME_LEN, "perl%16.16X", (uint32_t)imp_sth->stmt); isc_dsql_set_cursor_name(status, &(imp_sth->stmt), imp_sth->cursor_name, 0); if (ib_error_check(sth, status)) return FALSE; return TRUE; } void maybe_upgrade_to_utf8(imp_dbh_t *imp_dbh, SV *sv) { if (imp_dbh->ib_enable_utf8) { U8 *p; STRLEN len; p = (U8*)SvPV(sv, len); if (!is_ascii_string(p, len) && is_utf8_string(p, len)) { SvUTF8_on(sv); } } } void dbd_init(dbistate_t *dbistate) { DBISTATE_INIT; } void ib_cleanup_st_prepare (imp_sth_t *imp_sth) { FREE_SETNULL(imp_sth->in_sqlda); FREE_SETNULL(imp_sth->out_sqlda); FREE_SETNULL(imp_sth->dateformat); FREE_SETNULL(imp_sth->timeformat); FREE_SETNULL(imp_sth->timestampformat); } void ib_cleanup_st_execute (imp_sth_t *imp_sth) { if (imp_sth->in_sqlda) { int i; XSQLVAR *var = imp_sth->in_sqlda->sqlvar; for (i = 0; i < imp_sth->in_sqlda->sqln; i++, var++) { Safefree(var->sqldata); var->sqldata = NULL; if (var->sqlind) *(var->sqlind) = -1; /* isNULL */ } } } /* lower level error handling */ void do_error(SV *h, int rc, char *what) { D_imp_xxh(h); SV *errstr = DBIc_ERRSTR(imp_xxh); sv_setiv(DBIc_ERR(imp_xxh), (IV)rc); sv_setpv(errstr, what); if (DBIc_TRACE_LEVEL(imp_xxh) >= 2) PerlIO_printf(DBIc_LOGPIO(imp_xxh), "%s error %d recorded: %s\n", what, rc, SvPV(errstr,PL_na)); } #define CALC_AVAILABLE(buff) sizeof(buff) - strlen(buff) - 1 /* decode status vector into a char pointer (implemented by a mortal scalar) Returns NULL if there is no error */ char* ib_error_decode(const ISC_STATUS *status) { SV *sv = NULL; long sqlcode; #if !defined(FB_API_VER) || FB_API_VER < 20 ISC_STATUS *pvector = status; #else const ISC_STATUS *pvector = status; #endif #if defined (INCLUDE_TYPES_PUB_H) ISC_SCHAR msg[1024]; #else char msg[1024]; #endif if (status[0] != 1 || status[1] <= 0) return NULL; if ((sqlcode = isc_sqlcode(status)) != 0) { isc_sql_interprete((short) sqlcode, msg, sizeof(msg)); sv = sv_2mortal(newSVpv(msg, 0)); } #if !defined(FB_API_VER) || FB_API_VER < 20 while (isc_interprete(msg, &pvector)) #else while (fb_interpret(msg, sizeof(msg), &pvector)) #endif { if ( sv != NULL ) { sv_catpvn(sv, "\n-", 2); sv_catpv(sv, msg); } else sv = sv_2mortal(newSVpv(msg,0)); } sv_catpvn(sv, "\0", 1); // NUL-terminate return SvPV_nolen(sv); } /* higher level error handling, check and decode status */ int ib_error_check(SV *h, ISC_STATUS *status) { char *msg = ib_error_decode(status); if (msg == NULL) return SUCCESS; do_error(h, isc_sqlcode(status), msg); return FAILURE; } static int ib2sql_type(int ibtype) { /* Firebird Internal (not external) types */ switch(ibtype) { case SQL_TEXT: case 453: return DBI_SQL_CHAR; case SQL_LONG: case 497: return DBI_SQL_INTEGER; /* integer */ case SQL_SHORT: case 501: return DBI_SQL_SMALLINT; /* smallint */ case SQL_FLOAT: case 483: return DBI_SQL_FLOAT; case SQL_DOUBLE: case 481: return DBI_SQL_DOUBLE; case SQL_TIMESTAMP: case 511: return DBI_SQL_TIMESTAMP; case SQL_TYPE_DATE: case 571: return DBI_SQL_DATE; case SQL_TYPE_TIME: case 561: return DBI_SQL_TIME; case SQL_VARYING: case 449: return DBI_SQL_VARCHAR; } /* else map type into DBI reserved standard range */ return -9000 - ibtype; } #if 0 /* from DBI (ANSI/ISO/ODBC) types to Firebird types */ static int ib_sql_type(imp_sth_t *imp_sth, char *name, int sql_type) { /* XXX should detect DBI reserved standard type range here */ switch (sql_type) { case DBI_SQL_NUMERIC: case DBI_SQL_DECIMAL: case DBI_SQL_INTEGER: case DBI_SQL_BIGINT: case DBI_SQL_TINYINT: case DBI_SQL_SMALLINT: case DBI_SQL_FLOAT: case DBI_SQL_REAL: case DBI_SQL_DOUBLE: return 481; case DBI_SQL_VARCHAR: return 449; case DBI_SQL_CHAR: return 453; case SQL_DATE: case SQL_TIME: case SQL_TIMESTAMP: default: if (DBIc_WARN(imp_sth) && imp_sth && name) warn("SQL type %d for '%s' is not fully supported, bound as SQL_VARCHAR instead"); return ib_sql_type(imp_sth, name, DBI_SQL_VARCHAR); } } #endif int dbd_db_login6(SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *uid, char *pwd, SV *attr) { dTHR; ISC_STATUS status[ISC_STATUS_LENGTH]; HV *hv; SV *sv; SV **svp; /* versatile scalar storage */ unsigned short ib_dialect, ib_cache; char *ib_role; char ISC_FAR *dpb_buffer, *dpb; int connect_timeout = 0; char ISC_FAR *database; STRLEN len, db_len; /* for SvPV */ char dbkey_scope = 0; short dpb_length = 0; unsigned int buflen = 0; /* buffer size is dynamic */ imp_dbh->db = 0L; imp_dbh->tr = 0L; imp_dbh->tpb_buffer = NULL; imp_dbh->tpb_length = 0; imp_dbh->sth_ddl = 0; imp_dbh->soft_commit = 0; /* use soft commit (isc_commit_retaining)? */ imp_dbh->ib_enable_utf8 = FALSE; /* default date/time formats + * * Old API: dateformat ........ %c * timeformat ........ (none) * timestampformat ... (none) * * v6 API: dateformat ........ %x * timeformat ........ %X * timestampformat ... %c */ Newxz(imp_dbh->dateformat, 3, char); strcpy(imp_dbh->dateformat, "%x"); Newxz(imp_dbh->timeformat, 3, char); strcpy(imp_dbh->timeformat, "%X"); Newxz(imp_dbh->timestampformat, 3, char); strcpy(imp_dbh->timestampformat, "%c"); /* linked list */ imp_dbh->first_sth = NULL; imp_dbh->last_sth = NULL; /* save the current context for thread/multiplicy safety */ #if defined(USE_THREADS) || defined(USE_ITHREADS) || defined(MULTIPLICITY) imp_dbh->context = PERL_GET_CONTEXT; #endif /* Parse DSN and init values */ sv = DBIc_IMP_DATA(imp_dbh); if (!sv || !SvROK(sv)) return FALSE; hv = (HV*) SvRV(sv); if (SvTYPE(hv) != SVt_PVHV) return FALSE; if (uid != NULL) { DPB_PREP_STRING(buflen, uid); } if (pwd != NULL) { DPB_PREP_STRING(buflen, pwd); } /* does't go to DPB -> no buflen inc */ if ((svp = hv_fetch(hv, "database", 8, FALSE))) database = SvPV(*svp, db_len); else database = NULL; /* role, cache, charset, sqldialect */ if ((svp = hv_fetch(hv, "ib_dialect", 10, FALSE))) ib_dialect = (unsigned short) SvIV(*svp); else ib_dialect = DEFAULT_SQL_DIALECT; DPB_PREP_INTEGER(buflen); if ((svp = hv_fetch(hv, "ib_cache", 8, FALSE))) { ib_cache = (unsigned short) SvIV(*svp); DPB_PREP_INTEGER(buflen); } else ib_cache = 0; if ((svp = hv_fetch(hv, "ib_charset", 10, FALSE))) { char *p = SvPV(*svp, len); DPB_PREP_STRING_LEN(buflen, len); Newx(imp_dbh->ib_charset, len+1, char); strncpy(imp_dbh->ib_charset, p, len); *(imp_dbh->ib_charset + len) = '\0'; } else { imp_dbh->ib_charset = NULL; } if ((svp = hv_fetch(hv, "ib_role", 7, FALSE))) { ib_role = SvPV(*svp, len); DPB_PREP_STRING_LEN(buflen, len); } else ib_role = NULL; if ((svp = hv_fetch(hv, "ib_dbkey_scope", 14, FALSE))) { dbkey_scope = (char)SvIV(*svp); if (dbkey_scope) DPB_PREP_INTEGER(buflen); } if ((svp = hv_fetch(hv, "timeout", 7, FALSE))) { int val = SvIV(*svp); if (val <= 0) croak("Positive timeout required"); connect_timeout = val; DPB_PREP_INTEGER(buflen); } /* add length of other parameters to needed buflen */ buflen += 1; /* dbpversion */ DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_db_login6\n")); /* Allocate DPB */ Newx(dpb_buffer, buflen, char); /* Default SQL dialect for every statement */ imp_dbh->sqldialect = ib_dialect; /* Fill DPB */ dpb = dpb_buffer; *dpb++ = isc_dpb_version1; DPB_FILL_STRING(dpb, isc_dpb_user_name, uid); DPB_FILL_STRING(dpb, isc_dpb_password, pwd); if (ib_cache) { /* * Safety check: Do not allocate a cache buffer greater than * 10000 pages, so we don't exhaust memory inadvertently. */ if (ib_cache > 10000) ib_cache = 10000; DPB_FILL_INTEGER(dpb, isc_dpb_num_buffers, ib_cache); } DPB_FILL_INTEGER(dpb, isc_dpb_sql_dialect, ib_dialect); if (dbkey_scope) { DPB_FILL_INTEGER(dpb, isc_dpb_dbkey_scope, dbkey_scope); } if (imp_dbh->ib_charset) { DPB_FILL_STRING(dpb, isc_dpb_lc_ctype, imp_dbh->ib_charset); } if (ib_role) { DPB_FILL_STRING(dpb, isc_dpb_sql_role_name, ib_role); } if (connect_timeout) { DPB_FILL_INTEGER(dpb, isc_dpb_connect_timeout, connect_timeout); } dpb_length = dpb - dpb_buffer; if ( dpb_length != buflen ) { fprintf(stderr, "# db_login6: %d != %d\n", dpb_length, buflen); fflush(stderr); abort(); } DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_db_login6: attaching to database %s..\n", database)); isc_attach_database(status, /* status vector */ db_len, database, /* connect string */ &(imp_dbh->db), /* ref to db handle */ dpb_length, /* length of dpb */ dpb_buffer); /* connect options */ /* freeing database parameter buffer */ Safefree(dpb_buffer); /* return false on failed attach */ if (ib_error_check(dbh, status)) return FALSE; imp_dbh->charset_bytes_per_char = NULL; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_db_login6: success attaching.\n")); /* Tell DBI, that dbh->destroy should be called for this handle */ DBIc_IMPSET_on(imp_dbh); /* Tell DBI, that dbh->disconnect should be called for this handle */ DBIc_ACTIVE_on(imp_dbh); return TRUE; } int dbd_db_login(SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *uid, char *pwd) { return dbd_db_login6(dbh, imp_dbh, dbname, uid, pwd, Nullsv); } int dbd_db_ping(SV *dbh) { D_imp_dbh(dbh); ISC_STATUS status[ISC_STATUS_LENGTH]; char buffer[100]; char req[] = { isc_info_ods_version, isc_info_end }; DBI_TRACE_imp_xxh(imp_dbh, 1, (DBIc_LOGPIO(imp_dbh), "dbd_db_ping\n")); if (isc_database_info(status, &(imp_dbh->db), sizeof(req), req, sizeof(buffer), buffer)) if (ib_error_check(dbh, status)) return FALSE; return TRUE; } int dbd_db_disconnect(SV *dbh, imp_dbh_t *imp_dbh) { dTHR; ISC_STATUS status[ISC_STATUS_LENGTH]; DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_db_disconnect\n")); /* set the database handle to inactive */ DBIc_ACTIVE_off(imp_dbh); /* always do a rollback if there's an open transaction. * Firebird requires to close open transactions before * detaching a database. */ if (imp_dbh->tr) { /* rollback and close trans context */ isc_rollback_transaction(status, &(imp_dbh->tr)); if (ib_error_check(dbh, status)) return FALSE; imp_dbh->tr = 0L; } FREE_SETNULL(imp_dbh->ib_charset); FREE_SETNULL(imp_dbh->tpb_buffer); FREE_SETNULL(imp_dbh->dateformat); FREE_SETNULL(imp_dbh->timeformat); FREE_SETNULL(imp_dbh->timestampformat); FREE_SETNULL(imp_dbh->charset_bytes_per_char); /* detach database */ isc_detach_database(status, &(imp_dbh->db)); if (ib_error_check(dbh, status)) return FALSE; return TRUE; } void dbd_db_destroy (SV *dbh, imp_dbh_t *imp_dbh) { DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_db_destroy\n")); if (DBIc_ACTIVE(imp_dbh)) dbd_db_disconnect(dbh, imp_dbh); /* Nothing in imp_dbh to be freed */ DBIc_IMPSET_off(imp_dbh); } int dbd_db_commit (SV *dbh, imp_dbh_t *imp_dbh) { DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_db_commit\n")); /* no commit if AutoCommit on */ if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) return FALSE; /* commit the transaction */ if (!ib_commit_transaction(dbh, imp_dbh)) return FALSE; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_db_commit succeed.\n")); return TRUE; } int dbd_db_rollback(SV *dbh, imp_dbh_t *imp_dbh) { DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_db_rollback\n")); /* no rollback if AutoCommit = on */ if (DBIc_has(imp_dbh, DBIcf_AutoCommit) != FALSE) return FALSE; /* rollback the transaction */ if (!ib_rollback_transaction(dbh, imp_dbh)) return FALSE; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_db_rollback succeed.\n")); return TRUE; } int dbd_db_STORE_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv) { STRLEN kl; char *key = SvPV(keysv, kl); int on = SvTRUE(valuesv)? 1: 0; int oldval; int set_frmts = 0; DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_db_STORE - %s\n", key)); /**************************************************************************/ if ((kl==10) && strEQ(key, "AutoCommit")) { oldval = DBIc_has(imp_dbh, DBIcf_AutoCommit)? 1: 0; DBIc_set(imp_dbh, DBIcf_AutoCommit, on); DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_db_STORE: switch AutoCommit from %d to %d\n", oldval, on)); if (oldval == FALSE && on) { /* AutoCommit set from 0 to 1, commit any outstanding changes */ if (imp_dbh->tr) { if (!ib_commit_transaction(dbh, imp_dbh)) return FALSE; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_db_STORE: commit open transaction\n")); } } return TRUE; /* handled */ } /**************************************************************************/ else if ((kl==13) && strEQ(key, "ib_softcommit")) { oldval = imp_dbh->soft_commit; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_db_STORE: switch ib_softcommit from %d to %d\n", oldval, on)); /* set new value */ imp_dbh->soft_commit = on; /* switching softcommit from 1 to 0 -> make a hard commit */ if (!on && oldval) { if (imp_dbh->tr) { if (!ib_commit_transaction(dbh, imp_dbh)) return FALSE; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_db_STORE: commit open transaction\n")); } } return TRUE; /* handled */ } else if ((kl==14) && strEQ(key, "ib_enable_utf8")) { if (on) { if (imp_dbh->ib_charset && strEQ(imp_dbh->ib_charset, "UTF8")) { imp_dbh->ib_enable_utf8 = TRUE; return TRUE; } else croak( "ib_enable_utf8 requires ib_charset=UTF8 in DSN (you gave %s)", imp_dbh->ib_charset ? imp_dbh->ib_charset : "" ); } else { imp_dbh->ib_enable_utf8 = FALSE; return TRUE; } } else if ((kl==11) && strEQ(key, "ib_time_all")) set_frmts = 1; /**************************************************************************/ if (set_frmts || ((kl==13) && strEQ(key, "ib_dateformat"))) { IB_SQLtimeformat(dbh, imp_dbh->dateformat, valuesv); if (!set_frmts) return TRUE; } if (set_frmts || ((kl==13) && strEQ(key, "ib_timeformat"))) { IB_SQLtimeformat(dbh, imp_dbh->timeformat, valuesv); if (!set_frmts) return TRUE; } if (set_frmts || ((kl==18) && strEQ(key, "ib_timestampformat"))) { IB_SQLtimeformat(dbh, imp_dbh->timestampformat, valuesv); if (!set_frmts) return TRUE; } /**************************************************************************/ if (set_frmts) return TRUE; else return FALSE; /* not handled */ } SV *dbd_db_FETCH_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv) { STRLEN kl; char * key = SvPV(keysv, kl); SV * result = NULL; DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_db_FETCH - %s\n", key)); if ((kl==10) && strEQ(key, "AutoCommit")) result = boolSV(DBIc_has(imp_dbh, DBIcf_AutoCommit)); else if ((kl==13) && strEQ(key, "ib_softcommit")) result = boolSV(imp_dbh->soft_commit); else if ((kl==14) && strEQ(key, "ib_enable_utf8")) result = boolSV(imp_dbh->ib_enable_utf8); else if ((kl==13) && strEQ(key, "ib_dateformat")) result = newSVpvn(imp_dbh->dateformat, strlen(imp_dbh->dateformat)); else if ((kl==13) && strEQ(key, "ib_timeformat")) result = newSVpvn(imp_dbh->timeformat, strlen(imp_dbh->timeformat)); else if ((kl==18) && strEQ(key, "ib_timestampformat")) result = newSVpvn(imp_dbh->timestampformat, strlen(imp_dbh->timestampformat)); else if ((kl==11) && strEQ(key, "ib_embedded")) #ifdef EMBEDDED result = &PL_sv_yes; #else result = &PL_sv_no; #endif if (result == NULL) return Nullsv; else { if ((result == &PL_sv_yes) || (result == &PL_sv_no)) return result; else return sv_2mortal(result); } } void dbd_preparse(SV *sth, imp_sth_t *imp_sth, char *statement) { ISC_STATUS status[ISC_STATUS_LENGTH]; DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "Enter dbd_preparse\n")); isc_dsql_describe_bind(status, &(imp_sth->stmt), 1, imp_sth->in_sqlda); if (ib_error_check(sth, status)) { ib_cleanup_st_prepare(imp_sth); return; } /* realloc in_sqlda and rebind if not enough XSQLVAR for bind params */ if (imp_sth->in_sqlda->sqld > imp_sth->in_sqlda->sqln) { IB_alloc_sqlda(imp_sth->in_sqlda, imp_sth->in_sqlda->sqld); if (imp_sth->in_sqlda == NULL) { do_error(sth, 1, "Fail to reallocate in_slqda"); ib_cleanup_st_prepare(imp_sth); return; } else { isc_dsql_describe_bind(status, &(imp_sth->stmt), 1, imp_sth->in_sqlda); if (ib_error_check(sth, status)) { ib_cleanup_st_prepare(imp_sth); return; } } } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_preparse: describe_bind passed.\n" "dbd_preparse: exit; in_sqlda: sqld: %d, sqln: %d.\n", imp_sth->in_sqlda->sqld, imp_sth->in_sqlda->sqln)); DBIc_NUM_PARAMS(imp_sth) = imp_sth->in_sqlda->sqld; } int dbd_st_prepare(SV *sth, imp_sth_t *imp_sth, char *statement, SV *attribs) { D_imp_dbh_from_sth; ISC_STATUS status[ISC_STATUS_LENGTH]; int i; short dtype; static char stmt_info[1]; char info_buffer[20], count_item; XSQLVAR *var; DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "Enter dbd_st_prepare\n")); if (!DBIc_ACTIVE(imp_dbh)) { do_error(sth, -1, "Database disconnected"); return FALSE; } /* init values */ count_item = 0; imp_sth->count_item = 0; imp_sth->affected = -1; imp_sth->in_sqlda = NULL; imp_sth->out_sqlda = NULL; imp_sth->cursor_name = NULL; imp_sth->dateformat = NULL; imp_sth->timestampformat = NULL; imp_sth->timeformat = NULL; /* double linked list */ imp_sth->prev_sth = NULL; imp_sth->next_sth = NULL; if (attribs) { SV **svp; if ((svp = DBD_ATTRIB_GET_SVP(attribs, "ib_time_all", 11)) != NULL) { IB_SQLtimeformat(sth, imp_sth->dateformat, *svp); IB_SQLtimeformat(sth, imp_sth->timestampformat, *svp); IB_SQLtimeformat(sth, imp_sth->timeformat, *svp); } if ((svp = DBD_ATTRIB_GET_SVP(attribs, "ib_dateformat", 13)) != NULL) IB_SQLtimeformat(sth, imp_sth->dateformat, *svp); if ((svp = DBD_ATTRIB_GET_SVP(attribs, "ib_timestampformat", 18)) != NULL) IB_SQLtimeformat(sth, imp_sth->timestampformat, *svp); if ((svp = DBD_ATTRIB_GET_SVP(attribs, "ib_timeformat", 13)) != NULL) IB_SQLtimeformat(sth, imp_sth->timeformat, *svp); } /* allocate 1 XSQLVAR to in_sqlda */ IB_alloc_sqlda(imp_sth->in_sqlda, 1); if (imp_sth->in_sqlda == NULL) { do_error(sth, 2, "Fail to allocate in_sqlda"); return FALSE; } /* allocate 1 XSQLVAR to out_sqlda */ IB_alloc_sqlda(imp_sth->out_sqlda, 1); if (imp_sth->out_sqlda == NULL) { do_error(sth, 2, "Fail to allocate out_sqlda"); ib_cleanup_st_prepare(imp_sth); return FALSE; } /* init statement handle */ isc_dsql_alloc_statement2(status, &(imp_dbh->db), &(imp_sth->stmt)); if (ib_error_check(sth, status)) { ib_cleanup_st_prepare(imp_sth); return FALSE; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: sqldialect: %d.\n", imp_dbh->sqldialect)); if (!imp_dbh->tr) { /* start a new transaction using current TPB */ if (!ib_start_transaction(sth, imp_dbh)) { ib_cleanup_st_prepare(imp_sth); return FALSE; } } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: statement: %s.\n", statement)); isc_dsql_prepare(status, &(imp_dbh->tr), &(imp_sth->stmt), 0, statement, imp_dbh->sqldialect, imp_sth->out_sqlda); if (ib_error_check(sth, status)) { ib_cleanup_st_prepare(imp_sth); return FALSE; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: isc_dsql_prepare succeed..\n")); stmt_info[0] = isc_info_sql_stmt_type; isc_dsql_sql_info(status, &(imp_sth->stmt), sizeof (stmt_info), stmt_info, sizeof (info_buffer), info_buffer); if (ib_error_check(sth, status)) { ib_cleanup_st_prepare(imp_sth); return FALSE; } { short l = (short) isc_vax_integer((char *) info_buffer + 1, 2); imp_sth->type = isc_vax_integer((char *) info_buffer + 3, l); } /* sanity check of statement type */ DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: statement type: %ld.\n", imp_sth->type)); switch (imp_sth->type) { /* Implemented statement types. */ case isc_info_sql_stmt_select: case isc_info_sql_stmt_select_for_upd: /* * Unfortunately, select count item doesn't work * in current versions of Firebird. * isql does it literally by fetching everything * and counting the number of rows it fetched. * Firebird doesn't seem to be able to estimate * the number of rows before the client app * fetches them all. */ //count_item = isc_info_req_select_count; break; case isc_info_sql_stmt_insert: count_item = isc_info_req_insert_count; break; case isc_info_sql_stmt_update: count_item = isc_info_req_update_count; break; case isc_info_sql_stmt_delete: count_item = isc_info_req_delete_count; break; case isc_info_sql_stmt_ddl: case isc_info_sql_stmt_set_generator: case isc_info_sql_stmt_exec_procedure: case isc_info_sql_stmt_savepoint: case isc_info_sql_stmt_start_trans: case isc_info_sql_stmt_commit: case isc_info_sql_stmt_rollback: /* no count_item to gather */ break; /* * Unimplemented statement types. Some may be implemented in the future. */ case isc_info_sql_stmt_get_segment: case isc_info_sql_stmt_put_segment: default: do_error(sth, 10, "Statement type is not implemented in this version of DBD::Firebird"); return FALSE; break; } imp_sth->count_item = count_item; /* scan statement for '?', ':1' and/or ':foo' style placeholders */ /* realloc in_sqlda where needed */ dbd_preparse(sth, imp_sth, statement); DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: dbd_describe passed.\n" "out_sqlda: sqld: %d, sqln: %d.\n", imp_sth->out_sqlda->sqld, imp_sth->out_sqlda->sqln)); /* enough output parameter block ? */ if (imp_sth->out_sqlda->sqld > imp_sth->out_sqlda->sqln) { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: realloc out_sqlda..\n")); IB_alloc_sqlda(imp_sth->out_sqlda, imp_sth->out_sqlda->sqld); if (imp_sth->out_sqlda == NULL) { do_error(sth, IB_ALLOC_FAIL, "Fail to reallocate out_sqlda"); ib_cleanup_st_prepare(imp_sth); return FALSE; } else { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: calling isc_dsql_describe again..\n")); isc_dsql_describe(status, &(imp_sth->stmt), 1, imp_sth->out_sqlda); if (ib_error_check(sth, status)) { ib_cleanup_st_prepare(imp_sth); return FALSE; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: success calling isc_dsql_describe.\n")); } } else if (imp_sth->out_sqlda->sqld == 0) /* not a select statement */ { Safefree(imp_sth->out_sqlda); imp_sth->out_sqlda = NULL; } if (imp_sth->out_sqlda) { for (i = 0, var = imp_sth->out_sqlda->sqlvar; i < imp_sth->out_sqlda->sqld; i++, var++) { dtype = (var->sqltype & ~1); var->sqlind = NULL; DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: field type: %d.\n", dtype)); /* Alloc space for sqldata */ Newx(var->sqldata, var->sqllen + (dtype == SQL_VARYING ? sizeof(short) : 0), ISC_SCHAR); /* Nullable? */ if (var->sqltype & 1) Newx(var->sqlind, 1, short); } } /* statment is valid -> insert into linked list (at begin) */ imp_sth->next_sth = imp_dbh->first_sth; if (imp_dbh->first_sth == NULL) imp_dbh->last_sth = imp_sth; else imp_dbh->first_sth->prev_sth = imp_sth; imp_dbh->first_sth = imp_sth; DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_prepare: sth inserted into linked list.\n")); /* tell DBI that we have a real statement handle now */ DBIc_IMPSET_on(imp_sth); return TRUE; } int dbd_st_finish_internal(SV *sth, imp_sth_t *imp_sth, int honour_auto_commit) { D_imp_dbh_from_sth; ISC_STATUS status[ISC_STATUS_LENGTH]; DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_st_finish\n")); if (!DBIc_ACTIVE(imp_sth)) /* already finished */ { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: nothing to do (not active)\n")); return TRUE; } /* Close the cursor, not drop the statement! */ if (imp_sth->type != isc_info_sql_stmt_exec_procedure) { isc_dsql_free_statement(status, (isc_stmt_handle *)&(imp_sth->stmt), DSQL_close); /* Ignore errors when closing already closed cursor (sqlcode -501). May happen when closing "select * from sample" statement, which was closed by the server because of a "drop table sample" statement. There is no point to error-out here, since nothing bad has happened -- the statement is closed, just without we knowing. There is no resource leak and the user can't and needs not do anything. */ if ((status[0] == 1) && (status[1] > 0)) { long sqlcode = isc_sqlcode(status); if (sqlcode != -501) { if (ib_error_check(sth, status)) return FALSE; } else { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: ignoring error -501 from isc_dsql_free_statement.\n")); } } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: isc_dsql_free_statement passed.\n")); } /* set statement to inactive - must be before ib_commit_transaction 'cos commit can call dbd_st_finish function again */ DBIc_ACTIVE_off(imp_sth); if ( imp_sth->param_values != NULL ) hv_clear(imp_sth->param_values); /* if AutoCommit on */ if (DBIc_has(imp_dbh, DBIcf_AutoCommit) && honour_auto_commit) { DBI_TRACE_imp_xxh(imp_sth, 4, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: Trying to call ib_commit_transaction.\n")); if (!ib_commit_transaction(sth, imp_dbh)) { DBI_TRACE_imp_xxh(imp_sth, 4, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: Call ib_commit_transaction finished returned FALSE.\n")); return FALSE; } DBI_TRACE_imp_xxh(imp_sth, 4, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: Call ib_commit_transaction succeeded.\n")); } return TRUE; } int dbd_st_finish(SV *sth, imp_sth_t *imp_sth) { return dbd_st_finish_internal(sth, imp_sth, TRUE); } int dbd_st_execute(SV *sth, imp_sth_t *imp_sth) { D_imp_dbh_from_sth; ISC_STATUS status[ISC_STATUS_LENGTH]; int result = -2; int row_count = 0; if (DBIc_ACTIVE(imp_sth)) dbd_st_finish_internal( sth, imp_sth, TRUE); DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_st_execute\n")); if (DBIc_ACTIVE(imp_sth)) dbd_st_finish_internal(sth, imp_sth, TRUE); /* if not already done: start new transaction */ if (!imp_dbh->tr) if (!ib_start_transaction(sth, imp_dbh)) return result; DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: statement type: %ld.\n", imp_sth->type)); /* we count DDL statments */ if (imp_sth->type == isc_info_sql_stmt_ddl) imp_dbh->sth_ddl++; /* exec procedure statement */ if (imp_sth->type == isc_info_sql_stmt_exec_procedure) { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: calling isc_dsql_execute2 (exec procedure)..\n")); isc_dsql_execute2(status, &(imp_dbh->tr), &(imp_sth->stmt), imp_dbh->sqldialect, (imp_sth->in_sqlda && (imp_sth->in_sqlda->sqld > 0))? imp_sth->in_sqlda: NULL, (imp_sth->out_sqlda && (imp_sth->out_sqlda->sqld > 0))? imp_sth->out_sqlda: NULL); if (ib_error_check(sth, status)) { ib_cleanup_st_execute(imp_sth); return result; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: isc_dsql_execute2 succeed.\n")); result = row_count = imp_sth->affected = 0; } else /* all other types of SQL statements */ { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: calling isc_dsql_execute..\n")); /* check for valid in_sqlda */ if (!imp_sth->in_sqlda) return FALSE; isc_dsql_execute(status, &(imp_dbh->tr), &(imp_sth->stmt), imp_dbh->sqldialect, imp_sth->in_sqlda->sqld > 0 ? imp_sth->in_sqlda: NULL); if (ib_error_check(sth, status)) { ib_cleanup_st_execute(imp_sth); /* rollback any active transaction */ if (DBIc_has(imp_dbh, DBIcf_AutoCommit) && imp_dbh->tr) ib_commit_transaction(sth, imp_dbh); return result; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: isc_dsql_execute succeed.\n")); } if (imp_sth->count_item) { //PerlIO_printf(PerlIO_stderr(), "calculating row count\n"); row_count = ib_rows(sth, &(imp_sth->stmt), imp_sth->count_item); if (row_count <= -2) ib_cleanup_st_execute(imp_sth); else result = imp_sth->affected = row_count; } else if (imp_sth->type == isc_info_sql_stmt_select) result = row_count = imp_sth->affected = 0; else result = -1; /* Jika AutoCommit On, commit_transaction() (bukan retaining), * dan reset imp_dbh->tr == 0L * For SELECT statement, commit_transaction() is called after fetch, * or within finish() */ if (DBIc_has(imp_dbh, DBIcf_AutoCommit) && imp_sth->type != isc_info_sql_stmt_select && imp_sth->type != isc_info_sql_stmt_select_for_upd && imp_sth->type != isc_info_sql_stmt_exec_procedure) { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: calling ib_commit_transaction..\n")); if (!ib_commit_transaction(sth, imp_dbh)) { ib_cleanup_st_execute(imp_sth); return result; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: ib_commit_transaction succeed.\n")); } /* Declare a unique cursor for this query */ if (imp_sth->type == isc_info_sql_stmt_select_for_upd) { /* We free the cursor_name buffer in dbd_st_destroy. */ if (!create_cursor_name(sth, imp_sth)) { ib_cleanup_st_execute(imp_sth); return result; } } switch (imp_sth->type) { case isc_info_sql_stmt_select: case isc_info_sql_stmt_select_for_upd: case isc_info_sql_stmt_exec_procedure: DBIc_NUM_FIELDS(imp_sth) = (imp_sth->out_sqlda)? imp_sth->out_sqlda->sqld: 0; DBIc_ACTIVE_on(imp_sth); break; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: row count: %d.\n" "dbd_st_execute: count_item: %d.\n", row_count, imp_sth->count_item)); return result; } unsigned get_charset_bytes_per_char(const ISC_SHORT subtype, SV *sth); /* from out_sqlda to AV */ AV *dbd_st_fetch(SV *sth, imp_sth_t *imp_sth) { D_imp_dbh_from_sth; /* declare imp_dbh from sth */ ISC_STATUS fetch = 0; ISC_STATUS status[ISC_STATUS_LENGTH]; int chopBlanks; /* chopBlanks ? */ AV *av; /* array buffer */ SV *sv, **svp; /* buffers */ XSQLVAR *var; /* working pointer XSQLVAR */ int i; /* loop */ short dtype; DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_st_fetch\n")); if (!DBIc_ACTIVE(imp_sth)) { do_error(sth, 0, "no statement executing (perhaps you need to call execute first)\n"); return Nullav; } chopBlanks = DBIc_is(imp_sth, DBIcf_ChopBlanks); av = DBIS->get_fbav(imp_sth); svp = AvARRAY(av); /* * if it's an execute procedure, we've already got the * output from the isc_dsql_execute2() call in dbd_st_execute(). */ if (imp_sth->type != isc_info_sql_stmt_exec_procedure) { fetch = isc_dsql_fetch(status, &(imp_sth->stmt), imp_dbh->sqldialect, imp_sth->out_sqlda); if (ib_error_check(sth, status)) return Nullav; /* * Code 100 means we've reached the end of the set * of rows that the SELECT will return. */ DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_fetch: fetch result: %ld\n", fetch)); if (imp_sth->affected < 0) imp_sth->affected = 0; if (fetch == 100) { /* close the cursor */ isc_dsql_free_statement(status, &(imp_sth->stmt), DSQL_close); if (ib_error_check(sth, status)) return Nullav; DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "isc_dsql_free_statement succeed.\n")); DBIc_ACTIVE_off(imp_sth); /* dbd_st_finish is no longer needed */ /* if AutoCommit on XXX. what to return if fails? */ if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) { if (!ib_commit_transaction(sth, imp_dbh)) return Nullav; DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "fetch ends: ib_commit_transaction succeed.\n")); } return Nullav; } else if (fetch != 0) /* something bad */ { do_error(sth, 0, "Fetch error"); DBIc_ACTIVE_off(imp_sth); return Nullav; } } /* !exec_procedure */ else { /* we only fetch one row for exec procedure */ if (imp_sth->affected) return Nullav; } var = imp_sth->out_sqlda->sqlvar; for (i = 0; i < imp_sth->out_sqlda->sqld; i++, var++) { sv = svp[i]; dtype = var->sqltype & ~1; if ((var->sqltype & 1) && (*(var->sqlind) == -1)) /* if nullable field */ { /* isNULL */ SvOK_off(sv); } else { /* * Got a non-null field. Got to pass it back to the * application, which means some datatype dependant code. */ switch (dtype) { case SQL_SHORT: if (var->sqlscale) /* handle NUMERICs */ { double numeric; numeric = ((double) (*(short *) var->sqldata)) / pow(10.0, (double) -var->sqlscale); sv_setnv(sv, numeric); } else sv_setiv(sv, *(short *) (var->sqldata)); break; case SQL_LONG: if (var->sqlscale) /* handle NUMERICs */ { double numeric; numeric = ((double) (*(ISC_LONG *) var->sqldata)) / pow(10.0, (double) -var->sqlscale); sv_setnv(sv, numeric); } else sv_setiv(sv, *(ISC_LONG *) (var->sqldata)); break; #ifdef SQL_INT64 case SQL_INT64: /* * This seemed difficult at first to return * a 64-bit scaled numeric to Perl through the * SV interface. But as luck would have it, * Perl treats strings and numerics identically. * I can return this numeric as a string and * nobody has a problem with it. */ { static ISC_INT64 const scales[] = { 1LL, 10LL, 100LL, 1000LL, 10000LL, 100000LL, 1000000LL, 10000000LL, 100000000LL, 1000000000LL, 10000000000LL, 100000000000LL, 1000000000000LL, 10000000000000LL, 100000000000000LL, 1000000000000000LL, 10000000000000000LL, 100000000000000000LL }; ISC_INT64 i; /* significand */ char buf[22]; /* NUMERIC(18,2) = -92233720368547758.08 + '\0' */ i = *((ISC_INT64 *) (var->sqldata)); /* We use the system snprintf(3) and system-specific * format codes. :( On my perl, I was unable to * persuade sv_setpvf to handle INT64 values with * IVdf (and there is no I64f). * - MJP 2010-03-25 */ #if defined(_MSC_VER) /* Microsoft C compiler/library */ # define DBD_IB_INT64f "I64d" #elif defined (__FreeBSD__) /* FreeBSD */ # define DBD_IB_INT64f "qd" #else /* others: linux, various unices */ # define DBD_IB_INT64f "lld" #endif if (var->sqlscale == 0) { snprintf(buf, sizeof(buf), "%"DBD_IB_INT64f, i); sv_setpvn(sv, buf, strlen(buf)); } else { bool sign = ( i < 0 ); ISC_INT64 divisor, remainder; divisor = scales[-var->sqlscale]; if (sign) divisor = -divisor; remainder = (i%divisor); if (remainder < 0) remainder = -remainder; snprintf(buf+1, sizeof(buf)-1, "%"DBD_IB_INT64f".%0*"DBD_IB_INT64f, i/divisor, -var->sqlscale, remainder); DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "-------------->SQLINT64=%"DBD_IB_INT64f".%0*"DBD_IB_INT64f,i/divisor, -var->sqlscale, remainder )); if (sign) { *buf = '-'; sv_setpvn(sv, buf, strlen(buf)); } else { sv_setpvn(sv, buf+1, strlen(buf+1)); } } } break; #endif case SQL_FLOAT: sv_setnv(sv, (double)(*(float *) (var->sqldata))); break; case SQL_DOUBLE: if (var->sqlscale) /* handle NUMERICs */ { double d = *(double *)var->sqldata; short q = -var->sqlscale; sv_setnv(sv, d > 0? floor(d * pow(10.0, (double) q)) / pow(10.0, (double) q): ceil(d * pow(10.0, (double) q)) / pow(10.0, (double) q)); } else sv_setnv(sv, *(double *) (var->sqldata)); break; case SQL_TEXT: /* * Thanks to DAM for pointing out that I * don't need to null-terminate this * buffer, and in fact it's a buffer * overrun if I do! */ DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "Fill in TEXT type..\nLength: %d\n", var->sqllen)); if (chopBlanks && (var->sqllen > 0)) { short len = var->sqllen; char *p = (char*)(var->sqldata); while (len && (p[len-1] == ' ')) len--; sv_setpvn(sv, p, len); maybe_upgrade_to_utf8(imp_dbh, sv); } else { /* we need to shrink the string for multy-byte character sets. the padding spaces are too many in this case */ unsigned bpc = get_charset_bytes_per_char( var->sqlsubtype, sth); unsigned len = var->sqllen; sv_setpvn(sv, var->sqldata, len); maybe_upgrade_to_utf8(imp_dbh, sv); SvCUR_set(sv, len/bpc); } break; case SQL_VARYING: { DBD_VARY *vary = (DBD_VARY *) var->sqldata; sv_setpvn(sv, vary->vary_string, vary->vary_length); /* Note that sqllen for VARCHARs is the max length */ maybe_upgrade_to_utf8(imp_dbh, sv); break; } /* * If user specifies a TimestampFormat, TimeFormat, or * DateFormat property of the Statement class, then that * string is the format string for strftime(). * * If the user doesn't specify an XxxFormat, then format * is %c, defined in /usr/lib/locale//LC_TIME/time, * where is the host's chosen locale. */ case SQL_TIMESTAMP: case SQL_TYPE_DATE: case SQL_TYPE_TIME: { char *format = NULL, buf[100]; struct tm times; long int fpsec = 0; switch (dtype) { case SQL_TIMESTAMP: isc_decode_timestamp((ISC_TIMESTAMP *) var->sqldata, ×); format = imp_sth->timestampformat ? imp_sth->timestampformat : imp_dbh->timestampformat; fpsec = TIMESTAMP_FPSECS(var->sqldata); break; case SQL_TYPE_DATE: isc_decode_sql_date((ISC_DATE *) var->sqldata, ×); format = imp_sth->dateformat ? imp_sth->dateformat : imp_dbh->dateformat; break; case SQL_TYPE_TIME: isc_decode_sql_time((ISC_TIME *) var->sqldata, ×); format = imp_sth->timeformat ? imp_sth->timeformat : imp_dbh->timeformat; fpsec = TIME_FPSECS(var->sqldata); break; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "Decode passed.\n")); /* hardcoded output format.... */ if (strEQ(format, "iso") || strEQ(format, "ISO")) { switch (dtype) { case SQL_TIMESTAMP: snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d.%04ld", times.tm_year + 1900, times.tm_mon + 1, times.tm_mday, times.tm_hour, times.tm_min, times.tm_sec, fpsec); break; case SQL_TYPE_DATE: snprintf(buf, sizeof(buf), "%04d-%02d-%02d", times.tm_year + 1900, times.tm_mon + 1, times.tm_mday); break; case SQL_TYPE_TIME: snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%04ld", times.tm_hour, times.tm_min, times.tm_sec, fpsec); break; } sv_setpvn(sv, buf, strlen(buf)); break; } /* output as array like perl's localtime? */ if (strEQ(format, "tm") || strEQ(format, "TM")) { AV *list = newAV(); av_push(list, newSViv(times.tm_sec)); av_push(list, newSViv(times.tm_min)); av_push(list, newSViv(times.tm_hour)); av_push(list, newSViv(times.tm_mday)); av_push(list, newSViv(times.tm_mon)); av_push(list, newSViv(times.tm_year)); av_push(list, newSViv(times.tm_wday)); av_push(list, newSViv(times.tm_yday)); av_push(list, newSViv(times.tm_isdst)); /* value returned is a reference to the array */ sv_setsv(sv, sv_2mortal(newRV_noinc((SV *) list))); break; } #ifndef WIN32 /* * may be we must here copy additional fields needed on * some platforms for some strftime formats. copy from a * dummy struct passed to mktime(). calling mktime() * directly with × is wrong. */ /* struct tm has 9 fields plus may be some more */ if (sizeof(struct tm) > (9*sizeof(int))) { struct tm dummy; Zero(&dummy, 1, struct tm); mktime(&dummy); memcpy(((char *)×) + 9*sizeof(int), ((char *)&dummy) + 9*sizeof(int), sizeof(struct tm) - (9*sizeof(int))); } #endif strftime(buf, sizeof(buf), format, ×); sv_setpvn(sv, buf, strlen(buf)); break; } case SQL_BLOB: { isc_blob_handle blob_handle = 0; int blob_stat; char blob_info_buffer[32], *p, blob_segment_buffer[BLOB_SEGMENT]; char blob_info_items[] = { isc_info_blob_type, isc_info_blob_max_segment, isc_info_blob_total_length }; long max_segment = -1L, total_length = -1L, t; unsigned short seg_length; short blob_type = -1; /* Open the Blob according to the Blob id. */ isc_open_blob2(status, &(imp_dbh->db), &(imp_dbh->tr), &blob_handle, (ISC_QUAD *) var->sqldata, #if defined(INCLUDE_FB_TYPES_H) || defined(INCLUDE_TYPES_PUB_H) (ISC_USHORT) 0, (ISC_UCHAR *) NULL); #else (short) 0, /* no Blob filter */ (char *) NULL); /* no Blob filter */ #endif if (ib_error_check(sth, status)) return FALSE; /* query blob information to find out the segment size */ isc_blob_info(status, &blob_handle, sizeof(blob_info_items), blob_info_items, sizeof(blob_info_buffer), blob_info_buffer); if (ib_error_check(sth, status)) { isc_cancel_blob(status, &blob_handle); return FALSE; } /* Get the information out of the info buffer. */ for (p = blob_info_buffer; *p != isc_info_end; ) { short length; char datum = *p++; length = (short) isc_vax_integer(p, 2); p += 2; switch (datum) { case isc_info_blob_max_segment: max_segment = isc_vax_integer(p, length); break; case isc_info_blob_total_length: total_length = isc_vax_integer(p, length); break; case isc_info_blob_type: blob_type = isc_vax_integer(p, length); break; default: croak("Unknown parameter %d", (int)datum); } p += length; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_fetch: BLOB info - max_segment: %ld, total_length: %ld, type: %d\n", max_segment, total_length, blob_type)); if (max_segment == -1L || total_length == -1L || blob_type == -1) { isc_cancel_blob(status, &blob_handle); do_error(sth, 1, "Cannot determine Blob dimensions or type."); return FALSE; break; } /* if maximum segment size is zero, don't pass it to isc_get_segment() */ if (max_segment == 0) { sv_setpv(sv, ""); isc_cancel_blob(status, &blob_handle); if (ib_error_check(sth, status)) return FALSE; break; } if ((DBIc_LongReadLen(imp_sth) < (unsigned long) total_length) && (! DBIc_is(imp_dbh, DBIcf_LongTruncOk))) { isc_close_blob(status, &blob_handle); do_error(sth, 1, "Not enough LongReadLen buffer."); return FALSE; break; } /* Create a zero-length string. */ sv_setpv(sv, ""); t = total_length; while (1) { blob_stat = isc_get_segment(status, &blob_handle, &seg_length, (short) BLOB_SEGMENT, blob_segment_buffer); if (status[1] == isc_segstr_eof) break; if (status[1] != isc_segment) if (ib_error_check(sth, status)) { isc_cancel_blob(status, &blob_handle); return FALSE; } if (seg_length > DBIc_LongReadLen(imp_sth)) break; /* * As long as the fetch was successful, concatenate the segment we fetched * into the growing Perl scalar. */ sv_catpvn(sv, blob_segment_buffer, seg_length); t -= seg_length; if (t <= 0) break; if (blob_stat == 100) break; } /* Clean up after ourselves. */ isc_close_blob(status, &blob_handle); if (ib_error_check(sth, status)) return FALSE; if ( blob_type == isc_blob_text || var->sqlsubtype == isc_blob_text ) maybe_upgrade_to_utf8(imp_dbh, sv); break; } case SQL_ARRAY: #ifdef ARRAY_SUPPORT !!! NOT IMPLEMENTED YET !!! #else sv_setpvn(sv, "** array **", 11); #endif break; default: sv_setpvn(sv, "** unknown **", 13); } /* * I use the column's alias name because in the absence * of an alias, it contains the column name anyway. * Only if the alias AND the column names are zero-length * do I want to use a generic "COLUMN%d" header. * This happens, for example, when the column is a * computed field and the query doesn't use an AS clause * to label the column. */ /* if (var->aliasname_length > 0) { sv_setpvn(sv, var->aliasname, var->aliasname_length)); } else { char s[20]; snprintf(s, sizeof(s), "COLUMN%d", i); sv_setpvn(sv, s, strlen(s)); } */ } } imp_sth->affected += 1; return av; } void dbd_st_destroy(SV *sth, imp_sth_t *imp_sth) { D_imp_dbh_from_sth; ISC_STATUS status[ISC_STATUS_LENGTH]; DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy\n")); /* freeing cursor name */ FREE_SETNULL(imp_sth->cursor_name); if ( imp_sth->param_values != NULL ) { hv_undef(imp_sth->param_values); imp_sth->param_values = NULL; } /* freeing in_sqlda */ if (imp_sth->in_sqlda) { int i; XSQLVAR *var = imp_sth->in_sqlda->sqlvar; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: found in_sqlda..\n")); for (i = 0; i < imp_sth->in_sqlda->sqld; i++, var++) { FREE_SETNULL(var->sqldata); FREE_SETNULL(var->sqlind); } DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: freeing in_sqlda..\n")); Safefree(imp_sth->in_sqlda); imp_sth->in_sqlda = NULL; } /* freeing out_sqlda */ if (imp_sth->out_sqlda) { int i; XSQLVAR *var = imp_sth->out_sqlda->sqlvar; for (i = 0; i < imp_sth->out_sqlda->sqld; i++, var++) { FREE_SETNULL(var->sqldata); FREE_SETNULL(var->sqlind); } Safefree(imp_sth->out_sqlda); imp_sth->out_sqlda = NULL; } /* free all other resources */ FREE_SETNULL(imp_sth->dateformat); FREE_SETNULL(imp_sth->timeformat); FREE_SETNULL(imp_sth->timestampformat); /* Drop the statement */ if (imp_sth->stmt) { isc_dsql_free_statement(status, &(imp_sth->stmt), DSQL_drop); if (ib_error_check(sth, status)) { DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: isc_dsql_free_statement failed.\n")); } else DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: isc_dsql_free_statement succeeded.\n")); imp_sth->stmt = 0L; } /* remove sth from linked list */ /* handle prev element */ if (imp_sth->prev_sth == NULL) imp_dbh->first_sth = imp_sth->next_sth; else imp_sth->prev_sth->next_sth = imp_sth->next_sth; /* handle next element*/ if (imp_sth->next_sth == NULL) imp_dbh->last_sth = imp_sth->prev_sth; else imp_sth->next_sth->prev_sth = imp_sth->prev_sth; /* set next/prev to NULL */ imp_sth->prev_sth = imp_sth->next_sth = NULL; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: sth removed from linked list.\n")); /* let DBI know we've done it */ if (sth) DBIc_IMPSET_off(imp_sth); } SV* dbd_st_FETCH_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv) { STRLEN kl; char *key = SvPV(keysv, kl); int i; SV *result = NULL; /* Default to caching results for DBI dispatch quick_FETCH */ int cacheit = TRUE; DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_st_FETCH - %s\n", key)); if (kl==13 && strEQ(key, "NUM_OF_PARAMS")) /* handled by DBI */ return Nullsv; i = DBIc_NUM_FIELDS(imp_sth); if (!imp_sth) return Nullsv; /**************************************************************************/ if (kl==4 && strEQ(key, "TYPE")) { AV *av; if (!imp_sth->in_sqlda || !imp_sth->out_sqlda) return Nullsv; av = newAV(); result = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, newSViv(ib2sql_type(imp_sth->out_sqlda->sqlvar[i].sqltype))); } /**************************************************************************/ else if (kl==5 && strEQ(key, "SCALE")) { AV *av; if (!imp_sth->in_sqlda || !imp_sth->out_sqlda) return Nullsv; av = newAV(); result = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, newSViv(imp_sth->out_sqlda->sqlvar[i].sqlscale)); } /**************************************************************************/ else if (kl==9 && strEQ(key, "PRECISION")) { AV *av; if (!imp_sth->in_sqlda || !imp_sth->out_sqlda) return Nullsv; av = newAV(); result = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, newSViv(imp_sth->out_sqlda->sqlvar[i].sqllen)); } /**************************************************************************/ else if (kl==4 && strEQ(key, "NAME")) { AV *av; if (!imp_sth->in_sqlda || !imp_sth->out_sqlda) return Nullsv; av = newAV(); result = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) { if (imp_sth->out_sqlda->sqlvar[i].aliasname_length > 0) { av_store(av, i, newSVpvn(imp_sth->out_sqlda->sqlvar[i].aliasname, imp_sth->out_sqlda->sqlvar[i].aliasname_length)); } else { char s[20]; snprintf(s, sizeof(s), "COLUMN%d", i); av_store(av, i, newSVpvn(s, strlen(s))); } } } /**************************************************************************/ else if (kl==8 && strEQ(key, "NULLABLE")) { AV *av; if (!imp_sth->in_sqlda || !imp_sth->out_sqlda) return Nullsv; av = newAV(); result = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, boolSV((imp_sth->out_sqlda->sqlvar[i].sqltype & 1) != 0)); } /**************************************************************************/ else if (kl==10 && strEQ(key, "CursorName")) { if (imp_sth->cursor_name == NULL) return Nullsv; result = newSVpv(imp_sth->cursor_name, strlen(imp_sth->cursor_name)); } /**************************************************************************/ else if (kl==11 && strEQ(key, "ParamValues")) { if (imp_sth->param_values == NULL) return Nullsv; result = newRV_inc((SV*)imp_sth->param_values); } else return Nullsv; if (cacheit) { /* cache for next time (via DBI quick_FETCH) */ SV **svp = hv_fetch((HV*)SvRV(sth), key, kl, 1); sv_free(*svp); *svp = result; (void)SvREFCNT_inc(result); /* so sv_2mortal won't free it */ } return sv_2mortal(result); } int dbd_st_STORE_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv) { STRLEN kl; char *key = SvPV(keysv, kl); DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_st_STORE - %s\n", key)); return FALSE; } int dbd_discon_all(SV *drh, imp_drh_t *imp_drh) { dTHR; /* The disconnect_all concept is flawed and needs more work */ if (!SvTRUE(perl_get_sv("DBI::PERL_ENDING", 0))) { sv_setiv(DBIc_ERR(imp_drh), (IV)1); sv_setpv(DBIc_ERRSTR(imp_drh), (char*)"disconnect_all not implemented"); (void)DBIh_EVENT2(drh, ERROR_event, DBIc_ERR(imp_drh), DBIc_ERRSTR(imp_drh)); return FALSE; } if (PL_perl_destruct_level) PL_perl_destruct_level = 0; return FALSE; } int ib_blob_write(SV *sth, imp_sth_t *imp_sth, XSQLVAR *var, SV *value) { D_imp_dbh_from_sth; isc_blob_handle handle = 0; ISC_STATUS status[ISC_STATUS_LENGTH]; STRLEN total_length; char *p, *seg, *string; int is_text_blob, seg_len; DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "ib_blob_write\n")); /* we need a transaction */ if (!imp_dbh->tr) if (!ib_start_transaction(sth, imp_dbh)) return FALSE; /* alloc mem for blob id */ if (var->sqldata == NULL) Newxc(var->sqldata, 1, ISC_QUAD, ISC_SCHAR); /* try to create blob handle */ isc_create_blob2(status, &(imp_dbh->db), &(imp_dbh->tr), &handle, (ISC_QUAD *)(var->sqldata), 0, NULL); if (ib_error_check(sth, status)) return FALSE; is_text_blob = (var->sqlsubtype == isc_bpb_type_stream)? 1: 0; /* SUBTYPE TEXT */ /* get length, pointer to data */ string = SvPV(value, total_length); /* write it segment by segment */ seg_len = BLOB_SEGMENT; p = string; while (total_length > 0) { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "ib_blob_write: %lld bytes left\n", (long long)total_length)); /* set new segment start pointer */ seg = p; if (is_text_blob) { seg_len = 0; while ((seg_len < BLOB_SEGMENT) && (total_length > 0)) { total_length--; p++; seg_len++; if (*(p-1) == '\n') break; } } else { /* no text blob, set seg len to max possible */ if (total_length < BLOB_SEGMENT) seg_len = total_length; /* update segment pointer */ p += seg_len; total_length -= seg_len; } isc_put_segment(status, &handle, (unsigned short) seg_len, seg); if (ib_error_check(sth, status)) { isc_cancel_blob(status, &handle); return FALSE; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "ib_blob_write: %d bytes written\n", seg_len)); } /* close blob, check for error */ isc_close_blob(status, &handle); if (ib_error_check(sth, status)) return FALSE; return TRUE; } /* fill in_sqlda with bind parameters */ static int ib_fill_isqlda(SV *sth, imp_sth_t *imp_sth, SV *param, SV *value, IV sql_type) { STRLEN len; XSQLVAR *ivar; int retval; int dtype; int i = (int)SvIV(param) - 1; retval = TRUE; ivar = &(imp_sth->in_sqlda->sqlvar[i]); DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "enter ib_fill_isqlda. processing %d XSQLVAR" " Type %ld" " ivar->sqltype=%d\n", i + 1, (long) sql_type, ivar->sqltype)); DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: XSQLDA len: %d\n", imp_sth->in_sqlda->sqln)); /* NULL indicator */ if (!(ivar->sqlind)) Newx(ivar->sqlind, 1, ISC_SHORT); /* *(ivar->sqlind) = ivar->sqltype & 1 ? 0 : 1; */ *(ivar->sqlind) = 0; /* default assume non-NULL */ /* it should be safe not to free here. test it */ #if 0 if (ivar->sqldata) { DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: Freeing sqldata\n")); DBI_TRACE_imp_xxh(imp_sth, 4, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: Freeing sqldata, sqltype is %d\n", ivar->sqltype)); Safefree(ivar->sqldata); ivar->sqldata = (char *)NULL; } #endif if (!SvOK(value)) /* user passed an undef */ { if (ivar->sqltype & 1) /* Field is NULLable */ { /* * The user has passed 'undef' for this scalar parameter and we use * this to indicate that the parameter should have a NULL state. */ *(ivar->sqlind) = -1; /* NULL */ /* * Hence no need to fill in sqldata for this sqlvar, because it's * NULL anyway.Skip to next loop iteration. */ return retval; } else { /* * User passed an undef to a field that is not nullable. */ char err[ERRBUFSIZE]; snprintf(err, sizeof(err), "You have not provided a value for non-nullable parameter #%d.", i); do_error(sth, 1, err); retval = FALSE; return retval; } } do { char *p; STRLEN len; if ( imp_sth->param_values == NULL ) imp_sth->param_values = newHV(); p = SvPV(param, len); (void)hv_store( imp_sth->param_values, p, len, newSVsv(value), 0 ); } while (0); /* data type minus nullable flag */ dtype = ivar->sqltype & ~1; /* workaround for date problem (bug #429820) */ if (dtype == SQL_TEXT) { if (ivar->sqlsubtype == 0x77) dtype = SQL_TIMESTAMP; } switch (dtype) { /**********************************************************************/ case SQL_VARYING: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_VARYING\n")); { char *string; STRLEN len; string = SvPV(value, len); if (len > ivar->sqllen) { char err[ERRBUFSIZE]; snprintf(err, sizeof(err), "String truncation (SQL_VARYING): attempted to bind %lu octets to column sized %lu", (long unsigned)len, (long unsigned)(sizeof(char) * (ivar->sqllen))); break; } if (!(ivar->sqldata)) Newxz(ivar->sqldata, ivar->sqllen + sizeof(short), char); *((short *)ivar->sqldata) = len; Copy(string, ivar->sqldata + sizeof(short), len, char); break; } /**********************************************************************/ case SQL_TEXT: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_TEXT\n")); { char *string; STRLEN len; string = SvPV(value, len); if (len > ivar->sqllen) { char err[ERRBUFSIZE]; snprintf(err, sizeof(err), "String truncation (SQL_TEXT): attempted to bind %lu octets to column sized %lu", (long unsigned)len, (long unsigned)(sizeof(char) * (ivar->sqllen))); break; } if (!(ivar->sqldata)) Newxc(ivar->sqldata, ivar->sqllen, char, ISC_SCHAR); /* Pad the entire field with blanks */ PoisonWith(ivar->sqldata, ivar->sqllen, char, ' '); Copy(string, ivar->sqldata, len, char); break; } /**********************************************************************/ case SQL_SHORT: case SQL_LONG: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_SHORT/SQL_LONG\n")); { char format[64]; long p, q, r, result; char *svalue; /* we need a bit of mem */ if (!(ivar->sqldata)) { Newxc(ivar->sqldata, (dtype == SQL_SHORT ? sizeof(short) : sizeof(long)), char, ISC_SCHAR); } /* See case SQL_INT64 for commentary. */ p = q = r = (long) 0; svalue = (char *)SvPV(value, len); /* with decimals? */ if (-ivar->sqlscale) { /* numeric(?,?) */ int scale = (int) (pow(10.0, (double) -ivar->sqlscale)); int dscale; char *tmp; char *neg; snprintf(format, sizeof(format), "%%ld.%%%dld%%1ld", -ivar->sqlscale); /* negative -0.x hack */ neg = strchr(svalue, '-'); if (neg) { svalue = neg + 1; len = strlen(svalue); } if (!sscanf(svalue, format, &p, &q, &r)) { /* here we handle values such as .78 passed as string */ snprintf(format, sizeof(format), ".%%%dld%%1ld", -ivar->sqlscale); if (!sscanf(svalue, format, &q, &r) && DBIc_WARN(imp_sth)) warn("problem parsing SQL_LONG type"); } /* Round up if r is 5 or greater */ if (r >= 5) { q++; /* round q up by one */ p += q / scale; /* round p up by one if q overflows */ q %= scale; /* modulus if q overflows */ } /* decimal scaling */ tmp = strchr(svalue, '.'); dscale = (tmp)? -ivar->sqlscale - (len - (int) (tmp - svalue)) + 1: 0; if (dscale < 0) dscale = 0; /* final result */ result = (long) (p * scale + q * (int) (pow(10.0, (double) dscale))) * (neg? -1: 1); } /******************************************************************/ else { /* numeric(?,0): scan for one decimal and do rounding*/ snprintf(format, sizeof(format), "%%ld.%%1ld"); if (!sscanf(svalue, format, &p, &r)) { snprintf(format, sizeof(format), ".%%1ld"); if (!sscanf(svalue, format, &r) && DBIc_WARN(imp_sth)) warn("problem parsing SQL_LONG type"); } /* rounding */ if (r >= 5) { if (p < 0) p--; else p++; } /* the final result */ result = (long) p; } /* result in short or long? */ if (dtype == SQL_SHORT) *(ISC_SHORT *) (ivar->sqldata) = (ISC_SHORT) result; else *(ISC_LONG *) (ivar->sqldata) = (ISC_LONG) result; break; } /**********************************************************************/ #ifdef SQL_INT64 case SQL_INT64: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_INT64\n")); { char *svalue; char format[64]; ISC_INT64 p, q, r; if (!(ivar->sqldata)) Newxc(ivar->sqldata, 1, ISC_INT64, ISC_SCHAR); /* * Here I handle both whole and scaled numerics. * The trick is to avoid converting the Perl scalar * to an IEEE floating value, because this would * introduce exactness errors in the conversion from * base-10 to base-2. * * I create a pattern for scanf() to read the whole * number portion (p), then a number of digits for * the scale (q), then one more digit (r) for the * round-up threshold. * * Note that sprintf replaces %% with a single %. * See the man page for sscanf() for more details * about how it interprets %Ld, %1Ld, etc. */ /* * Define INT64 sscanf formats for various platforms * using #defines eases the adding of a new platform (compiler/library) */ #if defined(_MSC_VER) /* Microsoft C compiler/library */ # define S_INT64_FULL "%%I64d.%%%dI64d%%1I64d" # define S_INT64_NOSCALE "%%I64d.%%1I64d" # define S_INT64_DEC_FULL ".%%%dI64d%%1I64d" # define S_INT64_DEC_NOSCALE ".%%1I64d" #elif defined (__FreeBSD__) /* FreeBSD */ # define S_INT64_FULL "%%qd.%%%dqd%%1qd" # define S_INT64_NOSCALE "%%qd.%%1qd" # define S_INT64_DEC_FULL ".%%%dqd%%1qd" # define S_INT64_DEC_NOSCALE ".%%1qd" #else /* others: linux, various unices */ # define S_INT64_FULL "%%lld.%%%dlld%%1lld" # define S_INT64_NOSCALE "%%lld.%%1lld" # define S_INT64_DEC_FULL ".%%%dlld%%1lld" # define S_INT64_DEC_NOSCALE ".%%1lld" #endif p = q = r = (ISC_INT64) 0; svalue = (char *)SvPV(value, len); /* with decimals? */ if (-ivar->sqlscale) { /* numeric(?,?) */ int scale = (int) (pow(10.0, (double) -ivar->sqlscale)); int dscale; char *tmp; char *neg; snprintf(format, sizeof(format), S_INT64_FULL, -ivar->sqlscale); /* negative -0.x hack */ neg = strchr(svalue, '-'); if (neg) { svalue = neg + 1; len = strlen(svalue); } if (!sscanf(svalue, format, &p, &q, &r)) { /* here we handle values such as .78 passed as string */ snprintf(format, sizeof(format), S_INT64_DEC_FULL, -ivar->sqlscale); if (!sscanf(svalue, format, &q, &r) && DBIc_WARN(imp_sth)) warn("problem parsing SQL_INT64 type"); } /* Round up if r is 5 or greater */ if (r >= 5) { q++; /* round q up by one */ p += q / scale; /* round p up by one if q overflows */ q %= scale; /* modulus if q overflows */ } /* decimal scaling */ tmp = strchr(svalue, '.'); dscale = (tmp)? -ivar->sqlscale - (len - (int) (tmp - svalue)) + 1: 0; if (dscale < 0) dscale = 0; /* final result */ *(ISC_INT64 *) (ivar->sqldata) = (ISC_INT64) (p * scale + q * (int) (pow(10.0, (double) dscale))) * (neg? -1: 1); } /******************************************************************/ else { /* numeric(?,0): scan for one decimal and do rounding*/ snprintf(format, sizeof(format), S_INT64_NOSCALE); if (!sscanf(svalue, format, &p, &r)) { snprintf(format, sizeof(format), S_INT64_DEC_NOSCALE); if (!sscanf(svalue, format, &r) && DBIc_WARN(imp_sth)) warn("problem parsing SQL_INT64 type"); } /* rounding */ if (r >= 5) { if (p < 0) p--; else p++; } /* the final result */ *(ISC_INT64 *) (ivar->sqldata) = (ISC_INT64) p; } break; } #endif /**********************************************************************/ case SQL_FLOAT: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_FLOAT\n")); if (!(ivar->sqldata)) Newxc(ivar->sqldata, 1, float, ISC_SCHAR); *(float *) (ivar->sqldata) = (float) SvNV(value); break; /**********************************************************************/ case SQL_DOUBLE: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_DOUBLE\n")); if (!(ivar->sqldata)) Newxc(ivar->sqldata, 1, double, ISC_SCHAR); *(double *) (ivar->sqldata) = SvNV(value); break; /**********************************************************************/ case SQL_TIMESTAMP: case SQL_TYPE_TIME: case SQL_TYPE_DATE: if (SvPOK(value) || SvTYPE(value) == SVt_PVMG) { /* * Coerce the date literal into a CHAR string, so as * to allow Firebird's internal date-string parsing * to interpret the date. */ char *datestring = SvPV(value, len); ivar->sqltype = SQL_TEXT | (ivar->sqltype & 1); /* prevent overflow */ if (len > 100) { do_error(sth, 2, "DATE input parameter too long, but will try...\n"); len = 100; } /* workaround for date problem (bug #429820) */ ivar->sqlsubtype = 0x77; /* (0x77 is a random value) */ ivar->sqllen = len; /* * I should not allocate based on len, I should allocate * a fixed length based on the max date/time string. * For now let's just call it 100. Okay, 101. */ if (!(ivar->sqldata)) Newx(ivar->sqldata, 101, ISC_SCHAR); Copy(datestring, ivar->sqldata, len, ISC_SCHAR); ivar->sqldata[len] = '\0'; } else if (SvROK(value)) { struct tm times; /* unix time struct */ AV *list = (AV *) SvRV(value); /* AV with time items */ SV **svp = AvARRAY(list); /* AV as a C array */ int items = av_len(list) + 1; /* item count in array */ /* check if we have enough items in the list */ if (items < 5) /* we ignore wday, yday, isdst */ { do_error(sth, 2, "Cannot bind date/time value. Not enough" "items in localtime() style array"); retval = FALSE; break; } /* fill in struct tm fields */ times.tm_sec = SvIV(svp[0]); times.tm_min = SvIV(svp[1]); times.tm_hour = SvIV(svp[2]); times.tm_mday = SvIV(svp[3]); times.tm_mon = SvIV(svp[4]); times.tm_year = SvIV(svp[5]); /* free ivar->sqldata (prior call wasn't necessary an localtimes * style list) */ if (ivar->sqldata) Safefree(ivar->sqldata); /* encode for firebird/interbase, store value*/ switch (dtype) { case SQL_TIMESTAMP: { ISC_TIMESTAMP timestamp; isc_encode_timestamp(×, ×tamp); Newxc(ivar->sqldata, 1, ISC_TIMESTAMP, ISC_SCHAR); if (items >= 10) TIMESTAMP_ADD_FPSECS(×tamp, SvIV(svp[9])); *(ISC_TIMESTAMP *) ivar->sqldata = timestamp; break; } case SQL_TYPE_TIME: { ISC_TIME sql_time; isc_encode_sql_time(×, &sql_time); Newxc(ivar->sqldata, 1, ISC_TIME, ISC_SCHAR); if (items >= 10) TIME_ADD_FPSECS(&sql_time, SvIV(svp[9])); *(ISC_TIME *) ivar->sqldata = sql_time; break; } case SQL_TYPE_DATE: { ISC_DATE sql_date; isc_encode_sql_date(×, &sql_date); Newxc(ivar->sqldata, 1, ISC_DATE, ISC_SCHAR); *(ISC_DATE *) ivar->sqldata = sql_date; break; } } } break; /**********************************************************************/ case SQL_BLOB: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_BLOB\n")); /* SELECT's can't have a blob as in_sqlda. */ if ((imp_sth->type == isc_info_sql_stmt_select) || (imp_sth->type == isc_info_sql_stmt_select_for_upd)) { do_error(sth, 2, "BLOB as an input param for SELECT is not allowed.\n"); retval = FALSE; break; } else /* we have an extra function for this */ retval = ib_blob_write(sth, imp_sth, ivar, value); break; /**********************************************************************/ case SQL_ARRAY: #ifdef ARRAY_SUPPORT !!! NOT IMPLEMENTED YET !!! #endif break; default: break; } DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "exiting ib_fill_isqlda: %d\n", retval)); return retval; } int dbd_bind_ph(SV *sth, imp_sth_t *imp_sth, SV *param, SV *value, IV sql_type, SV *attribs, int is_inout, IV maxlen) { DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_bind_ph\n")); if (SvTYPE(value) > SVt_PVLV) croak("Can't bind a non-scalar value (%s)", neatsvpv(value,0)); /* is_inout for stored procedure is not implemented yet */ if (is_inout) croak("Can't bind ``lvalue'' mode."); if (!imp_sth || !imp_sth->in_sqlda) return FALSE; /* param is the number of parameter: 1, 2, 3, or ... */ if ((int)SvIV(param) > imp_sth->in_sqlda->sqld) return TRUE; DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "Binding parameter: %d\n", (int)SvIV(param))); return ib_fill_isqlda(sth, imp_sth, param, value, sql_type); } int ib_start_transaction(SV *h, imp_dbh_t *imp_dbh) { ISC_STATUS status[ISC_STATUS_LENGTH]; if (imp_dbh->tr) { DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "ib_start_transaction: trans handle already started.\n")); return TRUE; } /* MUST initialized to 0, before it is used */ imp_dbh->tr = 0L; isc_start_transaction(status, &(imp_dbh->tr), 1, &(imp_dbh->db), imp_dbh->tpb_length, imp_dbh->tpb_buffer); if (ib_error_check(h, status)) return FALSE; DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "ib_start_transaction: transaction started.\n")); return TRUE; } int ib_commit_transaction(SV *h, imp_dbh_t *imp_dbh) { ISC_STATUS status[ISC_STATUS_LENGTH]; DBI_TRACE_imp_xxh(imp_dbh, 4, (DBIc_LOGPIO(imp_dbh), "ib_commit_transaction: DBIcf_AutoCommit = %lu, imp_dbh->sth_ddl = %u\n", (long unsigned)DBIc_has(imp_dbh, DBIcf_AutoCommit), imp_dbh->sth_ddl)); if (!imp_dbh->tr) { DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "ib_commit_transaction: transaction already NULL.\n")); /* In case we switched to use different TPB before we actually use */ /* This transaction handle */ imp_dbh->sth_ddl = 0; return TRUE; } /* do commit */ if ((imp_dbh->sth_ddl == 0) && (imp_dbh->soft_commit)) { DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "try isc_commit_retaining\n")); /* commit but don't close transaction */ isc_commit_retaining(status, &(imp_dbh->tr)); if (ib_error_check(h, status)) return FALSE; } else { /* close all open statement handles */ /* if ((imp_dbh->sth_ddl > 0) || !(DBIc_has(imp_dbh, DBIcf_AutoCommit))) */ /* remark: only necessary when we have DDL statement(s) */ if (imp_dbh->sth_ddl > 0) { while (imp_dbh->first_sth != NULL) { /* finish and destroy sth */ dbd_st_finish_internal((SV*)DBIc_MY_H(imp_dbh->first_sth), imp_dbh->first_sth, FALSE); dbd_st_destroy(NULL, imp_dbh->first_sth); } imp_dbh->sth_ddl = 0; } DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "try isc_commit_transaction\n")); /* commit and close transaction (sets handle to NULL) */ isc_commit_transaction(status, &(imp_dbh->tr)); if (ib_error_check(h, status)) return FALSE; imp_dbh->tr = 0L; } DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "ib_commit_transaction succeed.\n")); return TRUE; } int ib_rollback_transaction(SV *h, imp_dbh_t *imp_dbh) { ISC_STATUS status[ISC_STATUS_LENGTH]; DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "ib_rollback_transaction\n")); if (!imp_dbh->tr) { DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "ib_rollback_transaction: transaction already NULL.\n")); imp_dbh->sth_ddl = 0; return TRUE; } /* no isc_rollback_retaining in IB prior to 6 */ if ((imp_dbh->sth_ddl == 0) && (imp_dbh->soft_commit)) { DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "try isc_rollback_retaining\n")); /* rollback but don't close transaction */ isc_rollback_retaining(status, &(imp_dbh->tr)); if (ib_error_check(h, status)) return FALSE; } else { /* close all open statement handles */ if ((imp_dbh->sth_ddl > 0) || !(DBIc_has(imp_dbh, DBIcf_AutoCommit))) { while (imp_dbh->first_sth != NULL) { /* finish and destroy sth */ dbd_st_finish((SV*)DBIc_MY_H(imp_dbh->first_sth), imp_dbh->first_sth); dbd_st_destroy(NULL, imp_dbh->first_sth); } imp_dbh->sth_ddl = 0; } DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "try isc_rollback_transaction\n")); /* rollback and close transaction (sets handle to NULL) */ isc_rollback_transaction(status, &(imp_dbh->tr)); if (ib_error_check(h, status)) return FALSE; imp_dbh->tr = 0L; } DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "ib_rollback_transaction succeed\n")); return TRUE; } int dbd_st_blob_read(SV *sth, imp_sth_t *imp_sth, int field, long offset, long len, SV *destrv, long destoffset) { return FALSE; } int dbd_st_rows(SV* sth, imp_sth_t* imp_sth) { /* spot common mistake of checking $h->rows just after ->execut if (imp_sth->affected < 0 && DBIc_WARN(imp_sth) ) { warn("$h->rows count is incomplete before all rows fetched.\n"); } */ return imp_sth->affected; } long ib_rows(SV *xxh, isc_stmt_handle *h_stmt, char count_type) { ISC_STATUS status[ISC_STATUS_LENGTH]; short l; char count_is; char count_info[1], count_buffer[33]; char *p; long row_count = -1; count_info[0] = isc_info_sql_records; if (isc_dsql_sql_info(status, h_stmt, sizeof(count_info), count_info, sizeof(count_buffer), count_buffer)) { if (ib_error_check(xxh, status)) return -2; /* error */ } for (p = count_buffer + 3; *p != isc_info_end;) { count_is = *p++; l = (short) isc_vax_integer(p, 2); p += 2; row_count = (long) isc_vax_integer(p, l); p += l; if (count_is == count_type) break; } return row_count; } /* how many bytes per character in this charset? information is retrieved from RDB$CHARACTER_SETS the first time it is needed and is cached for later */ unsigned get_charset_bytes_per_char(const ISC_SHORT subtype, SV *sth) { unsigned char *p; D_imp_sth(sth); D_imp_dbh_from_sth; //warn("Q: How many bytes/char in CS %d?", subtype & 0xff); if ( (p = imp_dbh->charset_bytes_per_char) == NULL ) { XSQLDA *out = NULL; XSQLVAR *var; isc_stmt_handle stmt = 0; ISC_STATUS status[ISC_STATUS_LENGTH]; char sql[] = "SELECT RDB$CHARACTER_SET_ID, RDB$BYTES_PER_CHARACTER FROM RDB$CHARACTER_SETS"; int fetch_stat; unsigned i; Newxz( imp_dbh->charset_bytes_per_char, 256, unsigned char ); p = imp_dbh->charset_bytes_per_char; IB_alloc_sqlda(out, 2); if (out == NULL) { do_error(sth, 2, "Failed to allocate out sqlda"); goto cleanup; } isc_dsql_alloc_statement2(status, &(imp_dbh->db), &stmt); if (ib_error_check(sth, status)) goto cleanup; isc_dsql_prepare(status, &(imp_dbh->tr), &stmt, 0, sql, imp_dbh->sqldialect, out); if (ib_error_check(sth, status)) goto cleanup; isc_dsql_describe(status, &stmt, SQLDA_OK_VERSION, out); if (ib_error_check(sth, status)) goto cleanup; for (i=0, var = out->sqlvar; i < out->sqld; i++, var++) { unsigned dtype = (var->sqltype & ~1); switch(dtype) { case SQL_SHORT: Newxc(var->sqldata, 1, ISC_SHORT, void); break; default: do_error(sth, 2, "Unexpected datatype"); goto cleanup; break; } if (var->sqltype & 1) { /* allocate variable to hold NULL status */ Newx(var->sqlind, 1, ISC_SHORT); } } isc_dsql_execute(status, &(imp_dbh->tr), &stmt, 1, NULL); if (ib_error_check(sth, status)) goto cleanup; while ((fetch_stat = isc_dsql_fetch(status, &stmt, 1, out)) == 0) { unsigned char cs_id = (unsigned char) *(ISC_SHORT *)(out->sqlvar[0].sqldata); unsigned char bpc = (unsigned char) *(ISC_SHORT *)(out->sqlvar[1].sqldata); p[cs_id] = bpc; //warn("CS %d has %d bytes/char", cs_id, bpc); } cleanup: isc_dsql_free_statement(status, &stmt, DSQL_drop); Safefree(out->sqlvar[0].sqldata); Safefree(out->sqlvar[0].sqlind); Safefree(out->sqlvar[1].sqldata); Safefree(out->sqlvar[1].sqlind); Safefree(out); } //warn("A: %d", p[subtype & 0xff]); return p[ subtype & 0xff ]; } /* end */