/* $Id: dbdimp.c 14886 2011-06-11 06:35:39Z mjevans $ * * portions Copyright (c) 1994,1995,1996,1997 Tim Bunce * portions Copyright (c) 1997 Thomas K. Wenrich * portions Copyright (c) 1997-2001 Jeff Urlwin * portions Copyright (c) 2007-2011 Martin J. Evans * * You may distribute under the terms of either the GNU General Public * License or the Artistic License, as specified in the Perl README file. * */ /* * NOTES: * * o Trace levels 1 and 2 are reserved for DBI (see DBI::DBD) so don't * use them here in DBIc_TRACE_LEVEL tests. * "Trace Levels" in DBI defines trace levels as: * 0 - Trace disabled. * 1 - Trace DBI method calls returning with results or errors. * 2 - Trace method entry with parameters and returning with results. * 3 - As above, adding some high-level information from the driver * and some internal information from the DBI. * 4 - As above, adding more detailed information from the driver. * 5 to 15 - As above but with more and more obscure information. * * SV Manipulation Functions * http://perl.active-venture.com/pod/perlapi-svfunctions.html * Formatted Printing of IVs, UVs, and NVs * http://perldoc.perl.org/perlguts.html#Formatted-Printing-of-IVs,-UVs,-and-NVs * http://cpansearch.perl.org/src/RURBAN/illguts-0.36/index.html * Internal replacements for standard C library functions: * http://search.cpan.org/~jesse/perl-5.12.1/pod/perlclib.pod * http://search.cpan.org/dist/Devel-PPPort/PPPort.pm * * MS ODBC 64 bit: * http://msdn.microsoft.com/en-us/library/ms716287%28v=vs.85%29.aspx */ #define NEED_newRV_noinc #define NEED_sv_2pv_flags #define NEED_my_snprintf #include "ODBC.h" #if defined(WITH_UNICODE) # include "unicode_helper.h" #endif /* trap iODBC on Unicode builds */ #if defined(WITH_UNICODE) && (defined(_IODBCUNIX_H) || defined(_IODBCEXT_H)) #error DBD::ODBC will not run properly with iODBC in unicode mode as iODBC defines wide characters as being 4 bytes in size #endif /* DBI defines the following but not until 1.617 so we replicate here for now */ /* will remove when DBD::ODBC requires 1.617 or above */ #ifndef DBIf_TRACE_SQL # define DBIf_TRACE_SQL 0x00000100 #endif #ifndef DBIf_TRACE_CON # define DBIf_TRACE_CON 0x00000200 #endif #ifndef DBIf_TRACE_ENC # define DBIf_TRACE_ENC 0x00000400 #endif #ifndef DBIf_TRACE_DBD # define DBIf_TRACE_DBD 0x00000800 #endif #ifndef DBIf_TRACE_TXN # define DBIf_TRACE_TXN 0x000001000 #endif /* combined DBI trace connection and encoding flags with DBD::ODBC ones */ /* Historically DBD::ODBC had 2 flags before they were made DBI ones */ #define UNICODE_TRACING (0x02000000|DBIf_TRACE_ENC|DBIf_TRACE_DBD) #define CONNECTION_TRACING (0x04000000|DBIf_TRACE_CON|DBIf_TRACE_DBD) #define DBD_TRACING DBIf_TRACE_DBD #define TRANSACTION_TRACING (DBIf_TRACE_TXN|DBIf_TRACE_DBD) #define SQL_TRACING (DBIf_TRACE_SQL|DBIf_TRACE_DBD) #define TRACE0(a,b) PerlIO_printf(DBIc_LOGPIO(a), (b)) #define TRACE1(a,b,c) PerlIO_printf(DBIc_LOGPIO(a), (b), (c)) #define TRACE2(a,b,c,d) PerlIO_printf(DBIc_LOGPIO(a), (b), (c), (d)) #define TRACE3(a,b,c,d,e) PerlIO_printf(DBIc_LOGPIO(a), (b), (c), (d), (e)) /* An error return reserved for our internal use and should not clash with any ODBC error codes like SQL_ERROR, SQL_INVALID_HANDLE etc. It is used so we can call dbd_error but indicate there is no point in calling SQLError as the error is internal */ #define DBDODBC_INTERNAL_ERROR -999 static SQLSMALLINT default_parameter_type( char *why, imp_sth_t *imp_sth, phs_t *phs); static int post_connect(SV *dbh, imp_dbh_t *imp_dbh, SV *attr); static int set_odbc_version(SV *dbh, imp_dbh_t *imp_dbh, SV* attr); static const char *S_SqlTypeToString (SWORD sqltype); static const char *S_SqlCTypeToString (SWORD sqltype); static const char *cSqlTables = "SQLTables(%s,%s,%s,%s)"; static const char *cSqlPrimaryKeys = "SQLPrimaryKeys(%s,%s,%s)"; static const char *cSqlStatistics = "SQLStatistics(%s,%s,%s,%d,%d)"; static const char *cSqlForeignKeys = "SQLForeignKeys(%s,%s,%s,%s,%s,%s)"; static const char *cSqlColumns = "SQLColumns(%s,%s,%s,%s)"; static const char *cSqlGetTypeInfo = "SQLGetTypeInfo(%d)"; static SQLRETURN bind_columns(SV *h, imp_sth_t *imp_sth); static void AllODBCErrors(HENV henv, HDBC hdbc, HSTMT hstmt, int output, PerlIO *logfp); static int check_connection_active(SV *h); static int build_results(SV *sth, SV *dbh, RETCODE orc); int dbd_describe(SV *h, imp_sth_t *imp_sth, int more); int dbd_db_login6_sv(SV *dbh, imp_dbh_t *imp_dbh, SV *dbname, SV *uid, SV *pwd, SV *attr); int dbd_db_login6(SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *uid, char *pwd, SV *attr); int dbd_st_finish(SV *sth, imp_sth_t *imp_sth); #define av_sz(av) (av_len(av) + 1) /* for sanity/ease of use with potentially null strings */ #define XXSAFECHAR(p) ((p) ? (p) : "(null)") /* unique value for db attrib that won't conflict with SQL types, just * increment by one if you are adding */ #define ODBC_IGNORE_NAMED_PLACEHOLDERS 0x8332 #define ODBC_DEFAULT_BIND_TYPE 0x8333 #define ODBC_ASYNC_EXEC 0x8334 #define ODBC_ERR_HANDLER 0x8335 #define ODBC_ROWCACHESIZE 0x8336 #define ODBC_ROWSINCACHE 0x8337 #define ODBC_FORCE_REBIND 0x8338 #define ODBC_EXEC_DIRECT 0x8339 #define ODBC_VERSION 0x833A #define ODBC_CURSORTYPE 0x833B #define ODBC_QUERY_TIMEOUT 0x833C #define ODBC_HAS_UNICODE 0x833D #define ODBC_PUTDATA_START 0x833E #define ODBC_OUTCON_STR 0x833F #define ODBC_COLUMN_DISPLAY_SIZE 0x8340 #define ODBC_UTF8_ON 0x8341 #define ODBC_FORCE_BIND_TYPE 0x8342 #define ODBC_OLD_UNICODE 0x8343 #define ODBC_DESCRIBE_PARAMETERS 0x8344 /* This is the bind type for parameters we fall back to if the bind_param method was not given a parameter type and SQLDescribeParam is not supported or failed. It also defines the point we switch from VARCHAR to LONGVARCHAR */ #ifdef WITH_UNICODE # define ODBC_BACKUP_BIND_TYPE_VALUE SQL_WVARCHAR # define ODBC_BACKUP_LONG_BIND_TYPE_VALUE SQL_WLONGVARCHAR # define ODBC_SWITCH_TO_LONGVARCHAR 2000 #else # define ODBC_BACKUP_BIND_TYPE_VALUE SQL_VARCHAR # define ODBC_BACKUP_LONG_BIND_TYPE_VALUE SQL_LONGVARCHAR # define ODBC_SWITCH_TO_LONGVARCHAR 4000 #endif static int rebind_param(SV *sth, imp_sth_t *imp_sth, phs_t *phs); static void get_param_type(SV *sth, imp_sth_t *imp_sth, phs_t *phs); DBISTATE_DECLARE; void dbd_init(dbistate_t *dbistate) { DBIS = dbistate; } static RETCODE odbc_set_query_timeout(SV *h, HSTMT hstmt, UV odbc_timeout) { RETCODE rc; D_imp_xxh(h); if (DBIc_TRACE(imp_xxh, DBD_TRACING, 0, 3)) { TRACE1(imp_xxh, " Set timeout to: %"UVuf"\n", odbc_timeout); } rc = SQLSetStmtAttr(hstmt,(SQLINTEGER)SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER)odbc_timeout,(SQLINTEGER)SQL_IS_INTEGER); if (!SQL_SUCCEEDED(rc)) { /* Some drivers get upset with this so we ignore errors */ if (DBIc_TRACE(imp_xxh, DBD_TRACING, 0, 3)) TRACE1( imp_xxh, " !!Failed to set Statement ATTR Query Timeout to %"UVuf"\n", odbc_timeout); } return rc; } static void odbc_clear_result_set(SV *sth, imp_sth_t *imp_sth) { SV *value; char *key; I32 keylen; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) { TRACE0(imp_sth, "odbc_clear_result_set\n"); } Safefree(imp_sth->fbh); Safefree(imp_sth->ColNames); Safefree(imp_sth->RowBuffer); /* dgood - Yikes! I don't want to go down to this level, */ /* but if I don't, it won't figure out that the */ /* number of columns have changed... */ if (DBIc_FIELDS_AV(imp_sth)) { sv_free((SV*)DBIc_FIELDS_AV(imp_sth)); DBIc_FIELDS_AV(imp_sth) = Nullav; } while ( (value = hv_iternextsv((HV*)SvRV(sth), &key, &keylen)) ) { if (strncmp(key, "NAME_", 5) == 0 || strncmp(key, "TYPE", 4) == 0 || strncmp(key, "PRECISION", 9) == 0 || strncmp(key, "SCALE", 5) == 0 || strncmp(key, "NULLABLE", 8) == 0) { (void)hv_delete((HV*)SvRV(sth), key, keylen, G_DISCARD); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE2(imp_sth, " ODBC_CLEAR_RESULTS '%s' => %s\n", key, neatsvpv(value,0)); } } } imp_sth->fbh = NULL; imp_sth->ColNames = NULL; imp_sth->RowBuffer = NULL; imp_sth->done_desc = 0; } static void odbc_handle_outparams(imp_sth_t *imp_sth, int debug) { int i = (imp_sth->out_params_av) ? AvFILL(imp_sth->out_params_av)+1 : 0; if (debug >= 3) TRACE1(imp_sth, " processing %d output parameters\n", i); while (--i >= 0) { phs_t *phs = (phs_t*)(void*)SvPVX(AvARRAY(imp_sth->out_params_av)[i]); SV *sv = phs->sv; if (debug >= 8) { TRACE2(imp_sth, " outparam %s, length:%ld\n", phs->name, (long)phs->strlen_or_ind); } /* phs->strlen_or_ind has been updated by ODBC to hold the length * of the result */ if (phs->strlen_or_ind != SQL_NULL_DATA) { /* * When ODBC fills an output parameter buffer, the size of the * data that were available is written into the memory location * provided by strlen_or_ind pointer argument during the * SQLBindParameter() call. * * If the number of bytes available exceeds the size of the output * buffer, ODBC will truncate the data such that it fits in the * available buffer. However, the strlen_or_ind will still reflect * the size of the data before it was truncated. * * This fact provides us a way to detect truncation on this particular * output parameter. Otherwise, the only way to detect truncation is * through a follow-up to a SQL_SUCCESS_WITH_INFO result. Such a call * cannot return enough information to state exactly where the * truncation occurred. */ SvPOK_only(sv); /* string, disable other OK bits */ if (phs->strlen_or_ind > phs->maxlen) { /* out param truncated */ SvCUR_set(sv, phs->maxlen); *SvEND(sv) = '\0'; /* null terminate */ if (debug >= 2) { PerlIO_printf( DBIc_LOGPIO(imp_sth), " outparam %s = '%s'\t(TRUNCATED from %ld to %ld)\n", phs->name, SvPV_nolen(sv), (long)phs->strlen_or_ind, (long)phs->maxlen); } } else { /* no truncation occurred */ SvCUR_set(sv, phs->strlen_or_ind); /* new length */ *SvEND(sv) = '\0'; /* null terminate */ if (phs->strlen_or_ind == phs->maxlen && (phs->sql_type == SQL_NUMERIC || phs->sql_type == SQL_DECIMAL || phs->sql_type == SQL_INTEGER || phs->sql_type == SQL_SMALLINT || phs->sql_type == SQL_FLOAT || phs->sql_type == SQL_REAL || phs->sql_type == SQL_DOUBLE)) { /* * fix up for oracle, which leaves the buffer at the size * requested, but only returns a few characters. The * intent is to truncate down to the actual number of * characters necessary. Need to find the first null * byte and set the length there. */ char *pstart = SvPV_nolen(sv); char *p = pstart; while (*p != '\0') { p++; } if (debug >= 2) { PerlIO_printf( DBIc_LOGPIO(imp_sth), " outparam %s = '%s'\t(len %ld), is numeric end" " of buffer = %d\n", phs->name, SvPV(sv,PL_na), (long)phs->strlen_or_ind, p - pstart); } SvCUR_set(sv, p - pstart); } } } else { /* is NULL */ if (debug >= 2) TRACE1(imp_sth, " outparam %s = undef (NULL)\n", phs->name); (void)SvOK_off(phs->sv); } } } static int build_results(SV *sth, SV *dbh, RETCODE orc) { RETCODE rc; D_imp_dbh(dbh); D_imp_sth(sth); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE2(imp_sth, " build_results sql %p\n\t%s\n", imp_sth->hstmt, imp_sth->statement); /* init sth pointers */ imp_sth->fbh = NULL; imp_sth->ColNames = NULL; imp_sth->RowBuffer = NULL; imp_sth->RowCount = -1; imp_sth->odbc_column_display_size = imp_dbh->odbc_column_display_size; imp_sth->odbc_utf8_on = imp_dbh->odbc_utf8_on; if (!dbd_describe(sth, imp_sth, 0)) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) { TRACE0(imp_sth, " !!dbd_describe failed, build_results...!\n"); } SQLFreeHandle(SQL_HANDLE_STMT, imp_sth->hstmt); imp_sth->hstmt = SQL_NULL_HSTMT; return 0; /* dbd_describe already called dbd_error() */ } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) { TRACE0(imp_sth, " dbd_describe build_results #2...!\n"); } /* TO_DO why is dbd_describe called again? */ if (dbd_describe(sth, imp_sth, 0) <= 0) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) { TRACE0(imp_sth, " dbd_describe build_results #3...!\n"); } return 0; } DBIc_IMPSET_on(imp_sth); if (orc != SQL_NO_DATA) { imp_sth->RowCount = -1; rc = SQLRowCount(imp_sth->hstmt, &imp_sth->RowCount); dbd_error(sth, rc, "build_results/SQLRowCount"); if (rc != SQL_SUCCESS) return -1; } else { imp_sth->RowCount = 0; } DBIc_ACTIVE_on(imp_sth); /* XXX should only set for select ? */ return 1; } int odbc_discon_all(SV *drh, imp_drh_t *imp_drh) { /* The disconnect_all concept is flawed and needs more work */ if (!PL_dirty && !SvTRUE(get_sv("DBI::PERL_ENDING",0))) { DBIh_SET_ERR_CHAR(drh, (imp_xxh_t*)imp_drh, Nullch, 1, "disconnect_all not implemented", Nullch, Nullch); return FALSE; } return FALSE; } /* error : <=(-2), ok row count : >=0, unknown count : (-1) */ int dbd_db_execdirect(SV *dbh, SV *statement ) { D_imp_dbh(dbh); SQLRETURN ret; /* SQLxxx return value */ SQLLEN rows; SQLHSTMT stmt; int dbh_active; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; ret = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &stmt ); if (!SQL_SUCCEEDED(ret)) { dbd_error( dbh, ret, "Statement allocation error" ); return(-2); } /* if odbc_query_timeout has been set, set it in the driver */ if (imp_dbh->odbc_query_timeout != -1) { ret = odbc_set_query_timeout(dbh, stmt, imp_dbh->odbc_query_timeout); if (!SQL_SUCCEEDED(ret)) { dbd_error(dbh, ret, "execdirect set_query_timeout"); } /* don't fail if the query timeout can't be set. */ } if (DBIc_TRACE(imp_dbh, SQL_TRACING, 0, 3)) { TRACE1(imp_dbh, " SQLExecDirect %s\n", SvPV_nolen(statement)); } #ifdef WITH_UNICODE if (SvOK(statement) && DO_UTF8(statement)) { SQLWCHAR *wsql; STRLEN wsql_len; SV *sql_copy; if (DBIc_TRACE(imp_dbh, UNICODE_TRACING, 0, 0)) /* odbcunicode */ TRACE0(imp_dbh, " Processing utf8 sql in unicode mode\n"); sql_copy = sv_mortalcopy(statement); SV_toWCHAR(sql_copy); wsql = (SQLWCHAR *)SvPV(sql_copy, wsql_len); ret = SQLExecDirectW(stmt, wsql, wsql_len / sizeof(SQLWCHAR)); } else { if (DBIc_TRACE(imp_dbh, UNICODE_TRACING, 0, 0)) /* odbcunicode */ TRACE0(imp_dbh, " Processing non utf8 sql in unicode mode\n"); ret = SQLExecDirect(stmt, (SQLCHAR *)SvPV_nolen(statement), SQL_NTS); } #else if (DBIc_TRACE(imp_dbh, UNICODE_TRACING, 0, 0)) /* odbcunicode */ TRACE0(imp_dbh, " Processing sql in non-unicode mode\n"); ret = SQLExecDirect(stmt, (SQLCHAR *)SvPV_nolen(statement), SQL_NTS); #endif if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " SQLExecDirect = %d\n", ret); if (!SQL_SUCCEEDED(ret) && ret != SQL_NO_DATA) { dbd_error2(dbh, ret, "Execute immediate failed", imp_dbh->henv, imp_dbh->hdbc, stmt ); rows = -2; /* error */ } else { if (ret == SQL_NO_DATA) { rows = 0; } else if (ret != SQL_SUCCESS) { dbd_error2(dbh, ret, "Execute immediate success with info", imp_dbh->henv, imp_dbh->hdbc, stmt ); } ret = SQLRowCount(stmt, &rows); if (!SQL_SUCCEEDED(ret)) { dbd_error( dbh, ret, "SQLRowCount failed" ); rows = -1; } } ret = SQLFreeHandle(SQL_HANDLE_STMT,stmt); if (!SQL_SUCCEEDED(ret)) { dbd_error2(dbh, ret, "Statement destruction error", imp_dbh->henv, imp_dbh->hdbc, stmt); } return (int)rows; } void dbd_db_destroy(SV *dbh, imp_dbh_t *imp_dbh) { if (DBIc_ACTIVE(imp_dbh)) dbd_db_disconnect(dbh, imp_dbh); /* Nothing in imp_dbh to be freed */ DBIc_IMPSET_off(imp_dbh); if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 8)) TRACE0(imp_dbh, " DBD::ODBC Disconnected!\n"); } /* * quick dumb function to handle case insensitivity for DSN= or DRIVER= * in DSN...note this is becuase strncmpi is not available on all * platforms using that name (VC++, Debian, etc most notably). * Note, also, strupr doesn't seem to have a standard name, either... */ int dsnHasDriverOrDSN(char *dsn) { char upper_dsn[512]; char *cp = upper_dsn; strncpy(upper_dsn, dsn, sizeof(upper_dsn)-1); upper_dsn[sizeof(upper_dsn)-1] = '\0'; while (*cp != '\0') { *cp++ = toupper(*cp); } return (strncmp(upper_dsn, "DSN=", 4) == 0 || strncmp(upper_dsn, "DRIVER=", 7) == 0); } int dsnHasUIDorPWD(char *dsn) { char upper_dsn[512]; char *cp = upper_dsn; strncpy(upper_dsn, dsn, sizeof(upper_dsn)-1); upper_dsn[sizeof(upper_dsn)-1] = '\0'; while (*cp != '\0') { *cp++ = toupper(*cp); } return (strstr(upper_dsn, "UID=") != 0 || strstr(upper_dsn, "PWD=") != 0); } /************************************************************************/ /* */ /* dbd_db_login */ /* ============ */ /* */ /* NOTE: This is the old 5 argument version with no attribs */ /* */ /************************************************************************/ 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); } /************************************************************************/ /* */ /* dbd_db_login6_sv */ /* ================ */ /* */ /* This API was introduced in DBI after 1.607 (subversion revision */ /* 11723) and is the same as dbd_db_login6 except the connection */ /* strings are SVs so we can detect unicode strings and call */ /* SQLDriveConnectW. */ /* */ /************************************************************************/ int dbd_db_login6_sv( SV *dbh, imp_dbh_t *imp_dbh, SV *dbname, SV *uid, SV *pwd, SV *attr) { #ifndef WITH_UNICODE if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE0(imp_dbh, "non-Unicode login6_sv\n"); return dbd_db_login6(dbh, imp_dbh, SvPV_nolen(dbname), (SvOK(uid) ? SvPV_nolen(uid) : NULL), (SvOK(pwd) ? SvPV_nolen(pwd) : NULL), attr); #else D_imp_drh_from_dbh; SQLRETURN rc; SV *wconstr; /* copy of connection string in wide chrs */ /* decoded connection string in wide characters and its length to work around an issue in older unixODBCs */ SQLWCHAR dc_constr[512]; STRLEN dc_constr_len; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) { TRACE2(imp_dbh, "Unicode login6 dbname=%s, uid=%s, pwd=xxxxx\n", SvPV_nolen(dbname), neatsvpv(uid, 0)); } imp_dbh->out_connect_string = Nullsv; if (!imp_drh->connects) { rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &imp_drh->henv); dbd_error(dbh, rc, "db_login6_sv/SQLAllocHandle(env)"); if (!SQL_SUCCEEDED(rc)) return 0; if (set_odbc_version(dbh, imp_dbh, attr) != 1) return 0; } imp_dbh->henv = imp_drh->henv; /* needed for dbd_error */ rc = SQLAllocHandle(SQL_HANDLE_DBC, imp_drh->henv, &imp_dbh->hdbc); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_login6_sv/SQLAllocHandle(dbc)"); if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; imp_dbh->henv = SQL_NULL_HENV; /* needed for dbd_error */ } return 0; } /* If the connection string is too long to pass to SQLConnect or it contains DSN or DRIVER, we've little choice but to call SQLDriverConnect and need to tag the uid/pwd on the end of the connection string (unless they already exist). */ if ((SvCUR(dbname) > SQL_MAX_DSN_LENGTH || dsnHasDriverOrDSN(SvPV_nolen(dbname))) && !dsnHasUIDorPWD(SvPV_nolen(dbname))) { if (SvOK(uid)) { sv_catpv(dbname, ";UID="); sv_catsv(dbname, uid); } if (SvOK(pwd)) { sv_catpv(dbname, ";PWD="); sv_catsv(dbname, pwd); } sv_catpv(dbname, ";"); /*sv_catpvf(dbname, ";UID=%s;PWD=%s;", SvPV_nolen(uid), SvPV_nolen(pwd));*/ if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "Now using dbname = %s\n", SvPV_nolen(dbname)); } if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE2(imp_dbh, " SQLDriverConnect '%s', '%s', 'xxxx'\n", SvPV_nolen(dbname), neatsvpv(uid, 0)); wconstr = sv_mortalcopy(dbname); utf8sv_to_wcharsv(wconstr); /* The following is to work around a bug in SQLDriverConnectW in unixODBC which in at least 2.2.11 (and probably up to 2.2.13 official release [not pre-release]) core dumps if the wide connection string does not end in a 0 (even though it should not matter as we pass the length. */ { char *p; memset(dc_constr, '\0', sizeof(dc_constr)); p = SvPV(wconstr, dc_constr_len); if (dc_constr_len > (sizeof(dc_constr) - 2)) { croak("Cannot process connection string - too long"); } memcpy(dc_constr, p, dc_constr_len); } { SQLWCHAR wout_str[512]; SQLSMALLINT wout_str_len; rc = SQLDriverConnectW(imp_dbh->hdbc, 0, /* no hwnd */ dc_constr, (SQLSMALLINT)(dc_constr_len / sizeof(SQLWCHAR)), wout_str, sizeof(wout_str) / sizeof(wout_str[0]), &wout_str_len, SQL_DRIVER_NOPROMPT); if (SQL_SUCCEEDED(rc)) { imp_dbh->out_connect_string = sv_newwvn(wout_str, wout_str_len); if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "Out connection string: %s\n", SvPV_nolen(imp_dbh->out_connect_string)); } } if (!SQL_SUCCEEDED(rc)) { SV *wuid, *wpwd; SQLWCHAR *wuidp, *wpwdp; SQLSMALLINT uid_len, pwd_len; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE0(imp_dbh, " SQLDriverConnectW failed:\n"); /* * Added code for DBD::ODBC 0.39 to help return a better * error code in the case where the user is using a * DSN-less connection and the dbname doesn't look like a * true DSN. */ if (SvCUR(dbname) > SQL_MAX_DSN_LENGTH || dsnHasDriverOrDSN(SvPV_nolen(dbname))) { /* must be DSN= or some "direct" connection attributes, * probably best to error here and give the user a real * error code because the SQLConnect call could hide the * real problem. */ dbd_error(dbh, rc, "db_login6sv/SQLDriverConnectW"); SQLFreeHandle(SQL_HANDLE_DBC, imp_dbh->hdbc); if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; imp_dbh->henv = SQL_NULL_HENV; } return 0; } /* ok, the DSN is short, so let's try to use it to connect * and quietly take all error messages */ AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, 0, DBIc_LOGPIO(imp_dbh)); if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE2(imp_dbh, " SQLConnect '%s', '%s'\n", neatsvpv(dbname, 0), neatsvpv(uid, 0)); wconstr = sv_mortalcopy(dbname); utf8sv_to_wcharsv(wconstr); if (SvOK(uid)) { wuid = sv_mortalcopy(uid); utf8sv_to_wcharsv(wuid); wuidp = (SQLWCHAR *)SvPV_nolen(wuid); uid_len = SvCUR(wuid) / sizeof(SQLWCHAR); } else { wuidp = NULL; uid_len = 0; } if (SvOK(pwd)) { wpwd = sv_mortalcopy(pwd); utf8sv_to_wcharsv(wpwd); wpwdp = (SQLWCHAR *)SvPV_nolen(wpwd); pwd_len = SvCUR(wpwd) / sizeof(SQLWCHAR); } else { wpwdp = NULL; pwd_len = 0; } rc = SQLConnectW(imp_dbh->hdbc, (SQLWCHAR *)SvPV_nolen(wconstr), (SQLSMALLINT)(SvCUR(wconstr) / sizeof(SQLWCHAR)), wuidp, uid_len, wpwdp, pwd_len); } if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_login6sv/SQLConnectW"); SQLFreeHandle(SQL_HANDLE_DBC, imp_dbh->hdbc); imp_dbh->hdbc = SQL_NULL_HDBC; if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; imp_dbh->henv = SQL_NULL_HENV; } return 0; } else if (rc == SQL_SUCCESS_WITH_INFO) { dbd_error(dbh, rc, "db_login6sv/SQLConnectW"); } if (post_connect(dbh, imp_dbh, attr) != 1) return 0; imp_drh->connects++; DBIc_IMPSET_on(imp_dbh); /* imp_dbh set up now */ DBIc_ACTIVE_on(imp_dbh); /* call disconnect before freeing */ return 1; #endif /* WITH_UNICODE */ } /************************************************************************/ /* */ /* dbd_db_login6 */ /* ============= */ /* */ /* A newer version of the dbd_db_login API with the additional attr as */ /* the sixth argument. Once everyone upgrades to at least */ /* DBI 1.60X (where X > 7) this API won't get called anymore since */ /* dbd_db_login6_sv will be favoured. */ /* */ /* NOTE: I had hoped to make dbd_db_login6_sv support Unicode and */ /* dbd_db_login6 to not support Unicode but as no one (except me) has */ /* a DBI which supports dbd_db_login6_sv and unixODBC REQUIRES us to */ /* call SQLDriverConnectW if we are going to call other SQLXXXW */ /* functions later I've got no choice but to convert the ASCII strings */ /* passed to dbd_db_login6 to wide characters when DBD::ODBC is built */ /* for Unicode. */ /* */ /************************************************************************/ int dbd_db_login6( SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *uid, char *pwd, SV *attr) { D_imp_drh_from_dbh; RETCODE rc; char dbname_local[512]; #ifdef WITH_UNICODE SQLWCHAR wconstr[512]; STRLEN wconstr_len; unsigned int i; #endif if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE0(imp_dbh, "dbd_db_login6\n"); if (!imp_drh->connects) { rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &imp_drh->henv); dbd_error(dbh, rc, "db_login6/SQLAllocHandle(env)"); if (!SQL_SUCCEEDED(rc)) return 0; if (set_odbc_version(dbh, imp_dbh, attr) != 1) return 0; } imp_dbh->henv = imp_drh->henv; /* needed for dbd_error */ imp_dbh->out_connect_string = NULL; rc = SQLAllocHandle(SQL_HANDLE_DBC, imp_drh->henv, &imp_dbh->hdbc); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_login6/SQLAllocHandle(dbc)"); if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; imp_dbh->henv = SQL_NULL_HENV; /* needed for dbd_error */ } return 0; } #ifndef DBD_ODBC_NO_SQLDRIVERCONNECT /* If the connection string is too long to pass to SQLConnect or it contains DSN or DRIVER, we've little choice to but to call SQLDriverConnect and need to tag the uid/pwd on the end of the connection string (unless they already exist). */ if ((strlen(dbname) > SQL_MAX_DSN_LENGTH || dsnHasDriverOrDSN(dbname)) && !dsnHasUIDorPWD(dbname)) { if ((strlen(dbname) + (uid ? strlen(uid) : 0) + (pwd ? strlen(pwd) : 0) + 12) > sizeof(dbname_local)) { croak("Connection string too long"); } strcpy(dbname_local, dbname); if (uid) { strcat(dbname_local, ";UID="); strcat(dbname_local, uid); } if (pwd) { strcat(dbname_local, ";PWD="); strcat(dbname_local, pwd); } dbname = dbname_local; } if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE2(imp_dbh, " SQLDriverConnect '%s', '%s', 'xxxx'\n", dbname, (uid ? uid : "")); # ifdef WITH_UNICODE if (strlen(dbname) > (sizeof(wconstr) / sizeof(wconstr[0]))) { croak("Connection string too big to convert to wide characters"); } /* The following is a massive simplification assuming only 7-bit ASCII is ever passed to dbd_db_login6 */ for (i = 0; i < strlen(dbname); i++) { wconstr[i] = dbname[i]; } wconstr[i] = 0; wconstr_len = i; { SQLWCHAR wout_str[512]; SQLSMALLINT wout_str_len; rc = SQLDriverConnectW(imp_dbh->hdbc, 0, /* no hwnd */ wconstr, (SQLSMALLINT)wconstr_len, wout_str, sizeof(wout_str) / sizeof(wout_str[0]), &wout_str_len, SQL_DRIVER_NOPROMPT); if (SQL_SUCCEEDED(rc)) { imp_dbh->out_connect_string = sv_newwvn(wout_str, wout_str_len); if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "Out connection string: %s\n", SvPV_nolen(imp_dbh->out_connect_string)); } } # else /* WITH_UNICODE */ { char out_str[512]; SQLSMALLINT out_str_len; /* Work around a bug in mdbtools where the out connection string length can sometimes be unset. We set it to a ridiculous value and if it remains we know mdbtools did not return it. */ out_str_len = 9999; rc = SQLDriverConnect(imp_dbh->hdbc, 0, /* no hwnd */ dbname, (SQLSMALLINT)strlen(dbname), out_str, sizeof(out_str), &out_str_len, SQL_DRIVER_NOPROMPT); if (SQL_SUCCEEDED(rc)) { if (out_str_len == 9999) { imp_dbh->out_connect_string = newSVpv("", 0); } else { imp_dbh->out_connect_string = newSVpv(out_str, out_str_len); } if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "Out connection string: %s\n", SvPV_nolen(imp_dbh->out_connect_string)); } } # endif /* WITH_UNICODE */ #else /* if we are using something that can not handle SQLDriverconnect, * then set rc to a not OK state and we'll fall back on SQLConnect */ rc = SQL_ERROR; #endif if (!SQL_SUCCEEDED(rc)) { if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 4)) { #ifdef DBD_ODBC_NO_SQLDRIVERCONNECT TRACE0(imp_dbh, " !SQLDriverConnect unsupported.\n"); #else TRACE0(imp_dbh, " SQLDriverConnect failed:\n"); #endif } #ifndef DBD_ODBC_NO_SQLDRIVERCONNECT /* * Added code for DBD::ODBC 0.39 to help return a better * error code in the case where the user is using a * DSN-less connection and the dbname doesn't look like a * true DSN. */ if (strlen(dbname) > SQL_MAX_DSN_LENGTH || dsnHasDriverOrDSN(dbname)) { /* must be DSN= or some "direct" connection attributes, * probably best to error here and give the user a real * error code because the SQLConnect call could hide the * real problem. */ dbd_error(dbh, rc, "db_login/SQLConnect"); SQLFreeHandle(SQL_HANDLE_DBC, imp_dbh->hdbc); if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; imp_dbh->henv = SQL_NULL_HENV; } return 0; } /* ok, the DSN is short, so let's try to use it to connect * and quietly take all error messages */ AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, 0, DBIc_LOGPIO(imp_dbh)); #endif /* DriverConnect supported */ if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE2(imp_dbh, " SQLConnect '%s', '%s'\n", dbname, (uid ? uid : "")); #ifdef WITH_UNICODE { SQLWCHAR wuid[100], wpwd[100]; SQLSMALLINT uid_len, pwd_len; SQLWCHAR *wuidp, *wpwdp; if (uid) { for (i = 0; i < strlen(uid); i++) { wuid[i] = uid[i]; } wuid[i] = 0; wuidp = wuid; uid_len = strlen(uid); } else { wuidp = NULL; uid_len = 0; } if (pwd) { for (i = 0; i < strlen(pwd); i++) { wpwd[i] = pwd[i]; } wpwd[i] = 0; wpwdp = wpwd; pwd_len = strlen(pwd); } else { wpwdp = NULL; pwd_len = 0; } for (i = 0; i < strlen(dbname); i++) { wconstr[i] = dbname[i]; } wconstr[i] = 0; wconstr_len = i; rc = SQLConnectW(imp_dbh->hdbc, wconstr, wconstr_len, wuidp, uid_len, wpwdp, pwd_len); } #else rc = SQLConnect(imp_dbh->hdbc, dbname, (SQLSMALLINT)strlen(dbname), uid, (SQLSMALLINT)(uid ? strlen(uid) : 0), pwd, (SQLSMALLINT)(pwd ? strlen(pwd) : 0)); #endif } if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_login6/SQLConnect"); SQLFreeHandle(SQL_HANDLE_DBC, imp_dbh->hdbc); if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; imp_dbh->henv = SQL_NULL_HENV; } return 0; } else if (rc == SQL_SUCCESS_WITH_INFO) { dbd_error(dbh, rc, "db_login6/SQLConnect"); } if (post_connect(dbh, imp_dbh, attr) != 1) return 0; imp_drh->connects++; DBIc_IMPSET_on(imp_dbh); /* imp_dbh set up now */ DBIc_ACTIVE_on(imp_dbh); /* call disconnect before freeing */ return 1; } int dbd_db_disconnect(SV *dbh, imp_dbh_t *imp_dbh) { RETCODE rc; D_imp_xxh(dbh); D_imp_drh_from_dbh; SQLUINTEGER autoCommit = SQL_AUTOCOMMIT_OFF; /* We assume that disconnect will always work */ /* since most errors imply already disconnected. */ DBIc_ACTIVE_off(imp_dbh); if (imp_dbh->out_connect_string) { SvREFCNT_dec(imp_dbh->out_connect_string); } rc = SQLGetConnectAttr( imp_dbh->hdbc, SQL_ATTR_AUTOCOMMIT, &autoCommit, SQL_IS_UINTEGER, 0); if (!SQL_SUCCEEDED(rc)) { /* quietly handle a problem with SQLGetConnectAttr() */ AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 4), DBIc_LOGPIO(imp_dbh)); } rc = SQLDisconnect(imp_dbh->hdbc); if (!SQL_SUCCEEDED(rc)) { char state[SQL_SQLSTATE_SIZE+1]; SQLRETURN r; r = SQLGetDiagField(SQL_HANDLE_DBC, imp_dbh->hdbc, 1, SQL_DIAG_SQLSTATE, (SQLCHAR *)state, sizeof(state), NULL); if (strcmp(state, "25000") == 0) { if (DBIc_TRACE(imp_dbh, TRANSACTION_TRACING, 0, 3)) TRACE0(imp_dbh, "SQLDisconnect, Transaction in progress\n"); DBIh_SET_ERR_CHAR( dbh, imp_xxh, "0" /* warning state */, 1, "Disconnect with transaction in progress - rolling back", state, Nullch); r = dbd_db_rollback(dbh, imp_dbh); rc = SQLDisconnect(imp_dbh->hdbc); } if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_disconnect/SQLDisconnect"); /* if disconnect fails, fall through. Probably not disconnected */ } } if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "SQLDisconnect=%d\n", rc); SQLFreeHandle(SQL_HANDLE_DBC, imp_dbh->hdbc); imp_dbh->hdbc = SQL_NULL_HDBC; imp_drh->connects--; strcpy(imp_dbh->odbc_dbms_name, "disconnect"); if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; imp_dbh->henv = SQL_NULL_HENV; } /* We don't free imp_dbh since a reference still exists */ /* The DESTROY method is the only one to 'free' memory. */ /* Note that statement objects may still exist for this dbh! */ return 1; } int dbd_db_commit(SV *dbh, imp_dbh_t *imp_dbh) { RETCODE rc; rc = SQLEndTran(SQL_HANDLE_DBC, imp_dbh->hdbc, SQL_COMMIT); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_commit/SQLEndTran"); return 0; } /* support for DBI 1.20 begin_work */ if (DBIc_has(imp_dbh, DBIcf_BegunWork)) { /* reset autocommit */ rc = SQLSetConnectAttr( imp_dbh->hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0); DBIc_off(imp_dbh,DBIcf_BegunWork); } return 1; } int dbd_db_rollback(SV *dbh, imp_dbh_t *imp_dbh) { RETCODE rc; rc = SQLEndTran(SQL_HANDLE_DBC, imp_dbh->hdbc, SQL_ROLLBACK); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_rollback/SQLEndTran"); return 0; } /* support for DBI 1.20 begin_work */ if (DBIc_has(imp_dbh, DBIcf_BegunWork)) { /* reset autocommit */ rc = SQLSetConnectAttr( imp_dbh->hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0); DBIc_off(imp_dbh,DBIcf_BegunWork); } return 1; } void dbd_error2( SV *h, RETCODE err_rc, char *what, HENV henv, HDBC hdbc, HSTMT hstmt) { D_imp_xxh(h); int error_found = 0; /* * It's a shame to have to add all this stuff with imp_dbh and * imp_sth, but imp_dbh is needed to get the odbc_err_handler * and imp_sth is needed to get imp_dbh. */ struct imp_dbh_st *imp_dbh = NULL; struct imp_sth_st *imp_sth = NULL; if (err_rc == SQL_SUCCESS) return; if (DBIc_TRACE(imp_xxh, DBD_TRACING, 0, 4) && (err_rc != SQL_SUCCESS)) { PerlIO_printf( DBIc_LOGPIO(imp_xxh), " !!dbd_error2(err_rc=%d, what=%s, handles=(%p,%p,%p)\n", err_rc, (what ? what : "null"), henv, hdbc, hstmt); } switch(DBIc_TYPE(imp_xxh)) { case DBIt_ST: imp_sth = (struct imp_sth_st *)(imp_xxh); imp_dbh = (struct imp_dbh_st *)(DBIc_PARENT_COM(imp_sth)); break; case DBIt_DB: imp_dbh = (struct imp_dbh_st *)(imp_xxh); break; default: croak("panic: dbd_error2 on bad handle type"); } while(henv != SQL_NULL_HENV) { SQLCHAR sqlstate[SQL_SQLSTATE_SIZE+1]; /* * ODBC spec says ErrorMsg must not be greater than * SQL_MAX_MESSAGE_LENGTH but we concatenate a little * on the end later (e.g. sql state) so make room for more. */ SQLCHAR ErrorMsg[SQL_MAX_MESSAGE_LENGTH+512]; SQLSMALLINT ErrorMsgLen; SQLINTEGER NativeError; RETCODE rc = 0; /* TBD: 3.0 update */ /* It is important we check for DBDODBC_INTERNAL_ERROR first so if we issue an internal error AND there are ODBC diagnostics, ours come first */ while(err_rc == DBDODBC_INTERNAL_ERROR || SQL_SUCCEEDED(rc=SQLError( henv, hdbc, hstmt, sqlstate, &NativeError, ErrorMsg, sizeof(ErrorMsg)-1, &ErrorMsgLen))) { error_found = 1; if (err_rc == DBDODBC_INTERNAL_ERROR) { strcpy(ErrorMsg, what); strcpy(sqlstate, "HY000"); NativeError = 1; err_rc = SQL_ERROR; } else { ErrorMsg[ErrorMsgLen] = '\0'; sqlstate[SQL_SQLSTATE_SIZE] = '\0'; } if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3)) { PerlIO_printf(DBIc_LOGPIO(imp_dbh), " !SQLError(%p,%p,%p) = " "(%s, %ld, %s)\n", henv, hdbc, hstmt, sqlstate, NativeError, ErrorMsg); } /* * If there's an error handler, run it and see what it returns... * (lifted from DBD:Sybase 0.21) */ if(imp_dbh->odbc_err_handler) { dSP; int retval, count; ENTER; SAVETMPS; PUSHMARK(sp); if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3)) TRACE0(imp_dbh, " Calling error handler\n"); /* * Here are the args to the error handler routine: * 1. sqlstate (string) * 2. ErrorMsg (string) * 3. NativeError (integer) * That's it for now... */ XPUSHs(sv_2mortal(newSVpv(sqlstate, 0))); XPUSHs(sv_2mortal(newSVpv(ErrorMsg, 0))); XPUSHs(sv_2mortal(newSViv(NativeError))); XPUSHs(sv_2mortal(newSViv(err_rc))); PUTBACK; if((count = call_sv(imp_dbh->odbc_err_handler, G_SCALAR)) != 1) croak("An error handler can't return a LIST."); SPAGAIN; retval = POPi; PUTBACK; FREETMPS; LEAVE; /* If the called sub returns 0 then ignore this error */ if(retval == 0) { if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3)) TRACE0(imp_dbh, " Handler caused error to be ignored\n"); continue; } } strcat(ErrorMsg, " (SQL-"); strcat(ErrorMsg, sqlstate); strcat(ErrorMsg, ")"); if (SQL_SUCCEEDED(err_rc)) { DBIh_SET_ERR_CHAR(h, imp_xxh, "" /* information state */, 1, ErrorMsg, sqlstate, Nullch); } else { DBIh_SET_ERR_CHAR(h, imp_xxh, Nullch, 1, ErrorMsg, sqlstate, Nullch); } continue; } if (rc != SQL_NO_DATA_FOUND) { /* should never happen */ if (DBIc_TRACE(imp_xxh, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " !!SQLError returned %d unexpectedly.\n", rc); DBIh_SET_ERR_CHAR( h, imp_xxh, Nullch, 1, " Unable to fetch information about the error", "IM008", Nullch); } /* climb up the tree each time round the loop */ if (hstmt != SQL_NULL_HSTMT) hstmt = SQL_NULL_HSTMT; else if (hdbc != SQL_NULL_HDBC) hdbc = SQL_NULL_HDBC; else henv = SQL_NULL_HENV; /* done the top */ } /* some broken drivers may return an error and then not provide an error message */ if (!error_found && (err_rc != SQL_NO_DATA_FOUND)) { if (DBIc_TRACE(imp_xxh, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " ** No error found %d **\n", err_rc); DBIh_SET_ERR_CHAR( h, imp_xxh, Nullch, 1, " Unable to fetch information about the error", "IM008", Nullch); } } /*------------------------------------------------------------ empties entire ODBC error queue. ------------------------------------------------------------*/ void dbd_error(SV *h, RETCODE err_rc, char *what) { D_imp_xxh(h); struct imp_dbh_st *imp_dbh = NULL; struct imp_sth_st *imp_sth = NULL; HSTMT hstmt = SQL_NULL_HSTMT; switch(DBIc_TYPE(imp_xxh)) { case DBIt_ST: imp_sth = (struct imp_sth_st *)(imp_xxh); imp_dbh = (struct imp_dbh_st *)(DBIc_PARENT_COM(imp_sth)); hstmt = imp_sth->hstmt; break; case DBIt_DB: imp_dbh = (struct imp_dbh_st *)(imp_xxh); break; default: croak("panic: dbd_error on bad handle type"); } /* * If status is SQL_SUCCESS, there's no error, so we can just return. * There may be status or other non-error messsages though. * We want those messages if the debug level is set to at least 3. * If an error handler is installed, let it decide what messages * should or shouldn't be reported. */ if ((err_rc == SQL_SUCCESS) && !DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3) && !imp_dbh->odbc_err_handler) return; dbd_error2(h, err_rc, what, imp_dbh->henv, imp_dbh->hdbc, hstmt); } /*------------------------------------------------------------------------- dbd_preparse: - scan for placeholders (? and :xx style) and convert them to ?. - builds translation table to convert positional parameters of the execute() call to :nn type placeholders. We need two data structures to translate this stuff: - a hash to convert positional parameters to placeholders - an array, representing the actual '?' query parameters. %param = (name1=>plh1, name2=>plh2, ..., name_n=>plh_n) # @qm_param = (\$param{'name1'}, \$param{'name2'}, ...) -------------------------------------------------------------------------*/ void dbd_preparse(imp_sth_t *imp_sth, char *statement) { enum STATES {DEFAULT, LITERAL, COMMENT, LINE_COMMENT}; enum STATES state = DEFAULT; enum STYLES { STYLE_NONE, /* no style 0 */ STYLE_NUMBER, /* :N 1 */ STYLE_NAME, /* :name 2*/ STYLE_NORMAL /* ? 3 */ }; char literal_ch = '\0'; char *src, *dest; /* input and output SQL */ phs_t phs_tpl; int idx=0; /* parameter number */ enum STYLES style = STYLE_NONE; /* type of parameter */ enum STYLES laststyle = STYLE_NONE; /* last type of parameter */ imp_sth->statement = (char*)safemalloc(strlen(statement)+1); /* initialize phs ready to be cloned per placeholder */ memset(&phs_tpl, 0, sizeof(phs_tpl)); phs_tpl.value_type = SQL_C_CHAR; phs_tpl.sv = &PL_sv_undef; src = statement; dest = imp_sth->statement; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) { TRACE1(imp_sth, " ignore named placeholders = %d\n", imp_sth->odbc_ignore_named_placeholders); } while(*src) { enum STATES next_state = state; switch (state) { case DEFAULT: { if ((*src == '\'') || (*src == '"')) { literal_ch = *src; /* save quote chr */ next_state = LITERAL; } else if ((*src == '/') && (*(src + 1) == '*')) { next_state = COMMENT; /* in comment */ } else if ((*src == '-') && (*(src + 1) == '-')) { next_state = LINE_COMMENT; /* in line comment */ } else if ((*src == '?') || (*src == ':')) { STRLEN namelen; char name[256]; /* current named parameter */ SV **svpp; char ch; ch = *src++; if (ch == '?') { /* X/Open standard */ idx++; my_snprintf(name, sizeof(name), "%d", idx); *dest++ = ch; style = STYLE_NORMAL; } else if (isDIGIT(*src)) { /* ':1' */ char *p = name; *dest++ = '?'; idx = atoi(src); while(isDIGIT(*src)) *p++ = *src++; *p = 0; style = STYLE_NUMBER; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_sth, " found numbered parameter = %s\n", name); } else if (!imp_sth->odbc_ignore_named_placeholders && isALNUM(*src)) { /* ':foo' is valid, only if we are not ignoring named * parameters */ char *p = name; idx++; *dest++ = '?'; while(isALNUM(*src)) /* includes '_' */ *p++ = *src++; *p = 0; style = STYLE_NAME; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_sth, " found named parameter = %s\n", name); } else { /* perhaps ':=' PL/SQL construct */ *dest++ = ch; continue; } *dest = '\0'; /* handy for debugging */ if (laststyle && style != laststyle) croak("Can't mix placeholder styles (%d/%d)", style,laststyle); laststyle = style; if (imp_sth->all_params_hv == NULL) imp_sth->all_params_hv = newHV(); namelen = strlen(name); svpp = hv_fetch(imp_sth->all_params_hv, name, (I32)namelen, 0); if (svpp == NULL) { SV *phs_sv; phs_t *phs; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_sth, " creating new parameter key %s\n", name); /* create SV holding the placeholder */ phs_sv = newSVpv((char*)&phs_tpl, sizeof(phs_tpl)+namelen+1); phs = (phs_t*)SvPVX(phs_sv); strcpy(phs->name, name); phs->idx = idx; /* store placeholder to all_params_hv */ svpp = hv_store(imp_sth->all_params_hv, name, (I32)namelen, phs_sv, 0); } else { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_sth, " parameter key %s already exists\n", name); croak("DBD::ODBC does not yet support binding a named parameter more than once\n"); } break; } *dest++ = *src++; break; } case LITERAL: { if (*src == literal_ch) { next_state = DEFAULT; } *dest++ = *src++; break; } case COMMENT: { if ((*(src - 1) == '*') && (*src == '/')) { next_state = DEFAULT; } *dest++ = *src++; break; } case LINE_COMMENT: { if (*src == '\n') { next_state = DEFAULT; } *dest++ = *src++; break; } } state = next_state; } *dest = '\0'; if (imp_sth->all_params_hv) { DBIc_NUM_PARAMS(imp_sth) = (int)HvKEYS(imp_sth->all_params_hv); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_sth, " dbd_preparse scanned %d distinct placeholders\n", (int)DBIc_NUM_PARAMS(imp_sth)); } } int dbd_st_tables( SV *dbh, SV *sth, char *catalog, char *schema, char *table, char *table_type) { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; size_t max_stmt_len; imp_sth->henv = imp_dbh->henv; imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "st_tables/SQLAllocHandle(stmt)"); return 0; } max_stmt_len = strlen(cSqlTables)+ strlen(XXSAFECHAR(catalog)) + strlen(XXSAFECHAR(schema)) + strlen(XXSAFECHAR(table)) + strlen(XXSAFECHAR(table_type))+1; imp_sth->statement = (char *)safemalloc(max_stmt_len); my_snprintf(imp_sth->statement, max_stmt_len, cSqlTables, XXSAFECHAR(catalog), XXSAFECHAR(schema), XXSAFECHAR(table), XXSAFECHAR(table_type)); rc = SQLTables(imp_sth->hstmt, (catalog && *catalog) ? catalog : 0, SQL_NTS, (schema && *schema) ? schema : 0, SQL_NTS, (table && *table) ? table : 0, SQL_NTS, table_type && *table_type ? table_type : 0, SQL_NTS /* type (view, table, etc) */ ); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE2(imp_dbh, " Tables result %d (%s)\n", rc, table_type ? table_type : "(null)"); dbd_error(sth, rc, "st_tables/SQLTables"); if (!SQL_SUCCEEDED(rc)) { SQLFreeHandle(SQL_HANDLE_STMT,imp_sth->hstmt); imp_sth->hstmt = SQL_NULL_HSTMT; return 0; } return build_results(sth, dbh, rc); } int dbd_st_primary_keys( SV *dbh, SV *sth, char *catalog, char *schema, char *table) { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; size_t max_stmt_len; imp_sth->henv = imp_dbh->henv; imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "odbc_db_primary_key_info/SQLAllocHandle(stmt)"); return 0; } /* just for sanity, later. Any internals that may rely on this (including */ /* debugging) will have valid data */ max_stmt_len = strlen(cSqlPrimaryKeys)+ strlen(XXSAFECHAR(catalog))+ strlen(XXSAFECHAR(schema))+ strlen(XXSAFECHAR(table))+1; imp_sth->statement = (char *)safemalloc(max_stmt_len); my_snprintf(imp_sth->statement, max_stmt_len, cSqlPrimaryKeys, XXSAFECHAR(catalog), XXSAFECHAR(schema), XXSAFECHAR(table)); rc = SQLPrimaryKeys(imp_sth->hstmt, (catalog && *catalog) ? catalog : 0, SQL_NTS, (schema && *schema) ? schema : 0, SQL_NTS, (table && *table) ? table : 0, SQL_NTS); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) PerlIO_printf( DBIc_LOGPIO(imp_dbh), "SQLPrimaryKeys call: cat = %s, schema = %s, table = %s\n", XXSAFECHAR(catalog), XXSAFECHAR(schema), XXSAFECHAR(table)); dbd_error(sth, rc, "st_primary_key_info/SQLPrimaryKeys"); if (!SQL_SUCCEEDED(rc)) { SQLFreeHandle(SQL_HANDLE_STMT,imp_sth->hstmt); imp_sth->hstmt = SQL_NULL_HSTMT; return 0; } return build_results(sth, dbh, rc); } int dbd_st_statistics( SV *dbh, SV *sth, char *catalog, char *schema, char *table, int unique, int quick) { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; SQLUSMALLINT odbc_unique; SQLUSMALLINT odbc_quick; size_t max_stmt_len; imp_sth->henv = imp_dbh->henv; imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "odbc_db_primary_key_info/SQLAllocHandle(stmt)"); return 0; } odbc_unique = (unique ? SQL_INDEX_UNIQUE : SQL_INDEX_ALL); odbc_quick = (quick ? SQL_QUICK : SQL_ENSURE); /* just for sanity, later. Any internals that may rely on this (including */ /* debugging) will have valid data */ max_stmt_len = strlen(cSqlStatistics)+ strlen(XXSAFECHAR(catalog))+ strlen(XXSAFECHAR(schema))+ strlen(XXSAFECHAR(table))+1; imp_sth->statement = (char *)safemalloc(max_stmt_len); my_snprintf(imp_sth->statement, max_stmt_len, cSqlStatistics, XXSAFECHAR(catalog), XXSAFECHAR(schema), XXSAFECHAR(table), unique, quick); rc = SQLStatistics(imp_sth->hstmt, (catalog && *catalog) ? catalog : 0, SQL_NTS, (schema && *schema) ? schema : 0, SQL_NTS, (table && *table) ? table : 0, SQL_NTS, odbc_unique, odbc_quick); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { PerlIO_printf( DBIc_LOGPIO(imp_dbh), "SQLStatistics call: cat = %s, schema = %s, table = %s" ", unique=%d, quick = %d\n", XXSAFECHAR(catalog), XXSAFECHAR(schema), XXSAFECHAR(table), odbc_unique, odbc_quick); } dbd_error(sth, rc, "st_statistics/SQLStatistics"); if (!SQL_SUCCEEDED(rc)) { SQLFreeHandle(SQL_HANDLE_STMT,imp_sth->hstmt); imp_sth->hstmt = SQL_NULL_HSTMT; return 0; } return build_results(sth, dbh, rc); } /************************************************************************/ /* */ /* odbc_st_prepare */ /* =============== */ /* */ /* dbd_st_prepare is the old API which is now replaced with */ /* dbd_st_prepare_sv (taking a perl scalar) so this is now: */ /* */ /* a) just a wrapper around dbd_st_prepare_sv and */ /* b) not used - see ODBC.c */ /* */ /************************************************************************/ int odbc_st_prepare( SV *sth, imp_sth_t *imp_sth, char *statement, SV *attribs) { SV *sql; sql = sv_newmortal(); sv_setpvn(sql, statement, strlen(statement)); return dbd_st_prepare_sv(sth, imp_sth, sql, attribs); } /************************************************************************/ /* */ /* odbc_st_prepare_sv */ /* ================== */ /* */ /* dbd_st_prepare_sv is the newer version of dbd_st_prepare taking a */ /* a perl scalar for the sql statement instead of a char* so it may be */ /* unicode */ /* */ /************************************************************************/ int odbc_st_prepare_sv( SV *sth, imp_sth_t *imp_sth, SV *statement, SV *attribs) { D_imp_dbh_from_sth; RETCODE rc; int dbh_active; char *sql; sql = SvPV_nolen(statement); imp_sth->done_desc = 0; imp_sth->henv = imp_dbh->henv; imp_sth->hdbc = imp_dbh->hdbc; /* inherit from connection */ imp_sth->odbc_ignore_named_placeholders = imp_dbh->odbc_ignore_named_placeholders; imp_sth->odbc_default_bind_type = imp_dbh->odbc_default_bind_type; imp_sth->odbc_force_bind_type = imp_dbh->odbc_force_bind_type; imp_sth->odbc_force_rebind = imp_dbh->odbc_force_rebind; imp_sth->odbc_query_timeout = imp_dbh->odbc_query_timeout; imp_sth->odbc_putdata_start = imp_dbh->odbc_putdata_start; imp_sth->odbc_column_display_size = imp_dbh->odbc_column_display_size; imp_sth->odbc_utf8_on = imp_dbh->odbc_utf8_on; imp_sth->odbc_exec_direct = imp_dbh->odbc_exec_direct; imp_sth->odbc_old_unicode = imp_dbh->odbc_old_unicode; imp_sth->odbc_describe_parameters = imp_dbh->odbc_describe_parameters; if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 5)) { TRACE1(imp_dbh, " initializing sth query timeout to %ld\n", (long)imp_dbh->odbc_query_timeout); } if ((dbh_active = check_connection_active(sth)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "st_prepare/SQLAllocHandle(stmt)"); return 0; } { /* * allow setting of odbc_execdirect in prepare() or overriding */ SV **attr_sv; /* if the attribute is there, let it override what the default * value from the dbh is (set above). * NOTE: * There are unfortunately two possible attributes because of an early * typo in DBD::ODBC which we keep for backwards compatibility. */ if ((attr_sv = DBD_ATTRIB_GET_SVP(attribs, "odbc_execdirect", (I32)strlen("odbc_execdirect"))) != NULL) { imp_sth->odbc_exec_direct = SvIV(*attr_sv) != 0; } if ((attr_sv = DBD_ATTRIB_GET_SVP(attribs, "odbc_exec_direct", (I32)strlen("odbc_exec_direct"))) != NULL) { imp_sth->odbc_exec_direct = SvIV(*attr_sv) != 0; } } { /* * allow setting of odbc_old_unicode in prepare() or overriding */ SV **attr_sv; /* if the attribute is there, let it override what the default * value from the dbh is (set above). */ if ((attr_sv = DBD_ATTRIB_GET_SVP(attribs, "odbc_old_unicode", (I32)strlen("odbc_old_unicode"))) != NULL) { imp_sth->odbc_old_unicode = SvIV(*attr_sv) != 0; } } { /* * allow setting of odbc_describe_parameters in prepare() or overriding */ SV **attr_sv; /* if the attribute is there, let it override what the default * value from the dbh is (set above). */ if ((attr_sv = DBD_ATTRIB_GET_SVP( attribs, "odbc_describe_parameters", (I32)strlen("odbc_describe_parameters"))) != NULL) { imp_sth->odbc_describe_parameters = SvIV(*attr_sv) != 0; } } /* scan statement for '?', ':1' and/or ':foo' style placeholders */ dbd_preparse(imp_sth, sql); /* Hold this statement for subsequent call of dbd_execute */ if (!imp_sth->odbc_exec_direct) { if (DBIc_TRACE(imp_dbh, SQL_TRACING, 0, 3)) { TRACE1(imp_dbh, " SQLPrepare %s\n", imp_sth->statement); } #ifdef WITH_UNICODE if (SvOK(statement) && DO_UTF8(statement)) { SQLWCHAR *wsql; STRLEN wsql_len; SV *sql_copy; if (DBIc_TRACE(imp_dbh, UNICODE_TRACING, 0, 0)) /* odbcunicode */ TRACE0(imp_dbh, " Processing utf8 sql in unicode mode\n"); sql_copy = sv_newmortal(); sv_setpv(sql_copy, imp_sth->statement); #ifdef sv_utf8_decode sv_utf8_decode(sql_copy); #else SvUTF8_on(sql_copy); #endif SV_toWCHAR(sql_copy); wsql = (SQLWCHAR *)SvPV(sql_copy, wsql_len); rc = SQLPrepareW(imp_sth->hstmt, wsql, wsql_len / sizeof(SQLWCHAR)); } else { if (DBIc_TRACE(imp_dbh, UNICODE_TRACING, 0, 0)) /* odbcunicode */ TRACE0(imp_dbh, " Processing non-utf8 sql in unicode mode\n"); rc = SQLPrepare(imp_sth->hstmt, imp_sth->statement, SQL_NTS); } #else /* !WITH_UNICODE */ if (DBIc_TRACE(imp_dbh, UNICODE_TRACING, 0, 0)) /* odbcunicode */ TRACE0(imp_dbh, " Processing sql in non-unicode mode\n"); rc = SQLPrepare(imp_sth->hstmt, imp_sth->statement, SQL_NTS); #endif if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " SQLPrepare = %d\n", rc); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "st_prepare/SQLPrepare"); SQLFreeHandle(SQL_HANDLE_STMT,imp_sth->hstmt); imp_sth->hstmt = SQL_NULL_HSTMT; return 0; } } else if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3)) { TRACE1(imp_dbh, " odbc_exec_direct=1, statement (%s) " "held for later exec\n", imp_sth->statement); } /* init sth pointers */ imp_sth->henv = imp_dbh->henv; imp_sth->hdbc = imp_dbh->hdbc; imp_sth->fbh = NULL; imp_sth->ColNames = NULL; imp_sth->RowBuffer = NULL; imp_sth->RowCount = -1; /* * If odbc_async_exec is set and odbc_async_type is SQL_AM_STATEMENT, * we need to set the SQL_ATTR_ASYNC_ENABLE attribute. */ if (imp_dbh->odbc_async_exec && imp_dbh->odbc_async_type == SQL_AM_STATEMENT){ rc = SQLSetStmtAttr(imp_sth->hstmt, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER) SQL_ASYNC_ENABLE_ON, SQL_IS_UINTEGER); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "st_prepare/SQLSetStmtAttr"); SQLFreeStmt(imp_sth->hstmt, SQL_DROP); imp_sth->hstmt = SQL_NULL_HSTMT; return 0; } } /* * If odbc_query_timeout is set (not -1) * we need to set the SQL_ATTR_QUERY_TIMEOUT */ if (imp_sth->odbc_query_timeout != -1){ odbc_set_query_timeout(sth, imp_sth->hstmt, imp_sth->odbc_query_timeout); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "set_query_timeout"); } /* don't fail if the query timeout can't be set. */ } DBIc_IMPSET_on(imp_sth); return 1; } /* Given SQL type return string description - only used in debug output */ static const char *S_SqlTypeToString (SWORD sqltype) { switch(sqltype) { case SQL_CHAR: return "CHAR"; case SQL_NUMERIC: return "NUMERIC"; case SQL_DECIMAL: return "DECIMAL"; case SQL_INTEGER: return "INTEGER"; case SQL_SMALLINT: return "SMALLINT"; case SQL_FLOAT: return "FLOAT"; case SQL_REAL: return "REAL"; case SQL_DOUBLE: return "DOUBLE"; case SQL_VARCHAR: return "VARCHAR"; #ifdef SQL_WCHAR case SQL_WCHAR: return "UNICODE CHAR"; #endif #ifdef SQL_WVARCHAR /* added for SQLServer 7 ntext type 2/24/2000 */ case SQL_WVARCHAR: return "UNICODE VARCHAR"; #endif #ifdef SQL_WLONGVARCHAR case SQL_WLONGVARCHAR: return "UNICODE LONG VARCHAR"; #endif case SQL_DATE: return "DATE"; case SQL_TYPE_DATE: return "DATE"; case SQL_TIME: return "TIME"; case SQL_TYPE_TIME: return "TIME"; case SQL_TIMESTAMP: return "TIMESTAMP"; case SQL_TYPE_TIMESTAMP: return "TIMESTAMP"; case SQL_LONGVARCHAR: return "LONG VARCHAR"; case SQL_BINARY: return "BINARY"; case SQL_VARBINARY: return "VARBINARY"; case SQL_LONGVARBINARY: return "LONG VARBINARY"; case SQL_BIGINT: return "BIGINT"; case SQL_TINYINT: return "TINYINT"; case SQL_BIT: return "BIT"; case MS_SQLS_XML_TYPE: return "MS SQL Server XML"; } return "unknown"; } static const char *S_SqlCTypeToString (SWORD sqltype) { static char s_buf[100]; #define s_c(x) case x: return #x switch(sqltype) { s_c(SQL_C_CHAR); s_c(SQL_C_WCHAR); s_c(SQL_C_BIT); s_c(SQL_C_STINYINT); s_c(SQL_C_UTINYINT); s_c(SQL_C_SSHORT); s_c(SQL_C_USHORT); s_c(SQL_C_FLOAT); s_c(SQL_C_DOUBLE); s_c(SQL_C_BINARY); s_c(SQL_C_DATE); s_c(SQL_C_TIME); s_c(SQL_C_TIMESTAMP); s_c(SQL_C_TYPE_DATE); s_c(SQL_C_TYPE_TIME); s_c(SQL_C_TYPE_TIMESTAMP); } #undef s_c my_snprintf(s_buf, sizeof(s_buf), "(CType %d)", sqltype); return s_buf; } /* * describes the output variables of a query, * allocates buffers for result rows, * and binds this buffers to the statement. */ int dbd_describe(SV *h, imp_sth_t *imp_sth, int more) { SQLRETURN rc; /* ODBC fn return value */ SQLSMALLINT column_n; /* column we are describing */ imp_fbh_t *fbh; SQLLEN colbuf_bytes_reqd = 0; SQLSMALLINT num_fields; /* number resultant columns */ SQLCHAR *cur_col_name; struct imp_dbh_st *imp_dbh = NULL; imp_dbh = (struct imp_dbh_st *)(DBIc_PARENT_COM(imp_sth)); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_sth, " dbd_describe done_desc=%d\n", imp_sth->done_desc); if (imp_sth->done_desc) return 1; /* success, already done it */ imp_sth->done_bind = 0; /* Find out how many columns there are in the result-set */ if (!SQL_SUCCEEDED(rc = SQLNumResultCols(imp_sth->hstmt, &num_fields))) { dbd_error(h, rc, "dbd_describe/SQLNumResultCols"); return 0; } else if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE2(imp_sth, " dbd_describe SQLNumResultCols=%d (columns=%d)\n", rc, num_fields); } /* * If "more" is not set (set in dbd_st_fetch) and SQLMoreResults is * supported then we skip over non-result-set generating statements. */ imp_sth->done_desc = 1; /* assume ok from here on */ if (!more) { while (num_fields == 0 && imp_dbh->odbc_sqlmoreresults_supported == 1) { rc = SQLMoreResults(imp_sth->hstmt); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) TRACE1(imp_sth, " Numfields = 0, SQLMoreResults = %d\n", rc); if (rc == SQL_NO_DATA) { /* mo more results */ imp_sth->moreResults = 0; break; } else if (rc == SQL_SUCCESS_WITH_INFO) { /* warn about an info returns */ dbd_error(h, rc, "dbd_describe/SQLMoreResults"); } else if (!SQL_SUCCEEDED(rc)) { dbd_error(h, rc, "dbd_describe/SQLMoreResults"); return 0; } /* reset describe flags, so that we re-describe */ imp_sth->done_desc = 0; /* force future executes to rebind automatically */ imp_sth->odbc_force_rebind = 1; if (!SQL_SUCCEEDED( rc = SQLNumResultCols(imp_sth->hstmt, &num_fields))) { dbd_error(h, rc, "dbd_describe/SQLNumResultCols"); return 0; } else if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) { TRACE1(imp_dbh, " num fields after MoreResults = %d\n", num_fields); } } /* end of SQLMoreResults */ } /* end of more */ DBIc_NUM_FIELDS(imp_sth) = num_fields; if (0 == num_fields) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " dbd_describe skipped (no resultant cols)\n"); imp_sth->done_desc = 1; return 1; } DBIc_ACTIVE_on(imp_sth); /*HERE*/ /* allocate field buffers */ Newz(42, imp_sth->fbh, num_fields, imp_fbh_t); /* the +255 below instead is due to an old comment in this code before this change claiming foxpro wrote off end of memory (and so 255 bytes were added - kept here without evidence) */ Newz(42, imp_sth->ColNames, (num_fields + 1) * imp_dbh->max_column_name_len + 255, UCHAR); cur_col_name = imp_sth->ColNames; /* Pass 1: Get space needed for field names, display buffer and dbuf */ for (fbh=imp_sth->fbh, column_n=0; column_n < num_fields; column_n++, fbh++) { fbh->imp_sth = imp_sth; #ifdef WITH_UNICODE rc = SQLDescribeColW(imp_sth->hstmt, (SQLSMALLINT)(column_n + 1), (SQLWCHAR *) cur_col_name, (SQLSMALLINT)imp_dbh->max_column_name_len, &fbh->ColNameLen, &fbh->ColSqlType, &fbh->ColDef, &fbh->ColScale, &fbh->ColNullable); #else /* WITH_UNICODE */ rc = SQLDescribeCol(imp_sth->hstmt, (SQLSMALLINT)(column_n + 1), cur_col_name, (SQLSMALLINT)imp_dbh->max_column_name_len, &fbh->ColNameLen, &fbh->ColSqlType, /* column size or precision depending on type */ &fbh->ColDef, &fbh->ColScale, /* decimal digits */ &fbh->ColNullable); #endif /* WITH_UNICODE */ if (!SQL_SUCCEEDED(rc)) { /* should never fail */ dbd_error(h, rc, "describe/SQLDescribeCol"); break; } fbh->ColName = cur_col_name; #ifdef WITH_UNICODE cur_col_name += fbh->ColNameLen * sizeof(SQLWCHAR); #else cur_col_name += fbh->ColNameLen + 1; cur_col_name[fbh->ColNameLen] = '\0'; /* should not be necessary */ #endif if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) /* TO_DO the following print for column name won't work well for UCS2 strings when SQLDescribeW above called */ PerlIO_printf(DBIc_LOGPIO(imp_dbh), " DescribeCol column = %d, name = %s, " "namelen = %d, type = %s(%d), " "precision/column size = %ld, scale = %d, " "nullable = %d\n", column_n + 1, fbh->ColName, fbh->ColNameLen, S_SqlTypeToString(fbh->ColSqlType), fbh->ColSqlType, fbh->ColDef, fbh->ColScale, fbh->ColNullable); #ifdef SQL_DESC_DISPLAY_SIZE rc = SQLColAttribute(imp_sth->hstmt, (SQLSMALLINT)(column_n + 1), SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL , &fbh->ColDisplaySize); if (!SQL_SUCCEEDED(rc)) { /* Some ODBC drivers don't support SQL_COLUMN_DISPLAY_SIZE on some result-sets. e.g., The "Infor Integration ODBC driver" cannot handle SQL_COLUMN_DISPLAY_SIZE and SQL_COLUMN_LENGTH for SQLTables and SQLColumns calls. We used to fail here but there is a prescident not to as this code is already in an ifdef for drivers that do not define SQL_COLUMN_DISPLAY_SIZE. Since just about everyone will be using an ODBC driver manager now it is unlikely these attributes will not be defined so we default if the call fails now */ if( DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8) ) { TRACE0(imp_sth, " describe/SQLColAttributes/SQL_COLUMN_DISPLAY_SIZE " "not supported, will be equal to SQL_COLUMN_LENGTH\n"); } /* ColDisplaySize will be made equal to ColLength */ fbh->ColDisplaySize = 0; rc = SQL_SUCCESS; } else if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) { TRACE1(imp_sth, " SQL_COLUMN_DISPLAY_SIZE = %ld\n", (long)fbh->ColDisplaySize); } /* TBD: should we only add a terminator if it's a char??? */ fbh->ColDisplaySize += 1; /* add terminator */ #else /* !SQL_COLUMN_DISPLAY_SIZE */ fbh->ColDisplaySize = imp_sth->odbc_column_display_size; #endif /* SQL_COLUMN_DISPLAY_SIZE */ /* Workaround bug in Firebird driver that reports timestamps are display size 24 when in fact it can return the longer e.g., 1998-05-15 00:01:00.100000000 */ if ((imp_dbh->driver_type == DT_FIREBIRD) && (fbh->ColSqlType == SQL_TYPE_TIMESTAMP)) { fbh->ColDisplaySize = 30; } #ifdef SQL_DESC_LENGTH rc = SQLColAttribute(imp_sth->hstmt,(SQLSMALLINT)(column_n + 1), SQL_DESC_LENGTH, NULL, 0, NULL ,&fbh->ColLength); if (!SQL_SUCCEEDED(rc)) { /* See comment above under SQL_COLUMN_DISPLAY_SIZE */ fbh->ColLength = imp_sth->odbc_column_display_size; if( DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8) ) { TRACE1(imp_sth, " describe/SQLColAttributes/SQL_COLUMN_LENGTH not " "supported, fallback on %ld\n", (long)fbh->ColLength); } rc = SQL_SUCCESS; } else if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) { TRACE1(imp_sth, " SQL_COLUMN_LENGTH = %ld\n", (long)fbh->ColLength); } # if defined(WITH_UNICODE) fbh->ColLength += 1; /* add extra byte for double nul terminator */ /* Unless old unicode behavior map SQL_CHAR to SQL_WCHAR */ if (!imp_sth->odbc_old_unicode && (fbh->ColSqlType == SQL_CHAR || fbh->ColSqlType == SQL_VARCHAR)) { fbh->ColSqlType = SQL_WCHAR; } # endif #else /* !SQL_COLUMN_LENGTH */ fbh->ColLength = imp_sth->odbc_column_display_size; #endif /* SQL_COLUMN_LENGTH */ /* may want to ensure Display Size at least as large as column * length -- workaround for some drivers which report a shorter * display length * */ fbh->ColDisplaySize = fbh->ColDisplaySize > fbh->ColLength ? fbh->ColDisplaySize : fbh->ColLength; /* * change fetched size, decimal digits etc for some types, * The tests for ColDef = 0 are for when the driver does not give * us a length for the column e.g., "max" column types in SQL Server * like varbinary(max). */ fbh->ftype = SQL_C_CHAR; switch(fbh->ColSqlType) { case SQL_VARBINARY: case SQL_BINARY: fbh->ftype = SQL_C_BINARY; if (fbh->ColDef == 0) { /* cope with varbinary(max) */ fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth); } break; #if defined(WITH_UNICODE) case SQL_WCHAR: case SQL_WVARCHAR: fbh->ftype = SQL_C_WCHAR; /* MS SQL returns bytes, Oracle returns characters ... */ if (fbh->ColDef == 0) { /* cope with nvarchar(max) */ fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth); fbh->ColLength = DBIc_LongReadLen(imp_sth); } else if (fbh->ColDef > 2147483590) { /* * The new MS Access driver ACEODBC.DLL cannot cope with the * 40UnicodeRoundTrip test which contains a * select ?, LEN(?) * returning a massive number for the column display size of * the first column. This leads to a memory allocation error * unless we trap it as a large column. */ fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth); fbh->ColLength = DBIc_LongReadLen(imp_sth); } fbh->ColDisplaySize *= sizeof(SQLWCHAR); fbh->ColLength *= sizeof(SQLWCHAR); break; #else /* WITH_UNICODE */ # if defined(SQL_WCHAR) case SQL_WCHAR: if (fbh->ColDef == 0) { fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth); } break; # endif # if defined(SQL_WVARCHAR) case SQL_WVARCHAR: if (fbh->ColDef == 0) { fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth); } break; # endif #endif /* WITH_UNICODE */ case SQL_LONGVARBINARY: fbh->ftype = SQL_C_BINARY; fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth); break; #ifdef SQL_WLONGVARCHAR case SQL_WLONGVARCHAR: /* added for SQLServer 7 ntext type */ # if defined(WITH_UNICODE) fbh->ftype = SQL_C_WCHAR; /* MS SQL returns bytes, Oracle returns characters ... */ fbh->ColLength*=sizeof(SQLWCHAR); fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth)+1; # else /* !WITH_UNICODE */ fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth) + 1; # endif /* WITH_UNICODE */ break; #endif /* SQL_WLONGVARCHAR */ case SQL_VARCHAR: if (fbh->ColDef == 0) { fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth)+1; } break; case SQL_LONGVARCHAR: fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth)+1; break; case MS_SQLS_XML_TYPE: fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth)+1; break; #ifdef TIMESTAMP_STRUCT /* XXX! */ case SQL_TIMESTAMP: case SQL_TYPE_TIMESTAMP: fbh->ftype = SQL_C_TIMESTAMP; fbh->ColDisplaySize = sizeof(TIMESTAMP_STRUCT); break; #endif } colbuf_bytes_reqd += fbh->ColDisplaySize; /* * We later align columns in the buffer on integer boundaries so we * we need to tak account of this here. The last % is to avoid adding * sizeof(int) if we are already aligned. */ colbuf_bytes_reqd += (sizeof(int) - (colbuf_bytes_reqd % sizeof(int))) % sizeof(int); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) PerlIO_printf(DBIc_LOGPIO(imp_dbh), " now using col %d: type = %s (%d), len = %ld, " "display size = %ld, prec = %ld, scale = %lu\n", column_n + 1, S_SqlTypeToString(fbh->ColSqlType), fbh->ColSqlType, (long)fbh->ColLength, (long)fbh->ColDisplaySize, (long)fbh->ColDef, (unsigned long)fbh->ColScale); } if (!SQL_SUCCEEDED(rc)) { /* dbd_error called above */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE0(imp_sth, "Freeing fbh\n"); Safefree(imp_sth->fbh); imp_sth->fbh = NULL; return 0; } imp_sth->RowBufferSizeReqd = colbuf_bytes_reqd; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_sth, " -dbd_describe done_bind=%d\n", imp_sth->done_bind); return 1; } static SQLRETURN bind_columns( SV *h, imp_sth_t *imp_sth) { SQLSMALLINT num_fields; UCHAR *rbuf_ptr; imp_fbh_t *fbh; SQLRETURN rc = SQL_SUCCESS; /* ODBC fn return value */ SQLSMALLINT i; num_fields = DBIc_NUM_FIELDS(imp_sth); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE2(imp_sth, " bind_columns fbh=%p fields=%d\n", imp_sth->fbh, num_fields); /* allocate Row memory */ Newz(42, imp_sth->RowBuffer, imp_sth->RowBufferSizeReqd + num_fields, UCHAR); rbuf_ptr = imp_sth->RowBuffer; for(i=0, fbh = imp_sth->fbh; i < num_fields && SQL_SUCCEEDED(rc); i++, fbh++) { if (fbh->req_type != 0) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE2(imp_sth, " Overriding bound sql type %d with requested type %"IVdf"\n", fbh->ftype, fbh->req_type); fbh->ftype = (SWORD)fbh->req_type; } if (!(fbh->bind_flags & ODBC_TREAT_AS_LOB)) { fbh->data = rbuf_ptr; rbuf_ptr += fbh->ColDisplaySize; /* alignment -- always pad so the next column is aligned on a word boundary */ rbuf_ptr += (sizeof(int) - ((rbuf_ptr - imp_sth->RowBuffer) % sizeof(int))) % sizeof(int); /* Bind output column variables */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) PerlIO_printf(DBIc_LOGPIO(imp_sth), " Bind %d: type = %s(%d), buf=%p, buflen=%ld\n", i+1, S_SqlTypeToString(fbh->ftype), fbh->ftype, fbh->data, fbh->ColDisplaySize); rc = SQLBindCol(imp_sth->hstmt, (SQLSMALLINT)(i+1), fbh->ftype, fbh->data, fbh->ColDisplaySize, &fbh->datalen); if (!SQL_SUCCEEDED(rc)) { dbd_error(h, rc, "describe/SQLBindCol"); break; } } else if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE1(imp_sth, " TreatAsLOB bind_flags = %lu\n", fbh->bind_flags); } } imp_sth->done_bind = 1; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_sth, " bind_columns=%d\n", rc); return rc; } /*======================================================================*/ /* */ /* dbd_st_execute */ /* ============== */ /* */ /* returns: */ /* -2 - error */ /* >=0 - ok, row count */ /* -1 - unknown count */ /* */ /*======================================================================*/ int dbd_st_execute( SV *sth, imp_sth_t *imp_sth) { D_imp_xxh(sth); RETCODE rc; D_imp_dbh_from_sth; int outparams = 0; int ret; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " +dbd_st_execute(%p)\n", sth); if (SQL_NULL_HDBC == imp_dbh->hdbc) { DBIh_SET_ERR_CHAR(sth, imp_xxh, Nullch, 1, "Database handle has been disconnected", Nullch, Nullch); return -2; } /* * if the handle is active, we need to finish it here. * Note that dbd_st_finish already checks to see if it's active. */ dbd_st_finish(sth, imp_sth);; /* * bind_param_inout support */ outparams = (imp_sth->out_params_av) ? AvFILL(imp_sth->out_params_av)+1 : 0; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE1(imp_dbh, " outparams = %d\n", outparams); } if (imp_dbh->odbc_defer_binding) { rc = SQLFreeStmt(imp_sth->hstmt, SQL_RESET_PARAMS); /* check bind input parameters */ if (imp_sth->all_params_hv) { HV *hv = imp_sth->all_params_hv; SV *sv; char *key; I32 retlen; hv_iterinit(hv); while( (sv = hv_iternextsv(hv, &key, &retlen)) != NULL ) { if (sv != &PL_sv_undef) { phs_t *phs = (phs_t*)(void*)SvPVX(sv); if (!rebind_param(sth, imp_sth, phs)) return -2; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) { if (SvOK(phs->sv) && (phs->value_type == SQL_C_CHAR)) { char sbuf[256]; unsigned int i = 0; while((phs->sv_buf[i] != 0) && (i < (sizeof(sbuf) - 6))) { sbuf[i] = phs->sv_buf[i]; i++; } strcpy(&sbuf[i], "..."); TRACE2(imp_dbh, " rebind check char Param %d (%s)\n", phs->idx, sbuf); } } } } } } if (outparams) { /* check validity of bind_param_inout SV's */ int i = outparams; while(--i >= 0) { phs_t *phs = (phs_t*)(void*)SvPVX(AvARRAY(imp_sth->out_params_av)[i]); /* Make sure we have the value in string format. Typically a number */ /* will be converted back into a string using the same bound buffer */ /* so the sv_buf test below will not trip. */ /* mutation check */ if (SvTYPE(phs->sv) != phs->sv_type /* has the type changed? */ || (SvOK(phs->sv) && !SvPOK(phs->sv)) /* is there still a string? */ || SvPVX(phs->sv) != phs->sv_buf /* has the string buffer moved? */ ) { if (!rebind_param(sth, imp_sth, phs)) croak("Can't rebind placeholder %s", phs->name); } else { /* no mutation found */ } } } if (imp_sth->odbc_exec_direct) { /* statement ready for SQLExecDirect */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) { TRACE0(imp_dbh, " odbc_exec_direct=1, using SQLExecDirect\n"); } rc = SQLExecDirect(imp_sth->hstmt, imp_sth->statement, SQL_NTS); } else { rc = SQLExecute(imp_sth->hstmt); } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) TRACE2(imp_dbh, " SQLExecute/SQLExecDirect(%p)=%d\n", imp_sth->hstmt, rc); /* * If asynchronous execution has been enabled, SQLExecute will * return SQL_STILL_EXECUTING until it has finished. * Grab whatever messages occur during execution... */ while (rc == SQL_STILL_EXECUTING){ dbd_error(sth, rc, "st_execute/SQLExecute"); /* * Wait a second so we don't loop too fast and bring the machine * to its knees */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_dbh, " SQLExecute(%p) still executing", imp_sth->hstmt); sleep(1); rc = SQLExecute(imp_sth->hstmt); } /* patches to handle blobs better, via Jochen Wiedmann */ while (rc == SQL_NEED_DATA) { phs_t* phs; STRLEN len; UCHAR* ptr; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_dbh, " NEED DATA %p\n", imp_sth->hstmt); while ((rc = SQLParamData(imp_sth->hstmt, (PTR*) &phs)) == SQL_STILL_EXECUTING) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_dbh, " SQLParamData(%p) still executing", imp_sth->hstmt); /* * wait a while to avoid looping too fast waiting for SQLParamData * to complete. */ sleep(1); } if (rc != SQL_NEED_DATA) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_dbh, " SQLParamData=%d\n", rc); break; } /* phs->sv is already upgraded to a PV in rebind_param. * It is not NULL, because we otherwise won't be called here * (value_len = 0). */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE2(imp_dbh, " SQLParamData needs phs %p, sending %i bytes\n", phs, len); ptr = SvPV(phs->sv, len); rc = SQLPutData(imp_sth->hstmt, ptr, len); if (!SQL_SUCCEEDED(rc)) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_dbh, " SQLPutData=%d\n", rc); break; } rc = SQL_NEED_DATA; /* So the loop continues ... */ } /* * Call dbd_error regardless of the value of rc so we can * get any status messages that are desired. */ dbd_error(sth, rc, "st_execute/SQLExecute"); if (!SQL_SUCCEEDED(rc) && rc != SQL_NO_DATA) { /* dbd_error(sth, rc, "st_execute/SQLExecute"); */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " -dbd_st_execute(%p)=-2\n", sth); return -2; } /* * If SQLExecute executes a searched update, insert, or delete statement * that does not affect any rows at the data source, the call to * SQLExecute returns SQL_NO_DATA. */ if (rc != SQL_NO_DATA) { /* SWORD num_fields; */ RETCODE rc2; rc2 = SQLRowCount(imp_sth->hstmt, &imp_sth->RowCount); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 7)) TRACE2(imp_dbh, " SQLRowCount=%d (rows=%ld)\n", rc2, (long)(SQL_SUCCEEDED(rc2) ? imp_sth->RowCount : (SQLLEN)-1)); if (!SQL_SUCCEEDED(rc2)) { dbd_error(sth, rc2, "st_execute/SQLRowCount"); /* XXX ? */ imp_sth->RowCount = -1; } /* sanity check for strange circumstances and multiple types of * result sets. Crazy that it can happen, but it can with * multiple result sets and stored procedures which return * result sets. * This seems to slow things down a bit and is rarely needed. * * This can happen in Sql Server in strange cases where stored * procs have multiple result sets. Sometimes, if there is an * select then an insert, etc. Maybe this should be a special * attribute to force a re-describe after every execute? */ if (imp_sth->odbc_force_rebind) { /* force calling dbd_describe after each execute */ odbc_clear_result_set(sth, imp_sth); } } else { /* SQL_NO_DATA returned, must have no rows :) */ /* seem to need to reset the done_desc, but not sure if this is * what we want yet */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 7)) TRACE0(imp_dbh, " SQL_NO_DATA...resetting done_desc!\n"); imp_sth->done_desc = 0; imp_sth->RowCount = 0; } /* * MS SQL Server is very picky wrt to completing a procedure i.e., * it says the output bound parameters are not available until the * procedure is complete and the procedure is not complete until you * have called SQLMoreResults and it has returned SQL_NO_DATA. So, if you * call a procedure multiple times in the same statement (e.g., by just * calling execute) DBD::ODBC will call dbd_describe to describe the first * execute, discover there is no result-set and call SQLMoreResults - ok, * but after that, the dbd_describe is done and SQLMoreResults will not * get called. The following is a kludge to get around this until * a) DBD::ODBC can be changed to stop skipping over non-result-set * generating statements and b) the SQLMoreResults calls move out of * dbd_describe. */ { SQLSMALLINT flds = 0; SQLRETURN sts; if (!SQL_SUCCEEDED(sts = SQLNumResultCols(imp_sth->hstmt, &flds))) { dbd_error(sth, sts, "dbd_describe/SQLNumResultCols"); return -2; } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_dbh, " SQLNumResultCols=0 (flds=%d)\n", flds); if (flds == 0) { /* not a result-set */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE2(imp_dbh, " Not a result-set nflds=(%d,%d), resetting done_desc\n", flds, DBIc_NUM_FIELDS(imp_sth)); imp_sth->done_desc = 0; } } if (!imp_sth->done_desc) { /* This needs to be done after SQLExecute for some drivers! */ /* Especially for order by and join queries. */ /* See Microsoft Knowledge Base article (#Q124899) */ /* describe and allocate storage for results (if any needed) */ if (!dbd_describe(sth, imp_sth, 0)) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) { TRACE0(imp_sth, " !!dbd_describe failed, dbd_st_execute #1...!\n"); } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " -dbd_st_execute(%p)=-2\n", sth); return -2; /* dbd_describe already called dbd_error() */ } } if (DBIc_NUM_FIELDS(imp_sth) > 0) { DBIc_ACTIVE_on(imp_sth); /* only set for select (?) */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE1(imp_sth, " have %d fields\n", DBIc_NUM_FIELDS(imp_sth)); } } else { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE0(imp_dbh, " got no rows: resetting ACTIVE, moreResults\n"); } imp_sth->moreResults = 0; /* flag that we've done the describe to avoid a problem * where calling describe after execute returned no rows * caused SQLServer to provide a description of a query * that didn't quite apply. */ /* imp_sth->done_desc = 1; */ DBIc_ACTIVE_off(imp_sth); } if (outparams) { /* check validity of bound output SV's */ odbc_handle_outparams(imp_sth, DBIc_TRACE_LEVEL(imp_sth)); } /* * JLU: Jon Smirl had: * return (imp_sth->RowCount == -1 ? -1 : abs(imp_sth->RowCount)); * why? Why do you need the abs() of the rowcount? Special reason? * The e-mail that accompanied the change indicated that Sybase would return * a negative value for an estimate. Wouldn't you WANT that to stay * negative? * * dgood: JLU had: * return imp_sth->RowCount; * Because you return -2 on errors so if you don't abs() it, a perfectly * valid return value will get flagged as an error... */ ret = (imp_sth->RowCount == -1 ? -1 : abs(imp_sth->RowCount)); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE2(imp_dbh, " -dbd_st_execute(%p)=%d\n", sth, ret); return ret; /* return imp_sth->RowCount; */ } /*---------------------------------------- * running $sth->fetch() *---------------------------------------- */ AV *dbd_st_fetch(SV *sth, imp_sth_t *imp_sth) { D_imp_dbh_from_sth; D_imp_xxh(sth); int i; AV *av; RETCODE rc; int num_fields; #ifdef TIMESTAMP_STRUCT /* iODBC doesn't define this */ char cvbuf[512]; #endif int ChopBlanks; /* Check that execute() was executed sucessfully. This also implies */ /* that dbd_describe() executed sucessfuly so the memory buffers */ /* are allocated and bound. */ if ( !DBIc_ACTIVE(imp_sth) ) { dbd_error(sth, DBDODBC_INTERNAL_ERROR, "no select statement currently executing"); return Nullav; } if (!imp_sth->done_bind) { rc = bind_columns(sth, imp_sth); if (!SQL_SUCCEEDED(rc)) { Safefree(imp_sth->fbh); imp_sth->fbh = NULL; dbd_st_finish(sth, imp_sth); return Nullav; } } rc = SQLFetch(imp_sth->hstmt); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_dbh, " SQLFetch rc %d\n", rc); if (!SQL_SUCCEEDED(rc)) { if (SQL_NO_DATA_FOUND == rc) { if (imp_dbh->odbc_sqlmoreresults_supported == 1) { rc = SQLMoreResults(imp_sth->hstmt); /* Check for multiple results */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 6)) TRACE1(imp_dbh, " Getting more results: %d\n", rc); if (rc == SQL_SUCCESS_WITH_INFO) { dbd_error(sth, rc, "st_fetch/SQLMoreResults"); /* imp_sth->moreResults = 0; */ } if (SQL_SUCCEEDED(rc)){ /* More results detected. Clear out the old result */ /* stuff and re-describe the fields. */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) { TRACE0(imp_dbh, " MORE Results!\n"); } odbc_clear_result_set(sth, imp_sth); /* force future executes to rebind automatically */ imp_sth->odbc_force_rebind = 1; /* tell the odbc driver that we need to unbind the * bound columns. Fix bug for 0.35 (2/8/02) */ rc = SQLFreeStmt(imp_sth->hstmt, SQL_UNBIND); if (!SQL_SUCCEEDED(rc)) { AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3), DBIc_LOGPIO(imp_dbh)); } if (!dbd_describe(sth, imp_sth, 1)) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE0(imp_dbh, " !!MORE Results dbd_describe failed...!\n"); return Nullav; /* dbd_describe already called dbd_error() */ } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE0(imp_dbh, " MORE Results dbd_describe success...!\n"); } /* set moreResults so we'll know we can keep fetching */ imp_sth->moreResults = 1; imp_sth->done_desc = 0; return Nullav; } else if (rc == SQL_NO_DATA_FOUND || rc == SQL_NO_DATA || rc == SQL_SUCCESS_WITH_INFO){ /* No more results */ /* need to check output params here... */ int outparams = (imp_sth->out_params_av) ? AvFILL(imp_sth->out_params_av)+1 : 0; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 6)) { TRACE1(imp_sth, " No more results -- outparams = %d\n", outparams); } imp_sth->moreResults = 0; imp_sth->done_desc = 1; if (outparams) { odbc_handle_outparams(imp_sth, DBIc_TRACE_LEVEL(imp_sth)); } /* XXX need to 'finish' here */ dbd_st_finish(sth, imp_sth); return Nullav; } else { dbd_error(sth, rc, "st_fetch/SQLMoreResults"); } } else { /* * SQLMoreResults not supported, just finish. * per bug found by Jarkko Hyöty [hyoty@medialab.sonera.fi] * No more results */ imp_sth->moreResults = 0; /* XXX need to 'finish' here */ dbd_st_finish(sth, imp_sth); return Nullav; } } else { dbd_error(sth, rc, "st_fetch/SQLFetch"); /* XXX need to 'finish' here */ dbd_st_finish(sth, imp_sth); return Nullav; } } if (imp_sth->RowCount == -1) imp_sth->RowCount = 0; imp_sth->RowCount++; av = DBIc_DBISTATE(imp_sth)->get_fbav(imp_sth); num_fields = AvFILL(av)+1; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_dbh, " fetch num_fields=%d\n", num_fields); ChopBlanks = DBIc_has(imp_sth, DBIcf_ChopBlanks); for(i=0; i < num_fields; ++i) { imp_fbh_t *fbh = &imp_sth->fbh[i]; SV *sv = AvARRAY(av)[i]; /* Note: we (re)use the SV in the AV */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) PerlIO_printf( DBIc_LOGPIO(imp_dbh), " fetch col#%d %s datalen=%ld displ=%lu\n", i+1, fbh->ColName, (long)fbh->datalen, (unsigned long)fbh->ColDisplaySize); if (fbh->datalen == SQL_NULL_DATA) { /* NULL value */ SvOK_off(sv); continue; } if (fbh->datalen > fbh->ColDisplaySize || fbh->datalen < 0) { /* truncated LONG ??? DBIcf_LongTruncOk() */ /* DBIcf_LongTruncOk this should only apply to LONG type fields */ /* truncation of other fields should always be an error since it's */ /* a sign of an internal error */ if (!DBIc_has(imp_sth, DBIcf_LongTruncOk) /* && rc == SQL_SUCCESS_WITH_INFO */) { /* * Since we've detected the problem locally via the datalen, * we don't need to worry about the value of rc. * * This used to make sure rc was set to SQL_SUCCESS_WITH_INFO * but since it's an error and not SUCCESS, call dbd_error() * with SQL_ERROR explicitly instead. */ #ifdef COULD_DO_THIS DBIh_SET_ERR_CHAR( sth, imp_xxh, Nullch, 1, "st_fetch/SQLFetch (long truncated DBI attribute LongTruncOk " "not set and/or LongReadLen too small)", Nullch, Nullch); #endif dbd_error( sth, DBDODBC_INTERNAL_ERROR, "st_fetch/SQLFetch (long truncated DBI attribute LongTruncOk " "not set and/or LongReadLen too small)"); return Nullav; } /* LongTruncOk true, just ensure perl has the right length * for the truncated data. */ sv_setpvn(sv, (char*)fbh->data, fbh->ColDisplaySize); } else { switch(fbh->ftype) { #ifdef TIMESTAMP_STRUCT /* iODBC doesn't define this */ case SQL_C_TIMESTAMP: case SQL_C_TYPE_TIMESTAMP: { TIMESTAMP_STRUCT *ts; ts = (TIMESTAMP_STRUCT *)fbh->data; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " adjusting timestamp\n"); my_snprintf(cvbuf, sizeof(cvbuf), "%04d-%02d-%02d %02d:%02d:%02d", ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second, ts->fraction); sv_setpv(sv, cvbuf); break; } #endif #if defined(WITH_UNICODE) case SQL_C_WCHAR: if (ChopBlanks && fbh->ColSqlType == SQL_WCHAR && fbh->datalen > 0) { SQLWCHAR *p = (SQLWCHAR*)fbh->data; SQLWCHAR blank = 0x20; SQLLEN orig_len = fbh->datalen; while(fbh->datalen && p[fbh->datalen/sizeof(SQLWCHAR)-1] == blank) { --fbh->datalen; } if (DBIc_TRACE(imp_sth, UNICODE_TRACING, 0, 0)) /* odbcunicode */ TRACE2(imp_sth, " Unicode ChopBlanks orig len=%ld, new len=%ld\n", orig_len, fbh->datalen); } sv_setwvn(sv,(SQLWCHAR*)fbh->data,fbh->datalen/sizeof(SQLWCHAR)); if (DBIc_TRACE(imp_sth, UNICODE_TRACING, 0, 0)) { /* odbcunicode */ /* unsigned char dlog[256]; */ /* unsigned char *src; */ /* char *dst = dlog; */ /* unsigned int n; */ /* STRLEN len; */ /* src = SvPV(sv, len); */ /* dst += sprintf(dst, "0x"); */ /* for (n = 0; (n < 126) && (n < len); n++, src++) { */ /* dst += sprintf(dst, "%2.2x", *src); */ /* } */ /*TRACE1(imp_sth, " SQL_C_WCHAR data = %s\n", dlog);*/ TRACE1(imp_sth, " SQL_C_WCHAR data = %.100s\n", neatsvpv(sv, 100)); } break; #endif /* WITH_UNICODE */ default: if (ChopBlanks && fbh->datalen > 0 && ((fbh->ColSqlType == SQL_CHAR) || (fbh->ColSqlType == SQL_WCHAR))) { char *p = (char*)fbh->data; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE0(imp_sth, " chopping blanks\n"); while(fbh->datalen && p[fbh->datalen - 1]==' ') --fbh->datalen; } sv_setpvn(sv, (char*)fbh->data, fbh->datalen); if (imp_sth->odbc_utf8_on && fbh->ftype != SQL_C_BINARY ) { if (DBIc_TRACE(imp_sth, UNICODE_TRACING, 0, 0)) /* odbcunicode */ TRACE0(imp_sth, " odbc_utf8 - decoding UTF-8"); #ifdef sv_utf8_decode sv_utf8_decode(sv); #else SvUTF8_on(sv); #endif } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE2(imp_sth, " %s(%ld)\n", neatsvpv(sv, fbh->datalen+5), fbh->datalen); } } #if DBIXS_REVISION > 13590 /* If a bind type was specified we use DBI's sql_type_cast to cast it - currently only number types are handled */ if ((fbh->req_type == SQL_INTEGER) || (fbh->req_type == SQL_NUMERIC) || (fbh->req_type == SQL_DECIMAL)) { int sts; char errstr[256]; sts = DBIc_DBISTATE(imp_sth)->sql_type_cast_svpv( aTHX_ sv, fbh->req_type, fbh->bind_flags, NULL); if (sts == 0) { sprintf(errstr, "over/under flow converting column %d to type %"IVdf"", i+1, fbh->req_type); DBIh_SET_ERR_CHAR(sth, imp_xxh, Nullch, 1, errstr, Nullch, Nullch); return Nullav; } else if (sts == -2) { sprintf(errstr, "unsupported bind type %"IVdf" for column %d", fbh->req_type, i+1); DBIh_SET_ERR_CHAR(sth, imp_xxh, Nullch, 1, errstr, Nullch, Nullch); return Nullav; } } #endif /* DBISTATE_VERSION > 94 */ } /* end of loop through bound columns */ return av; } int dbd_st_rows(SV *sth, imp_sth_t *imp_sth) { return (int)imp_sth->RowCount; } int dbd_st_finish(SV *sth, imp_sth_t *imp_sth) { D_imp_dbh_from_sth; RETCODE rc; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE1(imp_sth, " dbd_st_finish(%p)\n", sth); /* Cancel further fetches from this cursor. */ /* We don't close the cursor till DESTROY (dbd_st_destroy). */ /* The application may re execute(...) it. */ /* XXX semantics of finish (eg oracle vs odbc) need lots more thought */ /* re-read latest DBI specs and ODBC manuals */ if (DBIc_ACTIVE(imp_sth) && imp_dbh->hdbc != SQL_NULL_HDBC) { rc = SQLFreeStmt(imp_sth->hstmt, SQL_CLOSE);/* TBD: 3.0 update */ if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "finish/SQLFreeStmt(SQL_CLOSE)"); return 0; } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 6)) { TRACE0(imp_dbh, " dbd_st_finish closed query:\n"); } } DBIc_ACTIVE_off(imp_sth); return 1; } void dbd_st_destroy(SV *sth, imp_sth_t *imp_sth) { D_imp_dbh_from_sth; RETCODE rc; /* Free contents of imp_sth */ /* PerlIO_printf(DBIc_LOGPIO(imp_dbh), " dbd_st_destroy\n"); */ Safefree(imp_sth->fbh); Safefree(imp_sth->RowBuffer); Safefree(imp_sth->ColNames); Safefree(imp_sth->statement); if (imp_sth->out_params_av) sv_free((SV*)imp_sth->out_params_av); if (imp_sth->all_params_hv) { HV *hv = imp_sth->all_params_hv; SV *sv; char *key; I32 retlen; hv_iterinit(hv); while( (sv = hv_iternextsv(hv, &key, &retlen)) != NULL ) { if (sv != &PL_sv_undef) { phs_t *phs_tpl = (phs_t*)(void*)SvPVX(sv); sv_free(phs_tpl->sv); } } sv_free((SV*)imp_sth->all_params_hv); } /* SQLxxx functions dump core when no connection exists. This happens * when the db was disconnected before perl ending. Hence, * checking for the dirty flag. */ if (imp_dbh->hdbc != SQL_NULL_HDBC && !PL_dirty) { rc = SQLFreeHandle(SQL_HANDLE_STMT,imp_sth->hstmt); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_dbh, " SQLFreeStmt=%d.\n", rc); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "st_destroy/SQLFreeStmt(SQL_DROP)"); /* return 0; */ } } DBIc_IMPSET_off(imp_sth); /* let DBI know we've done it */ } /************************************************************************/ /* */ /* get_param_type */ /* ============== */ /* */ /* Sets the following fields for a parameter in the phs_st: */ /* */ /* sql_type - the SQL type to use when binding this parameter */ /* describe_param_called - set to 1 if we called SQLDescribeParam */ /* describe_param_status - set to result of SQLDescribeParam if */ /* SQLDescribeParam called */ /* described_sql_type - the sql type returned by SQLDescribeParam */ /* param_size - the parameter size returned by SQLDescribeParam */ /* */ /* The sql_type field is set to one of the following: */ /* value passed in bind method call if specified */ /* if SQLDescribeParam not supported: */ /* value of odbc_default_bind_type attribute if set else */ /* SQL_VARCHAR */ /* if SQLDescribeParam supported: */ /* if SQLDescribeParam succeeds: */ /* parameter type returned by SQLDescribeParam */ /* else if SQLDescribeParam fails: */ /* value of odbc_default_bind_type attribute if set else */ /* SQL_VARCHAR */ /* */ /* NOTE: Just because an ODBC driver says it supports SQLDescribeParam */ /* does not mean you can call it successfully e.g., MS SQL Server */ /* implements SQLDescribeParam by examining your SQL and rewriting it */ /* to be a select statement so it can find the column types etc. This */ /* fails horribly when the statement does not contain a table */ /* e.g., "select ?, LEN(?)" and so do most other SQL Server drivers. */ /* */ /************************************************************************/ static void get_param_type(SV *sth, imp_sth_t *imp_sth, phs_t *phs) { SWORD fNullable; SWORD ibScale; D_imp_dbh_from_sth; RETCODE rc; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE2(imp_sth, " +get_param_type(%p,%s)\n", sth, phs->name); if (imp_sth->odbc_force_bind_type != 0) { phs->sql_type = imp_sth->odbc_force_bind_type; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_dbh, " forced param type to %d\n", phs->sql_type); } else if (imp_dbh->odbc_sqldescribeparam_supported != 1) { /* As SQLDescribeParam is not supported by the ODBC driver we need to default a SQL type to bind the parameter as. The default is either the value set with odbc_default_bind_type or a fallback of SQL_VARCHAR. */ phs->sql_type = default_parameter_type( "SQLDescribeParam not supported", imp_sth, phs); } else if (!imp_sth->odbc_describe_parameters) { phs->sql_type = default_parameter_type( "SQLDescribeParam disabled", imp_sth, phs); } else if (!phs->describe_param_called) { /* If we haven't had a go at calling SQLDescribeParam before for this parameter, have a go now. If it fails we'll default the sql type as above when driver does not have SQLDescribeParam */ rc = SQLDescribeParam(imp_sth->hstmt, phs->idx, &phs->described_sql_type, &phs->param_size, &ibScale, &fNullable); phs->describe_param_called = 1; phs->describe_param_status = rc; if (!SQL_SUCCEEDED(rc)) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_dbh, " Parameter %d\n", phs->idx); phs->sql_type = default_parameter_type( "SQLDescribeParam failed", imp_sth, phs); /* show any odbc errors in log */ AllODBCErrors(imp_sth->henv, imp_sth->hdbc, imp_sth->hstmt, DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3), DBIc_LOGPIO(imp_sth)); } else if (phs->described_sql_type == 0) { /* unknown SQL type */ /* pretend it failed */ phs->describe_param_status = SQL_ERROR; phs->sql_type = default_parameter_type( "SQLDescribeParam returned unknown SQL type", imp_sth, phs); } else { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) PerlIO_printf(DBIc_LOGPIO(imp_dbh), " SQLDescribeParam %s: SqlType=%s(%d) " "param_size=%ld Scale=%d Nullable=%d\n", phs->name, S_SqlTypeToString(phs->described_sql_type), phs->described_sql_type, (unsigned long)phs->param_size, ibScale, fNullable); /* * for non-integral numeric types, let the driver/database handle * the conversion for us */ switch(phs->described_sql_type) { case SQL_NUMERIC: case SQL_DECIMAL: case SQL_FLOAT: case SQL_REAL: case SQL_DOUBLE: if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE3(imp_dbh, " Param %s is numeric SQL type %s " "(param size:%lu) changed to SQL_VARCHAR\n", phs->name, S_SqlTypeToString(phs->described_sql_type), (unsigned long)phs->param_size); phs->sql_type = SQL_VARCHAR; break; default: phs->sql_type = phs->described_sql_type; } } } else if (phs->describe_param_called) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_dbh, " SQLDescribeParam already run and returned rc=%d\n", phs->describe_param_status); } if (phs->requested_type != 0) { phs->sql_type = phs->requested_type; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) TRACE1(imp_dbh, " Overriding sql type with requested type %d\n", phs->requested_type); } #if defined(WITH_UNICODE) /* for Unicode string types, change value_type to SQL_C_WCHAR*/ switch (phs->sql_type) { case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: case MS_SQLS_XML_TYPE: /* SQL Server XML Type */ phs->value_type = SQL_C_WCHAR; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) { TRACE0(imp_dbh, " get_param_type: modified value type to SQL_C_WCHAR\n"); } break; } #endif /* WITH_UNICODE */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) TRACE0(imp_dbh, " -get_param_type\n"); } /*======================================================================*/ /* */ /* rebind_param */ /* ============ */ /* */ /*======================================================================*/ static int rebind_param( SV *sth, imp_sth_t *imp_sth, phs_t *phs) { D_imp_dbh_from_sth; SQLRETURN rc; SQLULEN default_column_size; STRLEN value_len = 0; /* args of SQLBindParameter() call */ SQLSMALLINT param_io_type; /* SQL_PARAM_INPUT_OUTPUT || SQL_PARAM_INPUT */ SQLSMALLINT value_type; /* C data type of parameter */ UCHAR *value_ptr; /* ptr to actual parameter data */ SQLULEN column_size; /* size of column/expression of the parameter */ SQLSMALLINT d_digits; /* decimal digits of parameter */ SQLLEN buffer_length; /* length in bytes of parameter buffer */ SQLLEN strlen_or_ind; /* parameter length or indicator */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { PerlIO_printf( DBIc_LOGPIO(imp_dbh), " +rebind_param %s %.100s (size svCUR=%d/SvLEN=%d/max=%ld) " "svtype %u, value type:%d sql type:%d\n", phs->name, neatsvpv(phs->sv, 0), SvOK(phs->sv) ? SvCUR(phs->sv) : -1, SvOK(phs->sv) ? SvLEN(phs->sv) : -1 ,phs->maxlen, SvTYPE(phs->sv), phs->value_type, phs->sql_type); } if (phs->is_inout) { /* * At the moment we always do sv_setsv() and rebind. * Later we may optimise this so that more often we can * just copy the value & length over and not rebind. */ if (SvREADONLY(phs->sv)) Perl_croak(aTHX_ "%s", PL_no_modify); /* phs->sv _is_ the real live variable, it may 'mutate' later */ /* pre-upgrade high to reduce risk of SvPVX realloc/move */ (void)SvUPGRADE(phs->sv, SVt_PVNV); /* ensure room for result, 28 is magic number (see sv_2pv) */ #if defined(WITH_UNICODE) SvGROW(phs->sv, (phs->maxlen + sizeof(SQLWCHAR) < 28) ? 28 : phs->maxlen + sizeof(SQLWCHAR)); #else SvGROW(phs->sv, (phs->maxlen < 28) ? 28 : phs->maxlen+1); #endif /* WITH_UNICODE */ } else { /* phs->sv is copy of real variable, upgrade to at least string */ (void)SvUPGRADE(phs->sv, SVt_PV); } /* * At this point phs->sv must be at least a PV with a valid buffer, * even if it's undef (null) */ if (SvOK(phs->sv)) { phs->sv_buf = SvPV(phs->sv, value_len); } else { /* it's undef but if it was inout param it would point to a * valid buffer, at least */ phs->sv_buf = SvPVX(phs->sv); value_len = 0; } get_param_type(sth, imp_sth, phs); #if defined(WITH_UNICODE) if (phs->value_type == SQL_C_WCHAR) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) { TRACE1(imp_dbh, " Need to modify phs->sv in place: old length = %i\n", value_len); } /* Convert the sv in place to UTF-16 encoded characters NOTE: the SV_toWCHAR may modify SvPV(phs->sv */ if (SvOK(phs->sv)) { SV_toWCHAR(phs->sv); /* get new buffer and length */ phs->sv_buf = SvPV(phs->sv, value_len); } else { /* it is undef */ /* need a valid buffer at least */ phs->sv_buf = SvPVX(phs->sv); value_len = 0; } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 8)) { TRACE1(imp_dbh, " Need to modify phs->sv in place: new length = %i\n", value_len); } } /* value_len has current value length now */ phs->sv_type = SvTYPE(phs->sv); /* part of mutation check */ phs->maxlen = SvLEN(phs->sv) - sizeof(SQLWCHAR); /* avail buffer space */ #else /* !WITH_UNICODE */ /* value_len has current value length now */ phs->sv_type = SvTYPE(phs->sv); /* part of mutation check */ phs->maxlen = SvLEN(phs->sv) - 1; /* avail buffer space */ #endif /* WITH_UNICODE */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { PerlIO_printf( DBIc_LOGPIO(imp_dbh), " bind %s %.100s value_len=%d maxlen=%ld null=%d)\n", phs->name, neatsvpv(phs->sv, value_len), value_len,(long)phs->maxlen, SvOK(phs->sv) ? 0 : 1); } /* * JLU: was SQL_PARAM_OUTPUT only, but that caused a problem with * Oracle's drivers and in/out parameters. Can't be output only * with Oracle. */ param_io_type = phs->is_inout ? SQL_PARAM_INPUT_OUTPUT : SQL_PARAM_INPUT; value_type = phs->value_type; d_digits = value_len; column_size = phs->is_inout ? phs->maxlen : value_len; /* per patch from Paul G. Weiss, who was experiencing re-preparing * of queries when the size of the bound string's were increasing * for example select * from tabtest where name = ? * then executing with 'paul' and then 'thomas' would cause * SQLServer to prepare the query twice, but if we ran 'thomas' * then 'paul', it would not re-prepare the query. The key seems * to be allocating enough space for the largest parameter. * TBD: the default for this should be a DBD::ODBC specific option * or attribute. */ if (phs->sql_type == SQL_VARCHAR && !phs->is_inout) { d_digits = 0; /* default to at least 80 if this is the first time through */ if (phs->biggestparam == 0) { phs->biggestparam = (value_len > 80) ? value_len : 80; } else { /* bump up max, if needed */ if (value_len > phs->biggestparam) { phs->biggestparam = value_len; } } } if ((phs->describe_param_called == 1) && (SQL_SUCCEEDED(phs->describe_param_status)) && (phs->requested_type == 0)) { /* type not overriden */ default_column_size = phs->param_size; } else if (phs->is_inout) { default_column_size = phs->maxlen; } else { if (phs->sql_type == SQL_VARCHAR) { default_column_size = phs->biggestparam; } else { default_column_size = value_len; } } /* Default buffer_length to specified output length or actual input length */ buffer_length = phs->is_inout ? phs->maxlen : value_len; /* When we fill a LONGVARBINARY, the CTYPE must be set to SQL_C_BINARY */ if (value_type == SQL_C_CHAR) { /* could be changed by bind_plh */ d_digits = 0; /* not relevent to char types */ switch(phs->sql_type) { case SQL_LONGVARBINARY: case SQL_BINARY: case SQL_VARBINARY: value_type = SQL_C_BINARY; column_size = default_column_size; break; #ifdef SQL_WLONGVARCHAR case SQL_WLONGVARCHAR: /* added for SQLServer 7 ntext type */ #endif case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: column_size = default_column_size; break; case SQL_DATE: case SQL_TYPE_DATE: case SQL_TIME: case SQL_TYPE_TIME: break; case SQL_TIMESTAMP: case SQL_TYPE_TIMESTAMP: d_digits = 0; /* tbd: millisecondS?) */ if (SvOK(phs->sv)) { /* Work out decimal digits value from milliseconds */ char *cp; if (phs->sv_buf && *phs->sv_buf) { cp = strchr(phs->sv_buf, '.'); if (cp) { ++cp; while (*cp != '\0' && isdigit(*cp)) { cp++; d_digits++; } } } } /* * 23 is YYYY-MM-DD HH:MM:SS.sss * We have to be really careful here to maintain the column size * whether we are passing NULL/undef or not as the ODBC driver * only has the values we pass to SQLBindParameter to go on and * cannot know until execute time whether we are passing a NULL or * not (i.e., although we pass the strlen_or_ind value - last arg to * SQLBindParameter with a length or SQL_NULL_DATA, this is a ptr * arg and the driver cannot look at it until execute time). * We may know we are going to pass a NULL but if we reduce the * the column size to 0 (or as this function used to do - 1) the * driver might decide it is not a full datetime and decide to * bind as a smalldatetime etc. In fact there is a test for * MS SQL Server in 20SqlServer which binds a datetime and passes * a NULL, a full datetime and lastly a NULL and if we don't maintain * 23 for the first NULL MS SQL Server decides it is a smalldatetime * and we lose the SS.sss in any full datetime passed later. */ column_size = 23; break; default: break; } } else if ( value_type == SQL_C_WCHAR) { d_digits = 0; } if (!SvOK(phs->sv)) { strlen_or_ind = SQL_NULL_DATA; /* if is_inout, shouldn't we null terminate the buffer and send * it, instead?? */ if (!phs->is_inout) { /* * We have to be really careful here to maintain the column size * whether we are passing NULL/undef or not as the ODBC driver * only has the values we pass to SQLBindParameter to go on and * cannot know until execute time whether we are passing a NULL or * not (i.e., although we pass the strlen_or_ind value - last arg to * SQLBindParameter with a length or SQL_NULL_DATA, this is a ptr * arg and the driver cannot look at it until execute time). * We may know we are going to pass a NULL but if we reduce the * the column size to 0 (or as this function used to do - 1) the * driver might decide it is a different type (e.g., smalldatetime * instead of datetime). * In fact there is a test for MS SQL Server in 20SqlServer which * binds a datetime and passes a NULL, a full datetime and lastly a * NULL and if we don't maintain the column_size for the first NULL * MS SQL Server decides it is a smalldatetime and we lose the * SS.sss in any full datetime passed later despite setting a correct * column_size and decimal digits. */ /* column_size = 1; Used to be this but see comment above */ /* * However, at this stage we could have column_size of 0 and * that is no good either or we'll get invalid precision */ if (column_size == 0) column_size = 1; } if (phs->is_inout) { if (!phs->sv_buf) { croak("panic: DBD::ODBC binding undef with bad buffer!!!!"); } /* just in case, we *know* we called SvGROW above */ phs->sv_buf[0] = '\0'; /* patch for binding undef inout params on sql server */ d_digits = 1; value_ptr = phs->sv_buf; } else { value_ptr = NULL; } } else { value_ptr = phs->sv_buf; strlen_or_ind = value_len; /* not undef, may be a blank string or something */ if (!phs->is_inout && strlen_or_ind == 0) { column_size = 1; } } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { PerlIO_printf(DBIc_LOGPIO(imp_dbh), " bind %s value_type:%d %s cs=%lu dd=%d bl=%ld\n", phs->name, value_type, S_SqlTypeToString(phs->sql_type), (unsigned long)column_size, d_digits, buffer_length); } if (value_len < imp_sth->odbc_putdata_start) { /* already set and should be left alone JLU */ /* d_digits = value_len; */ } else { SQLLEN vl = value_len; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_dbh, " using data_at_exec for size %lu\n", (unsigned long)value_len); d_digits = 0; /* not relevant to lobs */ strlen_or_ind = SQL_LEN_DATA_AT_EXEC(vl); value_ptr = (UCHAR*) phs; } #if THE_FOLLOWING_CODE_IS_FLAWED_AND_BROKEN /* * value_ptr is not null terminated - it is a byte array so PVallocW * won't work as it works on null terminated strings */ #if defined(WITH_UNICODE) if (value_type==SQL_C_WCHAR) { char * c1; c1 = PVallocW((SQLWCHAR *)value_ptr); TRACE1(imp_dbh, " Param value = L'%s'\n", c1); PVfreeW(c1); } #endif /* WITH_UNICODE */ #endif /* * The following code is a workaround for a problem in SQL Server * when inserting more than 400K into varbinary(max) or varchar(max) * columns. The older SQL Server driver (not the native client driver): * * o reports the size of xxx(max) columns as 2147483647 bytes in size * when in reality they can be a lot bigger than that. * o if you bind more than 400K you get the following errors: * (HY000, 0, [Microsoft][ODBC SQL Server Driver] * Warning: Partial insert/update. The insert/update of a text or * image column(s) did not succeed.) * (42000, 7125, [Microsoft][ODBC SQL Server Driver][SQL Server] * The text, ntext, or image pointer value conflicts with the column * name specified.) * * There appear to be 2 workarounds but I was not prepared to do the first. * The first is simply to set the indicator to SQL_LEN_DATA_AT_EXEC(409600) * if the parameter was larger than 409600 - miraculously it works but * shouldn't according to MSDN. * The second workaround (used here) is to set the indicator to * SQL_LEN_DATA_AT_EXEC(0) and the buffer_length to 0. * */ if ((imp_dbh->driver_type == DT_SQL_SERVER) && ((phs->sql_type == SQL_LONGVARCHAR) || (phs->sql_type == SQL_LONGVARBINARY) || (phs->sql_type == SQL_WLONGVARCHAR)) && /*(column_size == 2147483647) && (strlen_or_ind < 0) &&*/ ((-strlen_or_ind + SQL_LEN_DATA_AT_EXEC_OFFSET) >= 409600)) { strlen_or_ind = SQL_LEN_DATA_AT_EXEC(0); buffer_length = 0; } #if defined(WITH_UNICODE) /* * rt43384 - MS Access does not seem to like us binding parameters as * wide characters and then SQLBindParameter column_size to byte length. * e.g., if you have a text(255) column and try and insert 190 ascii chrs * then the unicode enabled version of DBD::ODBC will convert those 190 * ascii chrs to wide chrs and hence double the size to 380. If you pass * 380 to Access for column_size it just returns an invalid precision * value. This changes to column_size to chrs instead of bytes but * only if column_size is not reduced to 0 - which also produces * an access error e.g., in the empty string '' case. */ else if (((imp_dbh->driver_type == DT_MS_ACCESS_JET) || (imp_dbh->driver_type == DT_MS_ACCESS_ACE)) && (value_type == SQL_C_WCHAR) && (column_size > 1)) { column_size = column_size / 2; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " MSAccess - setting chrs not bytes\n"); } #endif /* * workaround bug in SQL Server ODBC driver where it can describe some * parameters (especially in SQL using sub selects) the wrong way. * If this is a varchar then the column_size must be at least as big * as the buffer size but if SQL Server associated the wrong column with * our parameter it could get a totally different size. Without this * a varchar(10) column can be desribed as a varchar(n) where n is less * than 10 and this leads to data truncation errors - see rt 39841. */ if (((imp_dbh->driver_type == DT_SQL_SERVER) || (imp_dbh->driver_type == DT_SQL_SERVER_NATIVE_CLIENT)) && (phs->sql_type == SQL_VARCHAR) && (column_size < buffer_length)) { column_size = buffer_length; } /* * Yet another workaround for SQL Server native client. * If you have a varbinary(max), varchar(max) or nvarchar(max) you have to * pass 0 for the column_size or you get HY104 "Invalid precision value". * See rt_38977.t which causes this. * The versions of native client I've seen this with are: * 2007.100.1600.22 sqlncli10.dll driver version = ? * 2005.90.1399.00 SQLNCLI.DLL driver version = 09.00.1399 * * Update, for nvarchar(max) it does not seem to simply be a driver issue * as with the Easysoft SQL Server ODBC Driver going to Microsoft SQL Server * 09.00.1399 we got the following error for all sizes between 4001 and 8000 * (inclusive). * [SQL Server]The size (4001) given to the parameter '@P1' exceeds the * maximum allowed (4000) * * So to sum up for the native client when the parameter size is 0 or * when the database is sql server and wchar and sql type not overwritten * we need to use column size 0. We cannot do this if the requested_type * was specified as if someone specifies a bind type we haven't called * SQLDescribeParam and it looks like param_size = 0 even when it is * not a xxx(max). e.g., the 40UnicodeRoundTrip tests will fail with * MS SQL Server because they override the type. */ if ((phs->param_size == 0) && (SQL_SUCCEEDED(phs->describe_param_status)) && (imp_sth->odbc_describe_parameters)) { /* SQLDescribeParam not disabled */ /* no point in believing param_size = 0 if SQLDescribeParam failed */ /* See rt 55736 */ if ((imp_dbh->driver_type == DT_SQL_SERVER_NATIVE_CLIENT) || ((strcmp(imp_dbh->odbc_dbms_name, "Microsoft SQL Server") == 0) && (phs->sql_type == SQL_WVARCHAR) && (phs->requested_type == 0))) { column_size = 0; printf("HERE\n"); } } /* for rt_38977 we get: * sloi = -500100 ps=0 sqlt=12 (SQL_VARCHAR) * sloi = -500100 ps=0 sqlt=-3 (SQL_VARBINARY) * sloi = 4001 ps=0 sqlt=-9 (SQL_WVARCHAR) <--- this one fails without above */ /*printf("sloi = %d ps=%d sqlt=%d\n", strlen_or_ind, phs->param_size, phs->sql_type);*/ /* * Avoid calling SQLBindParameter again if nothing has changed. * Why, because a) there is no point and b) MS SQL Server will * re-prepare the statement. */ /* phs' copy of strlen_or_ind is permanently allocated and the other strlen_or_ind is an automatic variable and won't survive this func but needs to. */ phs->strlen_or_ind = strlen_or_ind; if ((param_io_type == SQL_PARAM_INPUT_OUTPUT) || (!phs->bp_value_ptr) || /* not bound before */ ((param_io_type == SQL_PARAM_INPUT) && /* input parameter */ ((value_ptr != phs->bp_value_ptr) || (value_type != phs->value_type) || (column_size != phs->bp_column_size) || (d_digits != phs->bp_d_digits) || (buffer_length != phs->bp_buffer_length)))) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) { PerlIO_printf( DBIc_LOGPIO(imp_dbh), " SQLBindParameter: idx=%d: io_type=%d, name=%s, " "value_type=%d (%s), SQLType=%d (%s), column_size=%lu, " "d_digits=%d, value_ptr=%p, buffer_length=%ld, ind=%ld, " "param_size=%lu\n", phs->idx, param_io_type, phs->name, value_type, S_SqlCTypeToString(value_type), phs->sql_type, S_SqlTypeToString(phs->sql_type), (unsigned long)column_size, d_digits, value_ptr, (long)buffer_length, (long)strlen_or_ind, (unsigned long)phs->param_size); /* avoid tracing data_at_exec as value_ptr will point to phs */ if ((value_type == SQL_C_CHAR) && (strlen_or_ind > 0)) { TRACE1(imp_sth, " Param value = %s\n", value_ptr); } } #ifdef FRED printf("SQLBindParameter idx=%d pt=%d vt=%d, st=%d, cs=%lu dd=%d vp=%p bl=%ld slorind=%ld %s\n", phs->idx, param_io_type, value_type, phs->sql_type, (unsigned long)column_size, d_digits, value_ptr, buffer_length, (long)phs->strlen_or_ind, value_ptr); #endif rc = SQLBindParameter(imp_sth->hstmt, phs->idx, param_io_type, value_type, phs->sql_type, column_size, d_digits, value_ptr, buffer_length, &phs->strlen_or_ind); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "rebind_param/SQLBindParameter"); phs->bp_value_ptr = NULL; return 0; } phs->bp_value_ptr = value_ptr; phs->value_type = value_type; phs->bp_column_size = column_size; phs->bp_d_digits = d_digits; phs->bp_buffer_length = buffer_length; } else if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 5)) { TRACE1(imp_sth, " Not rebinding param %d - no change\n", phs->idx); } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " -rebind_param\n"); return 1; } int dbd_st_bind_col( SV *sth, imp_sth_t *imp_sth, SV *col, SV *ref, IV type, SV *attribs) { dTHX; int field; if (!SvIOK(col)) { croak ("Invalid column number") ; } field = SvIV(col); if ((field < 1) || (field > DBIc_NUM_FIELDS(imp_sth))) { croak("cannot bind to non-existent field %d", field); } imp_sth->fbh[field-1].req_type = type; imp_sth->fbh[field-1].bind_flags = 0; /* default to none */ /* DBIXS 13590 added StrictlyTyped and DiscardString attributes */ if (attribs) { SV **svp; DBD_ATTRIBS_CHECK("dbd_st_bind_col", sth, attribs); if (DBD_ATTRIB_TRUE(attribs, "TreatAsLOB", 10, svp)) { imp_sth->fbh[field-1].bind_flags |= ODBC_TREAT_AS_LOB; } #if DBIXS_REVISION >= 13590 if (DBD_ATTRIB_TRUE(attribs, "StrictlyTyped", 13, svp)) { imp_sth->fbh[field-1].bind_flags |= DBIstcf_STRICT; } if (DBD_ATTRIB_TRUE(attribs, "DiscardString", 13, svp)) { imp_sth->fbh[field-1].bind_flags |= DBIstcf_DISCARD_STRING; } #endif /* DBIXS_REVISION >= 13590 */ } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE3(imp_sth, " bind_col %d requested type:%"IVdf", flags:%lx\n", field, imp_sth->fbh[field-1].req_type, imp_sth->fbh[field-1].bind_flags); } return 1; } /*------------------------------------------------------------ * bind placeholder. * Is called from ODBC.xs execute() * AND from ODBC.xs bind_param() */ int dbd_bind_ph( SV *sth, imp_sth_t *imp_sth, SV *ph_namesv, SV *newvalue, IV in_sql_type, SV *attribs, int is_inout, IV maxlen) { SV **phs_svp; STRLEN name_len; char *name; char namebuf[30]; phs_t *phs; D_imp_dbh_from_sth; SQLSMALLINT sql_type; D_imp_xxh(sth); if (SQL_NULL_HDBC == imp_dbh->hdbc) { DBIh_SET_ERR_CHAR(sth, imp_xxh, Nullch, 1, "Database handle has been disconnected", Nullch, Nullch); return -2; } sql_type = (SQLSMALLINT)in_sql_type; if (SvNIOK(ph_namesv) ) { /* passed as a number */ name = namebuf; my_snprintf(name, sizeof(namebuf), "%d", (int)SvIV(ph_namesv)); name_len = strlen(name); } else { name = SvPV(ph_namesv, name_len); } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { PerlIO_printf( DBIc_LOGPIO(imp_dbh), " +dbd_bind_ph(%p, name=%s, value=%.200s, attribs=%s, " "sql_type=%d(%s), is_inout=%d, maxlen=%ld\n", sth, name, SvOK(newvalue) ? neatsvpv(newvalue, 0) : "undef", attribs ? SvPV_nolen(attribs) : "", sql_type, S_SqlTypeToString(sql_type), is_inout, maxlen); } /* the problem with the code below is we are getting SVt_PVLV when * an "undef" value from a hash lookup that doesn't exist. It's an * "undef" value, but it doesn't come in as a scalar. * from a hash is arriving. Let's leave this out until we are * handling arrays. JLU 7/12/02 */ #if 0 if (SvTYPE(newvalue) > SVt_PVMG) { /* hook for later array logic */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE2(imp_dbh, " !!bind %s perl type = %d -- croaking!\n", name, SvTYPE(newvalue)); croak("Can't bind non-scalar value (currently)"); } #endif /* * all_params_hv created during dbd_preparse. */ phs_svp = hv_fetch(imp_sth->all_params_hv, name, (I32)name_len, 0); if (phs_svp == NULL) croak("Can't bind unknown placeholder '%s'", name); phs = (phs_t*)SvPVX(*phs_svp); /* placeholder struct */ if (phs->sv == &PL_sv_undef) { /* first bind for this placeholder */ phs->value_type = SQL_C_CHAR; /* default */ phs->requested_type = sql_type; /* save type requested */ phs->maxlen = maxlen; /* 0 if not inout */ phs->is_inout = is_inout; if (is_inout) { phs->sv = SvREFCNT_inc(newvalue); /* point to live var */ imp_sth->has_inout_params++; /* build array of phs's so we can deal with out vars fast */ if (!imp_sth->out_params_av) imp_sth->out_params_av = newAV(); av_push(imp_sth->out_params_av, SvREFCNT_inc(*phs_svp)); } } else if (sql_type) { /* parameter attributes are supposed to be sticky until overriden so only replace requested_type if sql_type specified. See https://rt.cpan.org/Ticket/Display.html?id=46597 */ phs->requested_type = sql_type; /* save type requested */ } else if (is_inout != phs->is_inout) { croak("Can't rebind or change param %s in/out mode after first bind " "(%d => %d)", phs->name, phs->is_inout, is_inout); } else if (maxlen && maxlen > phs->maxlen) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) PerlIO_printf(DBIc_LOGPIO(imp_dbh), "!attempt to change param %s maxlen (%ld->%ld)\n", phs->name, phs->maxlen, maxlen); croak("Can't change param %s maxlen (%ld->%ld) after first bind", phs->name, phs->maxlen, maxlen); } if (!is_inout) { /* normal bind to take a (new) copy of current value */ if (phs->sv == &PL_sv_undef) /* (first time bind) */ phs->sv = newSV(0); sv_setsv(phs->sv, newvalue); } else if (newvalue != phs->sv) { if (phs->sv) SvREFCNT_dec(phs->sv); phs->sv = SvREFCNT_inc(newvalue); /* point to live var */ } if (imp_dbh->odbc_defer_binding) { get_param_type(sth, imp_sth, phs); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " -dbd_bind_ph=1\n"); return 1; } /* fall through for "immediate" binding */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " -dbd_bind_ph=rebind_param\n"); return rebind_param(sth, imp_sth, phs); } /*------------------------------------------------------------ * blob_read: * read part of a BLOB from a table. * XXX needs more thought */ int dbd_st_blob_read(sth, imp_sth, field, offset, len, destrv, destoffset) SV *sth; imp_sth_t *imp_sth; int field; long offset; long len; SV *destrv; long destoffset; { SQLLEN retl; SV *bufsv; RETCODE rc; croak("blob_read not supported yet"); bufsv = SvRV(destrv); sv_setpvn(bufsv,"",0); /* ensure it's writable string */ SvGROW(bufsv, len+destoffset+1); /* SvGROW doesn't do +1 */ /* XXX for this to work be probably need to avoid calling SQLGetData in * fetch. The definition of SQLGetData doesn't work well with the DBI's * notion of how LongReadLen would work. Needs more thought. */ rc = SQLGetData(imp_sth->hstmt, (SQLSMALLINT)(field+1), SQL_C_BINARY, ((UCHAR *)SvPVX(bufsv)) + destoffset, len, &retl ); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) PerlIO_printf( DBIc_LOGPIO(imp_sth), "SQLGetData(...,off=%ld, len=%ld)->rc=%d,len=%ld SvCUR=%d\n", destoffset, len, rc, (long)retl, SvCUR(bufsv)); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "dbd_st_blob_read/SQLGetData"); return 0; } if (rc == SQL_SUCCESS_WITH_INFO) { /* XXX should check for 01004 */ retl = len; } if (retl == SQL_NULL_DATA) { /* field is null */ (void)SvOK_off(bufsv); return 1; } #ifdef SQL_NO_TOTAL if (retl == SQL_NO_TOTAL) { /* unknown length! */ (void)SvOK_off(bufsv); return 0; } #endif SvCUR_set(bufsv, destoffset+retl); *SvEND(bufsv) = '\0'; /* consistent with perl sv_setpvn etc */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) TRACE1(imp_sth, " blob_read: SvCUR=%d\n", SvCUR(bufsv)); return 1; } /*======================================================================*/ /* */ /* S_db_storeOptions */ /* ================= */ /* S_db_fetchOptions */ /* ================= */ /* */ /* An array of options/attributes we support on database handles for */ /* storing and fetching. */ /* */ /*======================================================================*/ typedef struct { const char *str; UWORD fOption; UDWORD atrue; UDWORD afalse; } db_params; static db_params S_db_storeOptions[] = { { "AutoCommit", SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_ON, SQL_AUTOCOMMIT_OFF }, { "ReadOnly", SQL_ATTR_ACCESS_MODE, SQL_MODE_READ_ONLY, SQL_MODE_READ_WRITE}, #if 0 /* not defined by DBI/DBD specification */ { "TRANSACTION", SQL_ACCESS_MODE, SQL_MODE_READ_ONLY, SQL_MODE_READ_WRITE }, { "solid_trace", SQL_OPT_TRACE, SQL_OPT_TRACE_ON, SQL_OPT_TRACE_OFF }, { "solid_timeout", SQL_LOGIN_TIMEOUT }, { "ISOLATION", SQL_TXN_ISOLATION }, { "solid_tracefile", SQL_OPT_TRACEFILE }, #endif { "odbc_SQL_ROWSET_SIZE", SQL_ROWSET_SIZE }, { "odbc_ignore_named_placeholders", ODBC_IGNORE_NAMED_PLACEHOLDERS }, { "odbc_default_bind_type", ODBC_DEFAULT_BIND_TYPE }, { "odbc_force_bind_type", ODBC_FORCE_BIND_TYPE }, { "odbc_force_rebind", ODBC_FORCE_REBIND }, { "odbc_async_exec", ODBC_ASYNC_EXEC }, { "odbc_err_handler", ODBC_ERR_HANDLER }, { "odbc_exec_direct", ODBC_EXEC_DIRECT }, { "odbc_version", ODBC_VERSION }, { "odbc_cursortype", ODBC_CURSORTYPE }, { "odbc_query_timeout", ODBC_QUERY_TIMEOUT }, { "odbc_putdata_start", ODBC_PUTDATA_START }, { "odbc_column_display_size", ODBC_COLUMN_DISPLAY_SIZE }, { "odbc_utf8_on", ODBC_UTF8_ON }, { "odbc_old_unicode", ODBC_OLD_UNICODE }, { "odbc_describe_parameters", ODBC_DESCRIBE_PARAMETERS }, { NULL }, }; static db_params S_db_fetchOptions[] = { { "AutoCommit", SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_ON, SQL_AUTOCOMMIT_OFF }, { "ReadOnly", SQL_ATTR_ACCESS_MODE, SQL_MODE_READ_ONLY, SQL_MODE_READ_WRITE}, { "RowCacheSize", ODBC_ROWCACHESIZE }, { "odbc_SQL_ROWSET_SIZE", SQL_ROWSET_SIZE }, { "odbc_SQL_DRIVER_ODBC_VER", SQL_DRIVER_ODBC_VER }, { "odbc_ignore_named_placeholders", ODBC_IGNORE_NAMED_PLACEHOLDERS }, { "odbc_default_bind_type", ODBC_DEFAULT_BIND_TYPE }, { "odbc_force_bind_type", ODBC_FORCE_BIND_TYPE }, { "odbc_force_rebind", ODBC_FORCE_REBIND }, { "odbc_async_exec", ODBC_ASYNC_EXEC }, { "odbc_err_handler", ODBC_ERR_HANDLER }, { "odbc_SQL_DBMS_NAME", SQL_DBMS_NAME }, { "odbc_exec_direct", ODBC_EXEC_DIRECT }, { "odbc_query_timeout", ODBC_QUERY_TIMEOUT}, { "odbc_putdata_start", ODBC_PUTDATA_START}, { "odbc_column_display_size", ODBC_COLUMN_DISPLAY_SIZE}, { "odbc_utf8_on", ODBC_UTF8_ON}, { "odbc_has_unicode", ODBC_HAS_UNICODE}, { "odbc_out_connect_string", ODBC_OUTCON_STR}, { "odbc_describe_parameters", ODBC_DESCRIBE_PARAMETERS}, { NULL } }; /*======================================================================*/ /* */ /* S_dbOption */ /* ========== */ /* */ /* Given a string and a length, locate this option in the specified */ /* array of valid options. Typically used by STORE and FETCH methods */ /* to decide if this option/attribute is supported by us. */ /* */ /*======================================================================*/ static const db_params * S_dbOption(const db_params *pars, char *key, STRLEN len) { /* search option to set */ while (pars->str != NULL) { if (strncmp(pars->str, key, len) == 0 && len == strlen(pars->str)) break; pars++; } if (pars->str == NULL) { return NULL; } return pars; } /*======================================================================*/ /* */ /* dbd_db_STORE_attrib */ /* =================== */ /* */ /* This function handles: */ /* */ /* $dbh->{$key} = $value */ /* */ /* Method to handle the setting of driver specific attributes and DBI */ /* attributes AutoCommit and ChopBlanks (no other DBI attributes). */ /* */ /* Return TRUE if the attribute was handled, else FALSE. */ /* */ /*======================================================================*/ int dbd_db_STORE_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv) { RETCODE rc; STRLEN kl; STRLEN plen; char *key = SvPV(keysv,kl); int on; SQLPOINTER vParam; const db_params *pars; SQLINTEGER attr_length = SQL_IS_UINTEGER; int bSetSQLConnectionOption; if ((pars = S_dbOption(S_db_storeOptions, key, kl)) == NULL) { if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " !!DBD::ODBC unsupported attribute passed (%s)\n", key); return FALSE; } bSetSQLConnectionOption = TRUE; switch(pars->fOption) { case SQL_ATTR_LOGIN_TIMEOUT: case SQL_ATTR_TXN_ISOLATION: case SQL_ROWSET_SIZE: /* not ODBC 3 */ vParam = (SQLPOINTER)SvIV(valuesv); break; case SQL_OPT_TRACEFILE: vParam = (SQLPOINTER) SvPV(valuesv, plen); attr_length = SQL_NTS; break; case ODBC_IGNORE_NAMED_PLACEHOLDERS: bSetSQLConnectionOption = FALSE; /* * set value to ignore placeholders. Will affect all * statements from here on. */ imp_dbh->odbc_ignore_named_placeholders = SvTRUE(valuesv); break; case ODBC_DEFAULT_BIND_TYPE: bSetSQLConnectionOption = FALSE; /* * set value of default bind type. Default is SQL_VARCHAR, * but setting to 0 will cause SQLDescribeParam to be used. */ imp_dbh->odbc_default_bind_type = (SQLSMALLINT)SvIV(valuesv); break; case ODBC_FORCE_BIND_TYPE: bSetSQLConnectionOption = FALSE; /* * set value of the forced bind type. Default is 0 * which means the bind type is not forced to be anything - * we will use SQLDescribeParam or fall back on odbc_default_bind_type */ imp_dbh->odbc_force_bind_type = (SQLSMALLINT)SvIV(valuesv); break; case ODBC_FORCE_REBIND: bSetSQLConnectionOption = FALSE; /* * set value to force rebind */ imp_dbh->odbc_force_rebind = SvTRUE(valuesv); break; case ODBC_QUERY_TIMEOUT: bSetSQLConnectionOption = FALSE; imp_dbh->odbc_query_timeout = (SQLINTEGER)SvIV(valuesv); break; case ODBC_PUTDATA_START: bSetSQLConnectionOption = FALSE; imp_dbh->odbc_putdata_start = SvIV(valuesv); break; case ODBC_COLUMN_DISPLAY_SIZE: bSetSQLConnectionOption = FALSE; imp_dbh->odbc_column_display_size = SvIV(valuesv); break; case ODBC_UTF8_ON: bSetSQLConnectionOption = FALSE; imp_dbh->odbc_utf8_on = SvIV(valuesv); break; case ODBC_EXEC_DIRECT: bSetSQLConnectionOption = FALSE; /* * set value of odbc_exec_direct. Non-zero will * make prepare, essentially a noop and make execute * use SQLExecDirect. This is to support drivers that * _only_ support SQLExecDirect. */ imp_dbh->odbc_exec_direct = SvTRUE(valuesv); break; case ODBC_OLD_UNICODE: bSetSQLConnectionOption = FALSE; imp_dbh->odbc_old_unicode = SvTRUE(valuesv); break; case ODBC_DESCRIBE_PARAMETERS: bSetSQLConnectionOption = FALSE; imp_dbh->odbc_describe_parameters = SvTRUE(valuesv); break; case ODBC_ASYNC_EXEC: bSetSQLConnectionOption = FALSE; /* * set asynchronous execution. It can only be turned on if * the driver supports it, but will fail silently. */ if (SvTRUE(valuesv)) { /* Only bother setting the attribute if it's not already set! */ if (imp_dbh->odbc_async_exec) break; /* * Determine which method of async execution this * driver allows -- per-connection or per-statement */ rc = SQLGetInfo(imp_dbh->hdbc, SQL_ASYNC_MODE, &imp_dbh->odbc_async_type, sizeof(imp_dbh->odbc_async_type), NULL); /* * Normally, we'd do a if (!SQL_ok(rc)) ... here. * Unfortunately, if the driver doesn't support async * mode, it may return an error here. There doesn't * seem to be any other way to check (other than doing * a special check for the SQLSTATE). We'll just default * to doing nothing and not bother checking errors. */ if (imp_dbh->odbc_async_type == SQL_AM_CONNECTION){ /* * Driver has per-connection async option. Set it * now in the dbh. */ if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " Supported AsyncType is SQL_AM_CONNECTION\n"); rc = SQLSetConnectAttr(imp_dbh->hdbc, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_ON, SQL_IS_UINTEGER); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_STORE/SQLSetConnectAttr"); return FALSE; } imp_dbh->odbc_async_exec = 1; } else if (imp_dbh->odbc_async_type == SQL_AM_STATEMENT){ /* * Driver has per-statement async option. Just set * odbc_async_exec and the rest will be handled by * dbd_st_prepare. */ if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " Supported AsyncType is SQL_AM_STATEMENT\n"); imp_dbh->odbc_async_exec = 1; } else { /* (imp_dbh->odbc_async_type == SQL_AM_NONE) */ /* * We're out of luck. */ if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 4)) TRACE0(imp_dbh, " Supported AsyncType is SQL_AM_NONE\n"); imp_dbh->odbc_async_exec = 0; return FALSE; } } else { /* Only bother turning it off if it was previously set... */ if (imp_dbh->odbc_async_exec == 1) { /* We only need to do anything here if odbc_async_type is * SQL_AM_CONNECTION since the per-statement async type * is turned on only when the statement handle is created. */ if (imp_dbh->odbc_async_type == SQL_AM_CONNECTION){ rc = SQLSetConnectAttr(imp_dbh->hdbc, SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_OFF, SQL_IS_UINTEGER); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_STORE/SQLSetConnectAttr"); return FALSE; } } } imp_dbh->odbc_async_exec = 0; } break; case ODBC_ERR_HANDLER: bSetSQLConnectionOption = FALSE; /* This was taken from DBD::Sybase 0.21 */ /* I believe the following if test which has been in DBD::ODBC * for ages is wrong and should (at least now) use SvOK or * it is impossible to reset the error handler * * if(valuesv == &PL_sv_undef) { * imp_dbh->odbc_err_handler = NULL; */ if (!SvOK(valuesv)) { imp_dbh->odbc_err_handler = NULL; } else if(imp_dbh->odbc_err_handler == (SV*)NULL) { imp_dbh->odbc_err_handler = newSVsv(valuesv); } else { sv_setsv(imp_dbh->odbc_err_handler, valuesv); } break; case ODBC_VERSION: /* set only in connect, nothing to store */ bSetSQLConnectionOption = FALSE; break; case ODBC_CURSORTYPE: /* set only in connect, nothing to store */ bSetSQLConnectionOption = FALSE; break; case SQL_ATTR_ACCESS_MODE: on = SvTRUE(valuesv); vParam = (SQLPOINTER)(on ? pars->atrue : pars->afalse); break; default: on = SvTRUE(valuesv); vParam = (SQLPOINTER)(on ? pars->atrue : pars->afalse); break; } if (bSetSQLConnectionOption) { rc = SQLSetConnectAttr(imp_dbh->hdbc, pars->fOption, vParam, attr_length); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "db_STORE/SQLSetConnectAttr"); return FALSE; } if (pars->fOption == SQL_ROWSET_SIZE) imp_dbh->rowset_size = (SQLULEN)vParam; /* keep our flags in sync */ if (kl == 10 && strEQ(key, "AutoCommit")) DBIc_set(imp_dbh, DBIcf_AutoCommit, SvTRUE(valuesv)); } return TRUE; } /*======================================================================*/ /* */ /* dbd_db_FETCH_attrib */ /* =================== */ /* */ /* Counterpart of dbd_db_STORE_attrib handing: */ /* */ /* $value = $dbh->{$key}; */ /* */ /* returns an "SV" with the value */ /* */ /*======================================================================*/ SV *dbd_db_FETCH_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv) { RETCODE rc; STRLEN kl; char *key = SvPV(keysv,kl); SQLUINTEGER vParam = 0; const db_params *pars; SV *retsv = Nullsv; /* checking pars we need FAST */ if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 8)) TRACE1(imp_dbh, " FETCH %s\n", key); if ((pars = S_dbOption(S_db_fetchOptions, key, kl)) == NULL) return Nullsv; switch (pars->fOption) { case ODBC_OUTCON_STR: if (!imp_dbh->out_connect_string) { retsv = &PL_sv_undef; } else { retsv = newSVsv(imp_dbh->out_connect_string); } break; case SQL_DRIVER_ODBC_VER: retsv = newSVpv(imp_dbh->odbc_ver, 0); break; case SQL_DBMS_NAME: retsv = newSVpv(imp_dbh->odbc_dbms_name, 0); break; case ODBC_IGNORE_NAMED_PLACEHOLDERS: retsv = newSViv(imp_dbh->odbc_ignore_named_placeholders); break; case ODBC_QUERY_TIMEOUT: /* * fetch current value of query timeout * * -1 is our internal flag saying odbc_query_timeout has never been * set so we map it back to the default for ODBC which is 0 */ if (imp_dbh->odbc_query_timeout == -1) { retsv = newSViv(0); } else { retsv = newSViv(imp_dbh->odbc_query_timeout); } break; case ODBC_PUTDATA_START: retsv = newSViv(imp_dbh->odbc_putdata_start); break; case ODBC_COLUMN_DISPLAY_SIZE: retsv = newSViv(imp_dbh->odbc_column_display_size); break; case ODBC_UTF8_ON: retsv = newSViv(imp_dbh->odbc_utf8_on); break; case ODBC_HAS_UNICODE: retsv = newSViv(imp_dbh->odbc_has_unicode); break; case ODBC_DEFAULT_BIND_TYPE: retsv = newSViv(imp_dbh->odbc_default_bind_type); break; case ODBC_FORCE_BIND_TYPE: retsv = newSViv(imp_dbh->odbc_force_bind_type); break; case ODBC_FORCE_REBIND: retsv = newSViv(imp_dbh->odbc_force_rebind); break; case ODBC_EXEC_DIRECT: retsv = newSViv(imp_dbh->odbc_exec_direct); break; case ODBC_OLD_UNICODE: retsv = newSViv(imp_dbh->odbc_old_unicode); break; case ODBC_DESCRIBE_PARAMETERS: retsv = newSViv(imp_dbh->odbc_describe_parameters); break; case ODBC_ASYNC_EXEC: /* * fetch current value of asynchronous execution (should be * either 0 or 1). */ retsv = newSViv(imp_dbh->odbc_async_exec); break; case ODBC_ERR_HANDLER: /* fetch current value of the error handler (a coderef). */ if(imp_dbh->odbc_err_handler) { retsv = newSVsv(imp_dbh->odbc_err_handler); } else { retsv = &PL_sv_undef; } break; case ODBC_ROWCACHESIZE: retsv = newSViv(imp_dbh->RowCacheSize); break; default: /* * The remainders we support are ODBC attributes like * AutoCommit (SQL_AUTOCOMMIT) * odbc_SQL_ROWSET_SIZE (SQL_ROWSET_SIZE) * * Nothing else should get here for now unless any item is added * to S_db_fetchOptions. */ rc = SQLGetConnectAttr( imp_dbh->hdbc, pars->fOption, &vParam, SQL_IS_UINTEGER, 0); if (pars->fOption != SQL_ROWSET_SIZE) dbd_error(dbh, rc, "db_FETCH/SQLGetConnectAttr"); if (!SQL_SUCCEEDED(rc)) { if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " !!SQLGetConnectAttr=%d in dbd_db_FETCH\n", rc); if (pars->fOption == SQL_ROWSET_SIZE) { AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, 0, DBIc_LOGPIO(imp_dbh)); vParam = imp_dbh->rowset_size; } return Nullsv; } switch(pars->fOption) { case SQL_ROWSET_SIZE: retsv = newSViv(vParam); break; default: if (vParam == pars->atrue) retsv = newSViv(1); else retsv = newSViv(0); break; } /* inner switch */ } /* outer switch */ return sv_2mortal(retsv); } /*======================================================================*/ /* */ /* S_st_fetch_params */ /* ================= */ /* S_st_store_params */ /* ================= */ /* */ /* An array of options/attributes we support on statement handles for */ /* storing and fetching. */ /* */ /*======================================================================*/ /* * added "need_describe" flag to handle the situation where you don't * have a result set yet to describe. Certain attributes don't need * the result set to operate, hence don't do a describe unless you need * to do one. * DBD::ODBC 0.45_15 * */ typedef struct { const char *str; unsigned len:8; unsigned array:1; unsigned need_describe:1; unsigned filler:22; } T_st_params; #define s_A(str,need_describe) { str, sizeof(str)-1,0,need_describe } static T_st_params S_st_fetch_params[] = { s_A("NUM_OF_PARAMS",1), /* 0 */ s_A("NUM_OF_FIELDS",1), /* 1 */ s_A("NAME",1), /* 2 */ s_A("NULLABLE",1), /* 3 */ s_A("TYPE",1), /* 4 */ s_A("PRECISION",1), /* 5 */ s_A("SCALE",1), /* 6 */ s_A("sol_type",1), /* 7 */ s_A("sol_length",1), /* 8 */ s_A("CursorName",1), /* 9 */ s_A("odbc_more_results",1), /* 10 */ s_A("ParamValues",0), /* 11 */ s_A("LongReadLen",0), /* 12 */ s_A("odbc_ignore_named_placeholders",0), /* 13 */ s_A("odbc_default_bind_type",0), /* 14 */ s_A("odbc_force_rebind",0), /* 15 */ s_A("odbc_query_timeout",0), /* 16 */ s_A("odbc_putdata_start",0), /* 17 */ s_A("ParamTypes",0), /* 18 */ s_A("odbc_column_display_size",0), /* 19 */ s_A("odbc_force_bind_type",0), /* 20 */ s_A("",0), /* END */ }; static T_st_params S_st_store_params[] = { s_A("odbc_ignore_named_placeholders",0), /* 0 */ s_A("odbc_default_bind_type",0), /* 1 */ s_A("odbc_force_rebind",0), /* 2 */ s_A("odbc_query_timeout",0), /* 3 */ s_A("odbc_putdata_start",0), /* 4 */ s_A("odbc_column_display_size",0), /* 5 */ s_A("odbc_force_bind_type",0), /* 6 */ s_A("",0), /* END */ }; #undef s_A /*======================================================================*/ /* */ /* dbd_st_FETCH_attrib */ /* =================== */ /* */ /*======================================================================*/ SV *dbd_st_FETCH_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv) { STRLEN kl; char *key = SvPV(keysv,kl); int i; SV *retsv = NULL; T_st_params *par; char cursor_name[256]; SWORD cursor_name_len; RETCODE rc; for (par = S_st_fetch_params; par->len > 0; par++) if (par->len == kl && strEQ(key, par->str)) break; if (par->len <= 0) return Nullsv; if (par->need_describe && !imp_sth->done_desc && !dbd_describe(sth, imp_sth,0)) { /* dbd_describe has already called dbd_error() */ /* we can't return Nullsv here because the xs code will */ /* then just pass the attribute name to DBI for FETCH. */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE1(imp_sth, " !!!dbd_st_FETCH_attrib (%s) needed query description, " "but failed\n", par->str); } if (DBIc_WARN(imp_sth)) { warn("Describe failed during %s->FETCH(%s,%d)", SvPV(sth,PL_na), key,imp_sth->done_desc); } return &PL_sv_undef; } i = DBIc_NUM_FIELDS(imp_sth); switch(par - S_st_fetch_params) { AV *av; case 0: /* NUM_OF_PARAMS */ return Nullsv; /* handled by DBI */ case 1: /* NUM_OF_FIELDS */ if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 9)) { TRACE1(imp_sth, " dbd_st_FETCH_attrib NUM_OF_FIELDS %d\n", i); } retsv = newSViv(i); break; case 2: /* NAME */ av = newAV(); retsv = newRV_inc(sv_2mortal((SV*)av)); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 9)) { int j; TRACE1(imp_sth, " dbd_st_FETCH_attrib NAMES %d\n", i); for (j = 0; j < i; j++) TRACE1(imp_sth, "\t%s\n", imp_sth->fbh[j].ColName); } while(--i >= 0) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 9)) { TRACE2(imp_sth, " Colname %d => %s\n", i, imp_sth->fbh[i].ColName); } #ifdef WITH_UNICODE av_store(av, i, sv_newwvn((SQLWCHAR *)imp_sth->fbh[i].ColName, imp_sth->fbh[i].ColNameLen)); #else av_store(av, i, newSVpv(imp_sth->fbh[i].ColName, 0)); #endif } break; case 3: /* NULLABLE */ av = newAV(); retsv = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, (imp_sth->fbh[i].ColNullable == SQL_NO_NULLS) ? &PL_sv_no : &PL_sv_yes); break; case 4: /* TYPE */ av = newAV(); retsv = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, newSViv(imp_sth->fbh[i].ColSqlType)); break; case 5: /* PRECISION */ av = newAV(); retsv = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, newSViv(imp_sth->fbh[i].ColDef)); break; case 6: /* SCALE */ av = newAV(); retsv = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, newSViv(imp_sth->fbh[i].ColScale)); break; case 7: /* sol_type */ av = newAV(); retsv = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, newSViv(imp_sth->fbh[i].ColSqlType)); break; case 8: /* sol_length */ av = newAV(); retsv = newRV_inc(sv_2mortal((SV*)av)); while(--i >= 0) av_store(av, i, newSViv(imp_sth->fbh[i].ColLength)); break; case 9: /* CursorName */ rc = SQLGetCursorName(imp_sth->hstmt, cursor_name, sizeof(cursor_name), &cursor_name_len); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "st_FETCH/SQLGetCursorName"); return Nullsv; } retsv = newSVpv(cursor_name, cursor_name_len); break; case 10: /* odbc_more_results */ retsv = newSViv(imp_sth->moreResults); if (i == 0 && imp_sth->moreResults == 0) { int outparams = (imp_sth->out_params_av) ? AvFILL(imp_sth->out_params_av)+1 : 0; if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE0(imp_sth, " numfields == 0 && moreResults = 0 finish\n"); } if (outparams) { odbc_handle_outparams(imp_sth, DBIc_TRACE_LEVEL(imp_sth)); } imp_sth->done_desc = 0; /* redo describe */ /* XXX need to 'finish' here */ dbd_st_finish(sth, imp_sth); } else { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) { TRACE2(imp_sth, " fetch odbc_more_results, numfields == %d " "&& moreResults = %d\n", i, imp_sth->moreResults); } } break; case 11: /* ParamValues */ { /* not sure if there's a memory leak here. */ HV *paramvalues = newHV(); if (imp_sth->all_params_hv) { HV *hv = imp_sth->all_params_hv; SV *sv; char *key; I32 retlen; hv_iterinit(hv); while( (sv = hv_iternextsv(hv, &key, &retlen)) != NULL ) { if (sv != &PL_sv_undef) { phs_t *phs = (phs_t*)(void*)SvPVX(sv); (void)hv_store(paramvalues, phs->name, (I32)strlen(phs->name), newSVsv(phs->sv), 0); } } } /* ensure HV is freed when the ref is freed */ retsv = newRV_noinc((SV *)paramvalues); break; } case 12: /* LongReadLen */ retsv = newSViv(DBIc_LongReadLen(imp_sth)); break; case 13: /* odbc_ignore_named_placeholders */ retsv = newSViv(imp_sth->odbc_ignore_named_placeholders); break; case 14: /* odbc_default_bind_type */ retsv = newSViv(imp_sth->odbc_default_bind_type); break; case 15: /* odbc_force_rebind */ retsv = newSViv(imp_sth->odbc_force_rebind); break; case 16: /* odbc_query_timeout */ /* * -1 is our internal flag saying odbc_query_timeout has never been * set so we map it back to the default for ODBC which is 0 */ if (imp_sth->odbc_query_timeout == -1) { retsv = newSViv(0); } else { retsv = newSViv(imp_sth->odbc_query_timeout); } break; case 17: /* odbc_putdata_start */ retsv = newSViv(imp_sth->odbc_putdata_start); break; case 18: /* ParamTypes */ { /* not sure if there's a memory leak here. */ HV *paramtypes = newHV(); if (imp_sth->all_params_hv) { HV *hv = imp_sth->all_params_hv; SV *sv; char *key; I32 retlen; hv_iterinit(hv); while( (sv = hv_iternextsv(hv, &key, &retlen)) != NULL ) { if (sv != &PL_sv_undef) { HV *subh = newHV(); phs_t *phs = (phs_t*)(void*)SvPVX(sv); (void)hv_store(subh, "TYPE", 4, newSViv(phs->sql_type), 0); (void)hv_store(paramtypes, phs->name, (I32)strlen(phs->name), newRV_noinc((SV *)subh), 0); } } } /* ensure HV is freed when the ref is freed */ retsv = newRV_noinc((SV *)paramtypes); break; } case 19: /* odbc_column_display_size */ retsv = newSViv(imp_sth->odbc_column_display_size); break; case 20: /* odbc_force_bind_type */ retsv = newSViv(imp_sth->odbc_force_bind_type); break; default: return Nullsv; } return sv_2mortal(retsv); } /*======================================================================*/ /* */ /* dbd_st_STORE_attrib */ /* =================== */ /* */ /*======================================================================*/ int dbd_st_STORE_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv) { STRLEN kl; char *key = SvPV(keysv,kl); T_st_params *par; for (par = S_st_store_params; par->len > 0; par++) if (par->len == kl && strEQ(key, par->str)) break; if (par->len <= 0) return FALSE; switch(par - S_st_store_params) { case 0: imp_sth->odbc_ignore_named_placeholders = SvTRUE(valuesv); return TRUE; case 1: imp_sth->odbc_default_bind_type = (SQLSMALLINT)SvIV(valuesv); return TRUE; break; case 2: imp_sth->odbc_force_rebind = (int)SvIV(valuesv); return TRUE; break; case 3: imp_sth->odbc_query_timeout = SvIV(valuesv); return TRUE; break; case 4: imp_sth->odbc_putdata_start = SvIV(valuesv); return TRUE; break; case 5: imp_sth->odbc_column_display_size = SvIV(valuesv); return TRUE; break; case 6: imp_sth->odbc_force_bind_type = (SQLSMALLINT)SvIV(valuesv); return TRUE; break; } return FALSE; } SV *odbc_get_info(dbh, ftype) SV *dbh; int ftype; { D_imp_dbh(dbh); RETCODE rc; SV *retsv = NULL; int i; int size = 256; char *rgbInfoValue; SWORD cbInfoValue = -2; New(0, rgbInfoValue, size, char); /* See fancy logic below */ for (i = 0; i < 6; i++) rgbInfoValue[i] = (char)0xFF; rc = SQLGetInfo(imp_dbh->hdbc, (SQLUSMALLINT)ftype, rgbInfoValue, (SQLSMALLINT)(size-1), &cbInfoValue); if (cbInfoValue > size-1) { Renew(rgbInfoValue, cbInfoValue+1, char); rc = SQLGetInfo(imp_dbh->hdbc, (SQLUSMALLINT)ftype, rgbInfoValue, cbInfoValue, &cbInfoValue); } if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "odbc_get_info/SQLGetInfo"); Safefree(rgbInfoValue); /* patched 2/12/02, thanks to Steffen Goldner */ return &PL_sv_undef; /* return Nullsv; */ } /* Fancy logic here to determine if result is a string or int */ if (cbInfoValue == -2) /* is int */ retsv = newSViv(*(int *)rgbInfoValue); /* XXX cast */ else if (cbInfoValue != 2 && cbInfoValue != 4) /* must be string */ retsv = newSVpv(rgbInfoValue, 0); else if (rgbInfoValue[cbInfoValue] == '\0') /* must be string */ /* patch from Steffen Goldner 0.37 2/12/02 */ retsv = newSVpv(rgbInfoValue, 0); else if (cbInfoValue == 2) /* short */ retsv = newSViv(*(short *)rgbInfoValue); /* XXX cast */ else if (cbInfoValue == 4) /* int */ retsv = newSViv(*(int *)rgbInfoValue); /* XXX cast */ else croak("panic: SQLGetInfo cbInfoValue == %d", cbInfoValue); if (DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 4)) PerlIO_printf( DBIc_LOGPIO(imp_dbh), " SQLGetInfo: ftype %d, cbInfoValue %d: %s\n", ftype, cbInfoValue, neatsvpv(retsv,0)); Safefree(rgbInfoValue); return sv_2mortal(retsv); } #ifdef THE_FOLLOWING_NO_LONGER_USED_REPLACE_BY_dbd_st_primary_keys int odbc_get_statistics(dbh, sth, CatalogName, SchemaName, TableName, Unique) SV * dbh; SV * sth; char * CatalogName; char * SchemaName; char * TableName; int Unique; { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; imp_sth->henv = imp_dbh->henv; /* needed for dbd_error */ imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "odbc_get_statistics/SQLAllocHandle(stmt)"); return 0; } rc = SQLStatistics(imp_sth->hstmt, CatalogName, (SQLSMALLINT)strlen(CatalogName), SchemaName, (SQLSMALLINT)strlen(SchemaName), TableName, (SQLSMALLINT)strlen(TableName), (SQLUSMALLINT)Unique, (SQLUSMALLINT)0); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "odbc_get_statistics/SQLGetStatistics"); return 0; } return build_results(sth, dbh, rc); } #endif /* THE_FOLLOWING_NO_LONGER_USED_REPLACE_BY_dbd_st_primary_keys */ #ifdef THE_FOLLOWING_NO_LONGER_USED_REPLACE_BY_dbd_st_primary_keys int odbc_get_primary_keys(dbh, sth, CatalogName, SchemaName, TableName) SV * dbh; SV * sth; char * CatalogName; char * SchemaName; char * TableName; { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; imp_sth->henv = imp_dbh->henv; /* needed for dbd_error */ imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "odbc_get_primary_keys/SQLAllocHandle(stmt)"); return 0; } rc = SQLPrimaryKeys(imp_sth->hstmt, CatalogName, (SQLSMALLINT)strlen(CatalogName), SchemaName, (SQLSMALLINT)strlen(SchemaName), TableName, (SQLSMALLINT)strlen(TableName)); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE1(imp_dbh, " SQLPrimaryKeys rc = %d\n", rc); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "odbc_get_primary_keys/SQLPrimaryKeys"); return 0; } return build_results(sth, dbh, rc); } #endif /* THE_FOLLOWING_NO_LONGER_USED_REPLACE_BY_dbd_st_primary_keys */ int odbc_get_special_columns(dbh, sth, Identifier, CatalogName, SchemaName, TableName, Scope, Nullable) SV * dbh; SV * sth; int Identifier; char * CatalogName; char * SchemaName; char * TableName; int Scope; int Nullable; { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; imp_sth->henv = imp_dbh->henv; /* needed for dbd_error */ imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "odbc_get_special_columns/SQLAllocHandle(stmt)"); return 0; } rc = SQLSpecialColumns(imp_sth->hstmt, (SQLSMALLINT)Identifier, CatalogName, (SQLSMALLINT)strlen(CatalogName), SchemaName, (SQLSMALLINT)strlen(SchemaName), TableName, (SQLSMALLINT)strlen(TableName), (SQLSMALLINT)Scope, (SQLSMALLINT)Nullable); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "odbc_get_special_columns/SQLSpecialClumns"); return 0; } return build_results(sth, dbh, rc); } int odbc_get_foreign_keys(dbh, sth, PK_CatalogName, PK_SchemaName, PK_TableName, FK_CatalogName, FK_SchemaName, FK_TableName) SV * dbh; SV * sth; char * PK_CatalogName; char * PK_SchemaName; char * PK_TableName; char * FK_CatalogName; char * FK_SchemaName; char * FK_TableName; { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; size_t max_stmt_len; imp_sth->henv = imp_dbh->henv; /* needed for dbd_error */ imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "odbc_get_foreign_keys/SQLAllocHandle(stmt)"); return 0; } /* just for sanity, later. Any internals that may rely on this (including */ /* debugging) will have valid data */ max_stmt_len = strlen(cSqlForeignKeys)+ strlen(XXSAFECHAR(PK_CatalogName))+ strlen(XXSAFECHAR(PK_SchemaName))+ strlen(XXSAFECHAR(PK_TableName))+ strlen(XXSAFECHAR(FK_CatalogName))+ strlen(XXSAFECHAR(FK_SchemaName))+ strlen(XXSAFECHAR(FK_TableName))+ 1; imp_sth->statement = (char *)safemalloc(max_stmt_len); my_snprintf(imp_sth->statement, max_stmt_len, cSqlForeignKeys, XXSAFECHAR(PK_CatalogName), XXSAFECHAR(PK_SchemaName), XXSAFECHAR(PK_TableName), XXSAFECHAR(FK_CatalogName), XXSAFECHAR(FK_SchemaName),XXSAFECHAR(FK_TableName) ); /* fix to handle "" (undef) calls -- thanks to Kevin Shepherd */ rc = SQLForeignKeys( imp_sth->hstmt, (PK_CatalogName && *PK_CatalogName) ? PK_CatalogName : 0, SQL_NTS, (PK_SchemaName && *PK_SchemaName) ? PK_SchemaName : 0, SQL_NTS, (PK_TableName && *PK_TableName) ? PK_TableName : 0, SQL_NTS, (FK_CatalogName && *FK_CatalogName) ? FK_CatalogName : 0, SQL_NTS, (FK_SchemaName && *FK_SchemaName) ? FK_SchemaName : 0, SQL_NTS, (FK_TableName && *FK_TableName) ? FK_TableName : 0, SQL_NTS); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "odbc_get_foreign_keys/SQLForeignKeys"); return 0; } return build_results(sth, dbh, rc); } int odbc_describe_col(sth, colno, ColumnName, BufferLength, NameLength, DataType, ColumnSize, DecimalDigits, Nullable) SV *sth; int colno; char *ColumnName; I16 BufferLength; I16 *NameLength; I16 *DataType; U32 *ColumnSize; I16 *DecimalDigits; I16 *Nullable; { D_imp_sth(sth); SQLULEN ColSize; RETCODE rc; rc = SQLDescribeCol(imp_sth->hstmt, (SQLSMALLINT)colno, ColumnName, BufferLength, NameLength, DataType, &ColSize, DecimalDigits, Nullable); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "DescribeCol/SQLDescribeCol"); return 0; } *ColumnSize = (U32)ColSize; return 1; } int odbc_get_type_info(dbh, sth, ftype) SV *dbh; SV *sth; int ftype; { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; size_t max_stmt_len; #if 0 /* TBD: cursorname? */ char cname[128]; /* cursorname */ #endif imp_sth->henv = imp_dbh->henv; /* needed for dbd_error */ imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "odbc_get_type_info/SQLAllocHandle(stmt)"); return 0; } /* just for sanity, later. Any internals that may rely on this (including */ /* debugging) will have valid data */ max_stmt_len = strlen(cSqlGetTypeInfo)+(abs(ftype)/10)+2; imp_sth->statement = (char *)safemalloc(max_stmt_len); my_snprintf(imp_sth->statement, max_stmt_len, cSqlGetTypeInfo, ftype); rc = SQLGetTypeInfo(imp_sth->hstmt, (SQLSMALLINT)ftype); dbd_error(sth, rc, "odbc_get_type_info/SQLGetTypeInfo"); if (!SQL_SUCCEEDED(rc)) { SQLFreeHandle(SQL_HANDLE_STMT,imp_sth->hstmt); imp_sth->hstmt = SQL_NULL_HSTMT; return 0; } return build_results(sth, dbh, rc); } SV *odbc_cancel(sth) SV *sth; { D_imp_sth(sth); RETCODE rc; rc = SQLCancel(imp_sth->hstmt); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "odbc_cancel/SQLCancel"); return Nullsv; } return newSViv(1); } IV odbc_st_lob_read( SV *sth, int colno, SV *data, UV length, IV type) { D_imp_sth(sth); SQLLEN len = 0; SQLRETURN rc; imp_fbh_t *fbh; SQLSMALLINT col_type; IV retlen = 0; char *buf = SvPV_nolen(data); fbh = &imp_sth->fbh[colno-1]; /*printf("fbh->ColSqlType=%s\n", S_SqlTypeToString(fbh->ColSqlType));*/ if ((fbh->bind_flags & ODBC_TREAT_AS_LOB) == 0) { croak("Column %d was not bound with TreatAsLOB", colno); } if ((fbh->ColSqlType == SQL_BINARY) || (fbh->ColSqlType == SQL_VARBINARY) || (fbh->ColSqlType == SQL_LONGVARBINARY)) { col_type = SQL_C_BINARY; } else { #ifdef WITH_UNICODE col_type = SQL_C_WCHAR; #else col_type = SQL_C_CHAR; #endif /* WITH_UNICODE */ } if (type != 0) { col_type = (SQLSMALLINT)type; } rc = SQLGetData(imp_sth->hstmt, colno, col_type, buf, length, &len); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 4)) PerlIO_printf(DBIc_LOGPIO(imp_sth), " SQLGetData(col=%d,type=%d)=%d (retlen=%ld)\n", colno, col_type, rc, len); if (rc == SQL_NO_DATA) { /* finished - SQLGetData returns this when you call it after it has returned all of the data */ return 0; } else if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "odbc_st_lob_read/SQLGetData"); return -1; } else if (rc == SQL_SUCCESS_WITH_INFO) { /* we are assuming this is 01004 - string data right truncation unless len == SQL_NO_TOTAL */ if (len == SQL_NO_TOTAL) { dbd_error(sth, rc, "Driver did not return the lob length - SQL_NO_TOTAL)"); return -1; } retlen = length - 1; } else if (rc == SQL_SUCCESS) { if (len == SQL_NULL_DATA) { return 0; } retlen = len; } #ifdef WITH_UNICODE if (col_type == SQL_C_WCHAR) { char *c1; c1 = PVallocW((SQLWCHAR *)buf); buf = SvGROW(data, strlen(c1) + 1); retlen = retlen / sizeof(SQLWCHAR); strcpy(buf, c1); PVfreeW(c1); # ifdef sv_utf8_decode sv_utf8_decode(data); # else SvUTF8_on(data); # endif } # endif return retlen; } /************************************************************************/ /* */ /* odbc_col_attributes */ /* =================== */ /* */ /************************************************************************/ SV *odbc_col_attributes(SV *sth, int colno, int desctype) { D_imp_sth(sth); RETCODE rc; SV *retsv = NULL; unsigned char str_attr[512]; SWORD str_attr_len = 0; SQLLEN num_attr = 0; memset(str_attr, '\0', sizeof(str_attr)); if ( !DBIc_ACTIVE(imp_sth) ) { dbd_error(sth, DBDODBC_INTERNAL_ERROR, "no statement executing"); return Nullsv; } /* * At least on Win95, calling this with colno==0 would "core" dump/GPF. * protect, even though it's valid for some values of desctype * (e.g. SQL_COLUMN_COUNT, since it doesn't depend on the colcount) */ if (colno == 0) { dbd_error(sth, DBDODBC_INTERNAL_ERROR, "cannot obtain SQLColAttributes for column 0"); return Nullsv; } /* * workaround a problem in unixODBC 2.2.11 which can write off the * end of the str_attr buffer when built with unicode - lie about * buffer size - we've got more than we admit to. */ rc = SQLColAttributes(imp_sth->hstmt, (SQLUSMALLINT)colno, (SQLUSMALLINT)desctype, str_attr, sizeof(str_attr)/2, &str_attr_len, &num_attr); if (!SQL_SUCCEEDED(rc)) { dbd_error(sth, rc, "odbc_col_attributes/SQLColAttributes"); return Nullsv; } else if (SQL_SUCCESS_WITH_INFO == rc) { warn("SQLColAttributes has truncated returned data"); } if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) { PerlIO_printf( DBIc_LOGPIO(imp_sth), " SQLColAttributes: colno=%d, desctype=%d, str_attr=%s, " "str_attr_len=%d, num_attr=%ld", colno, desctype, str_attr, str_attr_len, (long)num_attr); } switch (desctype) { case SQL_COLUMN_AUTO_INCREMENT: case SQL_COLUMN_CASE_SENSITIVE: case SQL_COLUMN_COUNT: case SQL_COLUMN_DISPLAY_SIZE: case SQL_COLUMN_LENGTH: case SQL_COLUMN_MONEY: case SQL_COLUMN_NULLABLE: case SQL_COLUMN_PRECISION: case SQL_COLUMN_SCALE: case SQL_COLUMN_SEARCHABLE: case SQL_COLUMN_TYPE: case SQL_COLUMN_UNSIGNED: case SQL_COLUMN_UPDATABLE: { retsv = newSViv(num_attr); break; } case SQL_COLUMN_LABEL: case SQL_COLUMN_NAME: case SQL_COLUMN_OWNER_NAME: case SQL_COLUMN_QUALIFIER_NAME: case SQL_COLUMN_TABLE_NAME: case SQL_COLUMN_TYPE_NAME: { /* * NOTE: in unixODBC 2.2.11, if you called SQLDriverConnectW and * then called SQLColAttributes for a string type it would often * return half the number of characters it had written to * str_attr in str_attr_len. */ retsv = newSVpv(str_attr, strlen(str_attr)); break; } default: { dbd_error(sth, DBDODBC_INTERNAL_ERROR, "driver-specific column attributes not supported"); return Nullsv; break; } } #ifdef OLD_STUFF_THAT_SEEMS_FLAWED /* * sigh...Oracle's ODBC driver version 8.0.4 resets str_attr_len to 0, when * putting a value in num_attr. This is a change! * * double sigh. SQL Server (and MySql under Unix) set str_attr_len * but use num_attr, not str_attr. This change may be problematic * for other drivers. (the additional || num_attr != -2...) */ if (str_attr_len == -2 || str_attr_len == 0 || num_attr != -2) retsv = newSViv(num_attr); else if (str_attr_len != 2 && str_attr_len != 4) retsv = newSVpv(str_attr, 0); else if (str_attr[str_attr_len] == '\0') /* fix for DBD::ODBC 0.39 thanks to Nicolas DeRico */ retsv = newSVpv(str_attr, 0); else { if (str_attr_len == 2) retsv = newSViv(*(short *)str_attr); else retsv = newSViv(*(int *)str_attr); } #endif return sv_2mortal(retsv); } int odbc_db_columns(dbh, sth, catalog, schema, table, column) SV *dbh; SV *sth; char *catalog; char *schema; char *table; char *column; { D_imp_dbh(dbh); D_imp_sth(sth); RETCODE rc; int dbh_active; size_t max_stmt_len; imp_sth->henv = imp_dbh->henv; /* needed for dbd_error */ imp_sth->hdbc = imp_dbh->hdbc; imp_sth->done_desc = 0; if ((dbh_active = check_connection_active(dbh)) == 0) return 0; rc = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc, &imp_sth->hstmt); if (rc != SQL_SUCCESS) { dbd_error(sth, rc, "odbc_db_columns/SQLAllocHandle(stmt)"); return 0; } /* just for sanity, later. Any internals that may rely on this (including */ /* debugging) will have valid data */ max_stmt_len = strlen(cSqlColumns)+ strlen(XXSAFECHAR(catalog))+ strlen(XXSAFECHAR(schema))+ strlen(XXSAFECHAR(table))+ strlen(XXSAFECHAR(column))+1; imp_sth->statement = (char *)safemalloc(max_stmt_len); my_snprintf(imp_sth->statement, max_stmt_len, cSqlColumns, XXSAFECHAR(catalog), XXSAFECHAR(schema), XXSAFECHAR(table), XXSAFECHAR(column)); rc = SQLColumns(imp_sth->hstmt, (catalog && *catalog) ? catalog : 0, SQL_NTS, (schema && *schema) ? schema : 0, SQL_NTS, (table && *table) ? table : 0, SQL_NTS, (column && *column) ? column : 0, SQL_NTS); if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) PerlIO_printf( DBIc_LOGPIO(imp_dbh), " SQLColumns call: cat = %s, schema = %s, table = %s, " "column = %s\n", XXSAFECHAR(catalog), XXSAFECHAR(schema), XXSAFECHAR(table), XXSAFECHAR(column)); dbd_error(sth, rc, "odbc_columns/SQLColumns"); if (!SQL_SUCCEEDED(rc)) { SQLFreeHandle(SQL_HANDLE_STMT,imp_sth->hstmt); imp_sth->hstmt = SQL_NULL_HSTMT; return 0; } return build_results(sth, dbh, rc); } /* * AllODBCErrors * ============= * * Given ODBC environment, connection and statement handles (any of which may * be null) this function will retrieve all ODBC errors recorded and * optionally (if output is not 0) output then to the specified log handle. * */ static void AllODBCErrors( HENV henv, HDBC hdbc, HSTMT hstmt, int output, PerlIO *logfp) { SQLRETURN rc; do { UCHAR sqlstate[SQL_SQLSTATE_SIZE+1]; /* ErrorMsg must not be greater than SQL_MAX_MESSAGE_LENGTH */ UCHAR ErrorMsg[SQL_MAX_MESSAGE_LENGTH]; SWORD ErrorMsgLen; SDWORD NativeError; /* TBD: 3.0 update */ rc=SQLError(henv, hdbc, hstmt, sqlstate, &NativeError, ErrorMsg, sizeof(ErrorMsg)-1, &ErrorMsgLen); if (output && SQL_SUCCEEDED(rc)) PerlIO_printf(logfp, "%s %s\n", sqlstate, ErrorMsg); } while(SQL_SUCCEEDED(rc)); return; } /************************************************************************/ /* */ /* check_connection_active */ /* ======================= */ /* */ /************************************************************************/ static int check_connection_active(SV *h) { D_imp_xxh(h); struct imp_dbh_st *imp_dbh = NULL; struct imp_sth_st *imp_sth = NULL; switch(DBIc_TYPE(imp_xxh)) { case DBIt_ST: imp_sth = (struct imp_sth_st *)imp_xxh; imp_dbh = (struct imp_dbh_st *)(DBIc_PARENT_COM(imp_sth)); break; case DBIt_DB: imp_dbh = (struct imp_dbh_st *)imp_xxh; break; default: croak("panic: check_connection_active bad handle type"); } if (!DBIc_ACTIVE(imp_dbh)) { DBIh_SET_ERR_CHAR( h, imp_xxh, Nullch, 1, "Cannot allocate statement when disconnected from the database", "08003", Nullch); return 0; } return 1; } /************************************************************************/ /* */ /* set_odbc_version */ /* ================ */ /* */ /* Set the ODBC version we require. This defaults to ODBC 3 but if */ /* attr contains the odbc_version atttribute this overrides it. If we */ /* fail for any reason the env handle is freed, the error reported and */ /* 0 is returned. If all ok, 1 is returned. */ /* */ /************************************************************************/ static int set_odbc_version( SV *dbh, imp_dbh_t *imp_dbh, SV* attr) { D_imp_drh_from_dbh; SV **svp; UV odbc_version = 0; SQLRETURN rc; DBD_ATTRIB_GET_IV( attr, "odbc_version", 12, svp, odbc_version); if (svp && odbc_version) { rc = SQLSetEnvAttr(imp_drh->henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)odbc_version, SQL_IS_INTEGER); } else { /* make sure we request a 3.0 version */ rc = SQLSetEnvAttr(imp_drh->henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER); } if (!SQL_SUCCEEDED(rc)) { dbd_error2( dbh, rc, "db_login/SQLSetEnvAttr", imp_drh->henv, 0, 0); if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; } return 0; } return 1; } /************************************************************************/ /* */ /* post_connect */ /* ============ */ /* */ /* Operations to perform immediately after we have connected. */ /* */ /* NOTE: prior to DBI subversion version 11605 (fixed post 1.607) */ /* DBD_ATTRIB_DELETE segfaulted so instead of calling: */ /* DBD_ATTRIB_DELETE(attr, "odbc_cursortype", */ /* strlen("odbc_cursortype")); */ /* we do the following: */ /* hv_delete((HV*)SvRV(attr), "odbc_cursortype", */ /* strlen("odbc_cursortype"), G_DISCARD); */ /* */ /************************************************************************/ static int post_connect( SV *dbh, imp_dbh_t *imp_dbh, SV *attr) { D_imp_drh_from_dbh; SQLRETURN rc; SWORD dbvlen; UWORD supported; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE0(imp_dbh, "Turning autocommit on\n"); /* DBI spec requires AutoCommit on */ rc = SQLSetConnectAttr(imp_dbh->hdbc, SQL_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "post_connect/SQLSetConnectAttr(SQL_AUTOCOMMIT)"); SQLFreeHandle(SQL_HANDLE_DBC, imp_dbh->hdbc); if (imp_drh->connects == 0) { SQLFreeHandle(SQL_HANDLE_ENV, imp_drh->henv); imp_drh->henv = SQL_NULL_HENV; imp_dbh->henv = SQL_NULL_HENV; /* needed for dbd_error */ } return 0; } DBIc_set(imp_dbh,DBIcf_AutoCommit, 1); /* get the ODBC compatibility level for this driver */ rc = SQLGetInfo(imp_dbh->hdbc, SQL_DRIVER_ODBC_VER, &imp_dbh->odbc_ver, (SWORD)sizeof(imp_dbh->odbc_ver), &dbvlen); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "post_connect/SQLGetInfo(DRIVER_ODBC_VER)"); strcpy(imp_dbh->odbc_ver, "01.00"); } if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "DRIVER_ODBC_VER = %s\n", imp_dbh->odbc_ver); /* get ODBC driver name and version */ rc = SQLGetInfo(imp_dbh->hdbc, SQL_DRIVER_NAME, &imp_dbh->odbc_driver_name, (SQLSMALLINT)sizeof(imp_dbh->odbc_driver_name), &dbvlen); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "post_connect/SQLGetInfo(DRIVER_NAME)"); strcpy(imp_dbh->odbc_driver_name, "unknown"); imp_dbh->driver_type = DT_DONT_CARE; } else { if (strcmp(imp_dbh->odbc_driver_name, "SQLSRV32.DLL") == 0) { imp_dbh->driver_type = DT_SQL_SERVER; } else if ((strcmp(imp_dbh->odbc_driver_name, "sqlncli10.dll") == 0) || (strcmp(imp_dbh->odbc_driver_name, "SQLNCLI.DLL") == 0)) { imp_dbh->driver_type = DT_SQL_SERVER_NATIVE_CLIENT; } else if (strcmp(imp_dbh->odbc_driver_name, "odbcjt32.dll") == 0) { imp_dbh->driver_type = DT_MS_ACCESS_JET; } else if (strcmp(imp_dbh->odbc_driver_name, "ACEODBC.DLL") == 0) { imp_dbh->driver_type = DT_MS_ACCESS_ACE; } else if (strcmp(imp_dbh->odbc_driver_name, "esoobclient") == 0) { imp_dbh->driver_type = DT_ES_OOB; } else if (strcmp(imp_dbh->odbc_driver_name, "OdbcFb") == 0) { imp_dbh->driver_type = DT_FIREBIRD; } else { imp_dbh->driver_type = DT_DONT_CARE; } } if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "DRIVER_NAME = %s\n", imp_dbh->odbc_driver_name); rc = SQLGetInfo(imp_dbh->hdbc, SQL_DRIVER_VER, &imp_dbh->odbc_driver_version, (SQLSMALLINT)sizeof(imp_dbh->odbc_driver_version), &dbvlen); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "post_connect/SQLGetInfo(DRIVER_VERSION)"); strcpy(imp_dbh->odbc_driver_name, "unknown"); } if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "DRIVER_VERSION = %s\n", imp_dbh->odbc_driver_version); rc = SQLGetInfo(imp_dbh->hdbc, SQL_DBMS_NAME, &imp_dbh->odbc_dbms_name, (SQLSMALLINT)sizeof(imp_dbh->odbc_dbms_name), &dbvlen); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "post_connect/SQLGetInfo(SQL_DBMS_NAME)"); strcpy(imp_dbh->odbc_dbms_name, "unknown"); } rc = SQLGetInfo(imp_dbh->hdbc, SQL_DBMS_VER, &imp_dbh->odbc_dbms_version, (SQLSMALLINT)sizeof(imp_dbh->odbc_dbms_version), &dbvlen); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "post_connect/SQLGetInfo(SQL_DBMS_VER)"); strcpy(imp_dbh->odbc_dbms_version, "unknown"); } /* find maximum column name length */ rc = SQLGetInfo(imp_dbh->hdbc, SQL_MAX_COLUMN_NAME_LEN, &imp_dbh->max_column_name_len, (SWORD) sizeof(imp_dbh->max_column_name_len), &dbvlen); if (!SQL_SUCCEEDED(rc)) { dbd_error(dbh, rc, "post_connect/SQLGetInfo(MAX_COLUMN_NAME_LEN)"); imp_dbh->max_column_name_len = 256; } else if (imp_dbh->max_column_name_len == 0) { imp_dbh->max_column_name_len = 256; } else { if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "MAX_COLUMN_NAME_LEN = %d\n", imp_dbh->max_column_name_len); } #ifdef WITH_UNICODE imp_dbh->max_column_name_len = imp_dbh->max_column_name_len * sizeof(SQLWCHAR) + 2; #endif if (imp_dbh->max_column_name_len > 512) { imp_dbh->max_column_name_len = 512; DBIh_SET_ERR_CHAR( dbh, (imp_xxh_t*)imp_drh, "0", 1, "Max column name length pegged at 512", Nullch, Nullch); } /* default ignoring named parameters to false */ imp_dbh->odbc_ignore_named_placeholders = 0; #ifdef WITH_UNICODE imp_dbh->odbc_has_unicode = 1; #else imp_dbh->odbc_has_unicode = 0; #endif if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "DBD::ODBC is unicode built : %s\n", imp_dbh->odbc_has_unicode ? "YES" : "NO"); imp_dbh->odbc_default_bind_type = 0; imp_dbh->odbc_force_bind_type = 0; #ifdef SQL_ROWSET_SIZE_DEFAULT imp_dbh->rowset_size = SQL_ROWSET_SIZE_DEFAULT; #else /* it should be 1 anyway so above should be redundant but included here partly to remind me what it is */ imp_dbh->rowset_size = 1; #endif /* flag to see if SQLDescribeParam is supported */ imp_dbh->odbc_sqldescribeparam_supported = -1; /* flag to see if SQLDescribeParam is supported */ imp_dbh->odbc_sqlmoreresults_supported = -1; imp_dbh->odbc_defer_binding = 0; imp_dbh->odbc_force_rebind = 0; /* default value for query timeout is -1 which means do not set the query timeout at all. */ imp_dbh->odbc_query_timeout = -1; imp_dbh->odbc_putdata_start = 32768; imp_dbh->odbc_column_display_size = 2001; imp_dbh->odbc_utf8_on = 0; imp_dbh->odbc_exec_direct = 0; /* default to not having SQLExecDirect used */ imp_dbh->odbc_old_unicode = 0; imp_dbh->odbc_describe_parameters = 1; imp_dbh->RowCacheSize = 1; /* default value for now */ if (!strcmp(imp_dbh->odbc_dbms_name, "Microsoft SQL Server")) { if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE0(imp_dbh, "Deferring Binding\n"); imp_dbh->odbc_defer_binding = 0; } /* check to see if SQLMoreResults is supported */ rc = SQLGetFunctions(imp_dbh->hdbc, SQL_API_SQLMORERESULTS, &supported); if (SQL_SUCCEEDED(rc)) { if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "SQLMoreResults supported: %d\n", supported); imp_dbh->odbc_sqlmoreresults_supported = supported ? 1 : 0; } else { imp_dbh->odbc_sqlmoreresults_supported = 0; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE0(imp_dbh, " !!SQLGetFunctions(SQL_API_SQLMORERESULTS) failed:\n"); AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3), DBIc_LOGPIO(imp_dbh)); } /* call only once per connection / DBH -- may want to do * this during the connect to avoid potential threading * issues */ /* check to see if SQLDescribeParam is supported */ rc = SQLGetFunctions(imp_dbh->hdbc, SQL_API_SQLDESCRIBEPARAM, &supported); if (SQL_SUCCEEDED(rc)) { if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, "SQLDescribeParam supported: %d\n", supported); imp_dbh->odbc_sqldescribeparam_supported = supported ? 1 : 0; } else { imp_dbh->odbc_sqldescribeparam_supported = 0; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE0(imp_dbh, " !!SQLGetFunctions(SQL_API_SQLDESCRIBEPARAM) failed:\n"); AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, DBIc_TRACE(imp_dbh, DBD_TRACING, 0, 3), DBIc_LOGPIO(imp_dbh)); } /* odbc_cursortype */ { SV **svp; UV odbc_cursortype = 0; DBD_ATTRIB_GET_IV(attr, "odbc_cursortype", 15, svp, odbc_cursortype); if (svp && odbc_cursortype) { if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, " Setting cursor type to: %"UVuf"\n", odbc_cursortype); /* delete odbc_cursortype so we don't see it again via STORE */ (void)hv_delete((HV*)SvRV(attr), "odbc_cursortype", strlen("odbc_cursortype"), G_DISCARD); rc = SQLSetConnectAttr(imp_dbh->hdbc,(SQLINTEGER)SQL_CURSOR_TYPE, (SQLPOINTER)odbc_cursortype, (SQLINTEGER)SQL_IS_INTEGER); if (!SQL_SUCCEEDED(rc) && (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0))) TRACE1(imp_dbh, " !!Failed to set SQL_CURSORTYPE to %d\n", (int)odbc_cursortype); } } /* odbc_query_timeout */ { SV **svp; UV odbc_timeout = 0; DBD_ATTRIB_GET_IV( attr, "odbc_query_timeout", strlen("odbc_query_timeout"), svp, odbc_timeout); if (svp && odbc_timeout) { imp_dbh->odbc_query_timeout = odbc_timeout; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, " Setting DBH query timeout to %d\n", (int)odbc_timeout); /* delete odbc_cursortype so we don't see it again via STORE */ (void)hv_delete((HV*)SvRV(attr), "odbc_query_timeout", strlen("odbc_query_timeout"), G_DISCARD); } } /* odbc_putdata_start */ { SV **svp; IV putdata_start_value; DBD_ATTRIB_GET_IV( attr, "odbc_putdata_start", strlen("odbc_putdata_start"), svp, putdata_start_value); if (svp) { imp_dbh->odbc_putdata_start = putdata_start_value; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, " Setting DBH putdata_start to %d\n", (int)putdata_start_value); /* delete odbc_putdata_start so we don't see it again via STORE */ (void)hv_delete((HV*)SvRV(attr), "odbc_putdata_start", strlen("odbc_putdata_start"), G_DISCARD); } } /* odbc_column_display_size */ { SV **svp; IV column_display_size_value; DBD_ATTRIB_GET_IV( attr, "odbc_column_display_size", strlen("odbc_column_display_size"), svp, column_display_size_value); if (svp) { imp_dbh->odbc_column_display_size = column_display_size_value; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, " Setting DBH default column display size to %d\n", (int)column_display_size_value); /* delete odbc_column_display_size so we don't see it again via STORE */ (void)hv_delete((HV*)SvRV(attr), "odbc_column_display_size", strlen("odbc_column_display_size"), G_DISCARD); } } /* odbc_utf8_on */ { SV **svp; IV column_display_size_value; DBD_ATTRIB_GET_IV( attr, "odbc_utf8_on", strlen("odbc_utf8_on"), svp, column_display_size_value); if (svp) { imp_dbh->odbc_utf8_on = 0; if (DBIc_TRACE(imp_dbh, CONNECTION_TRACING, 0, 0)) TRACE1(imp_dbh, " Setting UTF8_ON to %d\n", (int)column_display_size_value); /* delete odbc_utf8_on so we don't see it again via STORE */ (void)hv_delete((HV*)SvRV(attr), "odbc_utf8_on", strlen("odbc_utf8_on"), G_DISCARD); } } return 1; } static SQLSMALLINT default_parameter_type( char *why, imp_sth_t *imp_sth, phs_t *phs) { if (imp_sth->odbc_default_bind_type != 0) { return imp_sth->odbc_default_bind_type; } else { /* MS Access can return an invalid precision error in the 12blob test unless the large value is bound as an SQL_LONGVARCHAR or SQL_WLONGVARCHAR. Who knows what large is, but for now it is 4000 */ /* Changed to 2000 for the varchar max switch as in a unicode build we can change a string of 'x' x 2001 into 4002 wide chrs and SQL Server will also return invalid precision in this case on a varchar(4000). Of course, being SQL Server, it also has this problem with the newer varchar(8000)! */ if (!SvOK(phs->sv)) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE2(imp_sth, "%s, sv is not OK, defaulting to %d\n", why, ODBC_BACKUP_BIND_TYPE_VALUE); return ODBC_BACKUP_BIND_TYPE_VALUE; } else if (SvCUR(phs->sv) > ODBC_SWITCH_TO_LONGVARCHAR) { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE3(imp_sth, "%s, sv=%d bytes, defaulting to %d\n", why, SvCUR(phs->sv), ODBC_BACKUP_LONG_BIND_TYPE_VALUE); return ODBC_BACKUP_LONG_BIND_TYPE_VALUE; } else { if (DBIc_TRACE(imp_sth, DBD_TRACING, 0, 3)) TRACE3(imp_sth, "%s, sv=%d bytes, defaulting to %d\n", why, SvCUR(phs->sv), ODBC_BACKUP_BIND_TYPE_VALUE); return ODBC_BACKUP_BIND_TYPE_VALUE; } } } /* end */