No date/time fields yet ...
-Firebird/InterBase PDO module
+Firebird/InterBase driver for PDO
Ard Biesheuvel
ARG_WITH("pdo-firebird", "Firebird support for PDO", "no");
if (PHP_PDO_FIREBIRD != "no") {
- if (CHECK_LIB("fbclient_ms.lib", "pdo_firebird", PHP_PDO_FIREBIRD) &&
+
+ if ((CHECK_LIB("fbclient_ms.lib", "pdo_firebird", PHP_PDO_FIREBIRD) ||
+ CHECK_LIB("gds32_ms.lib", "pdo_firebird", PHP_PDO_FIREBIRD)) &&
CHECK_HEADER_ADD_INCLUDE("ibase.h", "CFLAGS_PDO_FIREBIRD", PHP_PHP_BUILD + "\\include\\firebird;" + PHP_PDO_FIREBIRD)) {
EXTENSION("pdo_firebird", "pdo_firebird.c firebird_driver.c firebird_statement.c");
ADD_FLAG('CFLAGS_PDO_FIREBIRD', "/I ..\\pecl");
{
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
ISC_STATUS *s = H->isc_status;
- char buf[128];
+ char buf[400];
+ long i = 0, l;
add_next_index_long(info, isc_sqlcode(s));
- while (isc_interprete(buf,&s)) {
- add_next_index_string(info, buf, 1);
+ while (l = isc_interprete(&buf[i],&s)) {
+ i += l;
+ strcpy(&buf[i++], " ");
}
+ add_next_index_string(info, buf, 1);
+
return 1;
}
+/* 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)
+{
+ pdo_firebird_db_handle *H = stmt ? ((pdo_firebird_stmt *)stmt->driver_data)->H
+ : (pdo_firebird_db_handle *)dbh->driver_data;
+ long *error_code = stmt ? &stmt->error_code : &dbh->error_code;
+
+ 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;
+ case -607:
+ *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;
+ }
+}
+
+#define RECORD_ERROR(dbh) _firebird_error(dbh, NULL, __FILE__, __LINE__ TSRMLS_CC)
+
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)) {
- /* error */
+ RECORD_ERROR(dbh);
}
} else {
if (isc_rollback_transaction(H->isc_status, &H->tr)) {
- /* error */
+ RECORD_ERROR(dbh);
}
}
}
if (isc_detach_database(H->isc_status, &H->db)) {
- /* error */
+ RECORD_ERROR(dbh);
}
pefree(H, dbh->is_persistent);
num_sqlda.version = PDO_FB_SQLDA_VERSION;
num_sqlda.sqln = 1;
+ /* allocate */
+ if (isc_dsql_allocate_statement(H->isc_status, &H->db, &s)) {
+ RECORD_ERROR(dbh);
+ return -1;
+ }
+
/* prepare the statement */
- if (isc_dsql_prepare(H->isc_status, &H->tr, &s, (short)sql_len, /* sigh */ (char*) sql,
+ if (isc_dsql_prepare(H->isc_status, &H->tr, &s, (short)sql_len, const_cast(sql),
PDO_FB_DIALECT, &num_sqlda)) {
- /* error */
+ RECORD_ERROR(dbh);
break;
}
S = ecalloc(1, sizeof(*S)-sizeof(XSQLDA) + XSQLDA_LENGTH(num_sqlda.sqld));
S->H = H;
S->stmt = s;
+ S->out_sqlda.version = PDO_FB_SQLDA_VERSION;
+ S->out_sqlda.sqln = stmt->column_count = num_sqlda.sqld;
+
+ if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) {
+ RECORD_ERROR(dbh);
+ break;
+ }
- if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->out_sqlda)) {
- /* error */
+ /* allocate the input descriptors */
+ if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &num_sqlda)) {
+ RECORD_ERROR(dbh);
break;
}
- /* TODO what about input params */
+ 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)) {
+ RECORD_ERROR(dbh);
+ break;
+ }
+ }
+
stmt->driver_data = S;
stmt->methods = &firebird_stmt_methods;
} while (0);
if (S) {
+ if (S->in_sqlda) {
+ efree(S->in_sqlda);
+ }
efree(S);
}
static char info_count[] = { isc_info_sql_records };
char result[64];
int ret = 0;
+ XSQLDA in_sqlda, out_sqlda;
+ /* no placeholder in exec() for now */
+ in_sqlda.version = out_sqlda.version = PDO_FB_SQLDA_VERSION;
+ in_sqlda.sqld = out_sqlda.sqld = 0;
+
if (dbh->auto_commit && !dbh->in_txn) {
if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, 0, NULL)) {
- /* error */
+ RECORD_ERROR(dbh);
return -1;
}
dbh->in_txn = 1;
}
+ /* allocate */
+ if (isc_dsql_allocate_statement(H->isc_status, &H->db, &stmt)) {
+ RECORD_ERROR(dbh);
+ return -1;
+ }
+
+ /* Firebird allows SQL statements up to 64k */
+ if (sql_len > SHORT_MAX) {
+ dbh->error_code = PDO_ERR_TRUNCATED;
+ return -1;
+ }
+
/* prepare */
- if (isc_dsql_prepare(H->isc_status, &H->tr, &stmt, 0, (char*) sql, PDO_FB_DIALECT, NULL)) {
- /* error */
+ if (isc_dsql_prepare(H->isc_status, &H->tr, &stmt, (short) sql_len, const_cast(sql),
+ PDO_FB_DIALECT, &out_sqlda)) {
+ RECORD_ERROR(dbh);
return -1;
}
/* execute */
- if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, NULL, NULL)) {
- /* error */
+ if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) {
+ RECORD_ERROR(dbh);
return -1;
}
/* return the number of affected rows */
if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), info_count, sizeof(result),
result)) {
- /* error */
+ RECORD_ERROR(dbh);
return -1;
}
/* commit? */
if (dbh->auto_commit && isc_commit_retaining(H->isc_status, &H->tr)) {
- /* error */
+ RECORD_ERROR(dbh);
}
return ret;
}
+static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, int unquotedlen,
+ char **quoted, int *quotedlen TSRMLS_DC)
+{
+ pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
+ int qcount = 0;
+ char const *c;
+
+ /* Firebird only requires single quotes to be doubled if string lengths are used */
+
+ /* count the number of ' characters */
+ for (c = unquoted; c = strchr(c,'\''); qcount++, c++);
+
+ if (!qcount) {
+ return 0;
+ } else {
+ char const *l, *r;
+ char *c;
+
+ *quotedlen = unquotedlen + qcount;
+ *quoted = c = emalloc(*quotedlen+1);
+
+ /* foreach (chunk that ends in a quote) */
+ for (l = unquoted; r = strchr(l,'\''); l = r+1) {
+
+ /* copy the chunk */
+ strncpy(c, l, r-l);
+ c += (r-l);
+
+ /* add the second quote */
+ *c++ = '\'';
+ }
+
+ /* copy the remainder */
+ strncpy(c, l, *quotedlen-(c-*quoted));
+
+ return 1;
+ }
+}
+
+static int firebird_handle_begin(pdo_dbh_t *dbh TSRMLS_DC)
+{
+ pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
+
+ if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, 0, NULL)) {
+ RECORD_ERROR(dbh);
+ return 0;
+ }
+ return 1;
+}
+
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)) {
- /* error */
+ RECORD_ERROR(dbh);
return 0;
}
return 1;
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
if (isc_rollback_transaction(H->isc_status, &H->tr)) {
- /* error */
+ RECORD_ERROR(dbh);
return 0;
}
return 1;
if (dbh->in_txn) {
/* Assume they want to commit whatever is outstanding */
- if (isc_commit_retaining(H->isc_status, &H->tr)) {
- /* error */
+ if (isc_commit_transaction(H->isc_status, &H->tr)) {
+ RECORD_ERROR(dbh);
return 0;
}
dbh->in_txn = 0;
firebird_handle_closer,
firebird_handle_preparer,
firebird_handle_doer,
- NULL,
- NULL,
+ firebird_handle_quoter,
+ firebird_handle_begin,
firebird_handle_commit,
firebird_handle_rollback,
firebird_handle_set_attribute,
dbh->alloc_own_columns = 0;
dbh->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
dbh->native_case = PDO_CASE_UPPER;
+ dbh->alloc_own_columns = 1;
ret = 1;
#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"
-struct pdo_stmt_methods firebird_stmt_methods = {0,0,0,0,0,0};
-/*
+#define RECORD_ERROR(stmt) _firebird_error(NULL, stmt, __FILE__, __LINE__ TSRMLS_CC)
+
+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);
+ }
+ }
+}
+
+static int firebird_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
+{
+ pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data;
+
+ if (S->in_sqlda) {
+ free_sqlda(S->in_sqlda);
+ efree(S->in_sqlda);
+ }
+
+ free_sqlda(&S->out_sqlda);
+ efree(S);
+
+ return 1;
+}
+
+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;
+
+ /* named cursors should be closed first */
+ if (*S->name && isc_dsql_free_statement(H->isc_status, &S->stmt, DSQL_close)) {
+ RECORD_ERROR(stmt);
+ return 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)) {
+ RECORD_ERROR(stmt);
+ return 0;
+ }
+
+ S->exhausted = 0;
+
+ return 1;
+}
+
+static int firebird_stmt_fetch(pdo_stmt_t *stmt TSRMLS_DC)
+{
+ pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data;
+ pdo_firebird_db_handle *H = S->H;
+
+ switch (S->exhausted) {
+ default:
+ 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;
+ case 1:
+ return 0;
+ }
+ }
+ return 1;
+}
+
+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];
+
+ /* allocate storage for the column */
+ var->sqlind = (void*)emalloc(var->sqllen + 2*sizeof(short));
+ var->sqldata = &((char*)var->sqlind)[sizeof(short)];
+
+ col->precision = -var->sqlscale;
+ col->maxlen = var->sqllen;
+ col->namelen = var->aliasname_length;
+ col->name = estrndup(var->aliasname,var->aliasname_length);
+
+ return 1;
+}
+
+static void set_param_type(enum pdo_param_type *param_type, XSQLVAR const *var)
+{
+ /* set the param type after the field type */
+ switch (var->sqltype & ~1) {
+ case SQL_SHORT:
+ case SQL_LONG:
+ case SQL_INT64:
+ if (var->sqlscale < 0) {
+ case SQL_TEXT:
+ case SQL_VARYING:
+ *param_type = PDO_PARAM_STR;
+ } else {
+ *param_type = PDO_PARAM_INT;
+ }
+ break;
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ *param_type = PDO_PARAM_DBL;
+ break;
+ }
+}
+
+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 = &sqlda->sqlvar[param->paramno];
+
+ if (event_type == PDO_PARAM_EVT_FREE) { /* not used */
+ return 1;
+ }
+
+ if (!sqlda || param->paramno >= sqlda->sqld) {
+ stmt->error_code = PDO_ERR_NOT_FOUND;
+ return 0;
+ }
+
+ switch (event_type) {
+ zval *zparam;
+
+ case PDO_PARAM_EVT_ALLOC:
+
+ if (param->is_param) {
+ /* allocate the parameter */
+ 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;
+ }
+
+ zparam = param->parameter;
+
+ /* check if a NULL should be inserted */
+ switch (Z_TYPE_P(zparam)) {
+ int force_null;
+
+ 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(zparam) == 0);
+ }
+ if (! force_null) break;
+
+ case IS_NULL:
+ /* complain if this field doesn't allow NULL values */
+ if (! (var->sqltype & 1)) {
+ stmt->error_code = PDO_ERR_CONSTRAINT;
+ return 0;
+ }
+ *var->sqlind = -1;
+ return 1;
+ }
+
+ *var->sqlind = 0;
+
+ SEPARATE_ZVAL(&zparam);
+
+ convert_to_string(zparam);
+
+ var->sqltype = SQL_TEXT;
+ var->sqldata = Z_STRVAL_P(zparam);
+ var->sqllen = Z_STRLEN_P(zparam);
+
+ break;
+
+ case PDO_PARAM_EVT_FETCH_POST:
+ /* set the param value according to the fetched row */
+ return SUCCESS == _php_ibase_var_zval(param->parameter, var->sqldata, var->sqltype,
+ var->sqllen, var->sqlscale, 0 TSRMLS_CC);
+
+ }
+
+ return 1;
+}
+
+static int firebird_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned long *len TSRMLS_DC)
+{
+ pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data;
+ XSQLVAR *var = &S->out_sqlda.sqlvar[colno];
+
+ if (*var->sqlind == -1) {
+ /* A NULL value */
+ *ptr = NULL;
+ *len = 0;
+ } else {
+ /* override the column param type */
+ set_param_type(&stmt->columns[colno].param_type,var);
+
+ switch (var->sqltype & ~1) {
+ ISC_INT64 bint;
+
+ 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:
+ *(long*)*ptr = *(short*)var->sqldata;
+ *len = sizeof(short);
+ break;
+ case SQL_LONG:
+ *(long*)*ptr = *(ISC_LONG*)var->sqldata;
+ *len = sizeof(ISC_LONG);
+ break;
+ case SQL_INT64:
+ *len = sizeof(long);
+#if SIZEOF_LONG == 8
+ *ptr = var->sqldata;
+#else
+ bint = *(ISC_INT64*)var->sqldata;
+
+ if (bint >= LONG_MIN && bint <= LONG_MAX) {
+ *(long*)*ptr = (long)bint;
+ }
+#endif
+ break;
+
+ case SQL_DOUBLE:
+ *ptr = var->sqldata;
+ *len = sizeof(double);
+ break;
+
+ case SQL_TYPE_DATE:
+ case SQL_TYPE_TIME:
+ case SQL_TIMESTAMP:
+ ;
+ }
+ }
+ 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) {
+ 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;
+ }
+ strncpy(S->name, Z_STRVAL_P(val), sizeof(S->name));
+ S->name[sizeof(S->name)] = 0;
+ break;
+ }
+ 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_param_hook,
+ firebird_stmt_set_attribute
};
-*/
+
/*
* Local variables:
* tab-width: 4
<?xml version="1.0" encoding="iso-8859-1"?>\r
<!DOCTYPE package SYSTEM "../pear/package.dtd">\r
-<package version="1.0">\r
+<package version="1.0"><!-- $Id: package.xml,v 1.2 2004-06-12 03:09:47 abies Exp $ -->\r
<name>PDO_Firebird</name>\r
<summary>Firebird/InterBase support for PDO</summary>\r
<maintainers>\r
#define phpext_pdo_firebird_ptr &pdo_firebird_module_entry
#ifdef PHP_WIN32
-#define PHP_PDO_FB_API __declspec(dllexport)
+# ifdef PDO_FIREBIRD_EXPORTS
+# define PDO_FB_API __declspec(dllexport)
+# elif defined(COMPILE_DL_PDO_FIREBIRD)
+# define PDO_FB_API __declspec(dllimport)
+# else
+# define PDO_FB_API
+# endif
#else
-#define PHP_PDO_FB_API
+# define PDO_FB_API
#endif
#ifdef ZTS
#define PDO_FB_DIALECT 3
+#define SHORT_MAX (1 << 8*sizeof(short)-1)
-typedef struct {
- const char *file;
- int line;
- long errcode;
- char *errmsg;
-} pdo_firebird_error_info;
-
+/* Firebird API has a couple of missing const decls in its API */
+#define const_cast(s) ((char*)(s))
typedef struct {
/* the statement handle */
isc_stmt_handle stmt;
+ /* the name of the cursor (if it has one) */
+ char name[32];
+
+ /* whether EOF was reached for this statement */
+ unsigned exhausted:1;
+
+ unsigned _reserved:31;
+
+ /* the input SQLDA */
+ XSQLDA *in_sqlda;
+
/* the output SQLDA */
- XSQLDA out_sqlda[1]; /* last member */
+ XSQLDA out_sqlda; /* last member */
} pdo_firebird_stmt;
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);
+
#endif /* PHP_PDO_FIREBIRD_INT_H */
/*
--- /dev/null
+--TEST--
+PDO_Firebird: DDL/transactions
+--SKIPIF--
+<?php include("skipif.inc"); ?>
+--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