1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 5                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 1997-2014 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 TSRMLS_DC)
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 TSRMLS_DC) /* {{{ */
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 TSRMLS_CC, "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 TSRMLS_DC)
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, zend_long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC)
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    int 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 TSRMLS_CC);
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 TSRMLS_CC);
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, zend_long sql_len TSRMLS_DC)
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, int unquotedlen, char **quoted, int *quotedlen, enum pdo_param_type param_type  TSRMLS_DC)
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 TSRMLS_DC)
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 TSRMLS_DC)
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 TSRMLS_DC)
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 TSRMLS_DC)
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 TSRMLS_DC)
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 TSRMLS_DC) /* {{{ */
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 TSRMLS_CC);
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        short 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