1/* 2 +----------------------------------------------------------------------+ 3 | PHP Version 5 | 4 +----------------------------------------------------------------------+ 5 | Copyright (c) 1997-2013 The PHP Group | 6 +----------------------------------------------------------------------+ 7 | This source file is subject to version 3.0 of the PHP license, | 8 | that is bundled with this package in the file LICENSE, and is | 9 | available through the world-wide-web at the following url: | 10 | http://www.php.net/license/3_0.txt. | 11 | If you did not receive a copy of the PHP license and are unable to | 12 | obtain it through the world-wide-web, please send a note to | 13 | license@php.net so we can mail you a copy immediately. | 14 +----------------------------------------------------------------------+ 15 | Author: Wez Furlong <wez@php.net> | 16 +----------------------------------------------------------------------+ 17*/ 18 19/* $Id$ */ 20 21#ifdef HAVE_CONFIG_H 22#include "config.h" 23#endif 24 25#include "php.h" 26#include "php_ini.h" 27#include "ext/standard/info.h" 28#include "pdo/php_pdo.h" 29#include "pdo/php_pdo_driver.h" 30#include "php_pdo_odbc.h" 31#include "php_pdo_odbc_int.h" 32#include "zend_exceptions.h" 33 34static int pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info TSRMLS_DC) 35{ 36 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 37 pdo_odbc_errinfo *einfo = &H->einfo; 38 pdo_odbc_stmt *S = NULL; 39 char *message = NULL; 40 41 if (stmt) { 42 S = (pdo_odbc_stmt*)stmt->driver_data; 43 einfo = &S->einfo; 44 } 45 46 spprintf(&message, 0, "%s (%s[%ld] at %s:%d)", 47 einfo->last_err_msg, 48 einfo->what, einfo->last_error, 49 einfo->file, einfo->line); 50 51 add_next_index_long(info, einfo->last_error); 52 add_next_index_string(info, message, 0); 53 add_next_index_string(info, einfo->last_state, 1); 54 55 return 1; 56} 57 58 59void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line TSRMLS_DC) /* {{{ */ 60{ 61 SQLRETURN rc; 62 SQLSMALLINT errmsgsize = 0; 63 SQLHANDLE eh; 64 SQLSMALLINT htype, recno = 1; 65 pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data; 66 pdo_odbc_errinfo *einfo = &H->einfo; 67 pdo_odbc_stmt *S = NULL; 68 pdo_error_type *pdo_err = &dbh->error_code; 69 70 if (stmt) { 71 S = (pdo_odbc_stmt*)stmt->driver_data; 72 73 einfo = &S->einfo; 74 pdo_err = &stmt->error_code; 75 } 76 77 if (statement == SQL_NULL_HSTMT && S) { 78 statement = S->stmt; 79 } 80 81 if (statement) { 82 htype = SQL_HANDLE_STMT; 83 eh = statement; 84 } else if (H->dbc) { 85 htype = SQL_HANDLE_DBC; 86 eh = H->dbc; 87 } else { 88 htype = SQL_HANDLE_ENV; 89 eh = H->env; 90 } 91 92 rc = SQLGetDiagRec(htype, eh, recno++, einfo->last_state, &einfo->last_error, 93 einfo->last_err_msg, sizeof(einfo->last_err_msg)-1, &errmsgsize); 94 95 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { 96 errmsgsize = 0; 97 } 98 99 einfo->last_err_msg[errmsgsize] = '\0'; 100 einfo->file = file; 101 einfo->line = line; 102 einfo->what = what; 103 104 strcpy(*pdo_err, einfo->last_state); 105/* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ 106 if (!dbh->methods) { 107 zend_throw_exception_ex(php_pdo_get_exception(), einfo->last_error TSRMLS_CC, "SQLSTATE[%s] %s: %d %s", 108 *pdo_err, what, einfo->last_error, einfo->last_err_msg); 109 } 110 111 /* just like a cursor, once you start pulling, you need to keep 112 * going until the end; SQL Server (at least) will mess with the 113 * actual cursor state if you don't finish retrieving all the 114 * diagnostic records (which can be generated by PRINT statements 115 * in the query, for instance). */ 116 while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { 117 char discard_state[6]; 118 char discard_buf[1024]; 119 SQLINTEGER code; 120 rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, 121 discard_buf, sizeof(discard_buf)-1, &errmsgsize); 122 } 123 124} 125/* }}} */ 126 127static int odbc_handle_closer(pdo_dbh_t *dbh TSRMLS_DC) 128{ 129 pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data; 130 131 if (H->dbc != SQL_NULL_HANDLE) { 132 SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); 133 SQLDisconnect(H->dbc); 134 SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); 135 H->dbc = NULL; 136 } 137 SQLFreeHandle(SQL_HANDLE_ENV, H->env); 138 H->env = NULL; 139 pefree(H, dbh->is_persistent); 140 dbh->driver_data = NULL; 141 142 return 0; 143} 144 145static int odbc_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC) 146{ 147 RETCODE rc; 148 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 149 pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); 150 enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; 151 int ret; 152 char *nsql = NULL; 153 int nsql_len = 0; 154 155 S->H = H; 156 S->assume_utf8 = H->assume_utf8; 157 158 /* before we prepare, we need to peek at the query; if it uses named parameters, 159 * we want PDO to rewrite them for us */ 160 stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; 161 ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len TSRMLS_CC); 162 163 if (ret == 1) { 164 /* query was re-written */ 165 sql = nsql; 166 } else if (ret == -1) { 167 /* couldn't grok it */ 168 strcpy(dbh->error_code, stmt->error_code); 169 efree(S); 170 return 0; 171 } 172 173 rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); 174 175 if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { 176 efree(S); 177 if (nsql) { 178 efree(nsql); 179 } 180 pdo_odbc_drv_error("SQLAllocStmt"); 181 return 0; 182 } 183 184 cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY TSRMLS_CC); 185 if (cursor_type != PDO_CURSOR_FWDONLY) { 186 rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void*)SQL_SCROLLABLE, 0); 187 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { 188 pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); 189 SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); 190 if (nsql) { 191 efree(nsql); 192 } 193 return 0; 194 } 195 } 196 197 rc = SQLPrepare(S->stmt, (char*)sql, SQL_NTS); 198 if (nsql) { 199 efree(nsql); 200 } 201 202 stmt->driver_data = S; 203 stmt->methods = &odbc_stmt_methods; 204 205 if (rc != SQL_SUCCESS) { 206 pdo_odbc_stmt_error("SQLPrepare"); 207 if (rc != SQL_SUCCESS_WITH_INFO) { 208 /* clone error information into the db handle */ 209 strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); 210 H->einfo.file = S->einfo.file; 211 H->einfo.line = S->einfo.line; 212 H->einfo.what = S->einfo.what; 213 strcpy(dbh->error_code, stmt->error_code); 214 } 215 } 216 217 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { 218 return 0; 219 } 220 return 1; 221} 222 223static long odbc_handle_doer(pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC) 224{ 225 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 226 RETCODE rc; 227 SQLLEN row_count = -1; 228 PDO_ODBC_HSTMT stmt; 229 230 rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); 231 if (rc != SQL_SUCCESS) { 232 pdo_odbc_drv_error("SQLAllocHandle: STMT"); 233 return -1; 234 } 235 236 rc = SQLExecDirect(stmt, (char *)sql, sql_len); 237 238 if (rc == SQL_NO_DATA) { 239 /* If SQLExecDirect executes a searched update or delete statement that 240 * does not affect any rows at the data source, the call to 241 * SQLExecDirect returns SQL_NO_DATA. */ 242 row_count = 0; 243 goto out; 244 } 245 246 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { 247 pdo_odbc_doer_error("SQLExecDirect"); 248 goto out; 249 } 250 251 rc = SQLRowCount(stmt, &row_count); 252 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { 253 pdo_odbc_doer_error("SQLRowCount"); 254 goto out; 255 } 256 if (row_count == -1) { 257 row_count = 0; 258 } 259out: 260 SQLFreeHandle(SQL_HANDLE_STMT, stmt); 261 return row_count; 262} 263 264static 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) 265{ 266 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 267 /* TODO: figure it out */ 268 return 0; 269} 270 271static int odbc_handle_begin(pdo_dbh_t *dbh TSRMLS_DC) 272{ 273 if (dbh->auto_commit) { 274 /* we need to disable auto-commit now, to be able to initiate a transaction */ 275 RETCODE rc; 276 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 277 278 rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); 279 if (rc != SQL_SUCCESS) { 280 pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); 281 return 0; 282 } 283 } 284 return 1; 285} 286 287static int odbc_handle_commit(pdo_dbh_t *dbh TSRMLS_DC) 288{ 289 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 290 RETCODE rc; 291 292 rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); 293 294 if (rc != SQL_SUCCESS) { 295 pdo_odbc_drv_error("SQLEndTran: Commit"); 296 297 if (rc != SQL_SUCCESS_WITH_INFO) { 298 return 0; 299 } 300 } 301 302 if (dbh->auto_commit) { 303 /* turn auto-commit back on again */ 304 rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); 305 if (rc != SQL_SUCCESS) { 306 pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); 307 return 0; 308 } 309 } 310 return 1; 311} 312 313static int odbc_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC) 314{ 315 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 316 RETCODE rc; 317 318 rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); 319 320 if (rc != SQL_SUCCESS) { 321 pdo_odbc_drv_error("SQLEndTran: Rollback"); 322 323 if (rc != SQL_SUCCESS_WITH_INFO) { 324 return 0; 325 } 326 } 327 if (dbh->auto_commit && H->dbc) { 328 /* turn auto-commit back on again */ 329 rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); 330 if (rc != SQL_SUCCESS) { 331 pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); 332 return 0; 333 } 334 } 335 336 return 1; 337} 338 339static int odbc_handle_set_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) 340{ 341 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 342 switch (attr) { 343 case PDO_ODBC_ATTR_ASSUME_UTF8: 344 H->assume_utf8 = zval_is_true(val); 345 return 1; 346 default: 347 strcpy(H->einfo.last_err_msg, "Unknown Attribute"); 348 H->einfo.what = "setAttribute"; 349 strcpy(H->einfo.last_state, "IM001"); 350 return -1; 351 } 352} 353 354static int odbc_handle_get_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) 355{ 356 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; 357 switch (attr) { 358 case PDO_ATTR_CLIENT_VERSION: 359 ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE, 1); 360 return 1; 361 362 case PDO_ATTR_SERVER_VERSION: 363 case PDO_ATTR_PREFETCH: 364 case PDO_ATTR_TIMEOUT: 365 case PDO_ATTR_SERVER_INFO: 366 case PDO_ATTR_CONNECTION_STATUS: 367 break; 368 case PDO_ODBC_ATTR_ASSUME_UTF8: 369 ZVAL_BOOL(val, H->assume_utf8 ? 1 : 0); 370 return 1; 371 372 } 373 return 0; 374} 375 376static struct pdo_dbh_methods odbc_methods = { 377 odbc_handle_closer, 378 odbc_handle_preparer, 379 odbc_handle_doer, 380 odbc_handle_quoter, 381 odbc_handle_begin, 382 odbc_handle_commit, 383 odbc_handle_rollback, 384 odbc_handle_set_attr, 385 NULL, /* last id */ 386 pdo_odbc_fetch_error_func, 387 odbc_handle_get_attr, /* get attr */ 388 NULL, /* check_liveness */ 389}; 390 391static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) /* {{{ */ 392{ 393 pdo_odbc_db_handle *H; 394 RETCODE rc; 395 int use_direct = 0; 396 SQLUINTEGER cursor_lib; 397 398 H = pecalloc(1, sizeof(*H), dbh->is_persistent); 399 400 dbh->driver_data = H; 401 402 SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); 403 rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); 404 405 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { 406 pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); 407 goto fail; 408 } 409 410#ifdef SQL_ATTR_CONNECTION_POOLING 411 if (pdo_odbc_pool_on != SQL_CP_OFF) { 412 rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void*)pdo_odbc_pool_mode, 0); 413 if (rc != SQL_SUCCESS) { 414 pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); 415 goto fail; 416 } 417 } 418#endif 419 420 rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); 421 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { 422 pdo_odbc_drv_error("SQLAllocHandle (DBC)"); 423 goto fail; 424 } 425 426 rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, 427 (SQLPOINTER)(dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), SQL_IS_INTEGER); 428 if (rc != SQL_SUCCESS) { 429 pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); 430 goto fail; 431 } 432 433 /* set up the cursor library, if needed, or if configured explicitly */ 434 cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED TSRMLS_CC); 435 rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void*)cursor_lib, SQL_IS_INTEGER); 436 if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { 437 pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); 438 goto fail; 439 } 440 441 if (strchr(dbh->data_source, ';')) { 442 char dsnbuf[1024]; 443 short dsnbuflen; 444 445 use_direct = 1; 446 447 /* Force UID and PWD to be set in the DSN */ 448 if (dbh->username && *dbh->username && !strstr(dbh->data_source, "uid") 449 && !strstr(dbh->data_source, "UID")) { 450 char *dsn; 451 spprintf(&dsn, 0, "%s;UID=%s;PWD=%s", dbh->data_source, dbh->username, dbh->password); 452 pefree((char*)dbh->data_source, dbh->is_persistent); 453 dbh->data_source = dsn; 454 } 455 456 rc = SQLDriverConnect(H->dbc, NULL, (char*)dbh->data_source, strlen(dbh->data_source), 457 dsnbuf, sizeof(dsnbuf)-1, &dsnbuflen, SQL_DRIVER_NOPROMPT); 458 } 459 if (!use_direct) { 460 rc = SQLConnect(H->dbc, (char*)dbh->data_source, SQL_NTS, dbh->username, SQL_NTS, dbh->password, SQL_NTS); 461 } 462 463 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { 464 pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); 465 goto fail; 466 } 467 468 /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ 469 470 dbh->methods = &odbc_methods; 471 dbh->alloc_own_columns = 1; 472 473 return 1; 474 475fail: 476 dbh->methods = &odbc_methods; 477 return 0; 478} 479/* }}} */ 480 481pdo_driver_t pdo_odbc_driver = { 482 PDO_DRIVER_HEADER(odbc), 483 pdo_odbc_handle_factory 484}; 485 486/* 487 * Local variables: 488 * tab-width: 4 489 * c-basic-offset: 4 490 * End: 491 * vim600: noet sw=4 ts=4 fdm=marker 492 * vim<600: noet sw=4 ts=4 493 */ 494