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