Created
March 17, 2009 21:51
-
-
Save chadj/80782 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* $Id: dbdimp.c 12185 2008-12-19 19:39:22Z 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-2008 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. | |
*/ | |
#include "ODBC.h" | |
#if defined(WITH_UNICODE) | |
# include "unicode_helper.h" | |
#endif | |
#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 | |
/* Can't find a constant in DBI for SQL tracing but it is 256 */ | |
#define SQL_TRACE_FLAG 0x100 | |
#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)) | |
static SQLSMALLINT default_parameter_type(imp_sth_t *imp_sth); | |
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 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); | |
/* AV *dbd_st_fetch(SV * sth, imp_sth_t *imp_sth); */ | |
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 | |
/* 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 */ | |
#ifdef WITH_UNICODE | |
# define ODBC_BACKUP_BIND_TYPE_VALUE SQL_WVARCHAR | |
#else | |
# define ODBC_BACKUP_BIND_TYPE_VALUE SQL_VARCHAR | |
#endif | |
SV *dbd_param_err(SQLHANDLE h, int recno); | |
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, 0, 0, 3)) { | |
TRACE1(imp_xxh, " Set timeout to: %d\n", odbc_timeout); | |
} | |
rc = SQLSetStmtAttr(hstmt,(SQLINTEGER)SQL_ATTR_QUERY_TIMEOUT, | |
(SQLPOINTER)odbc_timeout,(SQLINTEGER)SQL_IS_INTEGER); | |
if (!SQL_SUCCEEDED(rc)) { | |
/* raise warnings if setting fails, but don't die? */ | |
if (DBIc_TRACE(imp_xxh, 0, 0, 3)) | |
TRACE1(imp_xxh, | |
" !!Failed to set Statement ATTR Query Timeout to %d\n", | |
(int)odbc_timeout); | |
} | |
return rc; | |
} | |
#if 0 | |
static UV odbc_get_query_timeout(imp_dbh_t *imp_dbh) { | |
RETCODE rc; | |
UV timeout; | |
rc = SQLGetConnectAttr(imp_dbh->hdbc, | |
(SQLINTEGER)SQL_ATTR_QUERY_TIMEOUT, | |
(SQLPOINTER)&timeout,sizeof(timeout), 0); | |
if (!SQL_SUCCEEDED(rc)) { | |
if (DBIc_TRACE(imp_dbh, 0, 0, 3)) | |
TRACE1(imp_dbh, " !!Failed to get ATTR query Timeout %d\n", rc); | |
} | |
return timeout; | |
} | |
#endif /* 0 */ | |
static void odbc_clear_result_set(SV *sth, imp_sth_t *imp_sth) | |
{ | |
SV *value; | |
char *key; | |
I32 keylen; | |
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 | |
) { | |
hv_delete((HV*)SvRV(sth), key, keylen, G_DISCARD); | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) { | |
TRACE2(imp_sth, " ODBC_CLEAR_RESULTS '%s' => %s\n", | |
key, neatsvpv(value,0)); | |
} | |
} | |
} | |
/* | |
PerlIO_printf(DBILOGFP,"CLEAR RESULTS cached attributes:\n"); | |
while ( (value = hv_iternextsv((HV*)SvRV(inner), &key, &keylen)) ) { | |
PerlIO_printf(DBILOGFP,"CLEARRESULTS '%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:%d\n", | |
phs->name, 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,na), (long)phs->strlen_or_ind, | |
phs->sql_type, 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); | |
dTHR; | |
if (DBIc_TRACE(imp_sth, 0, 0, 3)) | |
TRACE2(imp_sth, " build_results sql f%d\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->eod = -1; | |
imp_sth->odbc_column_display_size = imp_dbh->odbc_column_display_size; | |
if (!dbd_describe(sth, imp_sth, 0)) { | |
/* SQLFreeStmt(imp_sth->hstmt, SQL_DROP); */ /* TBD: 3.0 update */ | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 0, 3)) { | |
TRACE0(imp_sth, " dbd_describe build_results #2...!\n"); | |
} | |
if (dbd_describe(sth, imp_sth, 0) <= 0) { | |
if (DBIc_TRACE(imp_sth, 0, 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 ? */ | |
imp_sth->eod = SQL_SUCCESS; | |
return 1; | |
} | |
int odbc_discon_all(SV *drh, imp_drh_t *imp_drh) | |
{ | |
dTHR; | |
/* The disconnect_all concept is flawed and needs more work */ | |
if (!dirty && !SvTRUE(perl_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; | |
SQLLEN rows; | |
SQLHSTMT stmt; | |
int dbh_active; | |
char *sql; | |
STRLEN sql_len; | |
sql = SvPV(statement, sql_len); | |
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_TRACE_FLAG, 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, 0x02000000, 0, 0)) | |
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, 0x02000000, 0, 0)) | |
TRACE0(imp_dbh, " Processing utf8 sql in non-unicode mode\n"); | |
ret = SQLExecDirect(stmt, (SQLCHAR *)sql, SQL_NTS); | |
} | |
#else | |
if (DBIc_TRACE(imp_dbh, 0x02000000, 0, 0)) | |
TRACE0(imp_dbh, " Processing utf8 sql in non-unicode mode\n"); | |
ret = SQLExecDirect(stmt, (SQLCHAR *)sql, SQL_NTS); | |
#endif | |
if (DBIc_TRACE(imp_dbh, 0, 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 ); | |
if (ret < 0) { | |
rows = -2; | |
} | |
else { | |
rows = -3; /* ?? */ | |
} | |
} | |
else { | |
if (ret == SQL_NO_DATA) { | |
rows = 0; | |
} else { | |
ret = SQLRowCount(stmt, &rows); | |
if (!SQL_SUCCEEDED(ret)) { | |
dbd_error( dbh, ret, "SQLRowCount failed" ); | |
if (ret < 0) | |
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, 0, 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); | |
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); | |
cp++; | |
} | |
return (strstr(upper_dsn, "UID=") != 0 || strstr(upper_dsn, "PWD=") != 0); | |
} | |
/*------------------------------------------------------------ | |
connecting to a data source. | |
Allocates henv and hdbc. | |
NOTE: This is the old 5 argument version | |
------------------------------------------------------------*/ | |
int | |
dbd_db_login(dbh, imp_dbh, dbname, uid, pwd) | |
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 to 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 */ | |
/* SQLDriverConnectW. */ | |
/* */ | |
/************************************************************************/ | |
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, 0x04000000, 0, 0)) | |
TRACE0(imp_dbh, "non-Unicode login6\n"); | |
return dbd_db_login6(dbh, imp_dbh, SvPV_nolen(dbname), SvPV_nolen(uid), | |
SvPV_nolen(pwd), attr); | |
#else | |
D_imp_drh_from_dbh; | |
dTHR; | |
RETCODE rc; | |
SV *wconstr; | |
SQLWCHAR dc_constr[512]; | |
STRLEN dc_constr_len; | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) { | |
TRACE0(imp_dbh, "Unicode login6 \n"); | |
TRACE2(imp_dbh, "dbname=%s, uid=%s, pwd=xxxxx\n", | |
SvPV_nolen(dbname), SvPV_nolen(uid)); | |
} | |
imp_dbh->out_connect_string = NULL; | |
if (!imp_drh->connects) { | |
rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &imp_drh->henv); | |
dbd_error(dbh, rc, "db_login/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_login/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 to 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))) { | |
sv_catpvf(dbname, ";UID=%s;PWD=%s;", | |
SvPV_nolen(uid), SvPV_nolen(pwd)); | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) | |
TRACE1(imp_dbh, "Now using dbname = %s\n", SvPV_nolen(dbname)); | |
} | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) | |
TRACE2(imp_dbh, " SQLDriverConnect '%s', '%s', 'xxxx'\n", | |
SvPV_nolen(dbname), SvPV_nolen(uid)); | |
wconstr = sv_mortalcopy(dbname); | |
#ifdef sv_utf8_decode | |
sv_utf8_decode(wconstr); | |
#else | |
SvUTF8_on(wconstr); | |
#endif | |
SV_toWCHAR(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. */ | |
{ | |
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, 0x04000000, 0, 0)) | |
TRACE1(imp_dbh, "Out connection string: %s\n", | |
SvPV_nolen(imp_dbh->out_connect_string)); | |
} | |
} | |
if (!SQL_SUCCEEDED(rc)) { | |
SV *wuid, *wpwd; | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) | |
TRACE0(imp_dbh, " SQLDriverConnect 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. | |
*/ | |
/* wanted to use strncmpi, but couldn't find one on all | |
* platforms. Sigh. */ | |
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_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; /* needed for dbd_error */ | |
} | |
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, 0x04000000, 0, 0)) | |
TRACE2(imp_dbh, " SQLConnect '%s', '%s'\n", | |
neatsvpv(dbname, 0), neatsvpv(uid, 0)); | |
wconstr = sv_mortalcopy(dbname); | |
#ifdef sv_utf8_decode | |
sv_utf8_decode(wconstr); | |
#else | |
SvUTF8_on(wconstr); | |
#endif | |
SV_toWCHAR(wconstr); | |
wuid = sv_mortalcopy(uid); | |
#ifdef sv_utf8_decode | |
sv_utf8_decode(wuid); | |
#else | |
SvUTF8_on(wuid); | |
#endif | |
SV_toWCHAR(wuid); | |
wpwd = sv_mortalcopy(pwd); | |
#ifdef sv_utf8_decode | |
sv_utf8_decode(wpwd); | |
#else | |
SvUTF8_on(wpwd); | |
#endif | |
SV_toWCHAR(wpwd); | |
rc = SQLConnectW(imp_dbh->hdbc, | |
(SQLWCHAR *)SvPV_nolen(wconstr), | |
(SQLSMALLINT)(SvCUR(wconstr) / sizeof(SQLWCHAR)), | |
(SQLWCHAR *)SvPV_nolen(wuid), | |
(SQLSMALLINT)(SvCUR(wuid) / sizeof(SQLWCHAR)), | |
(SQLWCHAR *)SvPV_nolen(wpwd), | |
(SQLSMALLINT)(SvCUR(wpwd) / sizeof(SQLWCHAR))); | |
} | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(dbh, rc, "db_login/SQLConnect"); | |
SQLFreeHandle(SQL_HANDLE_DBC, imp_dbh->hdbc);/* TBD: 3.0 update */ | |
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; | |
} else if (rc == SQL_SUCCESS_WITH_INFO) { | |
dbd_error(dbh, rc, "db_login/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; | |
#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; | |
dTHR; | |
RETCODE rc; | |
char dbname_local[512]; | |
#ifdef WITH_UNICODE | |
SQLWCHAR wconstr[512]; | |
STRLEN wconstr_len; | |
unsigned int i; | |
#endif | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 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_login/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_login/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) + strlen(uid) + strlen(pwd) + 12) > | |
sizeof(dbname_local)) { | |
croak("Connection string too long"); | |
} | |
sprintf(dbname_local, "%s;UID=%s;PWD=%s;", dbname, uid, pwd); | |
dbname = dbname_local; | |
} | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) | |
TRACE2(imp_dbh, " SQLDriverConnect '%s', '%s', 'xxxx'\n", dbname, uid); | |
# ifdef WITH_UNICODE | |
if (strlen(dbname) > (sizeof(wconstr) / sizeof(wconstr[0]))) { | |
croak("Connection string too big to convert to wide characters"); | |
} | |
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, 0x04000000, 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; | |
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)) { | |
imp_dbh->out_connect_string = newSVpv(out_str, out_str_len); | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 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, 0, 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. | |
*/ | |
/* wanted to use strncmpi, but couldn't find one on all | |
* platforms. Sigh. */ | |
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; /* needed for dbd_error */ | |
} | |
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, 0x04000000, 0, 0)) | |
TRACE2(imp_dbh, " SQLConnect '%s', '%s'\n", dbname, uid); | |
#ifdef WITH_UNICODE | |
{ | |
SQLWCHAR wuid[100], wpwd[100]; | |
for (i = 0; i < strlen(uid); i++) { | |
wuid[i] = uid[i]; | |
} | |
wuid[i] = 0; | |
for (i = 0; i < strlen(pwd); i++) { | |
wpwd[i] = pwd[i]; | |
} | |
wpwd[i] = 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, | |
wuid, (SQLSMALLINT)strlen(uid), | |
wpwd, (SQLSMALLINT)strlen(pwd)); | |
} | |
#else | |
rc = SQLConnect(imp_dbh->hdbc, | |
dbname, (SQLSMALLINT)strlen(dbname), | |
uid, (SQLSMALLINT)strlen(uid), | |
pwd, (SQLSMALLINT)strlen(pwd)); | |
#endif | |
} | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(dbh, rc, "db_login/SQLConnect"); | |
SQLFreeHandle(SQL_HANDLE_DBC, imp_dbh->hdbc);/* TBD: 3.0 update */ | |
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; | |
} else if (rc == SQL_SUCCESS_WITH_INFO) { | |
dbd_error(dbh, rc, "db_login/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_drh_from_dbh; | |
UDWORD autoCommit = 0; | |
dTHR; | |
/* 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); | |
} | |
/* If not autocommit, should we rollback? I don't think that's | |
* appropriate. -- TBD: Need to check this, maybe we should | |
* rollback? | |
*/ | |
/* TBD: 3.0 update */ | |
rc = SQLGetConnectOption(imp_dbh->hdbc, SQL_AUTOCOMMIT, &autoCommit); | |
/* quietly handle a problem with SQLGetConnectOption() */ | |
if (!SQL_SUCCEEDED(rc)) { | |
AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, | |
DBIc_TRACE(imp_dbh, 0, 0, 4), DBIc_LOGPIO(imp_dbh)); | |
} | |
else { | |
if (!autoCommit) { | |
rc = dbd_db_rollback(dbh, imp_dbh); | |
if (DBIc_TRACE(imp_dbh, 0, 0, 3)) { | |
TRACE1(imp_dbh, | |
"** auto-rollback due to disconnect without commit" | |
" returned %d\n", rc); | |
} | |
} | |
} | |
rc = SQLDisconnect(imp_dbh->hdbc); | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(dbh, rc, "db_disconnect/SQLDisconnect"); | |
/* return 0; XXX if disconnect fails, fall through... */ | |
} | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 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_dbname, "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; /* needed for dbd_error */ | |
} | |
/* 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 exists for this dbh! */ | |
return 1; | |
} | |
int dbd_db_commit(SV *dbh, imp_dbh_t *imp_dbh) | |
{ | |
RETCODE rc; | |
dTHR; | |
/* TBD: 3.0 update */ | |
rc = SQLTransact(imp_dbh->henv, imp_dbh->hdbc, SQL_COMMIT); | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(dbh, rc, "db_commit/SQLTransact"); | |
return 0; | |
} | |
/* support for DBI 1.20 begin_work */ | |
if (DBIc_has(imp_dbh, DBIcf_BegunWork)) { | |
/* reset autocommit */ | |
rc = SQLSetConnectOption(imp_dbh->hdbc, SQL_AUTOCOMMIT, | |
SQL_AUTOCOMMIT_ON); | |
DBIc_off(imp_dbh,DBIcf_BegunWork); | |
} | |
return 1; | |
} | |
int dbd_db_rollback(SV *dbh, imp_dbh_t *imp_dbh) | |
{ | |
RETCODE rc; | |
dTHR; | |
/* TBD: 3.0 update */ | |
rc = SQLTransact(imp_dbh->henv, imp_dbh->hdbc, SQL_ROLLBACK); | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(dbh, rc, "db_rollback/SQLTransact"); | |
return 0; | |
} | |
/* support for DBI 1.20 begin_work */ | |
if (DBIc_has(imp_dbh, DBIcf_BegunWork)) { | |
/* reset autocommit */ | |
rc = SQLSetConnectOption(imp_dbh->hdbc, SQL_AUTOCOMMIT, | |
SQL_AUTOCOMMIT_ON); | |
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); | |
dTHR; | |
/* | |
* 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 (DBIc_TRACE(imp_xxh, 0, 0, 4)) { | |
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) { | |
UCHAR sqlstate[SQL_SQLSTATE_SIZE+1]; | |
/* | |
* ODBC spec says ErrorMsg must not be greater than | |
* SQL_MAX_MESSAGE_LENGTH (says spec) but we concatenate a little | |
* on the end later (e.g. sql state) so make room for more. | |
*/ | |
UCHAR ErrorMsg[SQL_MAX_MESSAGE_LENGTH+512]; | |
SWORD ErrorMsgLen; | |
SDWORD NativeError; | |
RETCODE rc = 0; | |
/* TBD: 3.0 update */ | |
while(SQL_SUCCEEDED(rc=SQLError( | |
henv, hdbc, hstmt, | |
sqlstate, &NativeError, | |
ErrorMsg, sizeof(ErrorMsg)-1, &ErrorMsgLen))) { | |
ErrorMsg[ErrorMsgLen] = '\0'; | |
sqlstate[SQL_SQLSTATE_SIZE] = '\0'; | |
if (DBIc_TRACE(imp_dbh, 0, 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; | |
/* SV *sv, **svp; */ | |
/* HV *hv; */ | |
int retval, count; | |
ENTER; | |
SAVETMPS; | |
PUSHMARK(sp); | |
if (DBIc_TRACE(imp_dbh, 0, 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))); | |
PUTBACK; | |
if((count = perl_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, 0, 0, 3)) | |
TRACE0(imp_dbh, | |
" Handler caused error to be ignored\n"); | |
continue; | |
} | |
} | |
strcat(ErrorMsg, " (SQL-"); | |
strcat(ErrorMsg, sqlstate); | |
strcat(ErrorMsg, ")"); | |
/* maybe bad way to add hint about invalid transaction | |
* state upon disconnect... | |
*/ | |
if (what && !strcmp(sqlstate, "25000") && | |
!strcmp(what, "db_disconnect/SQLDisconnect")) { | |
strcat(ErrorMsg, " You need to commit before disconnecting! "); | |
} | |
if (SQL_SUCCEEDED(err_rc)) { | |
DBIh_SET_ERR_CHAR(h, imp_xxh, "", 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, 0, 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 */ | |
} | |
} | |
/*------------------------------------------------------------ | |
empties entire ODBC error queue. | |
------------------------------------------------------------*/ | |
void dbd_error(SV *h, RETCODE err_rc, char *what) | |
{ | |
D_imp_xxh(h); | |
dTHR; | |
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, 0, 0, 3) && | |
!imp_dbh->odbc_err_handler) | |
return; | |
dbd_error2(h, err_rc, what, imp_dbh->henv, imp_dbh->hdbc, hstmt); | |
} | |
void dbd_caution(SV *h, char *what) | |
{ | |
D_imp_xxh(h); | |
dTHR; | |
SV *errstr; | |
errstr = DBIc_ERRSTR(imp_xxh); | |
sv_setpvn(errstr, "", 0); | |
sv_setiv(DBIc_ERR(imp_xxh), (IV)-1); | |
/* sqlstate isn't set for SQL_NO_DATA returns */ | |
sv_setpvn(DBIc_STATE(imp_xxh), "00000", 5); | |
if (what) { | |
sv_catpv(errstr, "(DBD: "); | |
sv_catpv(errstr, what); | |
sv_catpv(errstr, " err=-1)"); | |
} | |
if (DBIc_TRACE(imp_xxh, 0, 0, 3)) | |
PerlIO_printf(DBIc_LOGPIO(imp_xxh), " !!%s error %d recorded: %s\n", | |
what, -1, SvPV(errstr,na)); | |
} | |
/*------------------------------------------------------------------------- | |
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) | |
{ | |
dTHR; | |
bool in_literal = FALSE; | |
char *src, *start, *dest; | |
phs_t phs_tpl, *phs; | |
SV *phs_sv; | |
int idx=0, style=0, laststyle=0; | |
int param = 0; | |
STRLEN namelen; | |
char name[256]; | |
SV **svpp; | |
char ch; | |
char literal_ch = '\0'; | |
/* allocate room for copy of statement with spare capacity */ | |
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 = &sv_undef; | |
src = statement; | |
dest = imp_sth->statement; | |
if (DBIc_TRACE(imp_sth, 0, 0, 5)) | |
TRACE1(imp_sth, " ignore named placeholders = %d\n", | |
imp_sth->odbc_ignore_named_placeholders); | |
while(*src) { | |
/* | |
* JLU 10/6/2000 fixed to make the literal a " instead of ' | |
* JLU 1/28/2001 fixed to make literals either " or ', but deal | |
* with ' "foo" ' or " foo's " correctly (just to be safe). | |
* | |
*/ | |
if (*src == '"' || *src == '\'') { | |
if (!in_literal) { | |
literal_ch = *src; | |
in_literal = 1; | |
} else { | |
if (*src == literal_ch) { | |
in_literal = 0; | |
} | |
} | |
} | |
if ((*src != ':' && *src != '?') || in_literal) { | |
*dest++ = *src++; | |
continue; | |
} | |
start = dest; /* save name inc colon */ | |
ch = *src++; | |
if (ch == '?') { /* X/Open standard */ | |
idx++; | |
sprintf(name, "%d", idx); | |
*dest++ = ch; | |
style = 3; | |
} | |
else if (isDIGIT(*src)) { /* ':1' */ | |
char *p = name; | |
*dest++ = '?'; | |
idx = atoi(src); | |
while(isDIGIT(*src)) | |
*p++ = *src++; | |
*p = 0; | |
style = 1; | |
if (DBIc_TRACE(imp_sth, 0, 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 ignoring named | |
* parameters | |
*/ | |
char *p = name; | |
idx++; | |
*dest++ = '?'; | |
while(isALNUM(*src)) /* includes '_' */ | |
*p++ = *src++; | |
*p = 0; | |
style = 2; | |
if (DBIc_TRACE(imp_sth, 0, 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) { | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 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"); | |
} | |
} | |
*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, 0, 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; | |
/* SV **svp; */ | |
/* char cname[128]; */ /* cursorname */ | |
dTHR; | |
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, "st_tables/SQLAllocHandle(stmt)"); | |
return 0; | |
} | |
/* just for sanity, later. Any internals that may rely on this (including */ | |
/* debugging) will have valid data */ | |
imp_sth->statement = (char *)safemalloc(strlen(cSqlTables)+ | |
strlen(XXSAFECHAR(catalog)) + | |
strlen(XXSAFECHAR(schema)) + | |
strlen(XXSAFECHAR(table)) + | |
strlen(XXSAFECHAR(table_type))+1); | |
sprintf(imp_sth->statement, 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, 0, 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); | |
/* SQLFreeStmt(imp_sth->hstmt, SQL_DROP);*/ /* TBD: 3.0 update */ | |
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) | |
{ | |
dTHR; | |
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_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 */ | |
imp_sth->statement = (char *)safemalloc(strlen(cSqlPrimaryKeys)+ | |
strlen(XXSAFECHAR(catalog))+ | |
strlen(XXSAFECHAR(schema))+ | |
strlen(XXSAFECHAR(table))+1); | |
sprintf(imp_sth->statement, | |
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, 0, 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); | |
/* SQLFreeStmt(imp_sth->hstmt, SQL_DROP);*/ /* TBD: 3.0 update */ | |
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) | |
{ | |
dTHR; | |
D_imp_dbh(dbh); | |
D_imp_sth(sth); | |
RETCODE rc; | |
int dbh_active; | |
SQLUSMALLINT odbc_unique; | |
SQLUSMALLINT odbc_quick; | |
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_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 */ | |
imp_sth->statement = (char *)safemalloc(strlen(cSqlStatistics)+ | |
strlen(XXSAFECHAR(catalog))+ | |
strlen(XXSAFECHAR(schema))+ | |
strlen(XXSAFECHAR(table))+1); | |
sprintf(imp_sth->statement, | |
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, 0, 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); | |
/* SQLFreeStmt(imp_sth->hstmt, SQL_DROP);*/ /* TBD: 3.0 update */ | |
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) | |
{ | |
dTHR; | |
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*. */ | |
/* */ | |
/************************************************************************/ | |
int odbc_st_prepare_sv( | |
SV *sth, | |
imp_sth_t *imp_sth, | |
SV *statement, | |
SV *attribs) | |
{ | |
dTHR; | |
D_imp_dbh_from_sth; | |
RETCODE rc; | |
int dbh_active; | |
char *sql; | |
STRLEN sql_len; | |
sql = SvPV(statement, sql_len); | |
imp_sth->done_desc = 0; | |
imp_sth->henv = imp_dbh->henv; /* needed for dbd_error */ | |
imp_sth->hdbc = imp_dbh->hdbc; | |
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_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; | |
if (DBIc_TRACE(imp_dbh, 0, 0, 5)) | |
TRACE1(imp_dbh, " initializing sth query timeout to %d\n", | |
(int)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; | |
} | |
imp_sth->odbc_exec_direct = imp_dbh->odbc_exec_direct; | |
{ | |
/* | |
* allow setting of odbc_execdirect in prepare() or overriding | |
*/ | |
SV **odbc_exec_direct_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 ((odbc_exec_direct_sv = | |
DBD_ATTRIB_GET_SVP(attribs, "odbc_execdirect", | |
(I32)strlen("odbc_execdirect"))) != NULL) { | |
imp_sth->odbc_exec_direct = SvIV(*odbc_exec_direct_sv) != 0; | |
} | |
if ((odbc_exec_direct_sv = | |
DBD_ATTRIB_GET_SVP(attribs, "odbc_exec_direct", | |
(I32)strlen("odbc_exec_direct"))) != NULL) { | |
imp_sth->odbc_exec_direct = SvIV(*odbc_exec_direct_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) { | |
/* parse the (possibly edited) SQL statement */ | |
if (DBIc_TRACE(imp_dbh, SQL_TRACE_FLAG, 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, 0x02000000, 0, 0)) | |
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, 0x02000000, 0, 0)) | |
TRACE0(imp_dbh, " Processing non-utf8 sql in unicode mode\n"); | |
rc = SQLPrepare(imp_sth->hstmt, imp_sth->statement, | |
strlen(imp_sth->statement)); | |
} | |
#else | |
if (DBIc_TRACE(imp_dbh, 0x02000000, 0, 0)) | |
TRACE0(imp_dbh, " Processing sql in non-unicode mode\n"); | |
rc = SQLPrepare(imp_sth->hstmt, imp_sth->statement, | |
strlen(imp_sth->statement)); | |
#endif | |
if (DBIc_TRACE(imp_dbh, 0, 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); | |
/* SQLFreeStmt(imp_sth->hstmt, SQL_DROP);*/ /* TBD: 3.0 update */ | |
imp_sth->hstmt = SQL_NULL_HSTMT; | |
return 0; | |
} | |
} else if (DBIc_TRACE(imp_dbh, 0, 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; | |
imp_sth->eod = -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"; | |
} | |
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 | |
sprintf(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) | |
{ | |
dTHR; | |
SQLRETURN rc; /* ODBC fn return value */ | |
UCHAR *rbuf_ptr; | |
SQLSMALLINT i; | |
imp_fbh_t *fbh; | |
SQLLEN t_dbsize = 0; /* size of native type */ | |
SQLSMALLINT num_fields; | |
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, 0, 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 */ | |
rc = SQLNumResultCols(imp_sth->hstmt, &num_fields); | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(h, rc, "dbd_describe/SQLNumResultCols"); | |
return 0; | |
} else if (DBIc_TRACE(imp_sth, 0, 0, 4)) | |
TRACE1(imp_sth, " dbd_describe SQLNumResultCols=0 (rows=%d)\n", | |
num_fields); | |
/* | |
* A little extra check to see if SQLMoreResults is supported | |
* before trying to call it. This is to work around some strange | |
* behavior with SQLServer's driver and stored procedures which | |
* insert data. | |
*/ | |
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, 0, 0, 8)) | |
TRACE1(imp_sth, | |
" Numfields = 0, SQLMoreResults = %d\n", rc); | |
if (rc == SQL_SUCCESS_WITH_INFO) { | |
AllODBCErrors(imp_sth->henv, imp_sth->hdbc, imp_sth->hstmt, | |
DBIc_TRACE(imp_sth, 0, 0, 4), | |
DBIc_LOGPIO(imp_dbh)); | |
} | |
if (rc == SQL_NO_DATA) { | |
imp_sth->moreResults = 0; | |
break; | |
} else if (!SQL_SUCCEEDED(rc)) { | |
break; | |
} | |
/* 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; | |
rc = SQLNumResultCols(imp_sth->hstmt, &num_fields); | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(h, rc, "dbd_describe/SQLNumResultCols"); | |
return 0; | |
} else if (DBIc_TRACE(imp_sth, 0, 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 (num_fields == 0) { | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) | |
TRACE0(imp_dbh, " dbd_describe skipped (no result cols)\n"); | |
return 1; | |
} | |
/* 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, i=0; i < num_fields; i++, fbh++) { | |
fbh->imp_sth = imp_sth; | |
#ifdef WITH_UNICODE | |
rc = SQLDescribeColW(imp_sth->hstmt, | |
(SQLSMALLINT)(i+1), | |
(SQLWCHAR *) cur_col_name, | |
(SQLSMALLINT)imp_dbh->max_column_name_len, | |
&fbh->ColNameLen, | |
&fbh->ColSqlType, | |
&fbh->ColDef, | |
&fbh->ColScale, | |
&fbh->ColNullable); | |
/* | |
printf("strlen=%d, collen=%d\n", strlen(cur_col_name), fbh->ColNameLen); | |
{ | |
int i; | |
SQLWCHAR *wp = (SQLWCHAR *)cur_col_name; | |
printf("SQLDescribeCol = "); | |
for (i = 0; i < fbh->ColNameLen; i++) { | |
printf("%ld, ", wp[i]); | |
} | |
printf("\n"); | |
} | |
*/ | |
#else /* WITH_UNICODE */ | |
rc = SQLDescribeCol(imp_sth->hstmt, | |
(SQLSMALLINT)(i+1), | |
cur_col_name, | |
(SQLSMALLINT)imp_dbh->max_column_name_len, | |
&fbh->ColNameLen, | |
&fbh->ColSqlType, | |
&fbh->ColDef, | |
&fbh->ColScale, | |
&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'; | |
#endif | |
#ifdef SQL_COLUMN_DISPLAY_SIZE | |
if (DBIc_TRACE(imp_sth, 0, 0, 8)) | |
PerlIO_printf(DBIc_LOGPIO(imp_dbh), | |
" DescribeCol column = %d, name = %s, " | |
"len = %d, type = %s(%d), " | |
"precision = %ld, scale = %d, nullable = %d\n", | |
i+1, fbh->ColName, | |
fbh->ColNameLen, | |
S_SqlTypeToString(fbh->ColSqlType), | |
fbh->ColSqlType, | |
fbh->ColDef, fbh->ColScale, fbh->ColNullable); | |
rc = SQLColAttributes(imp_sth->hstmt, | |
(SQLSMALLINT)(i+1),SQL_COLUMN_DISPLAY_SIZE, | |
NULL, 0, NULL , | |
&fbh->ColDisplaySize);/* TBD: 3.0 update */ | |
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, 0, 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, 0, 0, 8)) { | |
TRACE1(imp_sth, " display size = %ld\n", fbh->ColDisplaySize); | |
} | |
/* TBD: should we only add a terminator if it's a char??? */ | |
fbh->ColDisplaySize += 1; /* add terminator */ | |
#else | |
fbh->ColDisplaySize = imp_sth->odbc_column_display_size; | |
#endif | |
#ifdef SQL_COLUMN_LENGTH | |
rc = SQLColAttributes(imp_sth->hstmt,(SQLSMALLINT)(i+1), | |
SQL_COLUMN_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, 0, 0, 8) ) { | |
TRACE1(imp_sth, | |
" describe/SQLColAttributes/SQL_COLUMN_LENGTH not " | |
"supported, fallback on %d\n", fbh->ColLength); | |
} | |
rc = SQL_SUCCESS; | |
} else if (DBIc_TRACE(imp_sth, 0, 0, 8)) { | |
TRACE1(imp_sth, " column length = %ld\n", fbh->ColLength); | |
} | |
# if defined(WITH_UNICODE) | |
fbh->ColLength += 1; /* add extra byte for double nul terminator */ | |
# endif | |
#else | |
fbh->ColLength = imp_sth->odbc_column_display_size; | |
#endif | |
/* 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); | |
} | |
fbh->ColDisplaySize*=sizeof(SQLWCHAR); | |
fbh->ColLength*=sizeof(SQLWCHAR); | |
break; | |
#else | |
# 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; | |
break; | |
# endif /* WITH_UNICODE */ | |
#endif | |
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; | |
#ifdef TIMESTAMP_STRUCT /* XXX! */ | |
case SQL_TIMESTAMP: | |
case SQL_TYPE_TIMESTAMP: | |
fbh->ftype = SQL_C_TIMESTAMP; | |
fbh->ColDisplaySize = sizeof(TIMESTAMP_STRUCT); | |
break; | |
#endif | |
} | |
/* make sure alignment is accounted for on all types, including | |
* chars */ | |
#if 0 | |
if (fbh->ftype != SQL_C_CHAR) { | |
t_dbsize += t_dbsize % sizeof(int); /* alignment (JLU incorrect!) */ | |
} | |
#endif | |
t_dbsize += fbh->ColDisplaySize; | |
/* alignment -- always pad so the next column is aligned on a | |
word boundary */ | |
t_dbsize += (sizeof(int) - (t_dbsize % sizeof(int))) % sizeof(int); | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) | |
PerlIO_printf(DBIc_LOGPIO(imp_dbh), | |
" now using col %d: type = %s (%d), len = %d, " | |
"display size = %d, prec = %d, scale = %d\n", | |
i+1, S_SqlTypeToString(fbh->ColSqlType), | |
fbh->ColSqlType, | |
fbh->ColLength, fbh->ColDisplaySize, | |
fbh->ColDef, fbh->ColScale); | |
} | |
if (!SQL_SUCCEEDED(rc)) { | |
/* dbd_error called above */ | |
Safefree(imp_sth->fbh); | |
return 0; | |
} | |
/* allocate Row memory */ | |
Newz(42, imp_sth->RowBuffer, t_dbsize + num_fields, UCHAR); | |
/* Second pass: | |
- get column names | |
- bind column output | |
*/ | |
rbuf_ptr = imp_sth->RowBuffer; | |
for(i=0, fbh = imp_sth->fbh; | |
i < num_fields && SQL_SUCCEEDED(rc); i++, fbh++) | |
{ | |
/* not sure I need this anymore, since we are trying to align | |
* the columns anyway | |
* */ | |
switch(fbh->ftype) | |
{ | |
case SQL_C_BINARY: | |
case SQL_C_TIMESTAMP: | |
case SQL_C_TYPE_TIMESTAMP: | |
/* make sure pointer is on word boundary for Solaris */ | |
rbuf_ptr += (sizeof(int) - | |
((rbuf_ptr - imp_sth->RowBuffer) % sizeof(int))) % | |
sizeof(int); | |
break; | |
} | |
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 */ | |
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; | |
} | |
} /* end pass 2 */ | |
if (!SQL_SUCCEEDED(rc)) { | |
/* dbd_error called above */ | |
Safefree(imp_sth->fbh); | |
return 0; | |
} | |
return 1; | |
} | |
/*======================================================================*/ | |
/* */ | |
/* dbd_st_execute */ | |
/* ============== */ | |
/* */ | |
/* returns: */ | |
/* -2 - error */ | |
/* >=0 - ok, row count */ | |
/* -1 - unknown count */ | |
/* */ | |
/*======================================================================*/ | |
int dbd_st_execute( | |
SV *sth, imp_sth_t *imp_sth) | |
{ | |
dTHR; | |
RETCODE rc; | |
D_imp_dbh_from_sth; | |
int outparams = 0; | |
int ret; | |
if (DBIc_TRACE(imp_sth, 0, 0, 3)) | |
TRACE1(imp_dbh, "+dbd_st_execute(%p)\n", sth); | |
/* | |
* 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, 0, 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 != &sv_undef) { | |
phs_t *phs = (phs_t*)(void*)SvPVX(sv); | |
if (!rebind_param(sth, imp_sth, phs)) return -2; | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 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, 0, 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, 0, 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, 0, 0, 5)) | |
TRACE1(imp_dbh, " NEED DATA\n", imp_sth->hstmt); | |
while ((rc = SQLParamData(imp_sth->hstmt, (PTR*) &phs)) == | |
SQL_STILL_EXECUTING) { | |
if (DBIc_TRACE(imp_sth, 0, 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) { | |
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). | |
*/ | |
ptr = SvPV(phs->sv, len); | |
rc = SQLPutData(imp_sth->hstmt, ptr, len); | |
if (!SQL_SUCCEEDED(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, 0, 0, 3)) | |
TRACE1(imp_dbh, "-dbd_st_execute(%p)=-2\n", sth); | |
return -2; | |
} | |
if (rc != SQL_NO_DATA) { | |
/* SWORD num_fields; */ | |
RETCODE rc2; | |
rc2 = SQLRowCount(imp_sth->hstmt, &imp_sth->RowCount); | |
if (DBIc_TRACE(imp_sth, 0, 0, 7)) | |
TRACE2(imp_dbh, " SQLRowCount=%d (rows=%d)\n", | |
rc2, (SQL_SUCCEEDED(rc2) ? imp_sth->RowCount : -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, 0, 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 klude 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; | |
sts = SQLNumResultCols(imp_sth->hstmt, &flds); | |
if (flds == 0) { /* not a result-set */ | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) | |
TRACE2(imp_dbh, " 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, 0, 0, 3)) { | |
TRACE0(imp_sth, | |
" !!dbd_describe failed, dbd_st_execute #1...!\n"); | |
} | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 0, 4)) { | |
TRACE1(imp_sth, " have %d fields\n", | |
DBIc_NUM_FIELDS(imp_sth)); | |
} | |
} else { | |
if (DBIc_TRACE(imp_sth, 0, 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); | |
} | |
imp_sth->eod = SQL_SUCCESS; | |
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, 0, 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) | |
{ | |
dTHR; | |
D_imp_dbh_from_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, SQL_ERROR, "no select statement currently executing"); | |
return Nullav; | |
} | |
rc = SQLFetch(imp_sth->hstmt); | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) | |
TRACE1(imp_dbh, " SQLFetch rc %d\n", rc); | |
imp_sth->eod = 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, 0, 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, 0, 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, 0, 0, 3), | |
DBIc_LOGPIO(imp_dbh)); | |
} | |
if (!dbd_describe(sth, imp_sth, 1)) { | |
if (DBIc_TRACE(imp_sth, 0, 0, 3)) | |
TRACE0(imp_dbh, | |
" !!MORE Results dbd_describe failed...!\n"); | |
return Nullav; /* dbd_describe already called dbd_error() */ | |
} | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 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 [[email protected]] | |
* 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, 0, 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, 0, 0, 4)) | |
PerlIO_printf( | |
DBIc_LOGPIO(imp_dbh), " fetch col#%d %s datalen=%d displ=%d\n", | |
i, fbh->ColName, fbh->datalen, 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. | |
*/ | |
dbd_error( | |
sth, SQL_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 */ | |
TIMESTAMP_STRUCT *ts; | |
case SQL_C_TIMESTAMP: | |
case SQL_C_TYPE_TIMESTAMP: | |
ts = (TIMESTAMP_STRUCT *)fbh->data; | |
sprintf(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; | |
while(fbh->datalen && p[fbh->datalen-1]==L' ') { | |
--fbh->datalen; | |
} | |
} | |
sv_setwvn(sv,(SQLWCHAR*)fbh->data,fbh->datalen/sizeof(SQLWCHAR)); | |
break; | |
#endif /* WITH_UNICODE */ | |
default: | |
if (ChopBlanks && fbh->ColSqlType == SQL_CHAR && fbh->datalen > 0) | |
{ | |
char *p = (char*)fbh->data; | |
while(fbh->datalen && p[fbh->datalen - 1]==' ') | |
--fbh->datalen; | |
} | |
sv_setpvn(sv, (char*)fbh->data, fbh->datalen); | |
} | |
} | |
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) | |
{ | |
dTHR; | |
D_imp_dbh_from_sth; | |
RETCODE rc; | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 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) | |
{ | |
dTHR; | |
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 != &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 && !dirty) { | |
rc = SQLFreeHandle(SQL_HANDLE_STMT,imp_sth->hstmt); | |
/* rc = SQLFreeStmt(imp_sth->hstmt, SQL_DROP);*/ /* TBD: 3.0 update */ | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 0, 4)) | |
TRACE2(imp_sth, " +get_param_type(%p,%s)\n", sth, phs->name); | |
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(imp_sth); | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) | |
TRACE1(imp_dbh, " defaulted param type to %d\n", phs->sql_type); | |
} 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)) { | |
phs->sql_type = default_parameter_type(imp_sth); | |
if (DBIc_TRACE(imp_sth, 0, 0, 3)) | |
TRACE1(imp_dbh, " SQLDescribeParam failed reverting to " | |
"default SQL bind type %d\n", phs->sql_type); | |
/* show any odbc errors in log */ | |
AllODBCErrors(imp_sth->henv, imp_sth->hdbc, imp_sth->hstmt, | |
DBIc_TRACE(imp_sth, 0, 0, 3), | |
DBIc_LOGPIO(imp_sth)); | |
} else { | |
if (DBIc_TRACE(imp_sth, 0, 0, 5)) | |
PerlIO_printf(DBIc_LOGPIO(imp_dbh), | |
" SQLDescribeParam %s: SqlType=%s(%d) " | |
"param_size=%d Scale=%d Nullable=%d\n", | |
phs->name, | |
S_SqlTypeToString(phs->described_sql_type), | |
phs->described_sql_type, | |
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, 0, 0, 5)) | |
TRACE3(imp_dbh, | |
" Param %s is numeric SQL type %s " | |
"(param size:%d) changed to SQL_VARCHAR", | |
phs->name, | |
S_SqlTypeToString(phs->described_sql_type), | |
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, 0, 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, 0, 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: | |
phs->value_type = SQL_C_WCHAR; | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 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) | |
{ | |
dTHR; | |
D_imp_dbh_from_sth; | |
SQLRETURN rc; | |
/* args of SQLBindParameter() call */ | |
SQLSMALLINT param_type; | |
SQLSMALLINT value_type; | |
UCHAR *value_ptr; | |
SQLULEN column_size; | |
SQLSMALLINT d_digits; | |
SQLLEN buffer_length; | |
SQLULEN default_column_size; | |
STRLEN value_len = 0; | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) { | |
char *text = neatsvpv(phs->sv,value_len); | |
PerlIO_printf( | |
DBIc_LOGPIO(imp_dbh), | |
" +rebind_param %s '%.100s' (size svCUR=%d/SvLEN=%d/max=%ld) " | |
"svtype %ld, value type:%d sql type:%d\n", | |
phs->name, text, 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)) | |
croak(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) | |
* Here we set phs->sv_buf, and value_len. | |
*/ | |
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, 0, 0, 8)) { | |
TRACE1(imp_dbh, | |
" Need to modify phs->sv in place: old length = %i\n", | |
value_len); | |
} | |
SV_toWCHAR(phs->sv); /* may modify SvPV(phs->sv), ... */ | |
/* ... so phs->sv_buf must be updated */ | |
phs->sv_buf=SvPV(phs->sv,value_len); | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 0, 4)) { | |
PerlIO_printf(DBIc_LOGPIO(imp_dbh), | |
" bind %s '%.100s' value_len=%d maxlen=%ld null=%d)\n", | |
phs->name, SvOK(phs->sv) ? phs->sv_buf : "(null)", | |
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. Need to test on other platforms to ensure this | |
* does not cause a problem. | |
*/ | |
param_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))) { | |
default_column_size = phs->param_size; | |
} else { | |
if ((phs->sql_type == SQL_VARCHAR) && !phs->is_inout) { | |
default_column_size = phs->biggestparam; | |
} else { | |
default_column_size = value_len; | |
} | |
} | |
/* JLU | |
* need to look at this. | |
* was buffer_length = value_len for some binding purposes | |
*/ | |
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 */ | |
switch(phs->sql_type) { | |
case SQL_LONGVARBINARY: | |
case SQL_BINARY: | |
case SQL_VARBINARY: | |
value_type = SQL_C_BINARY; | |
d_digits = 0; | |
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: | |
d_digits = 0; | |
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?) */ | |
/* bug fix! if phs->sv is not OK, then there's a chance | |
* we go through garbage data to determine the length */ | |
if (SvOK(phs->sv)) { | |
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++; | |
} | |
} | |
} else { | |
/* hard code for SQL Server when passing Undef to | |
bound parameters */ | |
column_size = 23; | |
} | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
if (!SvOK(phs->sv)) { | |
phs->strlen_or_ind = SQL_NULL_DATA; | |
/* if is_inout, shouldn't we null terminate the buffer and send | |
* it, instead?? */ | |
if (!phs->is_inout) { | |
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; | |
phs->strlen_or_ind = value_len; | |
/* not undef, may be a blank string or something */ | |
if (!phs->is_inout && phs->strlen_or_ind == 0) { | |
column_size = 1; | |
} | |
} | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) { | |
PerlIO_printf(DBIc_LOGPIO(imp_dbh), | |
" bind %s value_type:%d %s cs=%d dd=%d bl=%ld\n", | |
phs->name, value_type, S_SqlTypeToString(phs->sql_type), | |
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, 0, 0, 4)) | |
TRACE1(imp_dbh, " using data_at_exec for size %ld\n", value_len); | |
d_digits = 0; /* not relevant to lobs */ | |
phs->strlen_or_ind = SQL_LEN_DATA_AT_EXEC(vl); | |
value_ptr = (UCHAR*) phs; | |
} | |
if (DBIc_TRACE(imp_sth, 0, 0, 5)) { | |
PerlIO_printf( | |
DBIc_LOGPIO(imp_dbh), | |
" SQLBindParameter: idx=%d: param_type=%d, name=%s, " | |
"value_type=%d, SQL_Type=%d, column_size=%d, d_digits=%d, " | |
"value_ptr=%p, buffer_length=%d, cbValue=%d, param_size=%d\n", | |
phs->idx, param_type, phs->name, value_type, phs->sql_type, | |
column_size, d_digits, value_ptr, buffer_length, phs->strlen_or_ind, | |
phs->param_size); | |
/* avoid tracing data_at_exec as value_ptr will point to phs */ | |
if ((value_type == SQL_C_CHAR) && (phs->strlen_or_ind > 0)) { | |
TRACE1(imp_sth, " Param value = %s\n", value_ptr); | |
} | |
#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 ((strcmp(imp_dbh->odbc_driver_name, "SQLSRV32.DLL") == 0) && | |
((phs->sql_type == SQL_LONGVARCHAR) || (phs->sql_type == SQL_LONGVARBINARY)) && | |
(column_size == 2147483647) && (phs->strlen_or_ind < 0) && | |
((-phs->strlen_or_ind + SQL_LEN_DATA_AT_EXEC_OFFSET) >= 409600)) { | |
phs->strlen_or_ind = SQL_LEN_DATA_AT_EXEC(0); | |
buffer_length = 0; | |
} | |
/* | |
* 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 ((phs->sql_type == SQL_VARCHAR) && (column_size < buffer_length)) { | |
column_size = buffer_length; | |
} | |
rc = SQLBindParameter(imp_sth->hstmt, | |
phs->idx, param_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"); | |
return 0; | |
} | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) TRACE0(imp_dbh, " -rebind_param\n"); | |
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 sql_type, | |
SV *attribs, | |
int is_inout, | |
IV maxlen) | |
{ | |
dTHR; | |
SV **phs_svp; | |
STRLEN name_len; | |
char *name; | |
char namebuf[30]; | |
phs_t *phs; | |
D_imp_dbh_from_sth; | |
if (SvNIOK(ph_namesv) ) { /* passed as a number */ | |
name = namebuf; | |
sprintf(name, "%d", (int)SvIV(ph_namesv)); | |
name_len = strlen(name); | |
} | |
else { | |
name = SvPV(ph_namesv, name_len); | |
} | |
if (DBIc_TRACE(imp_sth, 0, 0, 4)) { | |
PerlIO_printf( | |
DBIc_LOGPIO(imp_dbh), | |
"+dbd_bind_ph(%p, %s, value='%.200s', attribs=%s, sql_type=%ld, is_inout=%d, maxlen=%ld\n", | |
sth, name, SvOK(newvalue) ? SvPV_nolen(newvalue) : "undef", | |
attribs ? SvPV_nolen(attribs) : "", 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, 0, 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 */ | |
phs->requested_type = sql_type; /* save type requested */ | |
if (phs->sv == &sv_undef) { /* first bind for this placeholder */ | |
phs->value_type = SQL_C_CHAR; /* default */ | |
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)); | |
} | |
} | |
/* check later rebinds for any changes */ | |
/* | |
* else if (is_inout || phs->is_inout) { | |
* croak("Can't rebind or change param %s in/out mode after first bind", phs->name); | |
* } | |
* */ | |
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, 0, 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 == &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, 0, 0, 4)) TRACE0(imp_dbh, "-dbd_bind_ph=1\n"); | |
return 1; | |
} | |
/* fall through for "immediate" binding */ | |
if (DBIc_TRACE(imp_sth, 0, 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; | |
{ | |
dTHR; | |
SQLLEN retl; | |
SV *bufsv; | |
RETCODE rc; | |
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, 0, 0, 4)) | |
PerlIO_printf(DBIc_LOGPIO(imp_sth), | |
"SQLGetData(...,off=%d, len=%d)->rc=%d,len=%d SvCUR=%d\n", | |
destoffset, len, rc, 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, 0, 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_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 }, | |
{ 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_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_has_unicode", ODBC_HAS_UNICODE}, | |
{ "odbc_out_connect_string", ODBC_OUTCON_STR}, | |
{ 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) | |
{ | |
dTHR; | |
D_imp_drh_from_dbh; | |
RETCODE rc; | |
STRLEN kl; | |
STRLEN plen; | |
char *key = SvPV(keysv,kl); | |
int on; | |
UDWORD vParam; | |
const db_params *pars; | |
int bSetSQLConnectionOption; | |
if ((pars = S_dbOption(S_db_storeOptions, key, kl)) == NULL) { | |
if (DBIc_TRACE(imp_dbh, 0, 0, 3)) | |
TRACE1(imp_dbh, | |
" !!DBD::ODBC unsupported attribute passed (%s)\n", key); | |
return FALSE; | |
} | |
bSetSQLConnectionOption = TRUE; | |
switch(pars->fOption) | |
{ | |
case SQL_LOGIN_TIMEOUT: | |
case SQL_TXN_ISOLATION: | |
case SQL_ROWSET_SIZE: | |
vParam = SvIV(valuesv); | |
break; | |
case SQL_OPT_TRACEFILE: | |
vParam = (UDWORD) SvPV(valuesv, plen); | |
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_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_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_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, 0, 0, 4)) | |
TRACE0(imp_dbh, | |
" Supported AsyncType is SQL_AM_CONNECTION\n"); | |
rc = SQLSetConnectOption(imp_dbh->hdbc, | |
SQL_ATTR_ASYNC_ENABLE, | |
SQL_ASYNC_ENABLE_ON | |
); | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(dbh, rc, "db_STORE/SQLSetConnectOption"); | |
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, 0, 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, 0, 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 = SQLSetConnectOption(imp_dbh->hdbc, | |
SQL_ATTR_ASYNC_ENABLE, | |
SQL_ASYNC_ENABLE_OFF | |
); | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(dbh, rc, "db_STORE/SQLSetConnectOption"); | |
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 = on ? pars->atrue : pars->afalse; | |
break; | |
default: | |
on = SvTRUE(valuesv); | |
vParam = on ? pars->atrue : pars->afalse; | |
break; | |
} | |
if (bSetSQLConnectionOption) { | |
/* TBD: 3.0 update */ | |
rc = SQLSetConnectOption(imp_dbh->hdbc, pars->fOption, vParam); | |
if (!SQL_SUCCEEDED(rc)) { | |
dbd_error(dbh, rc, "db_STORE/SQLSetConnectOption"); | |
return FALSE; | |
} | |
/* 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) | |
{ | |
dTHR; | |
D_imp_drh_from_dbh; | |
RETCODE rc; | |
STRLEN kl; | |
char *key = SvPV(keysv,kl); | |
UDWORD vParam = 0; | |
const db_params *pars; | |
SV *retsv = Nullsv; | |
/* checking pars we need FAST */ | |
if (DBIc_TRACE(imp_dbh, 0, 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_dbname, 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_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_REBIND: | |
retsv = newSViv(imp_dbh->odbc_force_rebind); | |
break; | |
case ODBC_EXEC_DIRECT: | |
retsv = newSViv(imp_dbh->odbc_exec_direct); | |
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 = &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 = SQLGetConnectOption(imp_dbh->hdbc, pars->fOption, &vParam);/* TBD: 3.0 update */ | |
dbd_error(dbh, rc, "db_FETCH/SQLGetConnectOption"); | |
if (!SQL_SUCCEEDED(rc)) { | |
if (DBIc_TRACE(imp_dbh, 0, 0, 3)) | |
TRACE1(imp_dbh, | |
" !!SQLGetConnectOption=%d in dbd_db_FETCH\n", rc); | |
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("",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("",0), /* END */ | |
}; | |
#undef s_A | |
/*======================================================================*/ | |
/* */ | |
/* dbd_st_FETCH_attrib */ | |
/* =================== */ | |
/* */ | |
/*======================================================================*/ | |
SV *dbd_st_FETCH_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv) | |
{ | |
dTHR; | |
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, 0, 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,na), key,imp_sth->done_desc); | |
} | |
return &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, 0, 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(sv_2mortal((SV*)av)); | |
if (DBIc_TRACE(imp_sth, 0, 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, 0, 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(sv_2mortal((SV*)av)); | |
while(--i >= 0) | |
av_store(av, i, | |
(imp_sth->fbh[i].ColNullable == SQL_NO_NULLS) | |
? &sv_no : &sv_yes); | |
break; | |
case 4: /* TYPE */ | |
av = newAV(); | |
retsv = newRV(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(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(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(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(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, 0, 0, 4)) { | |
TRACE0(imp_sth, | |
" numfields == 0 && moreResults = 0 finish\n"); | |
} | |
if (outparams) { | |
odbc_handle_outparams(imp_sth, DBIc_TRACE_LEVEL(imp_sth)); | |
} | |
/* XXX need to 'finish' here */ | |
dbd_st_finish(sth, imp_sth); | |
} | |
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 != &sv_undef) { | |
phs_t *phs = (phs_t*)(void*)SvPVX(sv); | |
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 != &sv_undef) { | |
HV *subh = newHV(); | |
phs_t *phs = (phs_t*)(void*)SvPVX(sv); | |
hv_store(subh, "TYPE", 4, newSViv(phs->sql_type), 0); | |
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; | |
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) | |
{ | |
dTHR; | |
D_imp_dbh_from_sth; | |
STRLEN kl; | |
STRLEN vl; | |
char *key = SvPV(keysv,kl); | |
char *value = SvPV(valuesv, vl); | |
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 = 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; | |
} | |
return FALSE; | |
} | |
SV * | |
odbc_get_info(dbh, ftype) | |
SV *dbh; | |
int ftype; | |
{ | |
dTHR; | |
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 &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, 0, 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; | |
{ | |
dTHR; | |
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; | |
{ | |
dTHR; | |
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, 0, 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; | |
{ | |
dTHR; | |
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; | |
{ | |
dTHR; | |
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_foreign_keys/SQLAllocHandle(stmt)"); | |
return 0; | |
} | |
/* just for sanity, later. Any internals that may rely on this (including */ | |
/* debugging) will have valid data */ | |
imp_sth->statement = (char *)safemalloc(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); | |
sprintf(imp_sth->statement, | |
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; | |
{ | |
dTHR; | |
D_imp_dbh(dbh); | |
D_imp_sth(sth); | |
RETCODE rc; | |
int dbh_active; | |
#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 */ | |
imp_sth->statement = (char *)safemalloc(strlen(cSqlGetTypeInfo)+ftype/10+1); | |
sprintf(imp_sth->statement, 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); | |
/* SQLFreeStmt(imp_sth->hstmt, SQL_DROP);*/ /* TBD: 3.0 update */ | |
imp_sth->hstmt = SQL_NULL_HSTMT; | |
return 0; | |
} | |
return build_results(sth, dbh, rc); | |
} | |
SV * | |
odbc_cancel(sth) | |
SV *sth; | |
{ | |
dTHR; | |
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); | |
} | |
/************************************************************************/ | |
/* */ | |
/* odbc_col_attributes */ | |
/* =================== */ | |
/* */ | |
/************************************************************************/ | |
SV *odbc_col_attributes(SV *sth, int colno, int desctype) | |
{ | |
dTHR; | |
D_imp_sth(sth); | |
RETCODE rc; | |
SV *retsv = NULL; | |
char str_attr[256]; | |
SWORD str_attr_len = 0; | |
SQLLEN num_attr = 0; | |
memset(str_attr, '\0', sizeof(str_attr)); | |
if ( !DBIc_ACTIVE(imp_sth) ) { | |
dbd_error(sth, SQL_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, SQL_ERROR, | |
"cannot obtain SQLColAttributes for column 0"); | |
return Nullsv; | |
} | |
rc = SQLColAttributes(imp_sth->hstmt, (SQLUSMALLINT)colno, | |
(SQLUSMALLINT)desctype, | |
str_attr, sizeof(str_attr), | |
&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, 0, 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, SQL_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; | |
{ | |
dTHR; | |
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_db_columns/SQLAllocHandle(stmt)"); | |
return 0; | |
} | |
/* just for sanity, later. Any internals that may rely on this (including */ | |
/* debugging) will have valid data */ | |
imp_sth->statement = (char *)safemalloc(strlen(cSqlColumns)+ | |
strlen(XXSAFECHAR(catalog))+ | |
strlen(XXSAFECHAR(schema))+ | |
strlen(XXSAFECHAR(table))+ | |
strlen(XXSAFECHAR(column))+1); | |
sprintf(imp_sth->statement, | |
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, 0, 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); | |
/* SQLFreeStmt(imp_sth->hstmt, SQL_DROP);*/ /* TBD: 3.0 update */ | |
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, 0x04000000, 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, 0x04000000, 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"); | |
} | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 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, 0x04000000, 0, 0)) | |
TRACE1(imp_dbh, "DRIVER_VERSION = %s\n", imp_dbh->odbc_driver_version); | |
/* 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 (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) | |
TRACE1(imp_dbh, "MAX_COLUMN_NAME_LEN = %d\n", | |
imp_dbh->max_column_name_len); | |
} | |
if (imp_dbh->max_column_name_len > 256) { | |
imp_dbh->max_column_name_len = 256; | |
DBIh_SET_ERR_CHAR( | |
dbh, (imp_xxh_t*)imp_drh, "0", 1, | |
"Max column name length pegged at 256", 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, 0x04000000, 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; | |
/* 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_exec_direct = 0; /* default to not having SQLExecDirect used */ | |
imp_dbh->RowCacheSize = 1; /* default value for now */ | |
/* see if we're connected to MS SQL Server */ | |
memset(imp_dbh->odbc_dbname, 'z', sizeof(imp_dbh->odbc_dbname)); | |
rc = SQLGetInfo(imp_dbh->hdbc, SQL_DBMS_NAME, imp_dbh->odbc_dbname, | |
(SWORD)sizeof(imp_dbh->odbc_dbname), &dbvlen); | |
if (SQL_SUCCEEDED(rc)) { | |
/* can't find stricmp on my Linux, nor strcmpi. must be a | |
* portable way to do this*/ | |
if (!strcmp(imp_dbh->odbc_dbname, "Microsoft SQL Server")) { | |
imp_dbh->odbc_defer_binding = 1; | |
} | |
} else { | |
strcpy(imp_dbh->odbc_dbname, "Unknown/Unsupported"); | |
} | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) | |
TRACE1(imp_dbh, "SQL_DBMS_NAME = %s\n", imp_dbh->odbc_dbname); | |
/* 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, 0x04000000, 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, 0x04000000, 0, 0)) | |
TRACE0(imp_dbh, | |
" !!SQLGetFunctions(SQL_API_SQLMORERESULTS) failed:\n"); | |
AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, | |
DBIc_TRACE(imp_dbh, 0, 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)) { | |
imp_dbh->odbc_sqldescribeparam_supported = supported ? 1 : 0; | |
} else { | |
imp_dbh->odbc_sqldescribeparam_supported = 0; | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) | |
TRACE0(imp_dbh, | |
" !!SQLGetFunctions(SQL_API_SQLDESCRIBEPARAM) failed:\n"); | |
AllODBCErrors(imp_dbh->henv, imp_dbh->hdbc, 0, | |
DBIc_TRACE(imp_dbh, 0, 0, 3), DBIc_LOGPIO(imp_dbh)); | |
} | |
if (DBIc_TRACE(imp_dbh, 0x04000000, 0, 0)) | |
TRACE1(imp_dbh, "SQLDescribeParam supported: %d\n", | |
imp_dbh->odbc_sqldescribeparam_supported); | |
/* 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, 0x04000000, 0, 0)) | |
TRACE1(imp_dbh, | |
" Setting cursor type to: %d", odbc_cursortype); | |
/* delete odbc_cursortype so we don't see it again via STORE */ | |
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, 0x04000000, 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, 0x04000000, 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 */ | |
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, 0x04000000, 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 */ | |
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, 0x04000000, 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 */ | |
hv_delete((HV*)SvRV(attr), "odbc_column_display_size", | |
strlen("odbc_column_display_size"), G_DISCARD); | |
} | |
} | |
return 1; | |
} | |
static SQLSMALLINT default_parameter_type(imp_sth_t *imp_sth) | |
{ | |
if (imp_sth->odbc_default_bind_type != 0) { | |
return imp_sth->odbc_default_bind_type; | |
} else { | |
return ODBC_BACKUP_BIND_TYPE_VALUE; | |
} | |
} | |
/* end */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment