1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.0 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 | http://www.php.net/license/3_0.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 int 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 return 1;
52 }
53
54
pdo_odbc_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt,PDO_ODBC_HSTMT statement,char * what,const char * file,int line)55 void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */
56 {
57 SQLRETURN rc;
58 SQLSMALLINT errmsgsize = 0;
59 SQLHANDLE eh;
60 SQLSMALLINT htype, recno = 1;
61 pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data;
62 pdo_odbc_errinfo *einfo = &H->einfo;
63 pdo_odbc_stmt *S = NULL;
64 pdo_error_type *pdo_err = &dbh->error_code;
65
66 if (stmt) {
67 S = (pdo_odbc_stmt*)stmt->driver_data;
68
69 einfo = &S->einfo;
70 pdo_err = &stmt->error_code;
71 }
72
73 if (statement == SQL_NULL_HSTMT && S) {
74 statement = S->stmt;
75 }
76
77 if (statement) {
78 htype = SQL_HANDLE_STMT;
79 eh = statement;
80 } else if (H->dbc) {
81 htype = SQL_HANDLE_DBC;
82 eh = H->dbc;
83 } else {
84 htype = SQL_HANDLE_ENV;
85 eh = H->env;
86 }
87
88 rc = SQLGetDiagRec(htype, eh, recno++, (SQLCHAR *) einfo->last_state, &einfo->last_error,
89 (SQLCHAR *) einfo->last_err_msg, sizeof(einfo->last_err_msg)-1, &errmsgsize);
90
91 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
92 errmsgsize = 0;
93 }
94
95 einfo->last_err_msg[errmsgsize] = '\0';
96 einfo->file = file;
97 einfo->line = line;
98 einfo->what = what;
99
100 strcpy(*pdo_err, einfo->last_state);
101 /* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */
102 if (!dbh->methods) {
103 zend_throw_exception_ex(php_pdo_get_exception(), einfo->last_error, "SQLSTATE[%s] %s: %d %s",
104 *pdo_err, what, einfo->last_error, einfo->last_err_msg);
105 }
106
107 /* just like a cursor, once you start pulling, you need to keep
108 * going until the end; SQL Server (at least) will mess with the
109 * actual cursor state if you don't finish retrieving all the
110 * diagnostic records (which can be generated by PRINT statements
111 * in the query, for instance). */
112 while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
113 SQLCHAR discard_state[6];
114 SQLCHAR discard_buf[1024];
115 SQLINTEGER code;
116 rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code,
117 discard_buf, sizeof(discard_buf)-1, &errmsgsize);
118 }
119
120 }
121 /* }}} */
122
odbc_handle_closer(pdo_dbh_t * dbh)123 static int odbc_handle_closer(pdo_dbh_t *dbh)
124 {
125 pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data;
126
127 if (H->dbc != SQL_NULL_HANDLE) {
128 SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK);
129 SQLDisconnect(H->dbc);
130 SQLFreeHandle(SQL_HANDLE_DBC, H->dbc);
131 H->dbc = NULL;
132 }
133 SQLFreeHandle(SQL_HANDLE_ENV, H->env);
134 H->env = NULL;
135 pefree(H, dbh->is_persistent);
136 dbh->driver_data = NULL;
137
138 return 0;
139 }
140
odbc_handle_preparer(pdo_dbh_t * dbh,const char * sql,size_t sql_len,pdo_stmt_t * stmt,zval * driver_options)141 static int odbc_handle_preparer(pdo_dbh_t *dbh, const char *sql, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options)
142 {
143 RETCODE rc;
144 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
145 pdo_odbc_stmt *S = ecalloc(1, sizeof(*S));
146 enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY;
147 int ret;
148 char *nsql = NULL;
149 size_t nsql_len = 0;
150
151 S->H = H;
152 S->assume_utf8 = H->assume_utf8;
153
154 /* before we prepare, we need to peek at the query; if it uses named parameters,
155 * we want PDO to rewrite them for us */
156 stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
157 ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len);
158
159 if (ret == 1) {
160 /* query was re-written */
161 sql = nsql;
162 } else if (ret == -1) {
163 /* couldn't grok it */
164 strcpy(dbh->error_code, stmt->error_code);
165 efree(S);
166 return 0;
167 }
168
169 rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt);
170
171 if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) {
172 efree(S);
173 if (nsql) {
174 efree(nsql);
175 }
176 pdo_odbc_drv_error("SQLAllocStmt");
177 return 0;
178 }
179
180 stmt->driver_data = S;
181
182 cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY);
183 if (cursor_type != PDO_CURSOR_FWDONLY) {
184 rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void*)SQL_SCROLLABLE, 0);
185 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
186 pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE");
187 SQLFreeHandle(SQL_HANDLE_STMT, S->stmt);
188 if (nsql) {
189 efree(nsql);
190 }
191 return 0;
192 }
193 }
194
195 rc = SQLPrepare(S->stmt, (SQLCHAR *) sql, SQL_NTS);
196 if (nsql) {
197 efree(nsql);
198 }
199
200 stmt->methods = &odbc_stmt_methods;
201
202 if (rc != SQL_SUCCESS) {
203 pdo_odbc_stmt_error("SQLPrepare");
204 if (rc != SQL_SUCCESS_WITH_INFO) {
205 /* clone error information into the db handle */
206 strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg);
207 H->einfo.file = S->einfo.file;
208 H->einfo.line = S->einfo.line;
209 H->einfo.what = S->einfo.what;
210 strcpy(dbh->error_code, stmt->error_code);
211 }
212 }
213
214 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
215 return 0;
216 }
217 return 1;
218 }
219
odbc_handle_doer(pdo_dbh_t * dbh,const char * sql,size_t sql_len)220 static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len)
221 {
222 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
223 RETCODE rc;
224 SQLLEN row_count = -1;
225 PDO_ODBC_HSTMT stmt;
226
227 rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt);
228 if (rc != SQL_SUCCESS) {
229 pdo_odbc_drv_error("SQLAllocHandle: STMT");
230 return -1;
231 }
232
233 rc = SQLExecDirect(stmt, (SQLCHAR *) sql, sql_len);
234
235 if (rc == SQL_NO_DATA) {
236 /* If SQLExecDirect executes a searched update or delete statement that
237 * does not affect any rows at the data source, the call to
238 * SQLExecDirect returns SQL_NO_DATA. */
239 row_count = 0;
240 goto out;
241 }
242
243 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
244 pdo_odbc_doer_error("SQLExecDirect");
245 goto out;
246 }
247
248 rc = SQLRowCount(stmt, &row_count);
249 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
250 pdo_odbc_doer_error("SQLRowCount");
251 goto out;
252 }
253 if (row_count == -1) {
254 row_count = 0;
255 }
256 out:
257 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
258 return row_count;
259 }
260
261 /* TODO: Do ODBC quoter
262 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 )
263 {
264 // pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
265 // TODO: figure it out
266 return 0;
267 }
268 */
269
odbc_handle_begin(pdo_dbh_t * dbh)270 static int odbc_handle_begin(pdo_dbh_t *dbh)
271 {
272 if (dbh->auto_commit) {
273 /* we need to disable auto-commit now, to be able to initiate a transaction */
274 RETCODE rc;
275 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
276
277 rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER);
278 if (rc != SQL_SUCCESS) {
279 pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF");
280 return 0;
281 }
282 }
283 return 1;
284 }
285
odbc_handle_commit(pdo_dbh_t * dbh)286 static int odbc_handle_commit(pdo_dbh_t *dbh)
287 {
288 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
289 RETCODE rc;
290
291 rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT);
292
293 if (rc != SQL_SUCCESS) {
294 pdo_odbc_drv_error("SQLEndTran: Commit");
295
296 if (rc != SQL_SUCCESS_WITH_INFO) {
297 return 0;
298 }
299 }
300
301 if (dbh->auto_commit) {
302 /* turn auto-commit back on again */
303 rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER);
304 if (rc != SQL_SUCCESS) {
305 pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON");
306 return 0;
307 }
308 }
309 return 1;
310 }
311
odbc_handle_rollback(pdo_dbh_t * dbh)312 static int odbc_handle_rollback(pdo_dbh_t *dbh)
313 {
314 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
315 RETCODE rc;
316
317 rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK);
318
319 if (rc != SQL_SUCCESS) {
320 pdo_odbc_drv_error("SQLEndTran: Rollback");
321
322 if (rc != SQL_SUCCESS_WITH_INFO) {
323 return 0;
324 }
325 }
326 if (dbh->auto_commit && H->dbc) {
327 /* turn auto-commit back on again */
328 rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER);
329 if (rc != SQL_SUCCESS) {
330 pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON");
331 return 0;
332 }
333 }
334
335 return 1;
336 }
337
odbc_handle_set_attr(pdo_dbh_t * dbh,zend_long attr,zval * val)338 static int odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
339 {
340 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
341 switch (attr) {
342 case PDO_ODBC_ATTR_ASSUME_UTF8:
343 H->assume_utf8 = zval_is_true(val);
344 return 1;
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 0;
350 }
351 }
352
odbc_handle_get_attr(pdo_dbh_t * dbh,zend_long attr,zval * val)353 static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
354 {
355 pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
356 switch (attr) {
357 case PDO_ATTR_CLIENT_VERSION:
358 ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE);
359 return 1;
360
361 case PDO_ATTR_SERVER_VERSION:
362 case PDO_ATTR_PREFETCH:
363 case PDO_ATTR_TIMEOUT:
364 case PDO_ATTR_SERVER_INFO:
365 case PDO_ATTR_CONNECTION_STATUS:
366 break;
367 case PDO_ODBC_ATTR_ASSUME_UTF8:
368 ZVAL_BOOL(val, H->assume_utf8 ? 1 : 0);
369 return 1;
370
371 }
372 return 0;
373 }
374
375 static const struct pdo_dbh_methods odbc_methods = {
376 odbc_handle_closer,
377 odbc_handle_preparer,
378 odbc_handle_doer,
379 NULL, /* quoter */
380 odbc_handle_begin,
381 odbc_handle_commit,
382 odbc_handle_rollback,
383 odbc_handle_set_attr,
384 NULL, /* last id */
385 pdo_odbc_fetch_error_func,
386 odbc_handle_get_attr, /* get attr */
387 NULL, /* check_liveness */
388 };
389
pdo_odbc_handle_factory(pdo_dbh_t * dbh,zval * driver_options)390 static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
391 {
392 pdo_odbc_db_handle *H;
393 RETCODE rc;
394 int use_direct = 0;
395 zend_ulong cursor_lib;
396
397 H = pecalloc(1, sizeof(*H), dbh->is_persistent);
398
399 dbh->driver_data = H;
400
401 SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env);
402 rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
403
404 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
405 pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3");
406 goto fail;
407 }
408
409 #ifdef SQL_ATTR_CONNECTION_POOLING
410 if (pdo_odbc_pool_on != SQL_CP_OFF) {
411 rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void*)pdo_odbc_pool_mode, 0);
412 if (rc != SQL_SUCCESS) {
413 pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH");
414 goto fail;
415 }
416 }
417 #endif
418
419 rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc);
420 if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
421 pdo_odbc_drv_error("SQLAllocHandle (DBC)");
422 goto fail;
423 }
424
425 rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT,
426 (SQLPOINTER)(intptr_t)(dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), SQL_IS_INTEGER);
427 if (rc != SQL_SUCCESS) {
428 pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT");
429 goto fail;
430 }
431
432 /* set up the cursor library, if needed, or if configured explicitly */
433 cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED);
434 rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void*)cursor_lib, SQL_IS_INTEGER);
435 if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) {
436 pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS");
437 goto fail;
438 }
439
440 /* a connection string may have = but not ; - i.e. "DSN=PHP" */
441 if (strchr(dbh->data_source, '=')) {
442 SQLCHAR dsnbuf[1024];
443 SQLSMALLINT 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, (SQLCHAR *) 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, (SQLCHAR *) dbh->data_source, SQL_NTS, (SQLCHAR *) dbh->username, SQL_NTS, (SQLCHAR *) 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
475 fail:
476 dbh->methods = &odbc_methods;
477 return 0;
478 }
479 /* }}} */
480
481 const pdo_driver_t pdo_odbc_driver = {
482 PDO_DRIVER_HEADER(odbc),
483 pdo_odbc_handle_factory
484 };
485