xref: /PHP-7.4/ext/pdo_odbc/odbc_driver.c (revision 6acfb792)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 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 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 #include "php_ini.h"
25 #include "ext/standard/info.h"
26 #include "pdo/php_pdo.h"
27 #include "pdo/php_pdo_driver.h"
28 #include "php_pdo_odbc.h"
29 #include "php_pdo_odbc_int.h"
30 #include "zend_exceptions.h"
31 
pdo_odbc_fetch_error_func(pdo_dbh_t * dbh,pdo_stmt_t * stmt,zval * info)32 static int pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info)
33 {
34 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
35 	pdo_odbc_errinfo *einfo = &H->einfo;
36 	pdo_odbc_stmt *S = NULL;
37 	zend_string *message = NULL;
38 
39 	if (stmt) {
40 		S = (pdo_odbc_stmt*)stmt->driver_data;
41 		einfo = &S->einfo;
42 	}
43 
44 	message = strpprintf(0, "%s (%s[%ld] at %s:%d)",
45 				einfo->last_err_msg,
46 				einfo->what, einfo->last_error,
47 				einfo->file, einfo->line);
48 
49 	add_next_index_long(info, einfo->last_error);
50 	add_next_index_str(info, message);
51 	add_next_index_string(info, einfo->last_state);
52 
53 	return 1;
54 }
55 
56 
pdo_odbc_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt,PDO_ODBC_HSTMT statement,char * what,const char * file,int line)57 void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */
58 {
59 	SQLRETURN rc;
60 	SQLSMALLINT	errmsgsize = 0;
61 	SQLHANDLE eh;
62 	SQLSMALLINT htype, recno = 1;
63 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data;
64 	pdo_odbc_errinfo *einfo = &H->einfo;
65 	pdo_odbc_stmt *S = NULL;
66 	pdo_error_type *pdo_err = &dbh->error_code;
67 
68 	if (stmt) {
69 		S = (pdo_odbc_stmt*)stmt->driver_data;
70 
71 		einfo = &S->einfo;
72 		pdo_err = &stmt->error_code;
73 	}
74 
75 	if (statement == SQL_NULL_HSTMT && S) {
76 		statement = S->stmt;
77 	}
78 
79 	if (statement) {
80 		htype = SQL_HANDLE_STMT;
81 		eh = statement;
82 	} else if (H->dbc) {
83 		htype = SQL_HANDLE_DBC;
84 		eh = H->dbc;
85 	} else {
86 		htype = SQL_HANDLE_ENV;
87 		eh = H->env;
88 	}
89 
90 	rc = SQLGetDiagRec(htype, eh, recno++, einfo->last_state, &einfo->last_error,
91 			einfo->last_err_msg, sizeof(einfo->last_err_msg)-1, &errmsgsize);
92 
93 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
94 		errmsgsize = 0;
95 	}
96 
97 	einfo->last_err_msg[errmsgsize] = '\0';
98 	einfo->file = file;
99 	einfo->line = line;
100 	einfo->what = what;
101 
102 	strcpy(*pdo_err, einfo->last_state);
103 /* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */
104 	if (!dbh->methods) {
105 		zend_throw_exception_ex(php_pdo_get_exception(), einfo->last_error, "SQLSTATE[%s] %s: %d %s",
106 				*pdo_err, what, einfo->last_error, einfo->last_err_msg);
107 	}
108 
109 	/* just like a cursor, once you start pulling, you need to keep
110 	 * going until the end; SQL Server (at least) will mess with the
111 	 * actual cursor state if you don't finish retrieving all the
112 	 * diagnostic records (which can be generated by PRINT statements
113 	 * in the query, for instance). */
114 	while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
115 		char discard_state[6];
116 		char discard_buf[1024];
117 		SQLINTEGER code;
118 		rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code,
119 				discard_buf, sizeof(discard_buf)-1, &errmsgsize);
120 	}
121 
122 }
123 /* }}} */
124 
odbc_handle_closer(pdo_dbh_t * dbh)125 static int odbc_handle_closer(pdo_dbh_t *dbh)
126 {
127 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data;
128 
129 	if (H->dbc != SQL_NULL_HANDLE) {
130 		SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK);
131 		SQLDisconnect(H->dbc);
132 		SQLFreeHandle(SQL_HANDLE_DBC, H->dbc);
133 		H->dbc = NULL;
134 	}
135 	SQLFreeHandle(SQL_HANDLE_ENV, H->env);
136 	H->env = NULL;
137 	pefree(H, dbh->is_persistent);
138 	dbh->driver_data = NULL;
139 
140 	return 0;
141 }
142 
odbc_handle_preparer(pdo_dbh_t * dbh,const char * sql,size_t sql_len,pdo_stmt_t * stmt,zval * driver_options)143 static int odbc_handle_preparer(pdo_dbh_t *dbh, const char *sql, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options)
144 {
145 	RETCODE rc;
146 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
147 	pdo_odbc_stmt *S = ecalloc(1, sizeof(*S));
148 	enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY;
149 	int ret;
150 	char *nsql = NULL;
151 	size_t nsql_len = 0;
152 
153 	S->H = H;
154 	S->assume_utf8 = H->assume_utf8;
155 
156 	/* before we prepare, we need to peek at the query; if it uses named parameters,
157 	 * we want PDO to rewrite them for us */
158 	stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
159 	ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len);
160 
161 	if (ret == 1) {
162 		/* query was re-written */
163 		sql = nsql;
164 	} else if (ret == -1) {
165 		/* couldn't grok it */
166 		strcpy(dbh->error_code, stmt->error_code);
167 		efree(S);
168 		return 0;
169 	}
170 
171 	rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt);
172 
173 	if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) {
174 		efree(S);
175 		if (nsql) {
176 			efree(nsql);
177 		}
178 		pdo_odbc_drv_error("SQLAllocStmt");
179 		return 0;
180 	}
181 
182 	stmt->driver_data = S;
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->methods = &odbc_stmt_methods;
203 
204 	if (rc != SQL_SUCCESS) {
205 		pdo_odbc_stmt_error("SQLPrepare");
206         if (rc != SQL_SUCCESS_WITH_INFO) {
207             /* clone error information into the db handle */
208             strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg);
209             H->einfo.file = S->einfo.file;
210             H->einfo.line = S->einfo.line;
211             H->einfo.what = S->einfo.what;
212             strcpy(dbh->error_code, stmt->error_code);
213         }
214 	}
215 
216 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
217 		return 0;
218 	}
219 	return 1;
220 }
221 
odbc_handle_doer(pdo_dbh_t * dbh,const char * sql,size_t sql_len)222 static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len)
223 {
224 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
225 	RETCODE rc;
226 	SQLLEN row_count = -1;
227 	PDO_ODBC_HSTMT	stmt;
228 
229 	rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt);
230 	if (rc != SQL_SUCCESS) {
231 		pdo_odbc_drv_error("SQLAllocHandle: STMT");
232 		return -1;
233 	}
234 
235 	rc = SQLExecDirect(stmt, (char *)sql, sql_len);
236 
237 	if (rc == SQL_NO_DATA) {
238 		/* If SQLExecDirect executes a searched update or delete statement that
239 		 * does not affect any rows at the data source, the call to
240 		 * SQLExecDirect returns SQL_NO_DATA. */
241 		row_count = 0;
242 		goto out;
243 	}
244 
245 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
246 		pdo_odbc_doer_error("SQLExecDirect");
247 		goto out;
248 	}
249 
250 	rc = SQLRowCount(stmt, &row_count);
251 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
252 		pdo_odbc_doer_error("SQLRowCount");
253 		goto out;
254 	}
255 	if (row_count == -1) {
256 		row_count = 0;
257 	}
258 out:
259 	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
260 	return row_count;
261 }
262 
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 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 )
264 {
265 	/* pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; */
266 	/* TODO: figure it out */
267 	return 0;
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 	odbc_handle_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)(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 	if (strchr(dbh->data_source, ';')) {
441 		char dsnbuf[1024];
442 		SQLSMALLINT dsnbuflen;
443 
444 		use_direct = 1;
445 
446 		/* Force UID and PWD to be set in the DSN */
447 		if (dbh->username && *dbh->username && !strstr(dbh->data_source, "uid")
448 				&& !strstr(dbh->data_source, "UID")) {
449 			char *dsn;
450 			spprintf(&dsn, 0, "%s;UID=%s;PWD=%s", dbh->data_source, dbh->username, dbh->password);
451 			pefree((char*)dbh->data_source, dbh->is_persistent);
452 			dbh->data_source = dsn;
453 		}
454 
455 		rc = SQLDriverConnect(H->dbc, NULL, (char*)dbh->data_source, strlen(dbh->data_source),
456 				dsnbuf, sizeof(dsnbuf)-1, &dsnbuflen, SQL_DRIVER_NOPROMPT);
457 	}
458 	if (!use_direct) {
459 		rc = SQLConnect(H->dbc, (char*)dbh->data_source, SQL_NTS, dbh->username, SQL_NTS, dbh->password, SQL_NTS);
460 	}
461 
462 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
463 		pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect");
464 		goto fail;
465 	}
466 
467 	/* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */
468 
469 	dbh->methods = &odbc_methods;
470 	dbh->alloc_own_columns = 1;
471 
472 	return 1;
473 
474 fail:
475 	dbh->methods = &odbc_methods;
476 	return 0;
477 }
478 /* }}} */
479 
480 const pdo_driver_t pdo_odbc_driver = {
481 	PDO_DRIVER_HEADER(odbc),
482 	pdo_odbc_handle_factory
483 };
484