mirror of
https://github.com/php/php-src.git
synced 2024-12-13 03:44:17 +08:00
494 lines
13 KiB
C
494 lines
13 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| PHP Version 7 |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 1997-2017 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)
|
|
{
|
|
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 -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:
|
|
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)(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
|
|
};
|
|
|
|
/*
|
|
* 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
|
|
*/
|