diff options
Diffstat (limited to 'ext/pdo_odbc')
-rwxr-xr-x | ext/pdo_odbc/CREDITS | 2 | ||||
-rw-r--r-- | ext/pdo_odbc/EXPERIMENTAL | 2 | ||||
-rwxr-xr-x | ext/pdo_odbc/config.m4 | 174 | ||||
-rwxr-xr-x | ext/pdo_odbc/config.w32 | 20 | ||||
-rw-r--r-- | ext/pdo_odbc/odbc_driver.c | 493 | ||||
-rw-r--r-- | ext/pdo_odbc/odbc_stmt.c | 841 | ||||
-rw-r--r-- | ext/pdo_odbc/package2.xml | 71 | ||||
-rw-r--r-- | ext/pdo_odbc/pdo_odbc.c | 188 | ||||
-rw-r--r-- | ext/pdo_odbc/php_pdo_odbc.h | 73 | ||||
-rw-r--r-- | ext/pdo_odbc/php_pdo_odbc_int.h | 195 | ||||
-rw-r--r-- | ext/pdo_odbc/tests/common.phpt | 77 | ||||
-rw-r--r-- | ext/pdo_odbc/tests/long_columns.phpt | 140 |
12 files changed, 2276 insertions, 0 deletions
diff --git a/ext/pdo_odbc/CREDITS b/ext/pdo_odbc/CREDITS new file mode 100755 index 0000000..b5af9df --- /dev/null +++ b/ext/pdo_odbc/CREDITS @@ -0,0 +1,2 @@ +ODBC driver for PDO +Wez Furlong diff --git a/ext/pdo_odbc/EXPERIMENTAL b/ext/pdo_odbc/EXPERIMENTAL new file mode 100644 index 0000000..139597f --- /dev/null +++ b/ext/pdo_odbc/EXPERIMENTAL @@ -0,0 +1,2 @@ + + diff --git a/ext/pdo_odbc/config.m4 b/ext/pdo_odbc/config.m4 new file mode 100755 index 0000000..b70dc9d --- /dev/null +++ b/ext/pdo_odbc/config.m4 @@ -0,0 +1,174 @@ +dnl $Id$ +dnl config.m4 for extension pdo_odbc +dnl vim:et:sw=2:ts=2: + +define([PDO_ODBC_HELP_TEXT],[[ + include and lib dirs are looked for under 'dir'. + + 'flavour' can be one of: ibm-db2, iODBC, unixODBC, generic + If ',dir' part is omitted, default for the flavour + you have selected will used. e.g.: + + --with-pdo-odbc=unixODBC + + will check for unixODBC under /usr/local. You may attempt + to use an otherwise unsupported driver using the \"generic\" + flavour. The syntax for generic ODBC support is: + + --with-pdo-odbc=generic,dir,libname,ldflags,cflags + + When build as shared the extension filename is always pdo_odbc.so]]) + +PHP_ARG_WITH(pdo-odbc, for ODBC v3 support for PDO, +[ --with-pdo-odbc=flavour,dir + PDO: Support for 'flavour' ODBC driver.]PDO_ODBC_HELP_TEXT) + + +AC_DEFUN([PDO_ODBC_CHECK_HEADER],[ + AC_MSG_CHECKING([for $1 in $PDO_ODBC_INCDIR]) + if test -f "$PDO_ODBC_INCDIR/$1"; then + php_pdo_have_header=yes + PHP_DEF_HAVE(translit($1,.,_)) + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi +]) + +if test "$PHP_PDO_ODBC" != "no"; then + + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + ifdef([PHP_CHECK_PDO_INCLUDES], + [ + PHP_CHECK_PDO_INCLUDES + ],[ + AC_MSG_CHECKING([for PDO includes]) + if test -f $abs_srcdir/include/php/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $abs_srcdir/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$abs_srcdir/ext + elif test -f $prefix/include/php/ext/pdo/php_pdo_driver.h; then + pdo_cv_inc_path=$prefix/include/php/ext + else + AC_MSG_ERROR([Cannot find php_pdo_driver.h.]) + fi + AC_MSG_RESULT($pdo_cv_inc_path) + ]) + + AC_MSG_CHECKING([for selected PDO ODBC flavour]) + + pdo_odbc_flavour="`echo $PHP_PDO_ODBC | cut -d, -f1`" + pdo_odbc_dir="`echo $PHP_PDO_ODBC | cut -d, -f2`" + + if test "$pdo_odbc_dir" = "$PHP_PDO_ODBC" ; then + pdo_odbc_dir= + fi + + case $pdo_odbc_flavour in + ibm-db2) + pdo_odbc_def_libdir=/home/db2inst1/sqllib/lib + pdo_odbc_def_incdir=/home/db2inst1/sqllib/include + pdo_odbc_def_lib=db2 + ;; + + iODBC|iodbc) + pdo_odbc_def_libdir=/usr/local/$PHP_LIBDIR + pdo_odbc_def_incdir=/usr/local/include + pdo_odbc_def_lib=iodbc + ;; + + unixODBC|unixodbc) + pdo_odbc_def_libdir=/usr/local/$PHP_LIBDIR + pdo_odbc_def_incdir=/usr/local/include + pdo_odbc_def_lib=odbc + ;; + + ODBCRouter|odbcrouter) + pdo_odbc_def_libdir=/usr/$PHP_LIBDIR + pdo_odbc_def_incdir=/usr/include + pdo_odbc_def_lib=odbcsdk + ;; + + generic) + pdo_odbc_def_lib="`echo $PHP_PDO_ODBC | cut -d, -f3`" + pdo_odbc_def_ldflags="`echo $PHP_PDO_ODBC | cut -d, -f4`" + pdo_odbc_def_cflags="`echo $PHP_PDO_ODBC | cut -d, -f5`" + pdo_odbc_flavour="generic-$pdo_odbc_def_lib" + ;; + + *) + AC_MSG_ERROR([Unknown ODBC flavour $pdo_odbc_flavour]PDO_ODBC_HELP_TEXT) + ;; + esac + + if test -n "$pdo_odbc_dir"; then + PDO_ODBC_INCDIR="$pdo_odbc_dir/include" + PDO_ODBC_LIBDIR="$pdo_odbc_dir/$PHP_LIBDIR" + else + PDO_ODBC_INCDIR="$pdo_odbc_def_incdir" + PDO_ODBC_LIBDIR="$pdo_odbc_def_libdir" + fi + + AC_MSG_RESULT([$pdo_odbc_flavour + libs $PDO_ODBC_LIBDIR, + headers $PDO_ODBC_INCDIR]) + + if test ! -d "$PDO_ODBC_LIBDIR" ; then + AC_MSG_WARN([library dir $PDO_ODBC_LIBDIR does not exist]) + fi + + PDO_ODBC_CHECK_HEADER(odbc.h) + PDO_ODBC_CHECK_HEADER(odbcsdk.h) + PDO_ODBC_CHECK_HEADER(iodbc.h) + PDO_ODBC_CHECK_HEADER(sqlunix.h) + PDO_ODBC_CHECK_HEADER(sqltypes.h) + PDO_ODBC_CHECK_HEADER(sqlucode.h) + PDO_ODBC_CHECK_HEADER(sql.h) + PDO_ODBC_CHECK_HEADER(isql.h) + PDO_ODBC_CHECK_HEADER(sqlext.h) + PDO_ODBC_CHECK_HEADER(isqlext.h) + PDO_ODBC_CHECK_HEADER(udbcext.h) + PDO_ODBC_CHECK_HEADER(sqlcli1.h) + PDO_ODBC_CHECK_HEADER(LibraryManager.h) + PDO_ODBC_CHECK_HEADER(cli0core.h) + PDO_ODBC_CHECK_HEADER(cli0ext.h) + PDO_ODBC_CHECK_HEADER(cli0cli.h) + PDO_ODBC_CHECK_HEADER(cli0defs.h) + PDO_ODBC_CHECK_HEADER(cli0env.h) + + if test "$php_pdo_have_header" != "yes"; then + AC_MSG_ERROR([Cannot find header file(s) for pdo_odbc]) + fi + + PDO_ODBC_INCLUDE="$pdo_odbc_def_cflags -I$PDO_ODBC_INCDIR -DPDO_ODBC_TYPE=\\\"$pdo_odbc_flavour\\\"" + PDO_ODBC_LDFLAGS="$pdo_odbc_def_ldflags -L$PDO_ODBC_LIBDIR -l$pdo_odbc_def_lib" + + PHP_EVAL_LIBLINE([$PDO_ODBC_LDFLAGS], [PDO_ODBC_SHARED_LIBADD]) + + dnl Check first for an ODBC 1.0 function to assert that the libraries work + PHP_CHECK_LIBRARY($pdo_odbc_def_lib, SQLBindCol, + [ + dnl And now check for an ODBC 3.0 function to assert that they're + dnl *good* libraries. + PHP_CHECK_LIBRARY($pdo_odbc_def_lib, SQLAllocHandle, + [], [ + AC_MSG_ERROR([ +Your ODBC library does not appear to be ODBC 3 compatible. +You should consider using iODBC or unixODBC instead, and loading your +libraries as a driver in that environment; it will emulate the +functions required for PDO support. +])], $PDO_ODBC_LDFLAGS) + ],[ + AC_MSG_ERROR([Your ODBC library does not exist or there was an error. Check config.log for more information]) + ], $PDO_ODBC_LDFLAGS) + + PHP_NEW_EXTENSION(pdo_odbc, pdo_odbc.c odbc_driver.c odbc_stmt.c, $ext_shared,,-I$pdo_cv_inc_path $PDO_ODBC_INCLUDE) + PHP_SUBST(PDO_ODBC_SHARED_LIBADD) + ifdef([PHP_ADD_EXTENSION_DEP], + [ + PHP_ADD_EXTENSION_DEP(pdo_odbc, pdo) + ]) +fi diff --git a/ext/pdo_odbc/config.w32 b/ext/pdo_odbc/config.w32 new file mode 100755 index 0000000..cd1017f --- /dev/null +++ b/ext/pdo_odbc/config.w32 @@ -0,0 +1,20 @@ +// $Id$ +// vim:ft=javascript + +ARG_WITH("pdo-odbc", "ODBC support for PDO", "no"); + +if (PHP_PDO_ODBC != "no") { + if (CHECK_LIB("odbc32.lib", "pdo_odbc") && CHECK_LIB("odbccp32.lib", "pdo_odbc") + && CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_PDO_ODBC') + && CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_PDO_ODBC')) { + + EXTENSION("pdo_odbc", "pdo_odbc.c odbc_driver.c odbc_stmt.c"); + ADD_EXTENSION_DEP('pdo_odbc', 'pdo'); + + } else { + WARNING("pdo_odbc support can't be enabled, headers or libraries are missing (SDK)") + PHP_PDO_ODBC = "no" + } +} + + diff --git a/ext/pdo_odbc/odbc_driver.c b/ext/pdo_odbc/odbc_driver.c new file mode 100644 index 0000000..3316368 --- /dev/null +++ b/ext/pdo_odbc/odbc_driver.c @@ -0,0 +1,493 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_odbc.h" +#include "php_pdo_odbc_int.h" +#include "zend_exceptions.h" + +static int pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + char *message = NULL; + + if (stmt) { + S = (pdo_odbc_stmt*)stmt->driver_data; + einfo = &S->einfo; + } + + spprintf(&message, 0, "%s (%s[%ld] at %s:%d)", + einfo->last_err_msg, + einfo->what, einfo->last_error, + einfo->file, einfo->line); + + add_next_index_long(info, einfo->last_error); + add_next_index_string(info, message, 0); + add_next_index_string(info, einfo->last_state, 1); + + return 1; +} + + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line TSRMLS_DC) /* {{{ */ +{ + SQLRETURN rc; + SQLSMALLINT errmsgsize = 0; + SQLHANDLE eh; + SQLSMALLINT htype, recno = 1; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_odbc_stmt*)stmt->driver_data; + + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } + + if (statement == SQL_NULL_HSTMT && S) { + statement = S->stmt; + } + + if (statement) { + htype = SQL_HANDLE_STMT; + eh = statement; + } else if (H->dbc) { + htype = SQL_HANDLE_DBC; + eh = H->dbc; + } else { + htype = SQL_HANDLE_ENV; + eh = H->env; + } + + rc = SQLGetDiagRec(htype, eh, recno++, einfo->last_state, &einfo->last_error, + einfo->last_err_msg, sizeof(einfo->last_err_msg)-1, &errmsgsize); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + errmsgsize = 0; + } + + einfo->last_err_msg[errmsgsize] = '\0'; + einfo->file = file; + einfo->line = line; + einfo->what = what; + + strcpy(*pdo_err, einfo->last_state); +/* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), einfo->last_error TSRMLS_CC, "SQLSTATE[%s] %s: %d %s", + *pdo_err, what, einfo->last_error, einfo->last_err_msg); + } + + /* just like a cursor, once you start pulling, you need to keep + * going until the end; SQL Server (at least) will mess with the + * actual cursor state if you don't finish retrieving all the + * diagnostic records (which can be generated by PRINT statements + * in the query, for instance). */ + while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + char discard_state[6]; + char discard_buf[1024]; + SQLINTEGER code; + rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, + discard_buf, sizeof(discard_buf)-1, &errmsgsize); + } + +} +/* }}} */ + +static int odbc_handle_closer(pdo_dbh_t *dbh TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data; + + if (H->dbc != SQL_NULL_HANDLE) { + SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + SQLDisconnect(H->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); + H->dbc = NULL; + } + SQLFreeHandle(SQL_HANDLE_ENV, H->env); + H->env = NULL; + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + + return 0; +} + +static int odbc_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC) +{ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); + enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; + int ret; + char *nsql = NULL; + int nsql_len = 0; + + S->H = H; + S->assume_utf8 = H->assume_utf8; + + /* before we prepare, we need to peek at the query; if it uses named parameters, + * we want PDO to rewrite them for us */ + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len TSRMLS_CC); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return 0; + } + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); + + if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { + efree(S); + if (nsql) { + efree(nsql); + } + pdo_odbc_drv_error("SQLAllocStmt"); + return 0; + } + + cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY TSRMLS_CC); + if (cursor_type != PDO_CURSOR_FWDONLY) { + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void*)SQL_SCROLLABLE, 0); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + efree(nsql); + } + return 0; + } + } + + rc = SQLPrepare(S->stmt, (char*)sql, SQL_NTS); + if (nsql) { + efree(nsql); + } + + stmt->driver_data = S; + stmt->methods = &odbc_stmt_methods; + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLPrepare"); + if (rc != SQL_SUCCESS_WITH_INFO) { + /* clone error information into the db handle */ + strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); + H->einfo.file = S->einfo.file; + H->einfo.line = S->einfo.line; + H->einfo.what = S->einfo.what; + strcpy(dbh->error_code, stmt->error_code); + } + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +static long odbc_handle_doer(pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + SQLLEN row_count = -1; + PDO_ODBC_HSTMT stmt; + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLAllocHandle: STMT"); + return -1; + } + + rc = SQLExecDirect(stmt, (char *)sql, sql_len); + + if (rc == SQL_NO_DATA) { + /* If SQLExecDirect executes a searched update or delete statement that + * does not affect any rows at the data source, the call to + * SQLExecDirect returns SQL_NO_DATA. */ + row_count = 0; + goto out; + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLExecDirect"); + goto out; + } + + rc = SQLRowCount(stmt, &row_count); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLRowCount"); + goto out; + } + if (row_count == -1) { + row_count = 0; + } +out: + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return row_count; +} + +static int odbc_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, int unquotedlen, char **quoted, int *quotedlen, enum pdo_param_type param_type TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + /* TODO: figure it out */ + return 0; +} + +static int odbc_handle_begin(pdo_dbh_t *dbh TSRMLS_DC) +{ + if (dbh->auto_commit) { + /* we need to disable auto-commit now, to be able to initiate a transaction */ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); + return 0; + } + } + return 1; +} + +static int odbc_handle_commit(pdo_dbh_t *dbh TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Commit"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + if (dbh->auto_commit) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return 0; + } + } + return 1; +} + +static int odbc_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Rollback"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + if (dbh->auto_commit && H->dbc) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return 0; + } + } + + return 1; +} + +static int odbc_handle_set_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + H->assume_utf8 = zval_is_true(val); + return 1; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_handle_get_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE, 1); + return 1; + + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_PREFETCH: + case PDO_ATTR_TIMEOUT: + case PDO_ATTR_SERVER_INFO: + case PDO_ATTR_CONNECTION_STATUS: + break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8 ? 1 : 0); + return 1; + + } + return 0; +} + +static struct pdo_dbh_methods odbc_methods = { + odbc_handle_closer, + odbc_handle_preparer, + odbc_handle_doer, + odbc_handle_quoter, + odbc_handle_begin, + odbc_handle_commit, + odbc_handle_rollback, + odbc_handle_set_attr, + NULL, /* last id */ + pdo_odbc_fetch_error_func, + odbc_handle_get_attr, /* get attr */ + NULL, /* check_liveness */ +}; + +static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) /* {{{ */ +{ + pdo_odbc_db_handle *H; + RETCODE rc; + int use_direct = 0; + SQLUINTEGER cursor_lib; + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + + dbh->driver_data = H; + + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); + rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); + goto fail; + } + +#ifdef SQL_ATTR_CONNECTION_POOLING + if (pdo_odbc_pool_on != SQL_CP_OFF) { + rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void*)pdo_odbc_pool_mode, 0); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); + goto fail; + } + } +#endif + + rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle (DBC)"); + goto fail; + } + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)(dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); + goto fail; + } + + /* set up the cursor library, if needed, or if configured explicitly */ + cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED TSRMLS_CC); + rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void*)cursor_lib, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); + goto fail; + } + + if (strchr(dbh->data_source, ';')) { + char dsnbuf[1024]; + short dsnbuflen; + + use_direct = 1; + + /* Force UID and PWD to be set in the DSN */ + if (dbh->username && *dbh->username && !strstr(dbh->data_source, "uid") + && !strstr(dbh->data_source, "UID")) { + char *dsn; + spprintf(&dsn, 0, "%s;UID=%s;PWD=%s", dbh->data_source, dbh->username, dbh->password); + pefree((char*)dbh->data_source, dbh->is_persistent); + dbh->data_source = dsn; + } + + rc = SQLDriverConnect(H->dbc, NULL, (char*)dbh->data_source, strlen(dbh->data_source), + dsnbuf, sizeof(dsnbuf)-1, &dsnbuflen, SQL_DRIVER_NOPROMPT); + } + if (!use_direct) { + rc = SQLConnect(H->dbc, (char*)dbh->data_source, SQL_NTS, dbh->username, SQL_NTS, dbh->password, SQL_NTS); + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); + goto fail; + } + + /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ + + dbh->methods = &odbc_methods; + dbh->alloc_own_columns = 1; + + return 1; + +fail: + dbh->methods = &odbc_methods; + return 0; +} +/* }}} */ + +pdo_driver_t pdo_odbc_driver = { + PDO_DRIVER_HEADER(odbc), + pdo_odbc_handle_factory +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c new file mode 100644 index 0000000..6ee2fcd --- /dev/null +++ b/ext/pdo_odbc/odbc_stmt.c @@ -0,0 +1,841 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_odbc.h" +#include "php_pdo_odbc_int.h" + +enum pdo_odbc_conv_result { + PDO_ODBC_CONV_NOT_REQUIRED, + PDO_ODBC_CONV_OK, + PDO_ODBC_CONV_FAIL +}; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SWORD sqltype) +{ + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2(pdo_stmt_t *stmt, int is_unicode, const char *buf, + unsigned long buflen, unsigned long *outlen) +{ +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR)S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, const char *buf, + unsigned long buflen, unsigned long *outlen) +{ +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + DWORD ret; + + ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)buf, buflen/sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + if (S->convbufsize <= ret) { + S->convbufsize = ret + 1; + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)buf, buflen/sizeof(WCHAR), S->convbuf, S->convbufsize, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + *outlen = ret; + S->convbuf[*outlen] = '\0'; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S TSRMLS_DC) +{ + if (S->cols) { + int i; + + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + efree(S->cols[i].data); + } + } + efree(S->cols); + S->cols = NULL; + } +} + +static int odbc_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + if (S->stmt != SQL_NULL_HANDLE) { + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + S->stmt = SQL_NULL_HANDLE; + } + + free_cols(stmt, S TSRMLS_CC); + if (S->convbuf) { + efree(S->convbuf); + } + efree(S); + + return 1; +} + +static int odbc_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) +{ + RETCODE rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + char *buf = NULL; + SQLLEN row_count = -1; + + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + + rc = SQLExecute(S->stmt); + + while (rc == SQL_NEED_DATA) { + struct pdo_bound_param_data *param; + + rc = SQLParamData(S->stmt, (SQLPOINTER*)¶m); + if (rc == SQL_NEED_DATA) { + php_stream *stm; + int len; + pdo_odbc_param *P; + + P = (pdo_odbc_param*)param->driver_data; + if (Z_TYPE_P(param->parameter) != IS_RESOURCE) { + /* they passed in a string */ + unsigned long ulen; + convert_to_string(param->parameter); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(param->parameter), + Z_STRLEN_P(param->parameter), + &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + SQLPutData(S->stmt, Z_STRVAL_P(param->parameter), + Z_STRLEN_P(param->parameter)); + break; + case PDO_ODBC_CONV_OK: + SQLPutData(S->stmt, S->convbuf, ulen); + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; + } + + /* we assume that LOBs are binary and don't need charset + * conversion */ + + php_stream_from_zval_no_verify(stm, ¶m->parameter); + if (!stm) { + /* shouldn't happen either */ + pdo_odbc_stmt_error("input LOB is no longer a stream"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + + /* now suck data from the stream and stick it into the database */ + if (buf == NULL) { + buf = emalloc(8192); + } + + do { + len = php_stream_read(stm, buf, 8192); + if (len == 0) { + break; + } + SQLPutData(S->stmt, buf, len); + } while (1); + } + } + + if (buf) { + efree(buf); + } + + switch (rc) { + case SQL_SUCCESS: + break; + case SQL_NO_DATA_FOUND: + case SQL_SUCCESS_WITH_INFO: + pdo_odbc_stmt_error("SQLExecute"); + break; + + default: + pdo_odbc_stmt_error("SQLExecute"); + return 0; + } + + SQLRowCount(S->stmt, &row_count); + stmt->row_count = row_count; + + if (!stmt->executed) { + /* do first-time-only definition of bind/mapping stuff */ + SQLSMALLINT colcount; + + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + + stmt->column_count = (int)colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + } + + return 1; +} + +static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type TSRMLS_DC) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + RETCODE rc; + SWORD sqltype = 0, ctype = 0, scale = 0, nullable = 0; + UDWORD precision = 0; + pdo_odbc_param *P; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + + switch (event_type) { + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: + { + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_LOB: + break; + + case PDO_PARAM_STMT: + return 0; + + default: + break; + } + + rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno+1, &sqltype, &precision, &scale, &nullable); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + /* MS Access, for instance, doesn't support SQLDescribeParam, + * so we need to guess */ + sqltype = PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB ? + SQL_LONGVARBINARY : + SQL_LONGVARCHAR; + precision = 4000; + scale = 5; + nullable = 1; + + if (param->max_value_len > 0) { + precision = param->max_value_len; + } + } + if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) { + ctype = SQL_C_BINARY; + } else { + ctype = SQL_C_CHAR; + } + + P = emalloc(sizeof(*P)); + param->driver_data = P; + + P->len = 0; /* is re-populated each EXEC_PRE */ + P->outbuf = NULL; + + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { + P->paramtype = SQL_PARAM_INPUT_OUTPUT; + } else if (param->max_value_len <= 0) { + P->paramtype = SQL_PARAM_INPUT; + } else { + P->paramtype = SQL_PARAM_OUTPUT; + } + + if (P->paramtype != SQL_PARAM_INPUT) { + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { + /* need an explicit buffer to hold result */ + P->len = param->max_value_len > 0 ? param->max_value_len : precision; + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1)); + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) { + pdo_odbc_stmt_error("Can't bind a lob for output"); + return 0; + } + + rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1, + P->paramtype, ctype, sqltype, precision, scale, + P->paramtype == SQL_PARAM_INPUT ? + (SQLPOINTER)param : + P->outbuf, + P->len, + &P->len + ); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return 0; + } + + case PDO_PARAM_EVT_EXEC_PRE: + P = param->driver_data; + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + if (Z_TYPE_P(param->parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_statbuf sb; + + php_stream_from_zval_no_verify(stm, ¶m->parameter); + + if (!stm) { + return 0; + } + + if (0 == php_stream_stat(stm, &sb)) { + if (P->outbuf) { + int len, amount; + char *ptr = P->outbuf; + char *end = P->outbuf + P->len; + + P->len = 0; + do { + amount = end - ptr; + if (amount == 0) { + break; + } + if (amount > 8192) + amount = 8192; + len = php_stream_read(stm, ptr, amount); + if (len == 0) { + break; + } + ptr += len; + P->len += len; + } while (1); + + } else { + P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size); + } + } else { + if (P->outbuf) { + P->len = 0; + } else { + P->len = SQL_LEN_DATA_AT_EXEC(0); + } + } + } else { + convert_to_string(param->parameter); + if (P->outbuf) { + P->len = Z_STRLEN_P(param->parameter); + memcpy(P->outbuf, Z_STRVAL_P(param->parameter), P->len); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(param->parameter)); + } + } + } else if (Z_TYPE_P(param->parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) { + P->len = SQL_NULL_DATA; + } else { + convert_to_string(param->parameter); + if (P->outbuf) { + unsigned long ulen; + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(param->parameter), + Z_STRLEN_P(param->parameter), + &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(param->parameter); + memcpy(P->outbuf, Z_STRVAL_P(param->parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(param->parameter)); + } + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + P = param->driver_data; + if (P->outbuf) { + if (P->outbuf) { + unsigned long ulen; + char *srcbuf; + unsigned long srclen; + + switch (P->len) { + case SQL_NULL_DATA: + zval_dtor(param->parameter); + ZVAL_NULL(param->parameter); + break; + default: + convert_to_string(param->parameter); + switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, P->outbuf, P->len, &ulen)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + srcbuf = P->outbuf; + srclen = P->len; + break; + case PDO_ODBC_CONV_OK: + srcbuf = S->convbuf; + srclen = ulen; + break; + } + + Z_STRVAL_P(param->parameter) = erealloc(Z_STRVAL_P(param->parameter), srclen+1); + memcpy(Z_STRVAL_P(param->parameter), srcbuf, srclen); + Z_STRLEN_P(param->parameter) = srclen; + Z_STRVAL_P(param->parameter)[srclen] = '\0'; + } + } + } + return 1; + } + } + return 1; +} + +static int odbc_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, long offset TSRMLS_DC) +{ + RETCODE rc; + SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: odbcori = SQL_FETCH_NEXT; break; + case PDO_FETCH_ORI_PRIOR: odbcori = SQL_FETCH_PRIOR; break; + case PDO_FETCH_ORI_FIRST: odbcori = SQL_FETCH_FIRST; break; + case PDO_FETCH_ORI_LAST: odbcori = SQL_FETCH_LAST; break; + case PDO_FETCH_ORI_ABS: odbcori = SQL_FETCH_ABSOLUTE; break; + case PDO_FETCH_ORI_REL: odbcori = SQL_FETCH_RELATIVE; break; + default: + strcpy(stmt->error_code, "HY106"); + return 0; + } + rc = SQLFetchScroll(S->stmt, odbcori, offset); + + if (rc == SQL_SUCCESS) { + return 1; + } + if (rc == SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFetchScroll"); + return 1; + } + + if (rc == SQL_NO_DATA) { + /* pdo_odbc_stmt_error("SQLFetchScroll"); */ + return 0; + } + + pdo_odbc_stmt_error("SQLFetchScroll"); + + return 0; +} + +static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + zend_bool dyn = FALSE; + RETCODE rc; + SWORD colnamelen; + SDWORD colsize, displaysize; + + rc = SQLDescribeCol(S->stmt, colno+1, S->cols[colno].colname, + sizeof(S->cols[colno].colname)-1, &colnamelen, + &S->cols[colno].coltype, &colsize, NULL, NULL); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLDescribeCol"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + rc = SQLColAttribute(S->stmt, colno+1, + SQL_DESC_DISPLAY_SIZE, + NULL, 0, NULL, &displaysize); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLColAttribute"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + colsize = displaysize; + + col->maxlen = S->cols[colno].datalen = colsize; + col->namelen = colnamelen; + col->name = estrdup(S->cols[colno].colname); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); + + /* returning data as a string */ + col->param_type = PDO_PARAM_STR; + + /* tell ODBC to put it straight into our buffer, but only if it + * isn't "long" data, and only if we haven't already bound a long + * column. */ + if (colsize < 256 && !S->going_long) { + S->cols[colno].data = emalloc(colsize+1); + S->cols[colno].is_long = 0; + + rc = SQLBindCol(S->stmt, colno+1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, + S->cols[colno].datalen+1, &S->cols[colno].fetched_len); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLBindCol"); + return 0; + } + } else { + /* allocate a smaller buffer to keep around for smaller + * "long" columns */ + S->cols[colno].data = emalloc(256); + S->going_long = 1; + S->cols[colno].is_long = 1; + } + + return 1; +} + +static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + pdo_odbc_column *C = &S->cols[colno]; + unsigned long ulen; + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { + unsigned long alloced = 4096; + unsigned long used = 0; + char *buf; + RETCODE rc; + + /* fetch it into C->data, which is allocated with a length + * of 256 bytes; if there is more to be had, we then allocate + * bigger buffer for the caller to free */ + + rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, + 256, &C->fetched_len); + + if (rc == SQL_SUCCESS) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + + if (rc == SQL_SUCCESS_WITH_INFO) { + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks + in order into the output buffer + + this loop has to work whether or not SQLGetData() provides the total column length. + calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read + for that size would be slower except maybe for extremely long columns.*/ + char *buf2; + + buf2 = emalloc(256); + buf = estrndup(C->data, 256); + used = 255; /* not 256; the driver NUL terminated the buffer */ + + do { + C->fetched_len = 0; + /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */ + rc = SQLGetData(S->stmt, colno+1, SQL_C_CHAR, buf2, 256, &C->fetched_len); + + /* resize output buffer and reassemble block */ + if (rc==SQL_SUCCESS_WITH_INFO) { + /* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx + states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size) + (if a driver fails to follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */ + buf = erealloc(buf, used + 255+1); + memcpy(buf + used, buf2, 255); + used = used + 255; + } else if (rc==SQL_SUCCESS) { + buf = erealloc(buf, used + C->fetched_len+1); + memcpy(buf + used, buf2, C->fetched_len); + used = used + C->fetched_len; + } else { + /* includes SQL_NO_DATA */ + break; + } + + } while (1); + + efree(buf2); + + /* NULL terminate the buffer once, when finished, for use with the rest of PHP */ + buf[used] = '\0'; + + *ptr = buf; + *caller_frees = 1; + *len = used; + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } + + /* something went caca */ + *ptr = NULL; + *len = 0; + return 1; + } + +in_data: + /* check the indicator to ensure that the data is intact */ + if (C->fetched_len == SQL_NULL_DATA) { + /* A NULL value */ + *ptr = NULL; + *len = 0; + return 1; + } else if (C->fetched_len >= 0) { + /* it was stored perfectly */ + *ptr = C->data; + *len = C->fetched_len; + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } else { + /* no data? */ + *ptr = NULL; + *len = 0; + return 1; + } + + unicode_conv: + switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, *ptr, *len, &ulen)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + + case PDO_ODBC_CONV_OK: + if (*caller_frees) { + efree(*ptr); + } + *ptr = emalloc(ulen + 1); + *len = ulen; + memcpy(*ptr, S->convbuf, ulen+1); + *caller_frees = 1; + return 1; + } + return 1; +} + +static int odbc_stmt_set_param(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) +{ + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + rc = SQLSetCursorName(S->stmt, Z_STRVAL_P(val), Z_STRLEN_P(val)); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLSetCursorName"); + return 0; + + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zval_is_true(val); + return 0; + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "setAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_get_attr(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) +{ + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + { + char buf[256]; + SQLSMALLINT len = 0; + rc = SQLGetCursorName(S->stmt, buf, sizeof(buf), &len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + ZVAL_STRINGL(val, buf, len, 1); + return 1; + } + pdo_odbc_stmt_error("SQLGetCursorName"); + return 0; + } + + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "getAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC) +{ + SQLRETURN rc; + SQLSMALLINT colcount; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + /* NOTE: can't guarantee that output or input/output parameters + * are set until this fella returns SQL_NO_DATA, according to + * MSDN ODBC docs */ + rc = SQLMoreResults(S->stmt); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + + free_cols(stmt, S TSRMLS_CC); + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + stmt->column_count = (int)colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + + return 1; +} + +struct pdo_stmt_methods odbc_stmt_methods = { + odbc_stmt_dtor, + odbc_stmt_execute, + odbc_stmt_fetch, + odbc_stmt_describe, + odbc_stmt_get_col, + odbc_stmt_param_hook, + odbc_stmt_set_param, + odbc_stmt_get_attr, /* get attr */ + NULL, /* get column meta */ + odbc_stmt_next_rowset +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_odbc/package2.xml b/ext/pdo_odbc/package2.xml new file mode 100644 index 0000000..3eb99d1 --- /dev/null +++ b/ext/pdo_odbc/package2.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package packagerversion="1.4.2" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 +http://pear.php.net/dtd/tasks-1.0.xsd +http://pear.php.net/dtd/package-2.0 +http://pear.php.net/dtd/package-2.0.xsd"> + <name>PDO_ODBC</name> + <channel>pecl.php.net</channel> + <summary>ODBC v3 Interface driver for PDO</summary> + <description>This extension provides an ODBC v3 driver for PDO. It supports unixODBC +and IBM DB2 libraries, and will support more in future releases. + </description> + <lead> + <name>Wez Furlong</name> + <user>wez</user> + <email>wez@php.net</email> + <active>yes</active> + </lead> + <date>2006-05-01</date> + <version> + <release>1.0.1</release> + <api>1.0.1</api> + </version> + <stability> + <release>stable</release> + <api>stable</api> + </stability> + <license uri="http://www.php.net/license">PHP</license> + <notes> +- Fixed bug #35552 (crash when pdo_odbc prepare fails). (Wez). +- Fixed bug #36632 (bad error reporting for pdo_odbc exec UPDATE). (Wez). +- repackage with package2.xml + +You require either IBM DB2 CLI libraries or unixODBC to use this package. + +If you are running on windows, you can download the binary from here: +http://pecl4win.php.net/ext.php/php_pdo_odbc.dll + + </notes> + <contents> + <dir name="/"> + <file name="config.m4" role="src" /> + <file name="config.w32" role="src" /> + <file name="CREDITS" role="doc" /> + <file name="odbc_driver.c" role="src" /> + <file name="odbc_stmt.c" role="src" /> + <file name="pdo_odbc.c" role="src" /> + <file name="php_pdo_odbc.h" role="src" /> + <file name="php_pdo_odbc_int.h" role="src" /> + </dir> <!-- / --> + </contents> + <dependencies> + <required> + <php> + <min>5.0.3</min> + </php> + <pearinstaller> + <min>1.4.0</min> + </pearinstaller> + <package> + <name>pdo</name> + <channel>pecl.php.net</channel> + <min>1.0.3</min> + <providesextension>PDO</providesextension> + </package> + </required> + </dependencies> + <providesextension>PDO_ODBC</providesextension> + <extsrcrelease> + <configureoption name="with-pdo-odbc" prompt="flavour,dir? (just leave blank for help)" /> + </extsrcrelease> +</package> diff --git a/ext/pdo_odbc/pdo_odbc.c b/ext/pdo_odbc/pdo_odbc.c new file mode 100644 index 0000000..f481c07 --- /dev/null +++ b/ext/pdo_odbc/pdo_odbc.c @@ -0,0 +1,188 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_odbc.h" +#include "php_pdo_odbc_int.h" + +/* {{{ pdo_odbc_functions[] */ +const zend_function_entry pdo_odbc_functions[] = { + PHP_FE_END +}; +/* }}} */ + +/* {{{ pdo_odbc_deps[] */ +#if ZEND_MODULE_API_NO >= 20050922 +static const zend_module_dep pdo_odbc_deps[] = { + ZEND_MOD_REQUIRED("pdo") + ZEND_MOD_END +}; +#endif +/* }}} */ + +/* {{{ pdo_odbc_module_entry */ +zend_module_entry pdo_odbc_module_entry = { +#if ZEND_MODULE_API_NO >= 20050922 + STANDARD_MODULE_HEADER_EX, NULL, + pdo_odbc_deps, +#else + STANDARD_MODULE_HEADER, +#endif + "PDO_ODBC", + pdo_odbc_functions, + PHP_MINIT(pdo_odbc), + PHP_MSHUTDOWN(pdo_odbc), + NULL, + NULL, + PHP_MINFO(pdo_odbc), + "1.0.1", + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_PDO_ODBC +ZEND_GET_MODULE(pdo_odbc) +#endif + +#ifdef SQL_ATTR_CONNECTION_POOLING +SQLUINTEGER pdo_odbc_pool_on = SQL_CP_OFF; +SQLUINTEGER pdo_odbc_pool_mode = SQL_CP_ONE_PER_HENV; +#endif + +#if defined(DB2CLI_VER) && !defined(PHP_WIN32) +PHP_INI_BEGIN() + PHP_INI_ENTRY("pdo_odbc.db2_instance_name", NULL, PHP_INI_SYSTEM, NULL) +PHP_INI_END() + +#endif + +/* {{{ PHP_MINIT_FUNCTION */ +PHP_MINIT_FUNCTION(pdo_odbc) +{ +#ifdef SQL_ATTR_CONNECTION_POOLING + char *pooling_val = NULL; +#endif + + if (FAILURE == php_pdo_register_driver(&pdo_odbc_driver)) { + return FAILURE; + } + +#if defined(DB2CLI_VER) && !defined(PHP_WIN32) + REGISTER_INI_ENTRIES(); + { + char *instance = INI_STR("pdo_odbc.db2_instance_name"); + if (instance) { + char *env = malloc(sizeof("DB2INSTANCE=") + strlen(instance)); + if (!env) { + return FAILURE; + } + strcpy(env, "DB2INSTANCE="); + strcat(env, instance); + putenv(env); + /* after this point, we can't free env without breaking the environment */ + } + } +#endif + +#ifdef SQL_ATTR_CONNECTION_POOLING + /* ugh, we don't really like .ini stuff in PDO, but since ODBC connection + * pooling is process wide, we can't set it from within the scope of a + * request without affecting others, which goes against our isolated request + * policy. So, we use cfg_get_string here to check it this once. + * */ + if (FAILURE == cfg_get_string("pdo_odbc.connection_pooling", &pooling_val) || pooling_val == NULL) { + pooling_val = "strict"; + } + if (strcasecmp(pooling_val, "strict") == 0 || strcmp(pooling_val, "1") == 0) { + pdo_odbc_pool_on = SQL_CP_ONE_PER_HENV; + pdo_odbc_pool_mode = SQL_CP_STRICT_MATCH; + } else if (strcasecmp(pooling_val, "relaxed") == 0) { + pdo_odbc_pool_on = SQL_CP_ONE_PER_HENV; + pdo_odbc_pool_mode = SQL_CP_RELAXED_MATCH; + } else if (*pooling_val == '\0' || strcasecmp(pooling_val, "off") == 0) { + pdo_odbc_pool_on = SQL_CP_OFF; + } else { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Error in pdo_odbc.connection_pooling configuration. Value MUST be one of 'strict', 'relaxed' or 'off'"); + return FAILURE; + } + + if (pdo_odbc_pool_on != SQL_CP_OFF) { + SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)pdo_odbc_pool_on, 0); + } +#endif + + REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_USE_CURSOR_LIBRARY", PDO_ODBC_ATTR_USE_CURSOR_LIBRARY); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_ASSUME_UTF8", PDO_ODBC_ATTR_ASSUME_UTF8); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_IF_NEEDED", SQL_CUR_USE_IF_NEEDED); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_DRIVER", SQL_CUR_USE_DRIVER); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_ODBC", SQL_CUR_USE_ODBC); + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(pdo_odbc) +{ +#if defined(DB2CLI_VER) && !defined(PHP_WIN32) + UNREGISTER_INI_ENTRIES(); +#endif + php_pdo_unregister_driver(&pdo_odbc_driver); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(pdo_odbc) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "PDO Driver for ODBC (" PDO_ODBC_TYPE ")" , "enabled"); +#ifdef SQL_ATTR_CONNECTION_POOLING + php_info_print_table_row(2, "ODBC Connection Pooling", pdo_odbc_pool_on == SQL_CP_OFF ? + "Disabled" : (pdo_odbc_pool_mode == SQL_CP_STRICT_MATCH ? "Enabled, strict matching" : "Enabled, relaxed matching")); +#else + php_info_print_table_row(2, "ODBC Connection Pooling", "Not supported in this build"); +#endif + php_info_print_table_end(); + +#if defined(DB2CLI_VER) && !defined(PHP_WIN32) + DISPLAY_INI_ENTRIES(); +#endif +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_odbc/php_pdo_odbc.h b/ext/pdo_odbc/php_pdo_odbc.h new file mode 100644 index 0000000..ee80e75 --- /dev/null +++ b/ext/pdo_odbc/php_pdo_odbc.h @@ -0,0 +1,73 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef PHP_PDO_ODBC_H +#define PHP_PDO_ODBC_H + +extern zend_module_entry pdo_odbc_module_entry; +#define phpext_pdo_odbc_ptr &pdo_odbc_module_entry + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_MINIT_FUNCTION(pdo_odbc); +PHP_MSHUTDOWN_FUNCTION(pdo_odbc); +PHP_RINIT_FUNCTION(pdo_odbc); +PHP_RSHUTDOWN_FUNCTION(pdo_odbc); +PHP_MINFO_FUNCTION(pdo_odbc); + +/* + Declare any global variables you may need between the BEGIN + and END macros here: + +ZEND_BEGIN_MODULE_GLOBALS(pdo_odbc) + long global_value; + char *global_string; +ZEND_END_MODULE_GLOBALS(pdo_odbc) +*/ + +/* In every utility function you add that needs to use variables + in php_pdo_odbc_globals, call TSRMLS_FETCH(); after declaring other + variables used by that function, or better yet, pass in TSRMLS_CC + after the last function argument and declare your utility function + with TSRMLS_DC after the last declared argument. Always refer to + the globals in your function as PDO_ODBC_G(variable). You are + encouraged to rename these macros something shorter, see + examples in any other php module directory. +*/ + +#ifdef ZTS +#define PDO_ODBC_G(v) TSRMG(pdo_odbc_globals_id, zend_pdo_odbc_globals *, v) +#else +#define PDO_ODBC_G(v) (pdo_odbc_globals.v) +#endif + +#endif /* PHP_PDO_ODBC_H */ + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_odbc/php_pdo_odbc_int.h b/ext/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 0000000..8594120 --- /dev/null +++ b/ext/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,195 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong <wez@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef PHP_WIN32 +# define PDO_ODBC_TYPE "Win32" +#endif + +#ifndef PDO_ODBC_TYPE +# warning Please fix configure to give your ODBC libraries a name +# define PDO_ODBC_TYPE "Unknown" +#endif + +/* {{{ Roll a dice, pick a header at random... */ +#if HAVE_SQLCLI1_H +# include <sqlcli1.h> +# if defined(DB268K) && HAVE_LIBRARYMANAGER_H +# include <LibraryManager.h> +# endif +#endif + +#if HAVE_ODBC_H +# include <odbc.h> +#endif + +#if HAVE_IODBC_H +# include <iodbc.h> +#endif + +#if HAVE_SQLUNIX_H && !defined(PHP_WIN32) +# include <sqlunix.h> +#endif + +#if HAVE_SQLTYPES_H +# include <sqltypes.h> +#endif + +#if HAVE_SQLUCODE_H +# include <sqlucode.h> +#endif + +#if HAVE_SQL_H +# include <sql.h> +#endif + +#if HAVE_ISQL_H +# include <isql.h> +#endif + +#if HAVE_SQLEXT_H +# include <sqlext.h> +#endif + +#if HAVE_ISQLEXT_H +# include <isqlext.h> +#endif + +#if HAVE_UDBCEXT_H +# include <udbcext.h> +#endif + +#if HAVE_CLI0CORE_H +# include <cli0core.h> +#endif + +#if HAVE_CLI0EXT1_H +# include <cli0ext.h> +#endif + +#if HAVE_CLI0CLI_H +# include <cli0cli.h> +#endif + +#if HAVE_CLI0DEFS_H +# include <cli0defs.h> +#endif + +#if HAVE_CLI0ENV_H +# include <cli0env.h> +#endif + +#if HAVE_ODBCSDK_H +# include <odbcsdk.h> +#endif + +/* }}} */ + +/* {{{ Figure out the type for handles */ +#if !defined(HENV) && !defined(SQLHENV) && defined(SQLHANDLE) +# define PDO_ODBC_HENV SQLHANDLE +# define PDO_ODBC_HDBC SQLHANDLE +# define PDO_ODBC_HSTMT SQLHANDLE +#elif !defined(HENV) && (defined(SQLHENV) || defined(DB2CLI_VER)) +# define PDO_ODBC_HENV SQLHENV +# define PDO_ODBC_HDBC SQLHDBC +# define PDO_ODBC_HSTMT SQLHSTMT +#else +# define PDO_ODBC_HENV HENV +# define PDO_ODBC_HDBC HDBC +# define PDO_ODBC_HSTMT HSTMT +#endif +/* }}} */ + +typedef struct { + char last_state[6]; + char last_err_msg[SQL_MAX_MESSAGE_LENGTH]; + SDWORD last_error; + const char *file, *what; + int line; +} pdo_odbc_errinfo; + +typedef struct { + PDO_ODBC_HENV env; + PDO_ODBC_HDBC dbc; + pdo_odbc_errinfo einfo; + unsigned assume_utf8:1; + unsigned _spare:31; +} pdo_odbc_db_handle; + +typedef struct { + char *data; + unsigned long datalen; + SQLLEN fetched_len; + SWORD coltype; + char colname[128]; + unsigned is_long; + unsigned is_unicode:1; + unsigned _spare:31; +} pdo_odbc_column; + +typedef struct { + PDO_ODBC_HSTMT stmt; + pdo_odbc_column *cols; + pdo_odbc_db_handle *H; + pdo_odbc_errinfo einfo; + char *convbuf; + unsigned long convbufsize; + unsigned going_long:1; + unsigned assume_utf8:1; + unsigned _spare:30; +} pdo_odbc_stmt; + +typedef struct { + SQLINTEGER len; + SQLSMALLINT paramtype; + char *outbuf; + unsigned is_unicode:1; + unsigned _spare:31; +} pdo_odbc_param; + +extern pdo_driver_t pdo_odbc_driver; +extern struct pdo_stmt_methods odbc_stmt_methods; + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line TSRMLS_DC); +#define pdo_odbc_drv_error(what) pdo_odbc_error(dbh, NULL, SQL_NULL_HSTMT, what, __FILE__, __LINE__ TSRMLS_CC) +#define pdo_odbc_stmt_error(what) pdo_odbc_error(stmt->dbh, stmt, SQL_NULL_HSTMT, what, __FILE__, __LINE__ TSRMLS_CC) +#define pdo_odbc_doer_error(what) pdo_odbc_error(dbh, NULL, stmt, what, __FILE__, __LINE__ TSRMLS_CC) + +void pdo_odbc_init_error_table(void); +void pdo_odbc_fini_error_table(void); + +#ifdef SQL_ATTR_CONNECTION_POOLING +extern SQLUINTEGER pdo_odbc_pool_on; +extern SQLUINTEGER pdo_odbc_pool_mode; +#endif + +enum { + PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF-8 when feeding data to unicode columns */ +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_odbc/tests/common.phpt b/ext/pdo_odbc/tests/common.phpt new file mode 100644 index 0000000..276f2b7 --- /dev/null +++ b/ext/pdo_odbc/tests/common.phpt @@ -0,0 +1,77 @@ +--TEST-- +ODBC +--SKIPIF-- +<?php # vim:ft=php +if (!extension_loaded('pdo_odbc')) print 'skip'; +if (substr(PHP_OS, 0, 3) == 'WIN' && + false === getenv('PDOTEST_DSN') && + false === getenv('PDO_ODBC_TEST_DSN') && + !extension_loaded('com_dotnet')) { + die('skip - either PDOTEST_DSN or com_dotnet extension is needed to setup the connection'); +} +--REDIRECTTEST-- +# magic auto-configuration + +$config = array( + 'TESTS' => 'ext/pdo/tests', + 'ENV' => array() +); + +// try loading PDO driver using ENV vars and if none given, and on Windows, try using MS Access +// and if not, skip the test +// +// try to use common PDO env vars, instead of PDO_ODBC specific +if (false !== getenv('PDOTEST_DSN')) { + // user should have to set PDOTEST_DSN so that: + // 1. test is skipped if user doesn't want to test it, even if they have MS Access installed + // 2. it detects if ODBC driver is not installed - to avoid test bug + // 3. it detects if ODBC driver is installed - so test will be run + // 4. so a specific ODBC driver can be tested - if system has multiple ODBC drivers + + $config['ENV']['PDOTEST_DSN'] = getenv('PDOTEST_DSN'); + $config['ENV']['PDOTEST_USER'] = getenv('PDOTEST_USER'); + $config['ENV']['PDOTEST_PASS'] = getenv('PDOTEST_PASS'); + if (false !== getenv('PDOTEST_ATTR')) { + $config['ENV']['PDOTEST_ATTR'] = getenv('PDOTEST_ATTR'); + } +} else if (false !== getenv('PDO_ODBC_TEST_DSN')) { + // user set these from their shell instead + $config['ENV']['PDOTEST_DSN'] = getenv('PDO_ODBC_TEST_DSN'); + $config['ENV']['PDOTEST_USER'] = getenv('PDO_ODBC_TEST_USER'); + $config['ENV']['PDOTEST_PASS'] = getenv('PDO_ODBC_TEST_PASS'); + if (false !== getenv('PDO_ODBC_TEST_ATTR')) { + $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_ODBC_TEST_ATTR'); + } +} elseif (preg_match('/^WIN/i', PHP_OS)) { + // on Windows and user didn't set PDOTEST_DSN, try this as a fallback: + // check if MS Access DB is installed, and if yes, try using it. create a temporary MS access database. + // + $path = realpath(dirname(__FILE__)) . '\pdo_odbc.mdb'; + if (!file_exists($path)) { + try { + // try to create database + $adox = new COM('ADOX.Catalog'); + $adox->Create('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=' . $path); + $adox = null; + + } catch (Exception $e) { + } + } + if (file_exists($path)) { + // database was created and written to file system + $config['ENV']['PDOTEST_DSN'] = "odbc:Driver={Microsoft Access Driver (*.mdb)};Dbq=$path;Uid=Admin"; + } // else: $config['ENV']['PDOTEST_DSN'] not set +} // else: $config['ENV']['PDOTEST_DSN'] not set +// test will be skipped. see SKIPIF section of long_columns.phpt + +# other magic autodetection here, eg: for DB2 by inspecting env +/* +$USER = 'db2inst1'; +$PASSWD = 'ibmdb2'; +$DBNAME = 'SAMPLE'; + +$CONNECTION = "odbc:DSN=$DBNAME;UID=$USER;PWD=$PASSWD;"; +*/ + + +return $config; diff --git a/ext/pdo_odbc/tests/long_columns.phpt b/ext/pdo_odbc/tests/long_columns.phpt new file mode 100644 index 0000000..e3430de --- /dev/null +++ b/ext/pdo_odbc/tests/long_columns.phpt @@ -0,0 +1,140 @@ +--TEST-- +PDO ODBC "long" columns +--SKIPIF-- +<?php # vim:ft=php +if (!extension_loaded('pdo_odbc')) print 'skip not loaded'; +// make sure there is an ODBC driver and a DSN, or the test will fail +include 'ext/pdo/tests/pdo_test.inc'; +$config = PDOTest::get_config('ext/pdo_odbc/tests/common.phpt'); +if (!isset($config['ENV']['PDOTEST_DSN']) || $config['ENV']['PDOTEST_DSN']===false) print 'skip'; +?> +--FILE-- +<?php +// setup: set PDOTEST_DSN environment variable +// for MyODBC (MySQL) and MS SQL Server, you need to also set PDOTEST_USER and PDOTEST_PASS +// +// can use MS SQL Server on Linux - using unixODBC +// -RHEL6.2 +// -download & instructions: http://www.microsoft.com/en-us/download/details.aspx?id=28160 +// -Linux6\sqlncli-11.0.1790.0.tar.gz (it calls RHEL6.x 'Linux6' for some reason) +// -follow instructions on web page and install script +// -may have to specify connection info in connection string without using a DSN (DSN-less connection) +// -for example: +// set PDOTEST_DSN='odbc:Driver=SQL Server Native Client 11.0;Server=10.200.51.179;Database=testdb' +// set PDOTEST_USER=sa +// set PDOTEST_PASS=Password01 +// +// on Windows, the easy way to do this: +// 1. install MS Access (part of MS Office) and include ODBC (Development tools feature) +// install the x86 build of the Drivers. You might not be able to load the x64 drivers. +// 2. in Control Panel, search for ODBC and open "Setup data sources (ODBC)" +// 3. click on System DSN tab +// 4. click Add and choose "Microsoft Access Driver (*.mdb, *.accdb)" driver +// 5. enter a DSN, ex: accdb12 +// 6. click 'Create' and select a file to save the database as +// -otherwise, you'll have to open MS Access, create a database, then load that file in this Window to map it to a DSN +// 7. set the environment variable PDOTEST_DSN="odbc:<system dsn from step 5>" ex: SET PDOTEST_DSN=odbc:accdb12 +// -note: on Windows, " is included in environment variable +// +// easy way to compile: +// configure --disable-all --enable-cli --enable-zts --enable-pdo --with-pdo-odbc --enable-debug +// configure --disable-all --eanble-cli --enable-pdo --with-pdo-odbc=unixODBC,/usr,/usr --with-unixODBC=/usr --enable-debug +// + +require 'ext/pdo/tests/pdo_test.inc'; +$db = PDOTest::test_factory('ext/pdo_odbc/tests/common.phpt'); +$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + +if (false === $db->exec('CREATE TABLE TEST (id INT NOT NULL PRIMARY KEY, data CLOB)')) { + if (false === $db->exec('CREATE TABLE TEST (id INT NOT NULL PRIMARY KEY, data longtext)')) { + if (false === $db->exec('CREATE TABLE TEST (id INT NOT NULL PRIMARY KEY, data varchar(4000))')) { + die("BORK: don't know how to create a long column here:\n" . implode(", ", $db->errorInfo())); + } + } +} + +$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +// the driver reads columns in blocks of 255 bytes and then reassembles those blocks into a single buffer. +// test sizes around 255 to make sure that the reassembly works (and that the column is split into 255 byte blocks by the database) +// also, test sizes below 255 to make sure that they work - and are not treated as a long column (should be read in a single read) +$sizes = array(32, 53, 64, 79, 128, 253, 254, 255, 256, 257, 258, 1022, 1023, 1024, 1025, 1026, 510, 511, 512, 513, 514, 1278, 1279, 1280, 1281, 1282, 2046, 2047, 2048, 2049, 2050, 1534, 1535, 1536, 1537, 1538, 3070, 3071, 3072, 3073, 3074, 3998, 3999, 4000); + +function alpha_repeat($len) { + // use the alphabet instead of 'i' characters to make sure the blocks don't overlap when they are reassembled + $out = ""; + while (strlen($out) < $len) { + $out .= "abcdefghijklmnopqrstuvwxyz"; + } + return substr($out, 0, $len); +} + +// don't use Prepared Statements. that fails on MS SQL server (works with Access, MyODBC), which is a separate failure, feature/code-path from what +// this test does - nice to be able to test using MS SQL server +foreach ($sizes as $num) { + $text = alpha_repeat($num); + $db->exec("INSERT INTO TEST VALUES($num, '$text')"); +} + +// verify data +foreach ($db->query('SELECT id, data from TEST') as $row) { + $expect = alpha_repeat($row[0]); + if (strcmp($expect, $row[1])) { + echo "Failed on size $row[id]:\n"; + printf("Expected %d bytes, got %d\n", strlen($expect), strlen($row['data'])); + echo ($expect) . "\n"; + echo ($row['data']) . "\n"; + } else { + echo "Passed on size $row[id]\n"; + } +} + +echo "Finished\n"; + +--EXPECT-- +Passed on size 32 +Passed on size 53 +Passed on size 64 +Passed on size 79 +Passed on size 128 +Passed on size 253 +Passed on size 254 +Passed on size 255 +Passed on size 256 +Passed on size 257 +Passed on size 258 +Passed on size 1022 +Passed on size 1023 +Passed on size 1024 +Passed on size 1025 +Passed on size 1026 +Passed on size 510 +Passed on size 511 +Passed on size 512 +Passed on size 513 +Passed on size 514 +Passed on size 1278 +Passed on size 1279 +Passed on size 1280 +Passed on size 1281 +Passed on size 1282 +Passed on size 2046 +Passed on size 2047 +Passed on size 2048 +Passed on size 2049 +Passed on size 2050 +Passed on size 1534 +Passed on size 1535 +Passed on size 1536 +Passed on size 1537 +Passed on size 1538 +Passed on size 3070 +Passed on size 3071 +Passed on size 3072 +Passed on size 3073 +Passed on size 3074 +Passed on size 3998 +Passed on size 3999 +Passed on size 4000 +Finished + |