1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 7                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 1997-2016 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
34static 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
59void 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
127static 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
145static 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
223static 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	}
259out:
260	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
261	return row_count;
262}
263
264static 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
271static 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
287static 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
313static 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
339static 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
354static 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
376static 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
391static 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	SQLUINTEGER 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
475fail:
476	dbh->methods = &odbc_methods;
477	return 0;
478}
479/* }}} */
480
481pdo_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