xref: /PHP-8.4/ext/pdo_odbc/odbc_driver.c (revision 11accb5c)
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 "ext/pdo/php_pdo.h"
25 #include "ext/pdo/php_pdo_driver.h"
26 /* this file actually lives in main/ */
27 #include "php_odbc_utils.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 void 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, (long) 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 
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 void 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 
odbc_handle_preparer(pdo_dbh_t * dbh,zend_string * sql,pdo_stmt_t * stmt,zval * driver_options)139 static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options)
140 {
141 	RETCODE rc;
142 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
143 	pdo_odbc_stmt *S = ecalloc(1, sizeof(*S));
144 	enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY;
145 	int ret;
146 	zend_string *nsql = NULL;
147 
148 	S->H = H;
149 	S->assume_utf8 = H->assume_utf8;
150 
151 	/* before we prepare, we need to peek at the query; if it uses named parameters,
152 	 * we want PDO to rewrite them for us */
153 	stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
154 	ret = pdo_parse_params(stmt, sql, &nsql);
155 
156 	if (ret == 1) {
157 		/* query was re-written */
158 		sql = nsql;
159 	} else if (ret == -1) {
160 		/* couldn't grok it */
161 		strcpy(dbh->error_code, stmt->error_code);
162 		efree(S);
163 		return false;
164 	}
165 
166 	rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt);
167 
168 	if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) {
169 		efree(S);
170 		if (nsql) {
171 			zend_string_release(nsql);
172 		}
173 		pdo_odbc_drv_error("SQLAllocStmt");
174 		return false;
175 	}
176 
177 	stmt->driver_data = S;
178 
179 	cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY);
180 	if (cursor_type != PDO_CURSOR_FWDONLY) {
181 		rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void*)SQL_SCROLLABLE, 0);
182 		if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
183 			pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE");
184 			SQLFreeHandle(SQL_HANDLE_STMT, S->stmt);
185 			if (nsql) {
186 				zend_string_release(nsql);
187 			}
188 			return false;
189 		}
190 	}
191 
192 	rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS);
193 	if (nsql) {
194 		zend_string_release(nsql);
195 	}
196 
197 	stmt->methods = &odbc_stmt_methods;
198 
199 	if (rc != SQL_SUCCESS) {
200 		pdo_odbc_stmt_error("SQLPrepare");
201 		if (rc != SQL_SUCCESS_WITH_INFO) {
202 			/* clone error information into the db handle */
203 			strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg);
204 			H->einfo.file = S->einfo.file;
205 			H->einfo.line = S->einfo.line;
206 			H->einfo.what = S->einfo.what;
207 			strcpy(dbh->error_code, stmt->error_code);
208 		}
209 	}
210 
211 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
212 		return false;
213 	}
214 	return true;
215 }
216 
odbc_handle_doer(pdo_dbh_t * dbh,const zend_string * sql)217 static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const zend_string *sql)
218 {
219 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
220 	RETCODE rc;
221 	SQLLEN row_count = -1;
222 	PDO_ODBC_HSTMT	stmt;
223 
224 	rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt);
225 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
226 		pdo_odbc_drv_error("SQLAllocHandle: STMT");
227 		return -1;
228 	}
229 
230 	rc = SQLExecDirect(stmt, (SQLCHAR *) ZSTR_VAL(sql), ZSTR_LEN(sql));
231 
232 	if (rc == SQL_NO_DATA) {
233 		/* If SQLExecDirect executes a searched update or delete statement that
234 		 * does not affect any rows at the data source, the call to
235 		 * SQLExecDirect returns SQL_NO_DATA. */
236 		row_count = 0;
237 		goto out;
238 	}
239 
240 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
241 		pdo_odbc_doer_error("SQLExecDirect");
242 		goto out;
243 	}
244 
245 	rc = SQLRowCount(stmt, &row_count);
246 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
247 		pdo_odbc_doer_error("SQLRowCount");
248 		goto out;
249 	}
250 	if (row_count == -1) {
251 		row_count = 0;
252 	}
253 out:
254 	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
255 	return row_count;
256 }
257 
258 /* TODO: Do ODBC quoter
259 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 )
260 {
261 	// pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
262 	// TODO: figure it out
263 	return 0;
264 }
265 */
266 
odbc_handle_begin(pdo_dbh_t * dbh)267 static bool odbc_handle_begin(pdo_dbh_t *dbh)
268 {
269 	if (dbh->auto_commit) {
270 		/* we need to disable auto-commit now, to be able to initiate a transaction */
271 		RETCODE rc;
272 		pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
273 
274 		rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER);
275 		if (rc != SQL_SUCCESS) {
276 			pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF");
277 			return false;
278 		}
279 	}
280 	return true;
281 }
282 
odbc_handle_commit(pdo_dbh_t * dbh)283 static bool odbc_handle_commit(pdo_dbh_t *dbh)
284 {
285 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
286 	RETCODE rc;
287 
288 	rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT);
289 
290 	if (rc != SQL_SUCCESS) {
291 		pdo_odbc_drv_error("SQLEndTran: Commit");
292 
293 		if (rc != SQL_SUCCESS_WITH_INFO) {
294 			return false;
295 		}
296 	}
297 
298 	if (dbh->auto_commit) {
299 		/* turn auto-commit back on again */
300 		rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER);
301 		if (rc != SQL_SUCCESS) {
302 			pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON");
303 			return false;
304 		}
305 	}
306 	return true;
307 }
308 
odbc_handle_rollback(pdo_dbh_t * dbh)309 static bool odbc_handle_rollback(pdo_dbh_t *dbh)
310 {
311 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
312 	RETCODE rc;
313 
314 	rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK);
315 
316 	if (rc != SQL_SUCCESS) {
317 		pdo_odbc_drv_error("SQLEndTran: Rollback");
318 
319 		if (rc != SQL_SUCCESS_WITH_INFO) {
320 			return false;
321 		}
322 	}
323 	if (dbh->auto_commit && H->dbc) {
324 		/* turn auto-commit back on again */
325 		rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER);
326 		if (rc != SQL_SUCCESS) {
327 			pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON");
328 			return false;
329 		}
330 	}
331 
332 	return true;
333 }
334 
odbc_handle_set_attr(pdo_dbh_t * dbh,zend_long attr,zval * val)335 static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
336 {
337 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
338 	bool bval;
339 
340 	switch (attr) {
341 		case PDO_ODBC_ATTR_ASSUME_UTF8:
342 			if (!pdo_get_bool_param(&bval, val)) {
343 				return false;
344 			}
345 			H->assume_utf8 = bval;
346 			return true;
347 		case PDO_ATTR_AUTOCOMMIT:
348 			if (!pdo_get_bool_param(&bval, val)) {
349 				return false;
350 			}
351 			if (dbh->in_txn) {
352 				pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open");
353 				return false;
354 			}
355 			if (dbh->auto_commit ^ bval) {
356 				dbh->auto_commit = bval;
357 				RETCODE rc = SQLSetConnectAttr(
358 					H->dbc,
359 					SQL_ATTR_AUTOCOMMIT,
360 					dbh->auto_commit ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : (SQLPOINTER) SQL_AUTOCOMMIT_OFF,
361 					SQL_IS_INTEGER
362 				);
363 				if (rc != SQL_SUCCESS) {
364 					pdo_odbc_drv_error(
365 						dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" : "SQLSetConnectAttr AUTOCOMMIT = OFF"
366 					);
367 					return false;
368 				}
369 			}
370 			return true;
371 		default:
372 			strcpy(H->einfo.last_err_msg, "Unknown Attribute");
373 			H->einfo.what = "setAttribute";
374 			strcpy(H->einfo.last_state, "IM001");
375 			return false;
376 	}
377 }
378 
pdo_odbc_get_info_string(pdo_dbh_t * dbh,SQLUSMALLINT type,zval * val)379 static int pdo_odbc_get_info_string(pdo_dbh_t *dbh, SQLUSMALLINT type, zval *val)
380 {
381 	RETCODE rc;
382 	SQLSMALLINT out_len;
383 	char buf[256];
384 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
385 	rc = SQLGetInfo(H->dbc, type, (SQLPOINTER)buf, sizeof(buf), &out_len);
386 	/* returning -1 is treated as an error, not as unsupported */
387 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
388 		return -1;
389 	}
390 	ZVAL_STRINGL(val, buf, out_len);
391 	return 1;
392 }
393 
odbc_handle_get_attr(pdo_dbh_t * dbh,zend_long attr,zval * val)394 static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
395 {
396 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
397 	switch (attr) {
398 		case PDO_ATTR_CLIENT_VERSION:
399 			ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE);
400 			return 1;
401 
402 		case PDO_ATTR_SERVER_VERSION:
403 			return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val);
404 		case PDO_ATTR_SERVER_INFO:
405 			return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val);
406 		case PDO_ATTR_PREFETCH:
407 		case PDO_ATTR_TIMEOUT:
408 		case PDO_ATTR_CONNECTION_STATUS:
409 			break;
410 		case PDO_ODBC_ATTR_ASSUME_UTF8:
411 			ZVAL_BOOL(val, H->assume_utf8);
412 			return 1;
413 		case PDO_ATTR_AUTOCOMMIT:
414 			ZVAL_BOOL(val, dbh->auto_commit);
415 			return 1;
416 	}
417 	return 0;
418 }
419 
odbc_handle_check_liveness(pdo_dbh_t * dbh)420 static zend_result odbc_handle_check_liveness(pdo_dbh_t *dbh)
421 {
422 	RETCODE ret;
423 	UCHAR d_name[32];
424 	SQLSMALLINT len;
425 	SQLUINTEGER dead = SQL_CD_FALSE;
426 	pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
427 
428 	ret = SQLGetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL);
429 	if (ret == SQL_SUCCESS && dead == SQL_CD_TRUE) {
430 		/* Bail early here, since we know it's gone */
431 		return FAILURE;
432 	}
433 	/*
434 	 * If the driver doesn't support SQL_ATTR_CONNECTION_DEAD, or if
435 	 * it returns false (which could be a false positive), fall back
436 	 * to using SQL_DATA_SOURCE_READ_ONLY, which isn't semantically
437 	 * correct, but works with many drivers.
438 	 */
439 	ret = SQLGetInfo(H->dbc, SQL_DATA_SOURCE_READ_ONLY, d_name,
440 		sizeof(d_name), &len);
441 
442 	if (ret != SQL_SUCCESS || len == 0) {
443 		return FAILURE;
444 	}
445 	return SUCCESS;
446 }
447 
448 static const struct pdo_dbh_methods odbc_methods = {
449 	odbc_handle_closer,
450 	odbc_handle_preparer,
451 	odbc_handle_doer,
452 	NULL, /* quoter */
453 	odbc_handle_begin,
454 	odbc_handle_commit,
455 	odbc_handle_rollback,
456 	odbc_handle_set_attr,
457 	NULL,	/* last id */
458 	pdo_odbc_fetch_error_func,
459 	odbc_handle_get_attr,	/* get attr */
460 	odbc_handle_check_liveness, /* check_liveness */
461 	NULL, /* get_driver_methods */
462 	NULL, /* request_shutdown */
463 	NULL, /* in transaction, use PDO's internal tracking mechanism */
464 	NULL, /* get_gc */
465 	NULL /* scanner */
466 };
467 
pdo_odbc_handle_factory(pdo_dbh_t * dbh,zval * driver_options)468 static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
469 {
470 	pdo_odbc_db_handle *H;
471 	RETCODE rc;
472 	int use_direct = 0;
473 	zend_ulong cursor_lib;
474 
475 	H = pecalloc(1, sizeof(*H), dbh->is_persistent);
476 
477 	dbh->driver_data = H;
478 
479 	rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env);
480 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
481 		pdo_odbc_drv_error("SQLAllocHandle: ENV");
482 		goto fail;
483 	}
484 
485 	rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
486 
487 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
488 		pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3");
489 		goto fail;
490 	}
491 
492 #ifdef SQL_ATTR_CONNECTION_POOLING
493 	if (pdo_odbc_pool_on != SQL_CP_OFF) {
494 		rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void*)pdo_odbc_pool_mode, 0);
495 		if (rc != SQL_SUCCESS) {
496 			pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH");
497 			goto fail;
498 		}
499 	}
500 #endif
501 
502 	rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc);
503 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
504 		pdo_odbc_drv_error("SQLAllocHandle: DBC");
505 		goto fail;
506 	}
507 
508 	rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT,
509 		(SQLPOINTER)(intptr_t)(dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), SQL_IS_INTEGER);
510 	if (rc != SQL_SUCCESS) {
511 		pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT");
512 		goto fail;
513 	}
514 
515 	/* set up the cursor library, if needed, or if configured explicitly */
516 	cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED);
517 	rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void*)cursor_lib, SQL_IS_INTEGER);
518 	if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) {
519 		pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS");
520 		goto fail;
521 	}
522 
523 	/* a connection string may have = but not ; - i.e. "DSN=PHP" */
524 	if (strchr(dbh->data_source, '=')) {
525 		SQLCHAR dsnbuf[1024];
526 		SQLSMALLINT dsnbuflen;
527 
528 		use_direct = 1;
529 
530 		size_t db_len = strlen(dbh->data_source);
531 		bool use_uid_arg = dbh->username != NULL && !php_memnistr(dbh->data_source, "uid=", strlen("uid="), dbh->data_source + db_len);
532 		bool use_pwd_arg = dbh->password != NULL && !php_memnistr(dbh->data_source, "pwd=", strlen("pwd="), dbh->data_source + db_len);
533 
534 		if (use_uid_arg || use_pwd_arg) {
535 			char *db = (char*) emalloc(db_len + 1);
536 			strcpy(db, dbh->data_source);
537 			char *db_end = db + db_len;
538 			db_end--;
539 			if ((unsigned char)*(db_end) == ';') {
540 				*db_end = '\0';
541 			}
542 
543 			char *uid = NULL, *pwd = NULL, *dsn = NULL;
544 			bool should_quote_uid, should_quote_pwd;
545 			size_t new_dsn_size;
546 
547 			if (use_uid_arg) {
548 				should_quote_uid = !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username);
549 				if (should_quote_uid) {
550 					size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username);
551 					uid = emalloc(estimated_length);
552 					php_odbc_connstr_quote(uid, dbh->username, estimated_length);
553 				} else {
554 					uid = dbh->username;
555 				}
556 
557 				if (!use_pwd_arg) {
558 					new_dsn_size = strlen(db) + strlen(uid) + strlen(";UID=;") + 1;
559 					dsn = pemalloc(new_dsn_size, dbh->is_persistent);
560 					snprintf(dsn, new_dsn_size, "%s;UID=%s;", db, uid);
561 				}
562 			}
563 
564 			if (use_pwd_arg) {
565 				should_quote_pwd = !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password);
566 				if (should_quote_pwd) {
567 					size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password);
568 					pwd = emalloc(estimated_length);
569 					php_odbc_connstr_quote(pwd, dbh->password, estimated_length);
570 				} else {
571 					pwd = dbh->password;
572 				}
573 
574 				if (!use_uid_arg) {
575 					new_dsn_size = strlen(db) + strlen(pwd) + strlen(";PWD=;") + 1;
576 					dsn = pemalloc(new_dsn_size, dbh->is_persistent);
577 					snprintf(dsn, new_dsn_size, "%s;PWD=%s;", db, pwd);
578 				}
579 			}
580 
581 			if (use_uid_arg && use_pwd_arg) {
582 				new_dsn_size = strlen(db)
583 					+ strlen(uid) + strlen(pwd)
584 					+ strlen(";UID=;PWD=;") + 1;
585 				dsn = pemalloc(new_dsn_size, dbh->is_persistent);
586 				snprintf(dsn, new_dsn_size, "%s;UID=%s;PWD=%s;", db, uid, pwd);
587 			}
588 
589 			pefree((char*)dbh->data_source, dbh->is_persistent);
590 			dbh->data_source = dsn;
591 			if (uid && should_quote_uid) {
592 				efree(uid);
593 			}
594 			if (pwd && should_quote_pwd) {
595 				efree(pwd);
596 			}
597 			efree(db);
598 		}
599 
600 		rc = SQLDriverConnect(H->dbc, NULL, (SQLCHAR *) dbh->data_source, strlen(dbh->data_source),
601 				dsnbuf, sizeof(dsnbuf)-1, &dsnbuflen, SQL_DRIVER_NOPROMPT);
602 	}
603 	if (!use_direct) {
604 		rc = SQLConnect(H->dbc, (SQLCHAR *) dbh->data_source, SQL_NTS, (SQLCHAR *) dbh->username, SQL_NTS, (SQLCHAR *) dbh->password, SQL_NTS);
605 	}
606 
607 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
608 		pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect");
609 		goto fail;
610 	}
611 
612 	/* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */
613 
614 	dbh->methods = &odbc_methods;
615 	dbh->alloc_own_columns = 1;
616 
617 	return 1;
618 
619 fail:
620 	dbh->methods = &odbc_methods;
621 	return 0;
622 }
623 /* }}} */
624 
625 const pdo_driver_t pdo_odbc_driver = {
626 	PDO_DRIVER_HEADER(odbc),
627 	pdo_odbc_handle_factory
628 };
629