/* +----------------------------------------------------------------------+ | Copyright (c) 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 | +----------------------------------------------------------------------+ */ #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) { pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; pdo_odbc_errinfo *einfo = &H->einfo; pdo_odbc_stmt *S = NULL; zend_string *message = NULL; if (stmt) { S = (pdo_odbc_stmt*)stmt->driver_data; einfo = &S->einfo; } message = strpprintf(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_str(info, message); add_next_index_string(info, einfo->last_state); 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) /* {{{ */ { 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, "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) { 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, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options) { 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; size_t 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); 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); 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 zend_long odbc_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len) { 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, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type param_type ) { /* 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) { 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) { 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) { 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, zend_long attr, zval *val) { 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 0; } } static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { 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); 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 const 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) /* {{{ */ { pdo_odbc_db_handle *H; RETCODE rc; int use_direct = 0; zend_ulong 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)(intptr_t)(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); 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]; SQLSMALLINT 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; } /* }}} */ const pdo_driver_t pdo_odbc_driver = { PDO_DRIVER_HEADER(odbc), pdo_odbc_handle_factory };