xref: /PHP-7.0/ext/pdo_odbc/odbc_driver.c (revision 478f119a)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2017 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 
pdo_odbc_fetch_error_func(pdo_dbh_t * dbh,pdo_stmt_t * stmt,zval * info)34 static int pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info)
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 	zend_string *message = NULL;
40 
41 	if (stmt) {
42 		S = (pdo_odbc_stmt*)stmt->driver_data;
43 		einfo = &S->einfo;
44 	}
45 
46 	message = strpprintf(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_str(info, message);
53 	add_next_index_string(info, einfo->last_state);
54 
55 	return 1;
56 }
57 
58 
pdo_odbc_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt,PDO_ODBC_HSTMT statement,char * what,const char * file,int line)59 void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */
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, "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 
odbc_handle_closer(pdo_dbh_t * dbh)127 static int odbc_handle_closer(pdo_dbh_t *dbh)
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 
odbc_handle_preparer(pdo_dbh_t * dbh,const char * sql,size_t sql_len,pdo_stmt_t * stmt,zval * driver_options)145 static int odbc_handle_preparer(pdo_dbh_t *dbh, const char *sql, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options)
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 	size_t 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);
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);
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 
odbc_handle_doer(pdo_dbh_t * dbh,const char * sql,size_t sql_len)223 static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len)
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 	}
259 out:
260 	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
261 	return row_count;
262 }
263 
odbc_handle_quoter(pdo_dbh_t * dbh,const char * unquoted,size_t unquotedlen,char ** quoted,size_t * quotedlen,enum pdo_param_type param_type)264 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 )
265 {
266 	/* pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; */
267 	/* TODO: figure it out */
268 	return 0;
269 }
270 
odbc_handle_begin(pdo_dbh_t * dbh)271 static int odbc_handle_begin(pdo_dbh_t *dbh)
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 
odbc_handle_commit(pdo_dbh_t * dbh)287 static int odbc_handle_commit(pdo_dbh_t *dbh)
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 
odbc_handle_rollback(pdo_dbh_t * dbh)313 static int odbc_handle_rollback(pdo_dbh_t *dbh)
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 
odbc_handle_set_attr(pdo_dbh_t * dbh,zend_long attr,zval * val)339 static int odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
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 
odbc_handle_get_attr(pdo_dbh_t * dbh,zend_long attr,zval * val)354 static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
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);
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 
376 static 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 
pdo_odbc_handle_factory(pdo_dbh_t * dbh,zval * driver_options)391 static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
392 {
393 	pdo_odbc_db_handle *H;
394 	RETCODE rc;
395 	int use_direct = 0;
396 	zend_ulong 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);
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 		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, (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 
475 fail:
476 	dbh->methods = &odbc_methods;
477 	return 0;
478 }
479 /* }}} */
480 
481 pdo_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