xref: /PHP-8.1/ext/pdo_odbc/odbc_driver.c (revision c4c8d6ce)
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