diff options
Diffstat (limited to 'ext/pdo_firebird')
-rw-r--r-- | ext/pdo_firebird/CREDITS | 2 | ||||
-rw-r--r-- | ext/pdo_firebird/config.m4 | 56 | ||||
-rw-r--r-- | ext/pdo_firebird/config.w32 | 19 | ||||
-rw-r--r-- | ext/pdo_firebird/firebird_driver.c | 728 | ||||
-rw-r--r-- | ext/pdo_firebird/firebird_statement.c | 710 | ||||
-rw-r--r-- | ext/pdo_firebird/package2.xml | 69 | ||||
-rw-r--r-- | ext/pdo_firebird/pdo_firebird.c | 106 | ||||
-rw-r--r-- | ext/pdo_firebird/php_pdo_firebird.h | 46 | ||||
-rw-r--r-- | ext/pdo_firebird/php_pdo_firebird_int.h | 150 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/bug_47415.phpt | 43 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/bug_48877.phpt | 43 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/bug_53280.phpt | 63 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/common.phpt | 27 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/connect.phpt | 17 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/ddl.phpt | 38 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/execute.phpt | 68 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/rowCount.phpt | 50 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/skipif.inc | 5 | ||||
-rw-r--r-- | ext/pdo_firebird/tests/testdb.inc | 34 |
19 files changed, 2274 insertions, 0 deletions
diff --git a/ext/pdo_firebird/CREDITS b/ext/pdo_firebird/CREDITS new file mode 100644 index 0000000..a33294b --- /dev/null +++ b/ext/pdo_firebird/CREDITS @@ -0,0 +1,2 @@ +Firebird/InterBase driver for PDO +Ard Biesheuvel diff --git a/ext/pdo_firebird/config.m4 b/ext/pdo_firebird/config.m4 new file mode 100644 index 0000000..7b6f669 --- /dev/null +++ b/ext/pdo_firebird/config.m4 @@ -0,0 +1,56 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(pdo-firebird,for Firebird support for PDO, +[ --with-pdo-firebird[=DIR] PDO: Firebird support. DIR is the Firebird base + install directory [/opt/firebird]]) + +if test "$PHP_PDO_FIREBIRD" != "no"; then + + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + if test "$PHP_PDO_FIREBIRD" = "yes"; then + FIREBIRD_INCDIR= + FIREBIRD_LIBDIR= + FIREBIRD_LIBDIR_FLAG= + else + FIREBIRD_INCDIR=$PHP_PDO_FIREBIRD/include + FIREBIRD_LIBDIR=$PHP_PDO_FIREBIRD/$PHP_LIBDIR + FIREBIRD_LIBDIR_FLAG=-L$FIREBIRD_LIBDIR + fi + + PHP_CHECK_LIBRARY(fbclient, isc_detach_database, + [ + FIREBIRD_LIBNAME=fbclient + ], [ + PHP_CHECK_LIBRARY(gds, isc_detach_database, + [ + FIREBIRD_LIBNAME=gds + ], [ + PHP_CHECK_LIBRARY(ib_util, isc_detach_database, + [ + FIREBIRD_LIBNAME=ib_util + ], [ + AC_MSG_ERROR([libfbclient, libgds or libib_util not found! Check config.log for more information.]) + ], [ + $FIREBIRD_LIBDIR_FLAG + ]) + ], [ + $FIREBIRD_LIBDIR_FLAG + ]) + ], [ + $FIREBIRD_LIBDIR_FLAG + ]) + + PHP_CHECK_PDO_INCLUDES + + PHP_ADD_LIBRARY_WITH_PATH($FIREBIRD_LIBNAME, $FIREBIRD_LIBDIR, PDO_FIREBIRD_SHARED_LIBADD) + PHP_ADD_INCLUDE($FIREBIRD_INCDIR) + AC_DEFINE(HAVE_PDO_FIREBIRD,1,[ ]) + PHP_NEW_EXTENSION(pdo_firebird, pdo_firebird.c firebird_driver.c firebird_statement.c, $ext_shared,,-I$pdo_cv_inc_path) + PHP_SUBST(PDO_FIREBIRD_SHARED_LIBADD) + PHP_ADD_EXTENSION_DEP(pdo_firebird, pdo) +fi diff --git a/ext/pdo_firebird/config.w32 b/ext/pdo_firebird/config.w32 new file mode 100644 index 0000000..9cf5ac1 --- /dev/null +++ b/ext/pdo_firebird/config.w32 @@ -0,0 +1,19 @@ +// $Id$ +// vim:ft=javascript + +ARG_WITH("pdo-firebird", "Firebird support for PDO", "no"); + +if (PHP_PDO_FIREBIRD != "no") { + + if ((CHECK_LIB("fbclient_ms.lib", "pdo_firebird", PHP_PHP_BUILD + "\\interbase\\lib_ms;" + PHP_PDO_FIREBIRD) + || CHECK_LIB("gds32_ms.lib", "pdo_firebird", PHP_PHP_BUILD + "\\interbase\\lib_ms;" + PHP_PDO_FIREBIRD) + ) && CHECK_HEADER_ADD_INCLUDE("ibase.h", "CFLAGS_PDO_FIREBIRD", + PHP_PHP_BUILD + "\\include\\interbase;" + PHP_PHP_BUILD + "\\interbase\\include;" + PHP_PDO_FIREBIRD) + ) { + + EXTENSION("pdo_firebird", "pdo_firebird.c firebird_driver.c firebird_statement.c"); + } else { + WARNING("pdo_firebird not enabled; libraries and headers not found"); + } + ADD_EXTENSION_DEP('pdo_firebird', 'pdo'); +} diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c new file mode 100644 index 0000000..bda6d23 --- /dev/null +++ b/ext/pdo_firebird/firebird_driver.c @@ -0,0 +1,728 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel <abies@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _GNU_SOURCE + +#include "php.h" +#ifdef ZEND_ENGINE_2 +# include "zend_exceptions.h" +#endif +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_firebird.h" +#include "php_pdo_firebird_int.h" + +static int firebird_alloc_prepare_stmt(pdo_dbh_t*, const char*, long, XSQLDA*, isc_stmt_handle*, + HashTable* TSRMLS_DC); + +/* map driver specific error message to PDO error */ +void _firebird_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, char const *file, long line TSRMLS_DC) /* {{{ */ +{ +#if 0 + pdo_firebird_db_handle *H = stmt ? ((pdo_firebird_stmt *)stmt->driver_data)->H + : (pdo_firebird_db_handle *)dbh->driver_data; +#endif + pdo_error_type *const error_code = stmt ? &stmt->error_code : &dbh->error_code; + +#if 0 + switch (isc_sqlcode(H->isc_status)) { + + case 0: + *error_code = PDO_ERR_NONE; + break; + default: + *error_code = PDO_ERR_CANT_MAP; + break; + case -104: + *error_code = PDO_ERR_SYNTAX; + break; + case -530: + case -803: + *error_code = PDO_ERR_CONSTRAINT; + break; + case -204: + case -205: + case -206: + case -829: + *error_code = PDO_ERR_NOT_FOUND; + break; + + *error_code = PDO_ERR_ALREADY_EXISTS; + break; + + *error_code = PDO_ERR_NOT_IMPLEMENTED; + break; + case -313: + case -804: + *error_code = PDO_ERR_MISMATCH; + break; + case -303: + case -314: + case -413: + *error_code = PDO_ERR_TRUNCATED; + break; + + *error_code = PDO_ERR_DISCONNECTED; + break; + } +#else + strcpy(*error_code, "HY000"); +#endif +} +/* }}} */ + +#define RECORD_ERROR(dbh) _firebird_error(dbh, NULL, __FILE__, __LINE__ TSRMLS_CC) + +/* called by PDO to close a db handle */ +static int firebird_handle_closer(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (dbh->in_txn) { + if (dbh->auto_commit) { + if (isc_commit_transaction(H->isc_status, &H->tr)) { + RECORD_ERROR(dbh); + } + } else { + if (isc_rollback_transaction(H->isc_status, &H->tr)) { + RECORD_ERROR(dbh); + } + } + } + + if (isc_detach_database(H->isc_status, &H->db)) { + RECORD_ERROR(dbh); + } + + if (H->date_format) { + efree(H->date_format); + } + if (H->time_format) { + efree(H->time_format); + } + if (H->timestamp_format) { + efree(H->timestamp_format); + } + + pefree(H, dbh->is_persistent); + + return 0; +} +/* }}} */ + +/* called by PDO to prepare an SQL query */ +static int firebird_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, /* {{{ */ + pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC) +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + pdo_firebird_stmt *S = NULL; + HashTable *np; + + do { + isc_stmt_handle s = NULL; + XSQLDA num_sqlda; + static char const info[] = { isc_info_sql_stmt_type }; + char result[8]; + + num_sqlda.version = PDO_FB_SQLDA_VERSION; + num_sqlda.sqln = 1; + + ALLOC_HASHTABLE(np); + zend_hash_init(np, 8, NULL, NULL, 0); + + /* allocate and prepare statement */ + if (!firebird_alloc_prepare_stmt(dbh, sql, sql_len, &num_sqlda, &s, np TSRMLS_CC)) { + break; + } + + /* allocate a statement handle struct of the right size (struct out_sqlda is inlined) */ + S = ecalloc(1, sizeof(*S)-sizeof(XSQLDA) + XSQLDA_LENGTH(num_sqlda.sqld)); + S->H = H; + S->stmt = s; + S->fetch_buf = ecalloc(1,sizeof(char*) * num_sqlda.sqld); + S->out_sqlda.version = PDO_FB_SQLDA_VERSION; + S->out_sqlda.sqln = stmt->column_count = num_sqlda.sqld; + S->named_params = np; + + /* determine the statement type */ + if (isc_dsql_sql_info(H->isc_status, &s, sizeof(info), const_cast(info), sizeof(result), + result)) { + break; + } + S->statement_type = result[3]; + + /* fill the output sqlda with information about the prepared query */ + if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { + RECORD_ERROR(dbh); + break; + } + + /* allocate the input descriptors */ + if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &num_sqlda)) { + break; + } + + if (num_sqlda.sqld) { + S->in_sqlda = ecalloc(1,XSQLDA_LENGTH(num_sqlda.sqld)); + S->in_sqlda->version = PDO_FB_SQLDA_VERSION; + S->in_sqlda->sqln = num_sqlda.sqld; + + if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { + break; + } + } + + stmt->driver_data = S; + stmt->methods = &firebird_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + + return 1; + + } while (0); + + RECORD_ERROR(dbh); + + zend_hash_destroy(np); + FREE_HASHTABLE(np); + + if (S) { + if (S->in_sqlda) { + efree(S->in_sqlda); + } + efree(S); + } + + return 0; +} +/* }}} */ + +/* called by PDO to execute a statement that doesn't produce a result set */ +static long firebird_handle_doer(pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + isc_stmt_handle stmt = NULL; + static char const info_count[] = { isc_info_sql_records }; + char result[64]; + int ret = 0; + XSQLDA in_sqlda, out_sqlda; + + /* TODO no placeholders in exec() for now */ + in_sqlda.version = out_sqlda.version = PDO_FB_SQLDA_VERSION; + in_sqlda.sqld = out_sqlda.sqld = 0; + out_sqlda.sqln = 1; + + /* allocate and prepare statement */ + if (!firebird_alloc_prepare_stmt(dbh, sql, sql_len, &out_sqlda, &stmt, 0 TSRMLS_CC)) { + return -1; + } + + /* execute the statement */ + if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) { + RECORD_ERROR(dbh); + return -1; + } + + /* find out how many rows were affected */ + if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), const_cast(info_count), + sizeof(result), result)) { + RECORD_ERROR(dbh); + return -1; + } + + if (result[0] == isc_info_sql_records) { + unsigned i = 3, result_size = isc_vax_integer(&result[1],2); + + while (result[i] != isc_info_end && i < result_size) { + short len = (short)isc_vax_integer(&result[i+1],2); + if (result[i] != isc_info_req_select_count) { + ret += isc_vax_integer(&result[i+3],len); + } + i += len+3; + } + } + + /* commit if we're in auto_commit mode */ + if (dbh->auto_commit && isc_commit_retaining(H->isc_status, &H->tr)) { + RECORD_ERROR(dbh); + } + + return ret; +} +/* }}} */ + +/* called by the PDO SQL parser to add quotes to values that are copied into SQL */ +static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, int unquotedlen, /* {{{ */ + char **quoted, int *quotedlen, enum pdo_param_type paramtype TSRMLS_DC) +{ + int qcount = 0; + char const *co, *l, *r; + char *c; + + if (!unquotedlen) { + *quotedlen = 2; + *quoted = emalloc(*quotedlen+1); + strcpy(*quoted, "''"); + return 1; + } + + /* Firebird only requires single quotes to be doubled if string lengths are used */ + /* count the number of ' characters */ + for (co = unquoted; (co = strchr(co,'\'')); qcount++, co++); + + *quotedlen = unquotedlen + qcount + 2; + *quoted = c = emalloc(*quotedlen+1); + *c++ = '\''; + + /* foreach (chunk that ends in a quote) */ + for (l = unquoted; (r = strchr(l,'\'')); l = r+1) { + strncpy(c, l, r-l+1); + c += (r-l+1); + /* add the second quote */ + *c++ = '\''; + } + + /* copy the remainder */ + strncpy(c, l, *quotedlen-(c-*quoted)-1); + (*quoted)[*quotedlen-1] = '\''; + (*quoted)[*quotedlen] = '\0'; + + return 1; +} +/* }}} */ + +/* called by PDO to start a transaction */ +static int firebird_handle_begin(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + char tpb[8] = { isc_tpb_version3 }, *ptpb = tpb+1; +#if abies_0 + if (dbh->transaction_flags & PDO_TRANS_ISOLATION_LEVEL) { + if (dbh->transaction_flags & PDO_TRANS_READ_UNCOMMITTED) { + /* this is a poor fit, but it's all we have */ + *ptpb++ = isc_tpb_read_committed; + *ptpb++ = isc_tpb_rec_version; + dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_UNCOMMITTED); + } else if (dbh->transaction_flags & PDO_TRANS_READ_COMMITTED) { + *ptpb++ = isc_tpb_read_committed; + *ptpb++ = isc_tpb_no_rec_version; + dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_COMMITTED); + } else if (dbh->transaction_flags & PDO_TRANS_REPEATABLE_READ) { + *ptpb++ = isc_tpb_concurrency; + dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_REPEATABLE_READ); + } else { + *ptpb++ = isc_tpb_consistency; + dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_SERIALIZABLE); + } + } + + if (dbh->transaction_flags & PDO_TRANS_ACCESS_MODE) { + if (dbh->transaction_flags & PDO_TRANS_READONLY) { + *ptpb++ = isc_tpb_read; + dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READONLY); + } else { + *ptpb++ = isc_tpb_write; + dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READWRITE); + } + } + + if (dbh->transaction_flags & PDO_TRANS_CONFLICT_RESOLUTION) { + if (dbh->transaction_flags & PDO_TRANS_RETRY) { + *ptpb++ = isc_tpb_wait; + dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_RETRY); + } else { + *ptpb++ = isc_tpb_nowait; + dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_ABORT); + } + } +#endif + if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, (unsigned short)(ptpb-tpb), tpb)) { + RECORD_ERROR(dbh); + return 0; + } + return 1; +} +/* }}} */ + +/* called by PDO to commit a transaction */ +static int firebird_handle_commit(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (isc_commit_transaction(H->isc_status, &H->tr)) { + RECORD_ERROR(dbh); + return 0; + } + return 1; +} +/* }}} */ + +/* called by PDO to rollback a transaction */ +static int firebird_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (isc_rollback_transaction(H->isc_status, &H->tr)) { + RECORD_ERROR(dbh); + return 0; + } + return 1; +} +/* }}} */ + +/* used by prepare and exec to allocate a statement handle and prepare the SQL */ +static int firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const char *sql, long sql_len, /* {{{ */ + XSQLDA *out_sqlda, isc_stmt_handle *s, HashTable *named_params TSRMLS_DC) +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + char *c, *new_sql, in_quote, in_param, pname[64], *ppname; + long l, pindex = -1; + + /* Firebird allows SQL statements up to 64k, so bail if it doesn't fit */ + if (sql_len > 65536) { + strcpy(dbh->error_code, "01004"); + return 0; + } + + /* start a new transaction implicitly if auto_commit is enabled and no transaction is open */ + if (dbh->auto_commit && !dbh->in_txn) { + /* dbh->transaction_flags = PDO_TRANS_READ_UNCOMMITTED; */ + + if (!firebird_handle_begin(dbh TSRMLS_CC)) { + return 0; + } + dbh->in_txn = 1; + } + + /* allocate the statement */ + if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) { + RECORD_ERROR(dbh); + return 0; + } + + /* in order to support named params, which Firebird itself doesn't, + we need to replace :foo by ?, and store the name we just replaced */ + new_sql = c = emalloc(sql_len+1); + + for (l = in_quote = in_param = 0; l <= sql_len; ++l) { + if ( !(in_quote ^= (sql[l] == '\''))) { + if (!in_param) { + switch (sql[l]) { + case ':': + in_param = 1; + ppname = pname; + *ppname++ = sql[l]; + case '?': + *c++ = '?'; + ++pindex; + continue; + } + } else { + if ((in_param &= ((sql[l] >= 'A' && sql[l] <= 'Z') || (sql[l] >= 'a' && sql[l] <= 'z') + || (sql[l] >= '0' && sql[l] <= '9') || sql[l] == '_' || sql[l] == '-'))) { + + + *ppname++ = sql[l]; + continue; + } else { + *ppname++ = 0; + if (named_params) { + zend_hash_update(named_params, pname, (unsigned int)(ppname-pname), + (void*)&pindex, sizeof(long)+1,NULL); + } + } + } + } + *c++ = sql[l]; + } + + /* prepare the statement */ + if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, PDO_FB_DIALECT, out_sqlda)) { + RECORD_ERROR(dbh); + efree(new_sql); + return 0; + } + + efree(new_sql); + return 1; +} +/* }}} */ + +/* called by PDO to set a driver-specific dbh attribute */ +static int firebird_handle_set_attribute(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_AUTOCOMMIT: + + convert_to_boolean(val); + + /* ignore if the new value equals the old one */ + if (dbh->auto_commit ^ Z_BVAL_P(val)) { + if (dbh->in_txn) { + if (Z_BVAL_P(val)) { + /* turning on auto_commit with an open transaction is illegal, because + we won't know what to do with it */ + H->last_app_error = "Cannot enable auto-commit while a transaction is already open"; + return 0; + } else { + /* close the transaction */ + if (!firebird_handle_commit(dbh TSRMLS_CC)) { + break; + } + dbh->in_txn = 0; + } + } + dbh->auto_commit = Z_BVAL_P(val); + } + return 1; + + case PDO_ATTR_FETCH_TABLE_NAMES: + convert_to_boolean(val); + H->fetch_table_names = Z_BVAL_P(val); + return 1; + + case PDO_FB_ATTR_DATE_FORMAT: + convert_to_string(val); + if (H->date_format) { + efree(H->date_format); + } + spprintf(&H->date_format, 0, "%s", Z_STRVAL_P(val)); + return 1; + + case PDO_FB_ATTR_TIME_FORMAT: + convert_to_string(val); + if (H->time_format) { + efree(H->time_format); + } + spprintf(&H->time_format, 0, "%s", Z_STRVAL_P(val)); + return 1; + + case PDO_FB_ATTR_TIMESTAMP_FORMAT: + convert_to_string(val); + if (H->timestamp_format) { + efree(H->timestamp_format); + } + spprintf(&H->timestamp_format, 0, "%s", Z_STRVAL_P(val)); + return 1; + } + return 0; +} +/* }}} */ + +/* callback to used to report database server info */ +static void firebird_info_cb(void *arg, char const *s) /* {{{ */ +{ + if (arg) { + if (*(char*)arg) { /* second call */ + strcat(arg, " "); + } + strcat(arg, s); + } +} +/* }}} */ + +/* called by PDO to get a driver-specific dbh attribute */ +static int firebird_handle_get_attribute(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + switch (attr) { + char tmp[512]; + + case PDO_ATTR_AUTOCOMMIT: + ZVAL_LONG(val,dbh->auto_commit); + return 1; + + case PDO_ATTR_CONNECTION_STATUS: + ZVAL_BOOL(val, !isc_version(&H->db, firebird_info_cb, NULL)); + return 1; + + case PDO_ATTR_CLIENT_VERSION: { +#if defined(__GNUC__) || defined(PHP_WIN32) + info_func_t info_func = NULL; +#ifdef __GNUC__ + info_func = (info_func_t)dlsym(RTLD_DEFAULT, "isc_get_client_version"); +#else + HMODULE l = GetModuleHandle("fbclient"); + + if (!l && !(l = GetModuleHandle("gds32"))) { + break; + } + info_func = (info_func_t)GetProcAddress(l, "isc_get_client_version"); +#endif + if (info_func) { + info_func(tmp); + ZVAL_STRING(val,tmp,1); + } else { + ZVAL_STRING(val,"Firebird 1.0/Interbase 6",1); + } +#else + ZVAL_NULL(val); +#endif + } + return 1; + + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: + *tmp = 0; + + if (!isc_version(&H->db, firebird_info_cb, (void*)tmp)) { + ZVAL_STRING(val,tmp,1); + return 1; + } + + case PDO_ATTR_FETCH_TABLE_NAMES: + ZVAL_BOOL(val, H->fetch_table_names); + return 1; + } + return 0; +} +/* }}} */ + +/* called by PDO to retrieve driver-specific information about an error that has occurred */ +static int pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + ISC_STATUS *s = H->isc_status; + char buf[400]; + long i = 0, l, sqlcode = isc_sqlcode(s); + + if (sqlcode) { + add_next_index_long(info, sqlcode); + + while ((l = isc_interprete(&buf[i],&s))) { + i += l; + strcpy(&buf[i++], " "); + } + add_next_index_string(info, buf, 1); + } else if (H->last_app_error) { + add_next_index_long(info, -999); + add_next_index_string(info, const_cast(H->last_app_error),1); + } + return 1; +} +/* }}} */ + +static struct pdo_dbh_methods firebird_methods = { /* {{{ */ + firebird_handle_closer, + firebird_handle_preparer, + firebird_handle_doer, + firebird_handle_quoter, + firebird_handle_begin, + firebird_handle_commit, + firebird_handle_rollback, + firebird_handle_set_attribute, + NULL, /* last_id not supported */ + pdo_firebird_fetch_error_func, + firebird_handle_get_attribute, + NULL /* check_liveness */ +}; +/* }}} */ + +/* the driver-specific PDO handle constructor */ +static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) /* {{{ */ +{ + struct pdo_data_src_parser vars[] = { + { "dbname", NULL, 0 }, + { "charset", NULL, 0 }, + { "role", NULL, 0 } + }; + int i, ret = 0; + short buf_len = 256, dpb_len; + + pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent); + + php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 3); + + do { + static char const dpb_flags[] = { + isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name }; + char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval }; + char dpb_buffer[256] = { isc_dpb_version1 }, *dpb; + + dpb = dpb_buffer + 1; + + /* loop through all the provided arguments and set dpb fields accordingly */ + for (i = 0; i < sizeof(dpb_flags); ++i) { + if (dpb_values[i] && buf_len > 0) { + dpb_len = slprintf(dpb, buf_len, "%c%c%s", dpb_flags[i], (unsigned char)strlen(dpb_values[i]), + dpb_values[i]); + dpb += dpb_len; + buf_len -= dpb_len; + } + } + + /* fire it up baby! */ + if (isc_attach_database(H->isc_status, 0, vars[0].optval, &H->db,(short)(dpb-dpb_buffer), dpb_buffer)) { + break; + } + + dbh->methods = &firebird_methods; + dbh->native_case = PDO_CASE_UPPER; + dbh->alloc_own_columns = 1; + + ret = 1; + + } while (0); + + for (i = 0; i < sizeof(vars)/sizeof(vars[0]); ++i) { + if (vars[i].freeme) { + efree(vars[i].optval); + } + } + + if (!dbh->methods) { + char errmsg[512]; + ISC_STATUS *s = H->isc_status; + isc_interprete(errmsg, &s); + zend_throw_exception_ex(php_pdo_get_exception(), H->isc_status[1] TSRMLS_CC, "SQLSTATE[%s] [%d] %s", + "HY000", H->isc_status[1], errmsg); + } + + if (!ret) { + firebird_handle_closer(dbh TSRMLS_CC); + } + + return ret; +} +/* }}} */ + + +pdo_driver_t pdo_firebird_driver = { /* {{{ */ + PDO_DRIVER_HEADER(firebird), + pdo_firebird_handle_factory +}; +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_firebird/firebird_statement.c b/ext/pdo_firebird/firebird_statement.c new file mode 100644 index 0000000..5c3e435 --- /dev/null +++ b/ext/pdo_firebird/firebird_statement.c @@ -0,0 +1,710 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel <abies@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_firebird.h" +#include "php_pdo_firebird_int.h" + +#include <time.h> + +#define RECORD_ERROR(stmt) _firebird_error(NULL, stmt, __FILE__, __LINE__ TSRMLS_CC) + +/* free the allocated space for passing field values to the db and back */ +static void free_sqlda(XSQLDA const *sqlda) /* {{{ */ +{ + int i; + + for (i = 0; i < sqlda->sqld; ++i) { + XSQLVAR const *var = &sqlda->sqlvar[i]; + + if (var->sqlind) { + efree(var->sqlind); + } + } +} +/* }}} */ + +/* called by PDO to clean up a statement handle */ +static int firebird_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + int result = 1, i; + + /* release the statement */ + if (isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) { + RECORD_ERROR(stmt); + result = 0; + } + + /* clean up the fetch buffers if they have been used */ + for (i = 0; i < S->out_sqlda.sqld; ++i) { + if (S->fetch_buf[i]) { + efree(S->fetch_buf[i]); + } + } + efree(S->fetch_buf); + + zend_hash_destroy(S->named_params); + FREE_HASHTABLE(S->named_params); + + /* clean up the input descriptor */ + if (S->in_sqlda) { + free_sqlda(S->in_sqlda); + efree(S->in_sqlda); + } + + free_sqlda(&S->out_sqlda); + efree(S); + + return result; +} +/* }}} */ + +/* called by PDO to execute a prepared query */ +static int firebird_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + unsigned long affected_rows = 0; + static char info_count[] = {isc_info_sql_records}; + char result[64]; + + do { + /* named or open cursors should be closed first */ + if ((*S->name || S->cursor_open) && isc_dsql_free_statement(H->isc_status, &S->stmt, DSQL_close)) { + break; + } + S->cursor_open = 0; + /* assume all params have been bound */ + + if (isc_dsql_execute(H->isc_status, &H->tr, &S->stmt, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { + break; + } + + /* Determine how many rows have changed. In this case we are + * only interested in rows changed, not rows retrieved. That + * should be handled by the client when fetching. */ + stmt->row_count = affected_rows; + + switch (S->statement_type) { + case isc_info_sql_stmt_insert: + case isc_info_sql_stmt_update: + case isc_info_sql_stmt_delete: + case isc_info_sql_stmt_exec_procedure: + if (isc_dsql_sql_info(H->isc_status, &S->stmt, sizeof ( info_count), + info_count, sizeof(result), result)) { + break; + } + if (result[0] == isc_info_sql_records) { + unsigned i = 3, result_size = isc_vax_integer(&result[1], 2); + while (result[i] != isc_info_end && i < result_size) { + short len = (short) isc_vax_integer(&result[i + 1], 2); + if (result[i] != isc_info_req_select_count) { + affected_rows += isc_vax_integer(&result[i + 3], len); + } + i += len + 3; + } + stmt->row_count = affected_rows; + } + default: + ; + } + + /* commit? */ + if (stmt->dbh->auto_commit && isc_commit_retaining(H->isc_status, &H->tr)) { + break; + } + + *S->name = 0; + S->cursor_open = (S->out_sqlda.sqln > 0); /* A cursor is opened, when more than zero columns returned */ + S->exhausted = !S->cursor_open; + + return 1; + } while (0); + + RECORD_ERROR(stmt); + + return 0; +} +/* }}} */ + +/* called by PDO to fetch the next row from a statement */ +static int firebird_stmt_fetch(pdo_stmt_t *stmt, /* {{{ */ + enum pdo_fetch_orientation ori, long offset TSRMLS_DC) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + + if (!stmt->executed) { + strcpy(stmt->error_code, "HY000"); + H->last_app_error = "Cannot fetch from a closed cursor"; + } else if (!S->exhausted) { + if (isc_dsql_fetch(H->isc_status, &S->stmt, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { + if (H->isc_status[0] && H->isc_status[1]) { + RECORD_ERROR(stmt); + } + S->exhausted = 1; + return 0; + } + if (S->statement_type == isc_info_sql_stmt_exec_procedure) { + S->exhausted = 1; + } + stmt->row_count++; + return 1; + } + return 0; +} +/* }}} */ + +/* called by PDO to retrieve information about the fields being returned */ +static int firebird_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + XSQLVAR *var = &S->out_sqlda.sqlvar[colno]; + int colname_len; + char *cp; + + /* allocate storage for the column */ + var->sqlind = (void*)ecalloc(1, var->sqllen + 2*sizeof(short)); + var->sqldata = &((char*)var->sqlind)[sizeof(short)]; + + colname_len = (S->H->fetch_table_names && var->relname_length) + ? (var->aliasname_length + var->relname_length + 1) + : (var->aliasname_length); + col->precision = -var->sqlscale; + col->maxlen = var->sqllen; + col->namelen = colname_len; + col->name = cp = emalloc(colname_len + 1); + if (colname_len > var->aliasname_length) { + memmove(cp, var->relname, var->relname_length); + cp += var->relname_length; + *cp++ = '.'; + } + memmove(cp, var->aliasname, var->aliasname_length); + *(cp+var->aliasname_length) = '\0'; + col->param_type = PDO_PARAM_STR; + + return 1; +} +/* }}} */ + +#define FETCH_BUF(buf,type,len,lenvar) ((buf) = (buf) ? (buf) : \ + emalloc((len) ? (len * sizeof(type)) : ((*(unsigned long*)lenvar) = sizeof(type)))) + +#define CHAR_BUF_LEN 24 + +/* fetch a blob into a fetch buffer */ +static int firebird_fetch_blob(pdo_stmt_t *stmt, int colno, char **ptr, /* {{{ */ + unsigned long *len, ISC_QUAD *blob_id TSRMLS_DC) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + isc_blob_handle blobh = NULL; + char const bl_item = isc_info_blob_total_length; + char bl_info[20]; + unsigned short i; + int result = *len = 0; + + if (isc_open_blob(H->isc_status, &H->db, &H->tr, &blobh, blob_id)) { + RECORD_ERROR(stmt); + return 0; + } + + if (isc_blob_info(H->isc_status, &blobh, 1, const_cast(&bl_item), + sizeof(bl_info), bl_info)) { + RECORD_ERROR(stmt); + goto fetch_blob_end; + } + + /* find total length of blob's data */ + for (i = 0; i < sizeof(bl_info); ) { + unsigned short item_len; + char item = bl_info[i++]; + + if (item == isc_info_end || item == isc_info_truncated || item == isc_info_error + || i >= sizeof(bl_info)) { + H->last_app_error = "Couldn't determine BLOB size"; + goto fetch_blob_end; + } + + item_len = (unsigned short) isc_vax_integer(&bl_info[i], 2); + + if (item == isc_info_blob_total_length) { + *len = isc_vax_integer(&bl_info[i+2], item_len); + break; + } + i += item_len+2; + } + + /* we've found the blob's length, now fetch! */ + + if (*len) { + unsigned long cur_len; + unsigned short seg_len; + ISC_STATUS stat; + + *ptr = S->fetch_buf[colno] = erealloc(*ptr, *len+1); + + for (cur_len = stat = 0; (!stat || stat == isc_segment) && cur_len < *len; cur_len += seg_len) { + + unsigned short chunk_size = (*len-cur_len) > USHRT_MAX ? USHRT_MAX + : (unsigned short)(*len-cur_len); + + stat = isc_get_segment(H->isc_status, &blobh, &seg_len, chunk_size, &(*ptr)[cur_len]); + } + + (*ptr)[*len++] = '\0'; + + if (H->isc_status[0] == 1 && (stat != 0 && stat != isc_segstr_eof && stat != isc_segment)) { + H->last_app_error = "Error reading from BLOB"; + goto fetch_blob_end; + } + } + result = 1; + +fetch_blob_end: + if (isc_close_blob(H->isc_status, &blobh)) { + RECORD_ERROR(stmt); + return 0; + } + return result; +} +/* }}} */ + +static int firebird_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, /* {{{ */ + unsigned long *len, int *caller_frees TSRMLS_DC) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + XSQLVAR const *var = &S->out_sqlda.sqlvar[colno]; + + if (*var->sqlind == -1) { + /* A NULL value */ + *ptr = NULL; + *len = 0; + } else { + if (var->sqlscale < 0) { + static ISC_INT64 const scales[] = { 1, 10, 100, 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + LL_LIT(10000000000), + LL_LIT(100000000000), + LL_LIT(1000000000000), + LL_LIT(10000000000000), + LL_LIT(100000000000000), + LL_LIT(1000000000000000), + LL_LIT(10000000000000000), + LL_LIT(100000000000000000), + LL_LIT(1000000000000000000) + }; + ISC_INT64 n, f = scales[-var->sqlscale]; + + switch (var->sqltype & ~1) { + case SQL_SHORT: + n = *(short*)var->sqldata; + break; + case SQL_LONG: + n = *(ISC_LONG*)var->sqldata; + break; + case SQL_INT64: + n = *(ISC_INT64*)var->sqldata; + } + + *ptr = FETCH_BUF(S->fetch_buf[colno], char, CHAR_BUF_LEN, NULL); + + if (n >= 0) { + *len = slprintf(*ptr, CHAR_BUF_LEN, "%" LL_MASK "d.%0*" LL_MASK "d", + n / f, -var->sqlscale, n % f); + } else if (n < -f) { + *len = slprintf(*ptr, CHAR_BUF_LEN, "%" LL_MASK "d.%0*" LL_MASK "d", + n / f, -var->sqlscale, -n % f); + } else { + *len = slprintf(*ptr, CHAR_BUF_LEN, "-0.%0*" LL_MASK "d", -var->sqlscale, -n % f); + } + } else { + switch (var->sqltype & ~1) { + struct tm t; + char *fmt; + + case SQL_VARYING: + *ptr = &var->sqldata[2]; + *len = *(short*)var->sqldata; + break; + case SQL_TEXT: + *ptr = var->sqldata; + *len = var->sqllen; + break; + case SQL_SHORT: + *ptr = FETCH_BUF(S->fetch_buf[colno], char, CHAR_BUF_LEN, NULL); + *len = slprintf(*ptr, CHAR_BUF_LEN, "%d", *(short*)var->sqldata); + break; + case SQL_LONG: + *ptr = FETCH_BUF(S->fetch_buf[colno], char, CHAR_BUF_LEN, NULL); + *len = slprintf(*ptr, CHAR_BUF_LEN, "%ld", *(ISC_LONG*)var->sqldata); + break; + case SQL_INT64: + *ptr = FETCH_BUF(S->fetch_buf[colno], char, CHAR_BUF_LEN, NULL); + *len = slprintf(*ptr, CHAR_BUF_LEN, "%" LL_MASK "d", *(ISC_INT64*)var->sqldata); + break; + case SQL_FLOAT: + *ptr = FETCH_BUF(S->fetch_buf[colno], char, CHAR_BUF_LEN, NULL); + *len = slprintf(*ptr, CHAR_BUF_LEN, "%F", *(float*)var->sqldata); + break; + case SQL_DOUBLE: + *ptr = FETCH_BUF(S->fetch_buf[colno], char, CHAR_BUF_LEN, NULL); + *len = slprintf(*ptr, CHAR_BUF_LEN, "%F" , *(double*)var->sqldata); + break; + case SQL_TYPE_DATE: + isc_decode_sql_date((ISC_DATE*)var->sqldata, &t); + fmt = S->H->date_format ? S->H->date_format : PDO_FB_DEF_DATE_FMT; + if (0) { + case SQL_TYPE_TIME: + isc_decode_sql_time((ISC_TIME*)var->sqldata, &t); + fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT; + } else if (0) { + case SQL_TIMESTAMP: + isc_decode_timestamp((ISC_TIMESTAMP*)var->sqldata, &t); + fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT; + } + /* convert the timestamp into a string */ + *len = 80; + *ptr = FETCH_BUF(S->fetch_buf[colno], char, *len, NULL); + *len = strftime(*ptr, *len, fmt, &t); + break; + case SQL_BLOB: + return firebird_fetch_blob(stmt,colno,ptr,len, + (ISC_QUAD*)var->sqldata TSRMLS_CC); + } + } + } + return 1; +} +/* }}} */ + +static int firebird_bind_blob(pdo_stmt_t *stmt, ISC_QUAD *blob_id, zval *param TSRMLS_DC) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + isc_blob_handle h = NULL; + unsigned long put_cnt = 0, rem_cnt; + unsigned short chunk_size; + int result = 1; + + if (isc_create_blob(H->isc_status, &H->db, &H->tr, &h, blob_id)) { + RECORD_ERROR(stmt); + return 0; + } + + SEPARATE_ZVAL(¶m); + + convert_to_string_ex(¶m); + + for (rem_cnt = Z_STRLEN_P(param); rem_cnt > 0; rem_cnt -= chunk_size) { + + chunk_size = rem_cnt > USHRT_MAX ? USHRT_MAX : (unsigned short)rem_cnt; + + if (isc_put_segment(H->isc_status, &h, chunk_size, &Z_STRVAL_P(param)[put_cnt])) { + RECORD_ERROR(stmt); + result = 0; + break; + } + put_cnt += chunk_size; + } + + zval_dtor(param); + + if (isc_close_blob(H->isc_status, &h)) { + RECORD_ERROR(stmt); + return 0; + } + return result; +} + +static int firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, /* {{{ */ + enum pdo_param_event event_type TSRMLS_DC) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + XSQLDA *sqlda = param->is_param ? S->in_sqlda : &S->out_sqlda; + XSQLVAR *var; + + if (event_type == PDO_PARAM_EVT_FREE) { /* not used */ + return 1; + } + + if (!sqlda || param->paramno >= sqlda->sqld) { + strcpy(stmt->error_code, "HY093"); + S->H->last_app_error = "Invalid parameter index"; + return 0; + } + if (param->is_param && param->paramno == -1) { + long *index; + + /* try to determine the index by looking in the named_params hash */ + if (SUCCESS == zend_hash_find(S->named_params, param->name, param->namelen+1, (void*)&index)) { + param->paramno = *index; + } else { + /* ... or by looking in the input descriptor */ + int i; + + for (i = 0; i < sqlda->sqld; ++i) { + XSQLVAR *var = &sqlda->sqlvar[i]; + + if ((var->aliasname_length && !strncasecmp(param->name, var->aliasname, + min(param->namelen, var->aliasname_length))) + || (var->sqlname_length && !strncasecmp(param->name, var->sqlname, + min(param->namelen, var->sqlname_length)))) { + param->paramno = i; + break; + } + } + if (i >= sqlda->sqld) { + strcpy(stmt->error_code, "HY093"); + S->H->last_app_error = "Invalid parameter name"; + return 0; + } + } + } + + var = &sqlda->sqlvar[param->paramno]; + + switch (event_type) { + char *value; + unsigned long value_len; + int caller_frees; + + case PDO_PARAM_EVT_ALLOC: + if (param->is_param) { + /* allocate the parameter */ + if (var->sqlind) { + efree(var->sqlind); + } + var->sqlind = (void*)emalloc(var->sqllen + 2*sizeof(short)); + var->sqldata = &((char*)var->sqlind)[sizeof(short)]; + } + break; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!param->is_param) { + break; + } + + *var->sqlind = 0; + + switch (var->sqltype & ~1) { + case SQL_ARRAY: + strcpy(stmt->error_code, "HY000"); + S->H->last_app_error = "Cannot bind to array field"; + return 0; + + case SQL_BLOB: + return firebird_bind_blob(stmt, (ISC_QUAD*)var->sqldata, + param->parameter TSRMLS_CC); + } + + /* check if a NULL should be inserted */ + switch (Z_TYPE_P(param->parameter)) { + int force_null; + + case IS_LONG: + var->sqltype = sizeof(long) == 8 ? SQL_INT64 : SQL_LONG; + var->sqldata = (void*)&Z_LVAL_P(param->parameter); + var->sqllen = sizeof(long); + break; + case IS_DOUBLE: + var->sqltype = SQL_DOUBLE; + var->sqldata = (void*)&Z_DVAL_P(param->parameter); + var->sqllen = sizeof(double); + break; + case IS_STRING: + force_null = 0; + + /* for these types, an empty string can be handled like a NULL value */ + switch (var->sqltype & ~1) { + case SQL_SHORT: + case SQL_LONG: + case SQL_INT64: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + force_null = (Z_STRLEN_P(param->parameter) == 0); + } + if (!force_null) { + var->sqltype = SQL_TEXT; + var->sqldata = Z_STRVAL_P(param->parameter); + var->sqllen = Z_STRLEN_P(param->parameter); + break; + } + case IS_NULL: + /* complain if this field doesn't allow NULL values */ + if (~var->sqltype & 1) { + strcpy(stmt->error_code, "HY105"); + S->H->last_app_error = "Parameter requires non-null value"; + return 0; + } + *var->sqlind = -1; + break; + default: + strcpy(stmt->error_code, "HY105"); + S->H->last_app_error = "Binding arrays/objects is not supported"; + return 0; + } + break; + + case PDO_PARAM_EVT_FETCH_POST: + if (param->paramno == -1) { + return 0; + } + if (param->is_param) { + break; + } + value = NULL; + value_len = 0; + caller_frees = 0; + + if (firebird_stmt_get_col(stmt, param->paramno, &value, &value_len, &caller_frees TSRMLS_CC)) { + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STR: + if (value) { + ZVAL_STRINGL(param->parameter, value, value_len, 1); + break; + } + case PDO_PARAM_INT: + if (value) { + ZVAL_LONG(param->parameter, *(long*)value); + break; + } + case PDO_PARAM_EVT_NORMALIZE: + if (!param->is_param) { + char *s = param->name; + while (*s != '\0') { + *s = toupper(*s); + s++; + } + } + break; + default: + ZVAL_NULL(param->parameter); + } + if (value && caller_frees) { + efree(value); + } + return 1; + } + return 0; + default: + ; + } + return 1; +} +/* }}} */ + +static int firebird_stmt_set_attribute(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + switch (attr) { + default: + return 0; + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + + if (isc_dsql_set_cursor_name(S->H->isc_status, &S->stmt, Z_STRVAL_P(val),0)) { + RECORD_ERROR(stmt); + return 0; + } + strlcpy(S->name, Z_STRVAL_P(val), sizeof(S->name)); + break; + } + return 1; +} +/* }}} */ + +static int firebird_stmt_get_attribute(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + switch (attr) { + default: + return 0; + case PDO_ATTR_CURSOR_NAME: + if (*S->name) { + ZVAL_STRING(val,S->name,1); + } else { + ZVAL_NULL(val); + } + break; + } + return 1; +} +/* }}} */ + +static int firebird_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + /* close the statement handle */ + if ((*S->name || S->cursor_open) && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_close)) { + RECORD_ERROR(stmt); + return 0; + } + *S->name = 0; + S->cursor_open = 0; + return 1; +} +/* }}} */ + + +struct pdo_stmt_methods firebird_stmt_methods = { /* {{{ */ + firebird_stmt_dtor, + firebird_stmt_execute, + firebird_stmt_fetch, + firebird_stmt_describe, + firebird_stmt_get_col, + firebird_stmt_param_hook, + firebird_stmt_set_attribute, + firebird_stmt_get_attribute, + NULL, /* get_column_meta_func */ + NULL, /* next_rowset_func */ + firebird_stmt_cursor_closer +}; +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_firebird/package2.xml b/ext/pdo_firebird/package2.xml new file mode 100644 index 0000000..5b5984c --- /dev/null +++ b/ext/pdo_firebird/package2.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package packagerversion="1.4.2" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 +http://pear.php.net/dtd/tasks-1.0.xsd +http://pear.php.net/dtd/package-2.0 +http://pear.php.net/dtd/package-2.0.xsd"> + <name>PDO_FIREBIRD</name> + <channel>pecl.php.net</channel> + <summary>Firebird/InterBase 6 driver for PDO</summary> + <description>This extension provides a Firebird/InterBase driver for PDO. It supports +all versions of Firebird and InterBase versions 6 and up. + </description> + <lead> + <name>Ard Biesheuvel</name> + <user>abies</user> + <email>abies@php.net</email> + <active>yes</active> + </lead> + <date>2006-05-01</date> + <version> + <release>0.3</release> + <api>0.3</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <license uri="http://www.php.net/license">PHP</license> + <notes>To compile and run this module, you will need to have the main PDO module and Firebird's +or InterBase's client library installed on your system. +Hope it works! + </notes> + <contents> + <dir name="/"> + <dir name="tests"> + <file name="connect.phpt" role="test" /> + <file name="ddl.phpt" role="test" /> + <file name="execute.phpt" role="test" /> + </dir> <!-- //tests --> + <file name="config.m4" role="src" /> + <file name="config.w32" role="src" /> + <file name="CREDITS" role="doc" /> + <file name="firebird_driver.c" role="src" /> + <file name="firebird_statement.c" role="src" /> + <file name="pdo_firebird.c" role="src" /> + <file name="php_pdo_firebird.h" role="src" /> + <file name="php_pdo_firebird_int.h" role="src" /> + </dir> <!-- / --> + </contents> + <dependencies> + <required> + <php> + <min>5.0.3</min> + </php> + <pearinstaller> + <min>1.4.0</min> + </pearinstaller> + <package> + <name>pdo</name> + <channel>pecl.php.net</channel> + <min>1.0.3</min> + <providesextension>PDO</providesextension> + </package> + </required> + </dependencies> + <providesextension>PDO_FIREBIRD</providesextension> + <extsrcrelease> + <configureoption name="with-pdo-firebird" prompt="dir" /> + </extsrcrelease> +</package> diff --git a/ext/pdo_firebird/pdo_firebird.c b/ext/pdo_firebird/pdo_firebird.c new file mode 100644 index 0000000..1f14b55 --- /dev/null +++ b/ext/pdo_firebird/pdo_firebird.c @@ -0,0 +1,106 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel <abies@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_firebird.h" +#include "php_pdo_firebird_int.h" + +const zend_function_entry pdo_firebird_functions[] = { /* {{{ */ + PHP_FE_END +}; +/* }}} */ + +/* {{{ pdo_firebird_deps + */ +#if ZEND_MODULE_API_NO >= 20050922 +static const zend_module_dep pdo_firebird_deps[] = { + ZEND_MOD_REQUIRED("pdo") + ZEND_MOD_END +}; +#endif +/* }}} */ + +zend_module_entry pdo_firebird_module_entry = { /* {{{ */ +#if ZEND_MODULE_API_NO >= 20050922 + STANDARD_MODULE_HEADER_EX, NULL, + pdo_firebird_deps, +#else + STANDARD_MODULE_HEADER, +#endif + "PDO_Firebird", + pdo_firebird_functions, + PHP_MINIT(pdo_firebird), + PHP_MSHUTDOWN(pdo_firebird), + NULL, + NULL, + PHP_MINFO(pdo_firebird), + "0.3", + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_PDO_FIREBIRD +ZEND_GET_MODULE(pdo_firebird) +#endif + +PHP_MINIT_FUNCTION(pdo_firebird) /* {{{ */ +{ + REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_DATE_FORMAT", (long) PDO_FB_ATTR_DATE_FORMAT); + REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIME_FORMAT", (long) PDO_FB_ATTR_TIME_FORMAT); + REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIMESTAMP_FORMAT", (long) PDO_FB_ATTR_TIMESTAMP_FORMAT); + + php_pdo_register_driver(&pdo_firebird_driver); + + return SUCCESS; +} +/* }}} */ + +PHP_MSHUTDOWN_FUNCTION(pdo_firebird) /* {{{ */ +{ + php_pdo_unregister_driver(&pdo_firebird_driver); + + return SUCCESS; +} +/* }}} */ + +PHP_MINFO_FUNCTION(pdo_firebird) /* {{{ */ +{ + php_info_print_table_start(); + php_info_print_table_header(2, "PDO Driver for Firebird/InterBase", "enabled"); + php_info_print_table_end(); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_firebird/php_pdo_firebird.h b/ext/pdo_firebird/php_pdo_firebird.h new file mode 100644 index 0000000..cbece41 --- /dev/null +++ b/ext/pdo_firebird/php_pdo_firebird.h @@ -0,0 +1,46 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel <abies@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef PHP_PDO_FIREBIRD_H +#define PHP_PDO_FIREBIRD_H + +extern zend_module_entry pdo_firebird_module_entry; +#define phpext_pdo_firebird_ptr &pdo_firebird_module_entry + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_MINIT_FUNCTION(pdo_firebird); +PHP_MSHUTDOWN_FUNCTION(pdo_firebird); +PHP_RINIT_FUNCTION(pdo_firebird); +PHP_RSHUTDOWN_FUNCTION(pdo_firebird); +PHP_MINFO_FUNCTION(pdo_firebird); + +#endif /* PHP_PDO_FIREBIRD_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_firebird/php_pdo_firebird_int.h b/ext/pdo_firebird/php_pdo_firebird_int.h new file mode 100644 index 0000000..9632edc --- /dev/null +++ b/ext/pdo_firebird/php_pdo_firebird_int.h @@ -0,0 +1,150 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel <abies@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef PHP_PDO_FIREBIRD_INT_H +#define PHP_PDO_FIREBIRD_INT_H + +#include <ibase.h> + +#ifdef SQLDA_VERSION +#define PDO_FB_SQLDA_VERSION SQLDA_VERSION +#else +#define PDO_FB_SQLDA_VERSION 1 +#endif + +#define PDO_FB_DIALECT 3 + +#define PDO_FB_DEF_DATE_FMT "%Y-%m-%d" +#define PDO_FB_DEF_TIME_FMT "%H:%M:%S" +#define PDO_FB_DEF_TIMESTAMP_FMT PDO_FB_DEF_DATE_FMT " " PDO_FB_DEF_TIME_FMT + +#define SHORT_MAX (1 << (8*sizeof(short)-1)) + +#if SIZEOF_LONG == 8 +# define LL_MASK "l" +# define LL_LIT(lit) lit ## L +#else +# ifdef PHP_WIN32 +# define LL_MASK "I64" +# define LL_LIT(lit) lit ## I64 +# else +# define LL_MASK "ll" +# define LL_LIT(lit) lit ## LL +# endif +#endif + +/* Firebird API has a couple of missing const decls in its API */ +#define const_cast(s) ((char*)(s)) + +#ifdef PHP_WIN32 +typedef void (__stdcall *info_func_t)(char*); +#else +typedef void (*info_func_t)(char*); +#endif + +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif + +typedef struct { + + /* the result of the last API call */ + ISC_STATUS isc_status[20]; + + /* the connection handle */ + isc_db_handle db; + + /* the transaction handle */ + isc_tr_handle tr; + + /* the last error that didn't come from the API */ + char const *last_app_error; + + /* date and time format strings, can be set by the set_attribute method */ + char *date_format; + char *time_format; + char *timestamp_format; + + /* prepend table names on column names in fetch */ + unsigned fetch_table_names:1; + + unsigned _reserved:31; + +} pdo_firebird_db_handle; + + +typedef struct { + + /* the link that owns this statement */ + pdo_firebird_db_handle *H; + + /* the statement handle */ + isc_stmt_handle stmt; + + /* the name of the cursor (if it has one) */ + char name[32]; + + /* the type of statement that was issued */ + char statement_type:8; + + /* whether EOF was reached for this statement */ + unsigned exhausted:1; + + /* successful isc_dsql_execute opens a cursor */ + unsigned cursor_open:1; + + unsigned _reserved:22; + + /* the named params that were converted to ?'s by the driver */ + HashTable *named_params; + + /* allocated space to convert fields values to other types */ + char **fetch_buf; + + /* the input SQLDA */ + XSQLDA *in_sqlda; + + /* the output SQLDA */ + XSQLDA out_sqlda; /* last member */ + +} pdo_firebird_stmt; + +extern pdo_driver_t pdo_firebird_driver; + +extern struct pdo_stmt_methods firebird_stmt_methods; + +void _firebird_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, char const *file, long line TSRMLS_DC); + +enum { + PDO_FB_ATTR_DATE_FORMAT = PDO_ATTR_DRIVER_SPECIFIC, + PDO_FB_ATTR_TIME_FORMAT, + PDO_FB_ATTR_TIMESTAMP_FORMAT, +}; + +#endif /* PHP_PDO_FIREBIRD_INT_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/pdo_firebird/tests/bug_47415.phpt b/ext/pdo_firebird/tests/bug_47415.phpt new file mode 100644 index 0000000..12cd782 --- /dev/null +++ b/ext/pdo_firebird/tests/bug_47415.phpt @@ -0,0 +1,43 @@ +--TEST-- +Bug #47415 PDO_Firebird segfaults when passing lowercased column name to bindColumn() +--SKIPIF-- +<?php extension_loaded("pdo_firebird") or die("skip"); ?> +<?php function_exists("ibase_query") or die("skip"); ?> +--FILE-- +<?php + +require("testdb.inc"); + +$dbh = new PDO("firebird:dbname=$test_base",$user,$password) or die; +$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); +$value = '2'; +@$dbh->exec('DROP TABLE testz'); +$dbh->exec('CREATE TABLE testz (idx int NOT NULL PRIMARY KEY, txt VARCHAR(20))'); +$dbh->exec('INSERT INTO testz VALUES(0, \'String0\')'); +$dbh->commit(); + +$query = "SELECT idx, txt FROM testz ORDER by idx"; +$idx = $txt = 0; +$stmt = $dbh->prepare($query); +$stmt->bindColumn('idx', $idx); +$stmt->bindColumn('txt', $txt); +$stmt->execute(); +$rows = $stmt->fetch(PDO::FETCH_BOUND); +var_dump($stmt->fetch()); +var_dump($stmt->rowCount()); + + +$stmt = $dbh->prepare('DELETE FROM testz'); +$stmt->execute(); + +$dbh->commit(); + +$dbh->exec('DROP TABLE testz'); + +unset($stmt); +unset($dbh); + +?> +--EXPECT-- +bool(false) +int(1) diff --git a/ext/pdo_firebird/tests/bug_48877.phpt b/ext/pdo_firebird/tests/bug_48877.phpt new file mode 100644 index 0000000..cbab84b --- /dev/null +++ b/ext/pdo_firebird/tests/bug_48877.phpt @@ -0,0 +1,43 @@ +--TEST-- +PDO_Firebird: bug 48877 The "bindValue" and "bindParam" do not work for PDO Firebird if we use named parameters (:parameter). +--SKIPIF-- +<?php extension_loaded("pdo_firebird") or die("skip"); ?> +<?php function_exists("ibase_query") or die("skip"); ?> +--FILE-- +<?php + +require("testdb.inc"); + +$dbh = new PDO("firebird:dbname=$test_base",$user,$password) or die; +$value = '2'; +@$dbh->exec('DROP TABLE testz'); +$dbh->exec('CREATE TABLE testz (A integer)'); +$dbh->exec("INSERT INTO testz VALUES ('1')"); +$dbh->exec("INSERT INTO testz VALUES ('2')"); +$dbh->exec("INSERT INTO testz VALUES ('3')"); +$dbh->commit(); + +$query = "SELECT * FROM testz WHERE A = :paramno"; + +$stmt = $dbh->prepare($query); +$stmt->bindParam(':paramno', $value, PDO::PARAM_STR); +$stmt->execute(); +$rows = $stmt->fetch(); +var_dump($stmt->fetch()); +var_dump($stmt->rowCount()); + + +$stmt = $dbh->prepare('DELETE FROM testz'); +$stmt->execute(); + +$dbh->commit(); + +$dbh->exec('DROP TABLE testz'); + +unset($stmt); +unset($dbh); + +?> +--EXPECT-- +bool(false) +int(1) diff --git a/ext/pdo_firebird/tests/bug_53280.phpt b/ext/pdo_firebird/tests/bug_53280.phpt new file mode 100644 index 0000000..3139867 --- /dev/null +++ b/ext/pdo_firebird/tests/bug_53280.phpt @@ -0,0 +1,63 @@ +--TEST-- +PDO_Firebird: bug 53280 segfaults if query column count is less than param count +--SKIPIF-- +<?php extension_loaded("pdo_firebird") or die("skip"); ?> +<?php function_exists("ibase_query") or die("skip"); ?> +--FILE-- +<?php + +require("testdb.inc"); + +$dbh = new PDO("firebird:dbname=$test_base",$user,$password) or die; +$value = '2'; +@$dbh->exec('DROP TABLE testz'); +$dbh->exec('CREATE TABLE testz(A VARCHAR(30), B VARCHAR(30), C VARCHAR(30))'); +$dbh->exec("INSERT INTO testz VALUES ('A', 'B', 'C')"); +$dbh->commit(); + +$stmt1 = "SELECT B FROM testz WHERE A = ? AND B = ?"; +$stmt2 = "SELECT B, C FROM testz WHERE A = ? AND B = ?"; + +$stmth2 = $dbh->prepare($stmt2); +$stmth2->execute(array('A', 'B')); +$rows = $stmth2->fetchAll(); // <------ OK +var_dump($rows); + +$stmth1 = $dbh->prepare($stmt1); +$stmth1->execute(array('A', 'B')); +$rows = $stmth1->fetchAll(); // <------- segfault +var_dump($rows); + +$dbh->commit(); +unset($stmth1); +unset($stmth2); + +$dbh->exec('DROP TABLE testz'); + +unset($stmt); +unset($dbh); + +?> +--EXPECT-- +array(1) { + [0]=> + array(4) { + ["B"]=> + string(1) "B" + [0]=> + string(1) "B" + ["C"]=> + string(1) "C" + [1]=> + string(1) "C" + } +} +array(1) { + [0]=> + array(2) { + ["B"]=> + string(1) "B" + [0]=> + string(1) "B" + } +} diff --git a/ext/pdo_firebird/tests/common.phpt b/ext/pdo_firebird/tests/common.phpt new file mode 100644 index 0000000..00d4b23 --- /dev/null +++ b/ext/pdo_firebird/tests/common.phpt @@ -0,0 +1,27 @@ +--TEST-- +FIREBIRD +--SKIPIF-- +<?php # vim:ft=php +if (!extension_loaded('pdo_firebird')) print 'skip'; ?> +--REDIRECTTEST-- +# magic auto-configuration + +$config = array( + 'TESTS' => 'ext/pdo/tests' +); + + +if (false !== getenv('PDO_FIREBIRD_TEST_DSN')) { + # user set them from their shell + $config['ENV']['PDOTEST_DSN'] = getenv('PDO_FIREBIRD_TEST_DSN'); + $config['ENV']['PDOTEST_USER'] = getenv('PDO_FIREBIRD_TEST_USER'); + $config['ENV']['PDOTEST_PASS'] = getenv('PDO_FIREBIRD_TEST_PASS'); + if (false !== getenv('PDO_FIREBIRD_TEST_ATTR')) { + $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_FIREBIRD_TEST_ATTR'); + } +} else { + $config['ENV']['PDOTEST_DSN'] = 'firebird:dbname=/opt/firebird/test.gdb'; + $config['ENV']['PDOTEST_USER'] = 'SYSDBA'; + $config['ENV']['PDOTEST_PASS'] = 'password'; +} +return $config; diff --git a/ext/pdo_firebird/tests/connect.phpt b/ext/pdo_firebird/tests/connect.phpt new file mode 100644 index 0000000..cbe76e6 --- /dev/null +++ b/ext/pdo_firebird/tests/connect.phpt @@ -0,0 +1,17 @@ +--TEST-- +PDO_Firebird: connect/disconnect +--SKIPIF-- +<?php include("skipif.inc"); ?> +<?php function_exists("ibase_query") or die("skip"); ?> +--FILE-- +<?php /* $Id$ */ + + require("testdb.inc"); + + $db = new PDO("firebird:dbname=$test_base",$user,$password) or die; + unset($db); + echo "done\n"; + +?> +--EXPECT-- +done diff --git a/ext/pdo_firebird/tests/ddl.phpt b/ext/pdo_firebird/tests/ddl.phpt new file mode 100644 index 0000000..9d2a095 --- /dev/null +++ b/ext/pdo_firebird/tests/ddl.phpt @@ -0,0 +1,38 @@ +--TEST-- +PDO_Firebird: DDL/transactions +--SKIPIF-- +<?php include("skipif.inc"); ?> +<?php function_exists("ibase_query") or die("skip"); ?> +--FILE-- +<?php /* $Id$ */ + + require("testdb.inc"); + + $db = new PDO("firebird:dbname=$test_base",$user,$password) or die; + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + $db->exec("CREATE TABLE ddl (id INT NOT NULL PRIMARY KEY, text BLOB SUB_TYPE 1)"); + $db->exec("CREATE GENERATOR gen_ddl_id"); + $db->exec("CREATE TRIGGER ddl_bi FOR ddl BEFORE INSERT AS + BEGIN IF (NEW.id IS NULL) THEN NEW.id=GEN_ID(gen_ddl_id,1); END"); + + $db->setAttribute(PDO::ATTR_AUTOCOMMIT,0); + + $db->beginTransaction(); + var_dump($db->exec("INSERT INTO ddl (text) VALUES ('bla')")); + var_dump($db->exec("UPDATE ddl SET text='blabla'")); + $db->rollback(); + + $db->beginTransaction(); + var_dump($db->exec("DELETE FROM ddl")); + $db->commit(); + + unset($db); + echo "done\n"; + +?> +--EXPECT-- +int(1) +int(1) +int(0) +done diff --git a/ext/pdo_firebird/tests/execute.phpt b/ext/pdo_firebird/tests/execute.phpt new file mode 100644 index 0000000..896347e --- /dev/null +++ b/ext/pdo_firebird/tests/execute.phpt @@ -0,0 +1,68 @@ +--TEST-- +PDO_Firebird: prepare/execute/binding +--SKIPIF-- +<?php include("skipif.inc"); ?> +<?php function_exists("ibase_query") or die("skip"); ?> +--INI-- +ibase.timestampformat=%Y-%m-%d %H:%M:%S +--FILE-- +<?php /* $Id$ */ + + require("testdb.inc"); + + $db = new PDO("firebird:dbname=$test_base",$user,$password) or die; + + var_dump($db->getAttribute(PDO::ATTR_CONNECTION_STATUS)); + + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + $db->exec("CREATE TABLE ddl (id SMALLINT NOT NULL PRIMARY KEY, text VARCHAR(32), + datetime TIMESTAMP DEFAULT '2000-02-12' NOT NULL)"); + $db->exec("INSERT INTO ddl (id,text) VALUES (1,'bla')"); + + $s = $db->prepare("SELECT * FROM ddl WHERE id=? FOR UPDATE"); + + $id = 0; + $s->bindParam(1,$id); + $var = null; + $s->bindColumn("TEXT",$var); + $id = 1; + $s->execute(); + $s->setAttribute(PDO::ATTR_CURSOR_NAME, "c"); + + var_dump($id); + + var_dump($s->fetch()); + + var_dump($var); + + var_dump($db->exec("UPDATE ddl SET id=2 WHERE CURRENT OF c")); + + var_dump($s->fetch()); + + unset($s); + unset($db); + echo "done\n"; + +?> +--EXPECT-- +bool(true) +int(1) +array(6) { + ["ID"]=> + string(1) "1" + [0]=> + string(1) "1" + ["TEXT"]=> + string(3) "bla" + [1]=> + string(3) "bla" + ["DATETIME"]=> + string(19) "2000-02-12 00:00:00" + [2]=> + string(19) "2000-02-12 00:00:00" +} +string(3) "bla" +int(1) +bool(false) +done diff --git a/ext/pdo_firebird/tests/rowCount.phpt b/ext/pdo_firebird/tests/rowCount.phpt new file mode 100644 index 0000000..1a00950 --- /dev/null +++ b/ext/pdo_firebird/tests/rowCount.phpt @@ -0,0 +1,50 @@ +--TEST-- +PDO_Firebird: rowCount +--SKIPIF-- +<?php extension_loaded("pdo_firebird") or die("skip"); ?> +<?php function_exists("ibase_query") or die("skip"); ?> +--FILE-- +<?php /* $Id$ */ + +require("testdb.inc"); + +$dbh = new PDO("firebird:dbname=$test_base",$user,$password) or die; + +@$dbh->exec('DROP TABLE testz'); +$dbh->exec('CREATE TABLE testz (A VARCHAR(10))'); +$dbh->exec("INSERT INTO testz VALUES ('A')"); +$dbh->exec("INSERT INTO testz VALUES ('A')"); +$dbh->exec("INSERT INTO testz VALUES ('B')"); +$dbh->commit(); + +$query = "SELECT * FROM testz WHERE A = ?"; + +$stmt = $dbh->prepare($query); +$stmt->execute(array('A')); +$rows = $stmt->fetch(); +$rows = $stmt->fetch(); +var_dump($stmt->fetch()); +var_dump($stmt->rowCount()); + +$stmt = $dbh->prepare('UPDATE testZ SET A="A" WHERE A != ?'); +$stmt->execute(array('A')); +var_dump($stmt->rowCount()); +$dbh->commit(); + +$stmt = $dbh->prepare('DELETE FROM testz'); +$stmt->execute(); +var_dump($stmt->rowCount()); + +$dbh->commit(); + +$dbh->exec('DROP TABLE testz'); + +unset($stmt); +unset($dbh); + +?> +--EXPECT-- +bool(false) +int(2) +int(1) +int(3) diff --git a/ext/pdo_firebird/tests/skipif.inc b/ext/pdo_firebird/tests/skipif.inc new file mode 100644 index 0000000..5a6cd37 --- /dev/null +++ b/ext/pdo_firebird/tests/skipif.inc @@ -0,0 +1,5 @@ +<?php /* $Id$ */ + +if (!extension_loaded("interbase") || !extension_loaded("pdo_firebird")) print "skip"; + +?> diff --git a/ext/pdo_firebird/tests/testdb.inc b/ext/pdo_firebird/tests/testdb.inc new file mode 100644 index 0000000..f6951a7 --- /dev/null +++ b/ext/pdo_firebird/tests/testdb.inc @@ -0,0 +1,34 @@ +<?php /* $Id$ */ + +$user = 'SYSDBA'; +$password = 'masterkey'; +ini_set('ibase.default_user',$user); +ini_set('ibase.default_password',$password); + +/* we need just the generated name, not the file itself */ +unlink($test_base = tempnam('/tmp',"php_ibase_test")); + +function init_db() +{ + global $test_base, $user, $password; + + $test_db = ibase_query(IBASE_CREATE,sprintf("CREATE DATABASE '%s' USER '%s' PASSWORD '%s'", + $test_base, $user, $password)); + $tr = ibase_trans($test_db); + ibase_query($tr,"create table test1 (i integer, c varchar(100))"); + ibase_commit_ret($tr); + ibase_query($tr,"insert into test1(i, c) values(1, 'test table not created with isql')"); + ibase_commit($tr); + ibase_close($test_db); +} + +function cleanup_db() +{ + global $test_base; + + $r = ibase_connect($test_base); + ibase_drop_db($r); +} + +register_shutdown_function('cleanup_db'); +init_db(); |