123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- /*
- +----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
- +----------------------------------------------------------------------+
- | This source file is subject to version 3.01 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: |
- | https://www.php.net/license/3_01.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> |
- +----------------------------------------------------------------------+
- */
- #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 void 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, (long) 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);
- }
- 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++, (SQLCHAR *) einfo->last_state, &einfo->last_error,
- (SQLCHAR *) 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) {
- SQLCHAR discard_state[6];
- SQLCHAR discard_buf[1024];
- SQLINTEGER code;
- rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code,
- discard_buf, sizeof(discard_buf)-1, &errmsgsize);
- }
- }
- /* }}} */
- static void 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;
- }
- static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, 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;
- zend_string *nsql = NULL;
- 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, sql, &nsql);
- 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 false;
- }
- rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt);
- if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) {
- efree(S);
- if (nsql) {
- zend_string_release(nsql);
- }
- pdo_odbc_drv_error("SQLAllocStmt");
- return false;
- }
- stmt->driver_data = S;
- 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) {
- zend_string_release(nsql);
- }
- return false;
- }
- }
- rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS);
- if (nsql) {
- zend_string_release(nsql);
- }
- 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 false;
- }
- return true;
- }
- static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const zend_string *sql)
- {
- 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, (SQLCHAR *) ZSTR_VAL(sql), ZSTR_LEN(sql));
- 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;
- }
- /* TODO: Do ODBC quoter
- 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 bool 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 false;
- }
- }
- return true;
- }
- static bool 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 false;
- }
- }
- 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 false;
- }
- }
- return true;
- }
- static bool 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 false;
- }
- }
- 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 false;
- }
- }
- return true;
- }
- static bool 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;
- bool bval;
- switch (attr) {
- case PDO_ODBC_ATTR_ASSUME_UTF8:
- if (!pdo_get_bool_param(&bval, val)) {
- return false;
- }
- H->assume_utf8 = bval;
- return true;
- default:
- strcpy(H->einfo.last_err_msg, "Unknown Attribute");
- H->einfo.what = "setAttribute";
- strcpy(H->einfo.last_state, "IM001");
- return false;
- }
- }
- static int pdo_odbc_get_info_string(pdo_dbh_t *dbh, SQLUSMALLINT type, zval *val)
- {
- RETCODE rc;
- SQLSMALLINT out_len;
- char buf[256];
- pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
- rc = SQLGetInfo(H->dbc, type, (SQLPOINTER)buf, sizeof(buf), &out_len);
- /* returning -1 is treated as an error, not as unsupported */
- if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
- return -1;
- }
- ZVAL_STRINGL(val, buf, out_len);
- return 1;
- }
- 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:
- return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val);
- case PDO_ATTR_SERVER_INFO:
- return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val);
- case PDO_ATTR_PREFETCH:
- case PDO_ATTR_TIMEOUT:
- 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 zend_result odbc_handle_check_liveness(pdo_dbh_t *dbh)
- {
- RETCODE ret;
- UCHAR d_name[32];
- SQLSMALLINT len;
- pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
- /*
- * SQL_ATTR_CONNECTION_DEAD is tempting, but only in ODBC 3.5,
- * and not all drivers implement it properly
- */
- ret = SQLGetInfo(H->dbc, SQL_DATA_SOURCE_READ_ONLY, d_name,
- sizeof(d_name), &len);
- if (ret != SQL_SUCCESS || len == 0) {
- return FAILURE;
- }
- return SUCCESS;
- }
- static const struct pdo_dbh_methods odbc_methods = {
- odbc_handle_closer,
- odbc_handle_preparer,
- odbc_handle_doer,
- NULL, /* 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 */
- odbc_handle_check_liveness, /* check_liveness */
- NULL, /* get_driver_methods */
- NULL, /* request_shutdown */
- NULL, /* in transaction, use PDO's internal tracking mechanism */
- NULL /* get_gc */
- };
- 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;
- }
- /* a connection string may have = but not ; - i.e. "DSN=PHP" */
- if (strchr(dbh->data_source, '=')) {
- SQLCHAR 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, (SQLCHAR *) dbh->data_source, strlen(dbh->data_source),
- dsnbuf, sizeof(dsnbuf)-1, &dsnbuflen, SQL_DRIVER_NOPROMPT);
- }
- if (!use_direct) {
- rc = SQLConnect(H->dbc, (SQLCHAR *) dbh->data_source, SQL_NTS, (SQLCHAR *) dbh->username, SQL_NTS, (SQLCHAR *) 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
- };
|