summaryrefslogtreecommitdiff
path: root/ext/pdo_odbc
diff options
context:
space:
mode:
Diffstat (limited to 'ext/pdo_odbc')
-rwxr-xr-xext/pdo_odbc/CREDITS2
-rw-r--r--ext/pdo_odbc/EXPERIMENTAL2
-rwxr-xr-xext/pdo_odbc/config.m4174
-rwxr-xr-xext/pdo_odbc/config.w3220
-rw-r--r--ext/pdo_odbc/odbc_driver.c493
-rw-r--r--ext/pdo_odbc/odbc_stmt.c841
-rw-r--r--ext/pdo_odbc/package2.xml71
-rw-r--r--ext/pdo_odbc/pdo_odbc.c188
-rw-r--r--ext/pdo_odbc/php_pdo_odbc.h73
-rw-r--r--ext/pdo_odbc/php_pdo_odbc_int.h195
-rw-r--r--ext/pdo_odbc/tests/common.phpt77
-rw-r--r--ext/pdo_odbc/tests/long_columns.phpt140
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*)&param);
+ 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, &param->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, &param->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
+