summaryrefslogtreecommitdiff
path: root/ext/pdo
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2013-03-14 05:42:27 +0000
committer <>2013-04-03 16:25:08 +0000
commitc4dd7a1a684490673e25aaf4fabec5df138854c4 (patch)
tree4d57c44caae4480efff02b90b9be86f44bf25409 /ext/pdo
downloadphp2-master.tar.gz
Imported from /home/lorry/working-area/delta_php2/php-5.4.13.tar.bz2.HEADphp-5.4.13master
Diffstat (limited to 'ext/pdo')
-rwxr-xr-xext/pdo/CREDITS3
-rw-r--r--ext/pdo/Makefile.frag30
-rwxr-xr-xext/pdo/README56
-rwxr-xr-xext/pdo/TODO92
-rwxr-xr-xext/pdo/config.m469
-rwxr-xr-xext/pdo/config.w3210
-rw-r--r--ext/pdo/package2.xml134
-rw-r--r--ext/pdo/pdo.c433
-rwxr-xr-xext/pdo/pdo.php62
-rw-r--r--ext/pdo/pdo_dbh.c1618
-rw-r--r--ext/pdo/pdo_sql_parser.c853
-rw-r--r--ext/pdo/pdo_sql_parser.re520
-rw-r--r--ext/pdo/pdo_sqlstate.c340
-rw-r--r--ext/pdo/pdo_stmt.c2822
-rw-r--r--ext/pdo/php_pdo.h94
-rw-r--r--ext/pdo/php_pdo_driver.h673
-rw-r--r--ext/pdo/php_pdo_int.h84
-rw-r--r--ext/pdo/tests/bug47769.phpt37
-rw-r--r--ext/pdo/tests/bug61292.phpt36
-rw-r--r--ext/pdo/tests/bug_34630.phpt57
-rw-r--r--ext/pdo/tests/bug_34687.phpt32
-rw-r--r--ext/pdo/tests/bug_35671.phpt45
-rw-r--r--ext/pdo/tests/bug_36428.phpt33
-rw-r--r--ext/pdo/tests/bug_36798.phpt39
-rw-r--r--ext/pdo/tests/bug_38253.phpt53
-rw-r--r--ext/pdo/tests/bug_38394.phpt51
-rw-r--r--ext/pdo/tests/bug_39398.phpt35
-rw-r--r--ext/pdo/tests/bug_39656.phpt51
-rw-r--r--ext/pdo/tests/bug_40285.phpt27
-rw-r--r--ext/pdo/tests/bug_42917.phpt40
-rw-r--r--ext/pdo/tests/bug_43130.phpt40
-rw-r--r--ext/pdo/tests/bug_43139.phpt41
-rw-r--r--ext/pdo/tests/bug_43663.phpt31
-rw-r--r--ext/pdo/tests/bug_44159.phpt43
-rw-r--r--ext/pdo/tests/bug_44173.phpt78
-rw-r--r--ext/pdo/tests/bug_44409.phpt51
-rw-r--r--ext/pdo/tests/bug_44861.phpt106
-rw-r--r--ext/pdo/tests/bug_50458.phpt29
-rw-r--r--ext/pdo/tests/pdo.inc10
-rw-r--r--ext/pdo/tests/pdo_001.phpt50
-rw-r--r--ext/pdo/tests/pdo_002.phpt50
-rw-r--r--ext/pdo/tests/pdo_003.phpt62
-rw-r--r--ext/pdo/tests/pdo_004.phpt50
-rw-r--r--ext/pdo/tests/pdo_005.phpt154
-rw-r--r--ext/pdo/tests/pdo_006.phpt77
-rw-r--r--ext/pdo/tests/pdo_007.phpt65
-rw-r--r--ext/pdo/tests/pdo_008.phpt40
-rw-r--r--ext/pdo/tests/pdo_009.phpt131
-rw-r--r--ext/pdo/tests/pdo_010.phpt119
-rw-r--r--ext/pdo/tests/pdo_011.phpt301
-rw-r--r--ext/pdo/tests/pdo_012.phpt98
-rw-r--r--ext/pdo/tests/pdo_013.phpt96
-rw-r--r--ext/pdo/tests/pdo_014.phpt102
-rw-r--r--ext/pdo/tests/pdo_015.phpt100
-rw-r--r--ext/pdo/tests/pdo_016.phpt204
-rw-r--r--ext/pdo/tests/pdo_016a.phpt204
-rw-r--r--ext/pdo/tests/pdo_017.phpt74
-rw-r--r--ext/pdo/tests/pdo_018.phpt276
-rw-r--r--ext/pdo/tests/pdo_019.phpt71
-rw-r--r--ext/pdo/tests/pdo_020.phpt34
-rw-r--r--ext/pdo/tests/pdo_021.phpt59
-rw-r--r--ext/pdo/tests/pdo_022.phpt62
-rw-r--r--ext/pdo/tests/pdo_023.phpt112
-rw-r--r--ext/pdo/tests/pdo_024.phpt35
-rw-r--r--ext/pdo/tests/pdo_025.phpt113
-rw-r--r--ext/pdo/tests/pdo_026.phpt111
-rw-r--r--ext/pdo/tests/pdo_027.phpt29
-rw-r--r--ext/pdo/tests/pdo_028.phpt45
-rw-r--r--ext/pdo/tests/pdo_029.phpt125
-rw-r--r--ext/pdo/tests/pdo_030.phpt139
-rw-r--r--ext/pdo/tests/pdo_031.phpt63
-rw-r--r--ext/pdo/tests/pdo_032.phpt83
-rw-r--r--ext/pdo/tests/pdo_033.phpt44
-rw-r--r--ext/pdo/tests/pdo_034.phpt62
-rw-r--r--ext/pdo/tests/pdo_035.phpt22
-rw-r--r--ext/pdo/tests/pdo_036.phpt23
-rw-r--r--ext/pdo/tests/pdo_037.phpt19
-rw-r--r--ext/pdo/tests/pdo_test.inc84
-rw-r--r--ext/pdo/tests/pdorow.phpt12
-rw-r--r--ext/pdo/tests/pecl_bug_5217.phpt29
-rw-r--r--ext/pdo/tests/pecl_bug_5772.phpt29
-rw-r--r--ext/pdo/tests/pecl_bug_5809.phpt34
82 files changed, 12550 insertions, 0 deletions
diff --git a/ext/pdo/CREDITS b/ext/pdo/CREDITS
new file mode 100755
index 0000000..5971cdb
--- /dev/null
+++ b/ext/pdo/CREDITS
@@ -0,0 +1,3 @@
+PHP Data Objects
+Wez Furlong, Marcus Boerger, Sterling Hughes, George Schlossnagle, Ilia Alshanetsky
+
diff --git a/ext/pdo/Makefile.frag b/ext/pdo/Makefile.frag
new file mode 100644
index 0000000..98f5c5f
--- /dev/null
+++ b/ext/pdo/Makefile.frag
@@ -0,0 +1,30 @@
+phpincludedir=$(prefix)/include/php
+
+PDO_HEADER_FILES= \
+ php_pdo.h \
+ php_pdo_driver.h
+
+
+$(srcdir)/pdo_sql_parser.c: $(srcdir)/pdo_sql_parser.re
+ (cd $(top_srcdir); $(RE2C) -o ext/pdo/pdo_sql_parser.c ext/pdo/pdo_sql_parser.re)
+
+install-pdo-headers:
+ @echo "Installing PDO headers: $(INSTALL_ROOT)$(phpincludedir)/ext/pdo/"
+ @$(mkinstalldirs) $(INSTALL_ROOT)$(phpincludedir)/ext/pdo
+ @for f in $(PDO_HEADER_FILES); do \
+ if test -f "$(top_srcdir)/$$f"; then \
+ $(INSTALL_DATA) $(top_srcdir)/$$f $(INSTALL_ROOT)$(phpincludedir)/ext/pdo; \
+ elif test -f "$(top_builddir)/$$f"; then \
+ $(INSTALL_DATA) $(top_builddir)/$$f $(INSTALL_ROOT)$(phpincludedir)/ext/pdo; \
+ elif test -f "$(top_srcdir)/ext/pdo/$$f"; then \
+ $(INSTALL_DATA) $(top_srcdir)/ext/pdo/$$f $(INSTALL_ROOT)$(phpincludedir)/ext/pdo; \
+ elif test -f "$(top_builddir)/ext/pdo/$$f"; then \
+ $(INSTALL_DATA) $(top_builddir)/ext/pdo/$$f $(INSTALL_ROOT)$(phpincludedir)/ext/pdo; \
+ else \
+ echo "hmmm"; \
+ fi \
+ done;
+
+# mini hack
+install: $(all_targets) $(install_targets) install-pdo-headers
+
diff --git a/ext/pdo/README b/ext/pdo/README
new file mode 100755
index 0000000..da806b0
--- /dev/null
+++ b/ext/pdo/README
@@ -0,0 +1,56 @@
+$Id$
+
+PHP Data Objects
+================
+
+Concept: Data Access Abstraction
+
+Goals:
+
+1/ Be light-weight
+2/ Provide common API for common database operations
+3/ Be performant
+4/ Keep majority of PHP specific stuff in the PDO core (such as persistent
+ resource management); drivers should only have to worry about getting the
+ data and not about PHP internals.
+
+
+Transactions and autocommit
+===========================
+
+When you create a database handle, you *should* specify the autocommit
+behaviour that you require. PDO will default to autocommit on.
+
+$dbh = new PDO("...", $user, $pass, array(PDO_ATTR_AUTOCOMMIT => true));
+
+When auto-commit is on, the driver will implicitly commit each query as it is
+executed. This works fine for most simple tasks but can be significantly
+slower when you are making a large number of udpates.
+
+$dbh = new PDO("...", $user, $pass, array(PDO_ATTR_AUTOCOMMIT => false));
+
+When auto-commit is off, you must then use $dbh->beginTransaction() to
+initiate a transaction. When your work is done, you then call $dbh->commit()
+or $dbh->rollBack() to persist or abort your changes respectively. Not all
+databases support transactions.
+
+You can change the auto-commit mode at run-time:
+
+$dbh->setAttribute(PDO_ATTR_AUTOCOMMIT, false);
+
+Regardless of the error handling mode set on the database handle, if the
+autocommit mode cannot be changed, an exception will be thrown.
+
+Some drivers will allow you to temporarily disable autocommit if you call
+$dbh->beginTransaction(). When you commit() or rollBack() such a transaction,
+the handle will switch back to autocommit mode again. If the mode could not
+be changed, an exception will be raised, as noted above.
+
+When the database handle is closed or destroyed (or at request end for
+persistent handles), the driver will implicitly rollBack(). It is your
+responsibility to call commit() when you are done making changes and
+autocommit is turned off.
+
+vim:tw=78:et
+
+
diff --git a/ext/pdo/TODO b/ext/pdo/TODO
new file mode 100755
index 0000000..e422bc0
--- /dev/null
+++ b/ext/pdo/TODO
@@ -0,0 +1,92 @@
+$Id$
+
+Roadmap for PDO
+
+Core, version 1.1:
+==================
+
+ - Add PDO::queryParams(), similar to PDO::query(), but accepts
+ an array of parameters as the second argument, pushing the remaining
+ args (which are args to setFetchMode()) up by one.
+
+ - Separate the handle factory call into two phases:
+ - handle creation
+ - connecting
+
+ This would then allow PDO to call setAttribute()
+ for each driver option specified in the constructor.
+ Right now, the handling of driver attributes is a bit sloppy.
+
+ - Add:
+ pdo.max_persistent
+ pdo.persistent_timeout
+ pdo.ping_interval
+
+ with the same meanings as those options from oci8.
+
+ - BLOB/CLOB.
+ Investigate the various APIs to determine if we can
+ transparently map BLOBs and CLOBs as PDO_PARAM_LOB.
+ If the API needs hints from the client side, we need
+ to introduce a PDO_PARAM_CLOB to differentiate between
+ binary and character data.
+
+ - Character set selection.
+ Generalize/standardize this.
+
+ - meta data.
+ Formalize getColumnMeta().
+ Look at retrieving lists of tables and other objects in the db.
+
+ - tracing/logging/debugging
+ Add ini options:
+
+ pdo.trace_file
+ pdo.enable_tracing
+
+ And corresponding attributes, ATTR_TRACE_FILE, ATTR_TRACING_ENABLE,
+ settable at dbh and stmt levels independently. If set at the dbh level,
+ the stmt will inherit its value. If not set explicitly in code, the
+ defaults for the dbh will come from the INI settings.
+
+ ATTR_TRACE_FILE will accept a string or a stream.
+
+ The INI options are useful for administrative tracing/debugging.
+ Trace mode will output very verbose info.
+
+
+General DB API Roundup:
+=========
+ Consider how the following can be implemented in PDO:
+
+ mysqli_change_user(); alters auth credentials on a live connection
+ mysqli_info(); info about rows affected by last query
+ mysqli_master_query(); force query to run on master
+ mysqli_ping(); ping / reconnect
+ mysqli_stat(); one line summary of server status
+
+ oci_password_change()
+
+ Also consider master/slave and/or failover server configuration.
+
+
+Postgres:
+=========
+
+ - Real large object support.
+ - Someone with more pgsql experience can suggest more features
+
+Oracle:
+=======
+
+ - Support for array types and collections.
+
+PDO Session module:
+===================
+
+ - Is it worth writing in C?
+ Probably not.
+
+
+vim:se et ts=2 sw=2 tw=78:
+
diff --git a/ext/pdo/config.m4 b/ext/pdo/config.m4
new file mode 100755
index 0000000..f8515dd
--- /dev/null
+++ b/ext/pdo/config.m4
@@ -0,0 +1,69 @@
+dnl $Id$
+dnl config.m4 for extension pdo
+dnl vim:se ts=2 sw=2 et:
+
+AC_DEFUN([PHP_PDO_PEAR_CHECK],[
+ pdo_running_under_pear=0
+ case `pwd` in
+ /var/tmp/pear-build-*)
+ pdo_running_under_pear=1
+ ;;
+ esac
+
+ if test "$pdo_running_under_pear$PHP_PEAR_VERSION" = "1"; then
+ # we're running in an environment that smells like pear,
+ # and the PHP_PEAR_VERSION env var is not set. That implies
+ # that we're running under a slightly broken pear installer
+ AC_MSG_ERROR([
+PDO requires that you upgrade your PEAR installer tools. Please
+do so now by running:
+
+ % sudo pear upgrade pear
+
+or by manually downloading and installing PEAR version 1.3.5 or higher.
+
+Once you've upgraded, please re-try your PDO install.
+ ])
+ fi
+])
+
+PHP_ARG_ENABLE(pdo, whether to enable PDO support,
+[ --disable-pdo Disable PHP Data Objects support], yes)
+
+if test "$PHP_PDO" != "no"; then
+
+ dnl Make sure $PHP_PDO is 'yes' when it's not 'no' :)
+ PHP_PDO=yes
+
+ PHP_PDO_PEAR_CHECK
+
+ if test "$ext_shared" = "yes" ; then
+ case $host_alias in
+ *darwin*)
+ AC_MSG_ERROR([
+Due to the way that loadable modules work on OSX/Darwin, you need to
+compile the PDO package statically into the PHP core.
+
+Please follow the instructions at: http://netevil.org/node.php?nid=202
+for more detail on this issue.
+ ])
+ ext_shared=no
+ ;;
+ esac
+ fi
+ PHP_NEW_EXTENSION(pdo, pdo.c pdo_dbh.c pdo_stmt.c pdo_sql_parser.c pdo_sqlstate.c, $ext_shared)
+ ifdef([PHP_ADD_EXTENSION_DEP],
+ [
+ PHP_ADD_EXTENSION_DEP(pdo, spl, true)
+ ])
+
+ ifdef([PHP_INSTALL_HEADERS],
+ [
+ dnl Sadly, this is a complete NOP for pecl extensions
+ PHP_INSTALL_HEADERS(ext/pdo, [php_pdo.h php_pdo_driver.h])
+ ])
+
+ dnl so we always include the known-good working hack.
+ PHP_ADD_MAKEFILE_FRAGMENT
+fi
+
diff --git a/ext/pdo/config.w32 b/ext/pdo/config.w32
new file mode 100755
index 0000000..c85f2e5
--- /dev/null
+++ b/ext/pdo/config.w32
@@ -0,0 +1,10 @@
+// $Id$
+// vim:ft=javascript
+
+ARG_ENABLE("pdo", "Enable PHP Data Objects support", "no");
+
+if (PHP_PDO != "no") {
+ EXTENSION('pdo', 'pdo.c pdo_dbh.c pdo_stmt.c pdo_sql_parser.c pdo_sqlstate.c', false /* force static, PHP_PDO_SHARED is broken yet somehow */);
+ ADD_EXTENSION_DEP('pdo', 'spl', true);
+ PHP_INSTALL_HEADERS("ext/pdo", "php_pdo.h php_pdo_driver.h");
+}
diff --git a/ext/pdo/package2.xml b/ext/pdo/package2.xml
new file mode 100644
index 0000000..add5a47
--- /dev/null
+++ b/ext/pdo/package2.xml
@@ -0,0 +1,134 @@
+<?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</name>
+ <channel>pecl.php.net</channel>
+ <summary>PHP Data Objects Interface</summary>
+ <description>PDO provides a uniform data access interface, sporting advanced features such
+as prepared statements and bound parameters. PDO drivers are dynamically
+loadable and may be developed independently from the core, but still accessed
+using the same API.
+Read the documentation at http://www.php.net/pdo for more information.
+ </description>
+ <lead>
+ <name>Wez Furlong</name>
+ <user>wez</user>
+ <email>wez@php.net</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Marcus Boerger</name>
+ <user>helly</user>
+ <email>helly@php.net</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Ilia Alshanetsky</name>
+ <user>iliaa</user>
+ <email>iliaa@php.net</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>George Schlossnagle</name>
+ <user>gschlossnagle</user>
+ <email>george@omniti.com</email>
+ <active>yes</active>
+ </lead>
+ <date>2006-05-01</date>
+ <version>
+ <release>1.0.3</release>
+ <api>1.0.3</api>
+ </version>
+ <stability>
+ <release>stable</release>
+ <api>stable</api>
+ </stability>
+ <license uri="http://www.php.net/license">PHP</license>
+ <notes>
+It is highly recommended that you update to PHP 5.1 or higher before using PDO.
+This PECL release corresponds to the PHP 5.1.3 release.
+
+You need to install a PDO database driver to make use of PDO,
+check http://pecl.php.net/package-search.php?pkg_name=PDO
+for a list of available PDO drivers.
+
+If you are running on Windows, you can find a precompiled binary at:
+http://pecl4win.php.net/ext.php/php_pdo.dll
+
+You can find additional PDO drivers at:
+http://pecl4win.php.net
+
+** Changes **
+- Added PDO::PARAM_EVT_* family of constants. (Sara)
+- Fixed bug #37167 (PDO segfaults when throwing exception from the
+ fetch handler). (Tony)
+- Fixed memory corruption when PDO::FETCH_LAZY mode is being used. (Ilia)
+- Fixed bug #36222 (errorInfo in PDOException is always NULL). (Ilia)
+- Fixed bug #35797 (segfault on PDOStatement::execute() with
+ zend.ze1_compatibility_mode = On). (Tony, Ilia)
+- Fixed bug #35543 (crash when calling non-existing method in extended class). (Tony)
+- Fixed bug #35508 (improved validation of fetch modes). (Tony)
+- Fixed bug #35431 (PDO crashes when using LAZY fetch with fetchAll). (Wez)
+- Fixed bug #35430 (PDO crashes on incorrect FETCH_FUNC use). (Tony)
+
+- Changed PDO_XXX constants to PDO::XXX
+- It is now possible to extend PDO and PDOStatement and override their constructors
+
+- Fixed Bug #35303; PDO::prepare() can cause crashes with invalid parameters
+- Fixed Bug #35135; &quot;new PDOStatement&quot; can cause crashes.
+- Fixed Bug #35293 and PECL Bug #5589; segfault when creating persistent connections
+- Fixed PECL Bug #5010, problem installing headers
+- renamed pdo_drivers() to PDO::getAvailableDrivers()
+- Various fixes when building with SPL
+- PDO::setAttribute(PDO::ATTR_STATEMENT_CLASS) allows you to set your own
+ PDOStatement replacement when extending PDO and PDOStatement
+- Fixed Bug #34687; error information from PDO::query() was not always returned
+- Fixed PECL Bug #5750; uri: DSN was not handled correctly
+- Fixed PECL Bug #5589; segfault when persistent connection attempt fails
+- Fixed Bug #34590; User defined PDOStatement class methods are not callable
+- Fixed Bug #34908; FETCH_INTO segfaults without destination object
+- Fixed PECL Bug #5809; PDOStatement::execute(array(...)) modifies args
+- Fixed PECL Bug #5772; FETCH_FUNC cannot call functions with mixed case names
+
+** Note **
+
+You should uninstall and re-install your individual database drivers whenever
+you upgrade the base PDO package, otherwise you will see an error about PDO API
+numbers when you run your PHP scripts.
+
+ </notes>
+ <contents>
+ <dir name="/">
+ <file name="config.m4" role="src" />
+ <file name="config.w32" role="src" />
+ <file name="CREDITS" role="doc" />
+ <file name="Makefile.frag" role="src" />
+ <file name="pdo.c" role="src" />
+ <file name="pdo.php" role="doc" />
+ <file name="pdo_dbh.c" role="src" />
+ <file name="pdo_sqlstate.c" role="src" />
+ <file name="pdo_sql_parser.c" role="src" />
+ <file name="pdo_sql_parser.re" role="src" />
+ <file name="pdo_stmt.c" role="src" />
+ <file name="php_pdo.h" role="src" />
+ <file name="php_pdo_driver.h" role="src" />
+ <file name="php_pdo_int.h" role="src" />
+ <file name="README" role="doc" />
+ <file name="TODO" role="doc" />
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.0.3</min>
+ </php>
+ <pearinstaller>
+ <min>1.4.0</min>
+ </pearinstaller>
+ </required>
+ </dependencies>
+ <providesextension>PDO</providesextension>
+ <extsrcrelease />
+</package>
diff --git a/ext/pdo/pdo.c b/ext/pdo/pdo.c
new file mode 100644
index 0000000..452c27b
--- /dev/null
+++ b/ext/pdo/pdo.c
@@ -0,0 +1,433 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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: Wez Furlong <wez@php.net> |
+ | Marcus Boerger <helly@php.net> |
+ | Sterling Hughes <sterling@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <ctype.h>
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_pdo.h"
+#include "php_pdo_driver.h"
+#include "php_pdo_int.h"
+#include "zend_exceptions.h"
+
+static zend_class_entry *spl_ce_RuntimeException;
+
+ZEND_DECLARE_MODULE_GLOBALS(pdo)
+static PHP_GINIT_FUNCTION(pdo);
+
+/* True global resources - no need for thread safety here */
+
+/* the registry of PDO drivers */
+HashTable pdo_driver_hash;
+
+/* we use persistent resources for the driver connection stuff */
+static int le_ppdo;
+
+int php_pdo_list_entry(void)
+{
+ return le_ppdo;
+}
+
+/* for exceptional circumstances */
+zend_class_entry *pdo_exception_ce;
+
+PDO_API zend_class_entry *php_pdo_get_dbh_ce(void)
+{
+ return pdo_dbh_ce;
+}
+
+PDO_API zend_class_entry *php_pdo_get_exception(void)
+{
+ return pdo_exception_ce;
+}
+
+PDO_API char *php_pdo_str_tolower_dup(const char *src, int len)
+{
+ char *dest = emalloc(len + 1);
+ zend_str_tolower_copy(dest, src, len);
+ return dest;
+}
+
+PDO_API zend_class_entry *php_pdo_get_exception_base(int root TSRMLS_DC)
+{
+#if can_handle_soft_dependency_on_SPL && defined(HAVE_SPL) && ((PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 1))
+ if (!root) {
+ if (!spl_ce_RuntimeException) {
+ zend_class_entry **pce;
+
+ if (zend_hash_find(CG(class_table), "runtimeexception", sizeof("RuntimeException"), (void **) &pce) == SUCCESS) {
+ spl_ce_RuntimeException = *pce;
+ return *pce;
+ }
+ } else {
+ return spl_ce_RuntimeException;
+ }
+ }
+#endif
+#if (PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION < 2)
+ return zend_exception_get_default();
+#else
+ return zend_exception_get_default(TSRMLS_C);
+#endif
+}
+
+zend_class_entry *pdo_dbh_ce, *pdo_dbstmt_ce, *pdo_row_ce;
+
+/* {{{ proto array pdo_drivers()
+ Return array of available PDO drivers */
+PHP_FUNCTION(pdo_drivers)
+{
+ HashPosition pos;
+ pdo_driver_t **pdriver;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ array_init(return_value);
+
+ zend_hash_internal_pointer_reset_ex(&pdo_driver_hash, &pos);
+ while (SUCCESS == zend_hash_get_current_data_ex(&pdo_driver_hash, (void**)&pdriver, &pos)) {
+ add_next_index_stringl(return_value, (char*)(*pdriver)->driver_name, (*pdriver)->driver_name_len, 1);
+ zend_hash_move_forward_ex(&pdo_driver_hash, &pos);
+ }
+}
+/* }}} */
+
+/* {{{ arginfo */
+ZEND_BEGIN_ARG_INFO(arginfo_pdo_drivers, 0)
+ZEND_END_ARG_INFO()
+/* }}} */
+
+/* {{{ pdo_functions[] */
+const zend_function_entry pdo_functions[] = {
+ PHP_FE(pdo_drivers, arginfo_pdo_drivers)
+ PHP_FE_END
+};
+/* }}} */
+
+/* {{{ pdo_functions[] */
+#if ZEND_MODULE_API_NO >= 20050922
+static const zend_module_dep pdo_deps[] = {
+#ifdef HAVE_SPL
+ ZEND_MOD_REQUIRED("spl")
+#endif
+ ZEND_MOD_END
+};
+#endif
+/* }}} */
+
+/* {{{ pdo_module_entry */
+zend_module_entry pdo_module_entry = {
+#if ZEND_MODULE_API_NO >= 20050922
+ STANDARD_MODULE_HEADER_EX, NULL,
+ pdo_deps,
+#else
+ STANDARD_MODULE_HEADER,
+#endif
+ "PDO",
+ pdo_functions,
+ PHP_MINIT(pdo),
+ PHP_MSHUTDOWN(pdo),
+ NULL,
+ NULL,
+ PHP_MINFO(pdo),
+ "1.0.4dev",
+ PHP_MODULE_GLOBALS(pdo),
+ PHP_GINIT(pdo),
+ NULL,
+ NULL,
+ STANDARD_MODULE_PROPERTIES_EX
+};
+/* }}} */
+
+/* TODO: visit persistent handles: for each persistent statement handle,
+ * remove bound parameter associations */
+
+#ifdef COMPILE_DL_PDO
+ZEND_GET_MODULE(pdo)
+#endif
+
+/* {{{ PHP_GINIT_FUNCTION */
+static PHP_GINIT_FUNCTION(pdo)
+{
+ pdo_globals->global_value = 0;
+}
+/* }}} */
+
+PDO_API int php_pdo_register_driver(pdo_driver_t *driver)
+{
+ if (driver->api_version != PDO_DRIVER_API) {
+ zend_error(E_ERROR, "PDO: driver %s requires PDO API version %ld; this is PDO version %d",
+ driver->driver_name, driver->api_version, PDO_DRIVER_API);
+ return FAILURE;
+ }
+ if (!zend_hash_exists(&module_registry, "pdo", sizeof("pdo"))) {
+ zend_error(E_ERROR, "You MUST load PDO before loading any PDO drivers");
+ return FAILURE; /* NOTREACHED */
+ }
+
+ return zend_hash_add(&pdo_driver_hash, (char*)driver->driver_name, driver->driver_name_len,
+ (void**)&driver, sizeof(pdo_driver_t *), NULL);
+}
+
+PDO_API void php_pdo_unregister_driver(pdo_driver_t *driver)
+{
+ if (!zend_hash_exists(&module_registry, "pdo", sizeof("pdo"))) {
+ return;
+ }
+
+ zend_hash_del(&pdo_driver_hash, (char*)driver->driver_name, driver->driver_name_len);
+}
+
+pdo_driver_t *pdo_find_driver(const char *name, int namelen)
+{
+ pdo_driver_t **driver = NULL;
+
+ zend_hash_find(&pdo_driver_hash, (char*)name, namelen, (void**)&driver);
+
+ return driver ? *driver : NULL;
+}
+
+PDO_API int php_pdo_parse_data_source(const char *data_source,
+ unsigned long data_source_len, struct pdo_data_src_parser *parsed,
+ int nparams)
+{
+ int i, j;
+ int valstart = -1;
+ int semi = -1;
+ int optstart = 0;
+ int nlen;
+ int n_matches = 0;
+ int n_semicolumns = 0;
+
+ i = 0;
+ while (i < data_source_len) {
+ /* looking for NAME= */
+
+ if (data_source[i] == '\0') {
+ break;
+ }
+
+ if (data_source[i] != '=') {
+ ++i;
+ continue;
+ }
+
+ valstart = ++i;
+
+ /* now we're looking for VALUE; or just VALUE<NUL> */
+ semi = -1;
+ n_semicolumns = 0;
+ while (i < data_source_len) {
+ if (data_source[i] == '\0') {
+ semi = i++;
+ break;
+ }
+ if (data_source[i] == ';') {
+ if ((i + 1 >= data_source_len) || data_source[i+1] != ';') {
+ semi = i++;
+ break;
+ } else {
+ n_semicolumns++;
+ i += 2;
+ continue;
+ }
+ }
+ ++i;
+ }
+
+ if (semi == -1) {
+ semi = i;
+ }
+
+ /* find the entry in the array */
+ nlen = valstart - optstart - 1;
+ for (j = 0; j < nparams; j++) {
+ if (0 == strncmp(data_source + optstart, parsed[j].optname, nlen) && parsed[j].optname[nlen] == '\0') {
+ /* got a match */
+ if (parsed[j].freeme) {
+ efree(parsed[j].optval);
+ }
+
+ if (n_semicolumns == 0) {
+ parsed[j].optval = estrndup(data_source + valstart, semi - valstart - n_semicolumns);
+ } else {
+ int vlen = semi - valstart;
+ const char *orig_val = data_source + valstart;
+ char *new_val = (char *) emalloc(vlen - n_semicolumns + 1);
+
+ parsed[j].optval = new_val;
+
+ while (vlen && *orig_val) {
+ *new_val = *orig_val;
+ new_val++;
+
+ if (*orig_val == ';') {
+ orig_val+=2;
+ vlen-=2;
+ } else {
+ orig_val++;
+ vlen--;
+ }
+ }
+ *new_val = '\0';
+ }
+
+ parsed[j].freeme = 1;
+ ++n_matches;
+ break;
+ }
+ }
+
+ while (i < data_source_len && isspace(data_source[i])) {
+ i++;
+ }
+
+ optstart = i;
+ }
+
+ return n_matches;
+}
+
+static const char digit_vec[] = "0123456789";
+PDO_API char *php_pdo_int64_to_str(pdo_int64_t i64 TSRMLS_DC)
+{
+ char buffer[65];
+ char outbuf[65] = "";
+ register char *p;
+ long long_val;
+ char *dst = outbuf;
+
+ if (i64 < 0) {
+ i64 = -i64;
+ *dst++ = '-';
+ }
+
+ if (i64 == 0) {
+ *dst++ = '0';
+ *dst++ = '\0';
+ return estrdup(outbuf);
+ }
+
+ p = &buffer[sizeof(buffer)-1];
+ *p = '\0';
+
+ while ((pdo_uint64_t)i64 > (pdo_uint64_t)LONG_MAX) {
+ pdo_uint64_t quo = (pdo_uint64_t)i64 / (unsigned int)10;
+ unsigned int rem = (unsigned int)(i64 - quo*10U);
+ *--p = digit_vec[rem];
+ i64 = (pdo_int64_t)quo;
+ }
+ long_val = (long)i64;
+ while (long_val != 0) {
+ long quo = long_val / 10;
+ *--p = digit_vec[(unsigned int)(long_val - quo * 10)];
+ long_val = quo;
+ }
+ while ((*dst++ = *p++) != 0)
+ ;
+ *dst = '\0';
+ return estrdup(outbuf);
+}
+
+/* {{{ PHP_MINIT_FUNCTION */
+PHP_MINIT_FUNCTION(pdo)
+{
+ zend_class_entry ce;
+
+ spl_ce_RuntimeException = NULL;
+
+ if (FAILURE == pdo_sqlstate_init_error_table()) {
+ return FAILURE;
+ }
+
+ zend_hash_init(&pdo_driver_hash, 0, NULL, NULL, 1);
+
+ le_ppdo = zend_register_list_destructors_ex(NULL, php_pdo_pdbh_dtor,
+ "PDO persistent database", module_number);
+
+ INIT_CLASS_ENTRY(ce, "PDOException", NULL);
+
+ pdo_exception_ce = zend_register_internal_class_ex(&ce, php_pdo_get_exception_base(0 TSRMLS_CC), NULL TSRMLS_CC);
+
+ zend_declare_property_null(pdo_exception_ce, "errorInfo", sizeof("errorInfo")-1, ZEND_ACC_PUBLIC TSRMLS_CC);
+
+ pdo_dbh_init(TSRMLS_C);
+ pdo_stmt_init(TSRMLS_C);
+
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MSHUTDOWN_FUNCTION */
+PHP_MSHUTDOWN_FUNCTION(pdo)
+{
+ zend_hash_destroy(&pdo_driver_hash);
+ pdo_sqlstate_fini_error_table();
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MINFO_FUNCTION */
+PHP_MINFO_FUNCTION(pdo)
+{
+ HashPosition pos;
+ char *drivers = NULL, *ldrivers = estrdup("");
+ pdo_driver_t **pdriver;
+
+ php_info_print_table_start();
+ php_info_print_table_header(2, "PDO support", "enabled");
+
+ zend_hash_internal_pointer_reset_ex(&pdo_driver_hash, &pos);
+ while (SUCCESS == zend_hash_get_current_data_ex(&pdo_driver_hash, (void**)&pdriver, &pos)) {
+ spprintf(&drivers, 0, "%s, %s", ldrivers, (*pdriver)->driver_name);
+ zend_hash_move_forward_ex(&pdo_driver_hash, &pos);
+ efree(ldrivers);
+ ldrivers = drivers;
+ }
+
+ php_info_print_table_row(2, "PDO drivers", drivers ? drivers+2 : "");
+
+ if (drivers) {
+ efree(drivers);
+ } else {
+ efree(ldrivers);
+ }
+
+ 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/pdo.php b/ext/pdo/pdo.php
new file mode 100755
index 0000000..1008f7b
--- /dev/null
+++ b/ext/pdo/pdo.php
@@ -0,0 +1,62 @@
+<?php
+dl('pdo.so');
+dl('pdo_sqlite.so');
+
+//$x = new PDO("oci:dbname=hostname", 'php', 'php');
+$x = new PDO("sqlite::memory:");
+
+$x->query("create table test(name string, value string)");
+debug_zval_dump($x);
+
+$stmt = $x->prepare("INSERT INTO test (NAME, VALUE) VALUES (:name, :value)");
+
+$stmt->bindParam(":name", $the_name, PDO_PARAM_STR, 32);
+$stmt->bindParam(":value", $the_value, PDO_PARAM_STR, 32);
+
+for ($i = 0; $i < 4; $i++) {
+ $the_name = "foo" . rand();
+ $the_value = "bar" . rand();
+
+ if (!$stmt->execute()) {
+ break;
+ }
+}
+
+$stmt = null;
+
+echo "DEFAULT:\n";
+foreach ($x->query("select NAME, VALUE from test") as $row) {
+ print_r($row);
+}
+
+echo "OBJ:\n";
+
+class Foo {
+ public $NAME = "Don't change me";
+}
+
+$foo = new foo;
+
+foreach ($x->query("select NAME, VALUE from test", PDO_FETCH_COLUMN, 1) as $row) {
+ debug_zval_dump($row);
+}
+
+echo "Done\n";
+exit;
+
+$stmt = $x->prepare("select NAME, VALUE from test where value like ?");
+$the_name = 'bar%';
+$stmt->execute(array($the_name)) or die("failed to execute!");
+$stmt->bindColumn('VALUE', $value);
+
+while ($row = $stmt->fetch()) {
+ echo "name=$row[NAME] value=$row[VALUE]\n";
+ echo "value is $value\n";
+ echo "\n";
+}
+
+echo "Let's try an update\n";
+
+echo "All done\n";
+
+?>
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
new file mode 100644
index 0000000..e6265f5
--- /dev/null
+++ b/ext/pdo/pdo_dbh.c
@@ -0,0 +1,1618 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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: Wez Furlong <wez@php.net> |
+ | Marcus Boerger <helly@php.net> |
+ | Sterling Hughes <sterling@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+/* The PDO Database Handle Class */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_pdo.h"
+#include "php_pdo_driver.h"
+#include "php_pdo_int.h"
+#include "zend_exceptions.h"
+#include "zend_object_handlers.h"
+#include "zend_hash.h"
+
+static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, long attr, zval *value TSRMLS_DC);
+
+void pdo_raise_impl_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *sqlstate, const char *supp TSRMLS_DC) /* {{{ */
+{
+ pdo_error_type *pdo_err = &dbh->error_code;
+ char *message = NULL;
+ const char *msg;
+
+ if (dbh && dbh->error_mode == PDO_ERRMODE_SILENT) {
+#if 0
+ /* BUG: if user is running in silent mode and hits an error at the driver level
+ * when they use the PDO methods to call up the error information, they may
+ * get bogus information */
+ return;
+#endif
+ }
+
+ if (stmt) {
+ pdo_err = &stmt->error_code;
+ }
+
+ strncpy(*pdo_err, sqlstate, 6);
+
+ /* hash sqlstate to error messages */
+ msg = pdo_sqlstate_state_to_description(*pdo_err);
+ if (!msg) {
+ msg = "<<Unknown error>>";
+ }
+
+ if (supp) {
+ spprintf(&message, 0, "SQLSTATE[%s]: %s: %s", *pdo_err, msg, supp);
+ } else {
+ spprintf(&message, 0, "SQLSTATE[%s]: %s", *pdo_err, msg);
+ }
+
+ if (dbh && dbh->error_mode != PDO_ERRMODE_EXCEPTION) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", message);
+ } else {
+ zval *ex, *info;
+ zend_class_entry *def_ex = php_pdo_get_exception_base(1 TSRMLS_CC), *pdo_ex = php_pdo_get_exception();
+
+ MAKE_STD_ZVAL(ex);
+ object_init_ex(ex, pdo_ex);
+
+ zend_update_property_string(def_ex, ex, "message", sizeof("message")-1, message TSRMLS_CC);
+ zend_update_property_string(def_ex, ex, "code", sizeof("code")-1, *pdo_err TSRMLS_CC);
+
+ MAKE_STD_ZVAL(info);
+ array_init(info);
+
+ add_next_index_string(info, *pdo_err, 1);
+ add_next_index_long(info, 0);
+
+ zend_update_property(pdo_ex, ex, "errorInfo", sizeof("errorInfo")-1, info TSRMLS_CC);
+ zval_ptr_dtor(&info);
+
+ zend_throw_exception_object(ex TSRMLS_CC);
+ }
+
+ if (message) {
+ efree(message);
+ }
+}
+/* }}} */
+
+void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
+{
+ pdo_error_type *pdo_err = &dbh->error_code;
+ const char *msg = "<<Unknown>>";
+ char *supp = NULL;
+ long native_code = 0;
+ char *message = NULL;
+ zval *info = NULL;
+
+ if (dbh == NULL || dbh->error_mode == PDO_ERRMODE_SILENT) {
+ return;
+ }
+
+ if (stmt) {
+ pdo_err = &stmt->error_code;
+ }
+
+ /* hash sqlstate to error messages */
+ msg = pdo_sqlstate_state_to_description(*pdo_err);
+ if (!msg) {
+ msg = "<<Unknown error>>";
+ }
+
+ if (dbh->methods->fetch_err) {
+ MAKE_STD_ZVAL(info);
+ array_init(info);
+
+ add_next_index_string(info, *pdo_err, 1);
+
+ if (dbh->methods->fetch_err(dbh, stmt, info TSRMLS_CC)) {
+ zval **item;
+
+ if (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(info), 1, (void**)&item)) {
+ native_code = Z_LVAL_PP(item);
+ }
+
+ if (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(info), 2, (void**)&item)) {
+ supp = estrndup(Z_STRVAL_PP(item), Z_STRLEN_PP(item));
+ }
+ }
+ }
+
+ if (supp) {
+ spprintf(&message, 0, "SQLSTATE[%s]: %s: %ld %s", *pdo_err, msg, native_code, supp);
+ } else {
+ spprintf(&message, 0, "SQLSTATE[%s]: %s", *pdo_err, msg);
+ }
+
+ if (dbh->error_mode == PDO_ERRMODE_WARNING) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", message);
+ } else if (EG(exception) == NULL) {
+ zval *ex;
+ zend_class_entry *def_ex = php_pdo_get_exception_base(1 TSRMLS_CC), *pdo_ex = php_pdo_get_exception();
+
+ MAKE_STD_ZVAL(ex);
+ object_init_ex(ex, pdo_ex);
+
+ zend_update_property_string(def_ex, ex, "message", sizeof("message")-1, message TSRMLS_CC);
+ zend_update_property_string(def_ex, ex, "code", sizeof("code")-1, *pdo_err TSRMLS_CC);
+
+ if (info) {
+ zend_update_property(pdo_ex, ex, "errorInfo", sizeof("errorInfo")-1, info TSRMLS_CC);
+ }
+
+ zend_throw_exception_object(ex TSRMLS_CC);
+ }
+
+ if (info) {
+ zval_ptr_dtor(&info);
+ }
+
+ if (message) {
+ efree(message);
+ }
+
+ if (supp) {
+ efree(supp);
+ }
+}
+/* }}} */
+
+static char *dsn_from_uri(char *uri, char *buf, size_t buflen TSRMLS_DC) /* {{{ */
+{
+ php_stream *stream;
+ char *dsn = NULL;
+
+ stream = php_stream_open_wrapper(uri, "rb", REPORT_ERRORS, NULL);
+ if (stream) {
+ dsn = php_stream_get_line(stream, buf, buflen, NULL);
+ php_stream_close(stream);
+ }
+ return dsn;
+}
+/* }}} */
+
+/* {{{ proto void PDO::__construct(string dsn, string username, string passwd [, array options])
+ */
+static PHP_METHOD(PDO, dbh_constructor)
+{
+ zval *object = getThis();
+ pdo_dbh_t *dbh = NULL;
+ zend_bool is_persistent = FALSE;
+ char *data_source;
+ int data_source_len;
+ char *colon;
+ char *username=NULL, *password=NULL;
+ int usernamelen, passwordlen;
+ pdo_driver_t *driver = NULL;
+ zval *options = NULL;
+ char alt_dsn[512];
+ int call_factory = 1;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s!s!a!", &data_source, &data_source_len,
+ &username, &usernamelen, &password, &passwordlen, &options)) {
+ ZVAL_NULL(object);
+ return;
+ }
+
+ /* parse the data source name */
+ colon = strchr(data_source, ':');
+
+ if (!colon) {
+ /* let's see if this string has a matching dsn in the php.ini */
+ char *ini_dsn = NULL;
+
+ snprintf(alt_dsn, sizeof(alt_dsn), "pdo.dsn.%s", data_source);
+ if (FAILURE == cfg_get_string(alt_dsn, &ini_dsn)) {
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "invalid data source name");
+ zval_dtor(object);
+ ZVAL_NULL(object);
+ return;
+ }
+
+ data_source = ini_dsn;
+ colon = strchr(data_source, ':');
+
+ if (!colon) {
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "invalid data source name (via INI: %s)", alt_dsn);
+ ZVAL_NULL(object);
+ return;
+ }
+ }
+
+ if (!strncmp(data_source, "uri:", sizeof("uri:")-1)) {
+ /* the specified URI holds connection details */
+ data_source = dsn_from_uri(data_source + sizeof("uri:")-1, alt_dsn, sizeof(alt_dsn) TSRMLS_CC);
+ if (!data_source) {
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "invalid data source URI");
+ ZVAL_NULL(object);
+ return;
+ }
+ colon = strchr(data_source, ':');
+ if (!colon) {
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "invalid data source name (via URI)");
+ ZVAL_NULL(object);
+ return;
+ }
+ }
+
+ driver = pdo_find_driver(data_source, colon - data_source);
+
+ if (!driver) {
+ /* NB: don't want to include the data_source in the error message as
+ * it might contain a password */
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "could not find driver");
+ ZVAL_NULL(object);
+ return;
+ }
+
+ dbh = (pdo_dbh_t *) zend_object_store_get_object(object TSRMLS_CC);
+
+ /* is this supposed to be a persistent connection ? */
+ if (options) {
+ zval **v;
+ int plen = 0;
+ char *hashkey = NULL;
+ zend_rsrc_list_entry *le;
+ pdo_dbh_t *pdbh = NULL;
+
+ if (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(options), PDO_ATTR_PERSISTENT, (void**)&v)) {
+ if (Z_TYPE_PP(v) == IS_STRING && !is_numeric_string(Z_STRVAL_PP(v), Z_STRLEN_PP(v), NULL, NULL, 0) && Z_STRLEN_PP(v) > 0) {
+ /* user specified key */
+ plen = spprintf(&hashkey, 0, "PDO:DBH:DSN=%s:%s:%s:%s", data_source,
+ username ? username : "",
+ password ? password : "",
+ Z_STRVAL_PP(v));
+ is_persistent = 1;
+ } else {
+ convert_to_long_ex(v);
+ is_persistent = Z_LVAL_PP(v) ? 1 : 0;
+ plen = spprintf(&hashkey, 0, "PDO:DBH:DSN=%s:%s:%s", data_source,
+ username ? username : "",
+ password ? password : "");
+ }
+ }
+
+ if (is_persistent) {
+ /* let's see if we have one cached.... */
+ if (SUCCESS == zend_hash_find(&EG(persistent_list), hashkey, plen+1, (void*)&le)) {
+ if (Z_TYPE_P(le) == php_pdo_list_entry()) {
+ pdbh = (pdo_dbh_t*)le->ptr;
+
+ /* is the connection still alive ? */
+ if (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh TSRMLS_CC)) {
+ /* nope... need to kill it */
+ pdbh = NULL;
+ }
+ }
+ }
+
+ if (pdbh) {
+ call_factory = 0;
+ } else {
+ /* need a brand new pdbh */
+ pdbh = pecalloc(1, sizeof(*pdbh), 1);
+
+ if (!pdbh) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "out of memory while allocating PDO handle");
+ /* NOTREACHED */
+ }
+
+ pdbh->is_persistent = 1;
+ if (!(pdbh->persistent_id = pemalloc(plen + 1, 1))) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "out of memory while allocating PDO handle");
+ }
+ memcpy((char *)pdbh->persistent_id, hashkey, plen+1);
+ pdbh->persistent_id_len = plen+1;
+ pdbh->refcount = 1;
+ pdbh->std.properties = NULL;
+ }
+ }
+
+ if (pdbh) {
+ /* let's copy the emalloc bits over from the other handle */
+ if (pdbh->std.properties) {
+ zend_hash_destroy(dbh->std.properties);
+ efree(dbh->std.properties);
+ } else {
+ pdbh->std.ce = dbh->std.ce;
+ pdbh->def_stmt_ce = dbh->def_stmt_ce;
+ pdbh->def_stmt_ctor_args = dbh->def_stmt_ctor_args;
+ pdbh->std.properties = dbh->std.properties;
+ pdbh->std.properties_table = dbh->std.properties_table;
+ }
+ /* kill the non-persistent thingamy */
+ efree(dbh);
+ /* switch over to the persistent one */
+ dbh = pdbh;
+ zend_object_store_set_object(object, dbh TSRMLS_CC);
+ dbh->refcount++;
+ }
+
+ if (hashkey) {
+ efree(hashkey);
+ }
+ }
+
+ if (call_factory) {
+ dbh->data_source_len = strlen(colon + 1);
+ dbh->data_source = (const char*)pestrdup(colon + 1, is_persistent);
+ dbh->username = username ? pestrdup(username, is_persistent) : NULL;
+ dbh->password = password ? pestrdup(password, is_persistent) : NULL;
+ dbh->default_fetch_type = PDO_FETCH_BOTH;
+ }
+
+ dbh->auto_commit = pdo_attr_lval(options, PDO_ATTR_AUTOCOMMIT, 1 TSRMLS_CC);
+
+ if (!dbh->data_source || (username && !dbh->username) || (password && !dbh->password)) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "out of memory");
+ }
+
+ if (!call_factory) {
+ /* we got a persistent guy from our cache */
+ goto options;
+ }
+
+ if (driver->db_handle_factory(dbh, options TSRMLS_CC)) {
+ /* all set */
+
+ if (is_persistent) {
+ zend_rsrc_list_entry le;
+
+ /* register in the persistent list etc. */
+ /* we should also need to replace the object store entry,
+ since it was created with emalloc */
+
+ le.type = php_pdo_list_entry();
+ le.ptr = dbh;
+
+ if (FAILURE == zend_hash_update(&EG(persistent_list),
+ (char*)dbh->persistent_id, dbh->persistent_id_len, (void*)&le,
+ sizeof(le), NULL)) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to register persistent entry");
+ }
+ }
+
+ dbh->driver = driver;
+options:
+ if (options) {
+ zval **attr_value;
+ char *str_key;
+ ulong long_key;
+
+ zend_hash_internal_pointer_reset(Z_ARRVAL_P(options));
+ while (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(options), (void**)&attr_value)
+ && HASH_KEY_IS_LONG == zend_hash_get_current_key(Z_ARRVAL_P(options), &str_key, &long_key, 0)) {
+
+ pdo_dbh_attribute_set(dbh, long_key, *attr_value TSRMLS_CC);
+ zend_hash_move_forward(Z_ARRVAL_P(options));
+ }
+ }
+
+ return;
+ }
+
+ /* the connection failed; things will tidy up in free_storage */
+ /* XXX raise exception */
+ ZVAL_NULL(object);
+}
+/* }}} */
+
+static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args TSRMLS_DC) /* {{{ */
+{
+ if (ctor_args) {
+ if (Z_TYPE_P(ctor_args) != IS_ARRAY) {
+ pdo_raise_impl_error(dbh, NULL, "HY000", "constructor arguments must be passed as an array" TSRMLS_CC);
+ return NULL;
+ }
+ if (!dbstmt_ce->constructor) {
+ pdo_raise_impl_error(dbh, NULL, "HY000", "user-supplied statement does not accept constructor arguments" TSRMLS_CC);
+ return NULL;
+ }
+ }
+
+ Z_TYPE_P(object) = IS_OBJECT;
+ object_init_ex(object, dbstmt_ce);
+ Z_SET_REFCOUNT_P(object, 1);
+ Z_SET_ISREF_P(object);
+
+ return object;
+} /* }}} */
+
+static void pdo_stmt_construct(pdo_stmt_t *stmt, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args TSRMLS_DC) /* {{{ */
+{
+ zval *query_string;
+ zval z_key;
+
+ MAKE_STD_ZVAL(query_string);
+ ZVAL_STRINGL(query_string, stmt->query_string, stmt->query_stringlen, 1);
+ ZVAL_STRINGL(&z_key, "queryString", sizeof("queryString")-1, 0);
+ std_object_handlers.write_property(object, &z_key, query_string, 0 TSRMLS_CC);
+ zval_ptr_dtor(&query_string);
+
+ if (dbstmt_ce->constructor) {
+ zend_fcall_info fci;
+ zend_fcall_info_cache fcc;
+ zval *retval;
+
+ fci.size = sizeof(zend_fcall_info);
+ fci.function_table = &dbstmt_ce->function_table;
+ fci.function_name = NULL;
+ fci.object_ptr = object;
+ fci.symbol_table = NULL;
+ fci.retval_ptr_ptr = &retval;
+ if (ctor_args) {
+ HashTable *ht = Z_ARRVAL_P(ctor_args);
+ Bucket *p;
+
+ fci.param_count = 0;
+ fci.params = safe_emalloc(sizeof(zval*), ht->nNumOfElements, 0);
+ p = ht->pListHead;
+ while (p != NULL) {
+ fci.params[fci.param_count++] = (zval**)p->pData;
+ p = p->pListNext;
+ }
+ } else {
+ fci.param_count = 0;
+ fci.params = NULL;
+ }
+ fci.no_separation = 1;
+
+ fcc.initialized = 1;
+ fcc.function_handler = dbstmt_ce->constructor;
+ fcc.calling_scope = EG(scope);
+ fcc.called_scope = Z_OBJCE_P(object);
+ fcc.object_ptr = object;
+
+ if (zend_call_function(&fci, &fcc TSRMLS_CC) == FAILURE) {
+ zval_dtor(object);
+ ZVAL_NULL(object);
+ object = NULL; /* marks failure */
+ } else {
+ zval_ptr_dtor(&retval);
+ }
+
+ if (fci.params) {
+ efree(fci.params);
+ }
+ }
+}
+/* }}} */
+
+/* {{{ proto object PDO::prepare(string statment [, array options])
+ Prepares a statement for execution and returns a statement object */
+static PHP_METHOD(PDO, prepare)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+ pdo_stmt_t *stmt;
+ char *statement;
+ int statement_len;
+ zval *options = NULL, **opt, **item, *ctor_args;
+ zend_class_entry *dbstmt_ce, **pce;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &statement,
+ &statement_len, &options)) {
+ RETURN_FALSE;
+ }
+
+ PDO_DBH_CLEAR_ERR();
+ PDO_CONSTRUCT_CHECK;
+
+ if (ZEND_NUM_ARGS() > 1 && SUCCESS == zend_hash_index_find(Z_ARRVAL_P(options), PDO_ATTR_STATEMENT_CLASS, (void**)&opt)) {
+ if (Z_TYPE_PP(opt) != IS_ARRAY || zend_hash_index_find(Z_ARRVAL_PP(opt), 0, (void**)&item) == FAILURE
+ || Z_TYPE_PP(item) != IS_STRING
+ || zend_lookup_class(Z_STRVAL_PP(item), Z_STRLEN_PP(item), &pce TSRMLS_CC) == FAILURE
+ ) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); "
+ "the classname must be a string specifying an existing class"
+ TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+ }
+ dbstmt_ce = *pce;
+ if (!instanceof_function(dbstmt_ce, pdo_dbstmt_ce TSRMLS_CC)) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "user-supplied statement class must be derived from PDOStatement" TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+ }
+ if (dbstmt_ce->constructor && !(dbstmt_ce->constructor->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED))) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "user-supplied statement class cannot have a public constructor" TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+ }
+ if (zend_hash_index_find(Z_ARRVAL_PP(opt), 1, (void**)&item) == SUCCESS) {
+ if (Z_TYPE_PP(item) != IS_ARRAY) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "PDO::ATTR_STATEMENT_CLASS requires format array(classname, ctor_args); "
+ "ctor_args must be an array"
+ TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+ }
+ ctor_args = *item;
+ } else {
+ ctor_args = NULL;
+ }
+ } else {
+ dbstmt_ce = dbh->def_stmt_ce;
+ ctor_args = dbh->def_stmt_ctor_args;
+ }
+
+ if (!pdo_stmt_instantiate(dbh, return_value, dbstmt_ce, ctor_args TSRMLS_CC)) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "failed to instantiate user-supplied statement class"
+ TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+ }
+ stmt = (pdo_stmt_t*)zend_object_store_get_object(return_value TSRMLS_CC);
+
+ /* unconditionally keep this for later reference */
+ stmt->query_string = estrndup(statement, statement_len);
+ stmt->query_stringlen = statement_len;
+ stmt->default_fetch_type = dbh->default_fetch_type;
+ stmt->dbh = dbh;
+ /* give it a reference to me */
+ zend_objects_store_add_ref(getThis() TSRMLS_CC);
+ php_pdo_dbh_addref(dbh TSRMLS_CC);
+ stmt->database_object_handle = *getThis();
+ /* we haven't created a lazy object yet */
+ ZVAL_NULL(&stmt->lazy_object_ref);
+
+ if (dbh->methods->preparer(dbh, statement, statement_len, stmt, options TSRMLS_CC)) {
+ pdo_stmt_construct(stmt, return_value, dbstmt_ce, ctor_args TSRMLS_CC);
+ return;
+ }
+
+ PDO_HANDLE_DBH_ERR();
+
+ /* kill the object handle for the stmt here */
+ zval_dtor(return_value);
+
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto bool PDO::beginTransaction()
+ Initiates a transaction */
+static PHP_METHOD(PDO, beginTransaction)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+ PDO_CONSTRUCT_CHECK;
+
+ if (dbh->in_txn) {
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "There is already an active transaction");
+ RETURN_FALSE;
+ }
+
+ if (!dbh->methods->begin) {
+ /* TODO: this should be an exception; see the auto-commit mode
+ * comments below */
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "This driver doesn't support transactions");
+ RETURN_FALSE;
+ }
+
+ if (dbh->methods->begin(dbh TSRMLS_CC)) {
+ dbh->in_txn = 1;
+ RETURN_TRUE;
+ }
+
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto bool PDO::commit()
+ Commit a transaction */
+static PHP_METHOD(PDO, commit)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+ PDO_CONSTRUCT_CHECK;
+
+ if (!dbh->in_txn) {
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "There is no active transaction");
+ RETURN_FALSE;
+ }
+
+ if (dbh->methods->commit(dbh TSRMLS_CC)) {
+ dbh->in_txn = 0;
+ RETURN_TRUE;
+ }
+
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto bool PDO::rollBack()
+ roll back a transaction */
+static PHP_METHOD(PDO, rollBack)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+ PDO_CONSTRUCT_CHECK;
+
+ if (!dbh->in_txn) {
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "There is no active transaction");
+ RETURN_FALSE;
+ }
+
+ if (dbh->methods->rollback(dbh TSRMLS_CC)) {
+ dbh->in_txn = 0;
+ RETURN_TRUE;
+ }
+
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto bool PDO::inTransaction()
+ determine if inside a transaction */
+static PHP_METHOD(PDO, inTransaction)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+ PDO_CONSTRUCT_CHECK;
+
+ if (!dbh->methods->in_transaction) {
+ RETURN_BOOL(dbh->in_txn);
+ }
+
+ RETURN_BOOL(dbh->methods->in_transaction(dbh TSRMLS_CC));
+}
+/* }}} */
+
+static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, long attr, zval *value TSRMLS_DC) /* {{{ */
+{
+
+#define PDO_LONG_PARAM_CHECK \
+ if (Z_TYPE_P(value) != IS_LONG && Z_TYPE_P(value) != IS_STRING && Z_TYPE_P(value) != IS_BOOL) { \
+ pdo_raise_impl_error(dbh, NULL, "HY000", "attribute value must be an integer" TSRMLS_CC); \
+ PDO_HANDLE_DBH_ERR(); \
+ return FAILURE; \
+ } \
+
+ switch (attr) {
+ case PDO_ATTR_ERRMODE:
+ PDO_LONG_PARAM_CHECK;
+ convert_to_long(value);
+ switch (Z_LVAL_P(value)) {
+ case PDO_ERRMODE_SILENT:
+ case PDO_ERRMODE_WARNING:
+ case PDO_ERRMODE_EXCEPTION:
+ dbh->error_mode = Z_LVAL_P(value);
+ return SUCCESS;
+ default:
+ pdo_raise_impl_error(dbh, NULL, "HY000", "invalid error mode" TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ return FAILURE;
+ }
+ return FAILURE;
+
+ case PDO_ATTR_CASE:
+ PDO_LONG_PARAM_CHECK;
+ convert_to_long(value);
+ switch (Z_LVAL_P(value)) {
+ case PDO_CASE_NATURAL:
+ case PDO_CASE_UPPER:
+ case PDO_CASE_LOWER:
+ dbh->desired_case = Z_LVAL_P(value);
+ return SUCCESS;
+ default:
+ pdo_raise_impl_error(dbh, NULL, "HY000", "invalid case folding mode" TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ return FAILURE;
+ }
+ return FAILURE;
+
+ case PDO_ATTR_ORACLE_NULLS:
+ PDO_LONG_PARAM_CHECK;
+ convert_to_long(value);
+ dbh->oracle_nulls = Z_LVAL_P(value);
+ return SUCCESS;
+
+ case PDO_ATTR_DEFAULT_FETCH_MODE:
+ if (Z_TYPE_P(value) == IS_ARRAY) {
+ zval **tmp;
+ if (zend_hash_index_find(Z_ARRVAL_P(value), 0, (void**)&tmp) == SUCCESS && Z_TYPE_PP(tmp) == IS_LONG) {
+ if (Z_LVAL_PP(tmp) == PDO_FETCH_INTO || Z_LVAL_PP(tmp) == PDO_FETCH_CLASS) {
+ pdo_raise_impl_error(dbh, NULL, "HY000", "FETCH_INTO and FETCH_CLASS are not yet supported as default fetch modes" TSRMLS_CC);
+ return FAILURE;
+ }
+ }
+ } else {
+ PDO_LONG_PARAM_CHECK;
+ }
+ convert_to_long(value);
+ if (Z_LVAL_P(value) == PDO_FETCH_USE_DEFAULT) {
+ pdo_raise_impl_error(dbh, NULL, "HY000", "invalid fetch mode type" TSRMLS_CC);
+ return FAILURE;
+ }
+ dbh->default_fetch_type = Z_LVAL_P(value);
+ return SUCCESS;
+
+ case PDO_ATTR_STRINGIFY_FETCHES:
+ PDO_LONG_PARAM_CHECK;
+ convert_to_long(value);
+ dbh->stringify = Z_LVAL_P(value) ? 1 : 0;
+ return SUCCESS;
+
+ case PDO_ATTR_STATEMENT_CLASS: {
+ /* array(string classname, array(mixed ctor_args)) */
+ zend_class_entry **pce;
+ zval **item;
+
+ if (dbh->is_persistent) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "PDO::ATTR_STATEMENT_CLASS cannot be used with persistent PDO instances"
+ TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ return FAILURE;
+ }
+ if (Z_TYPE_P(value) != IS_ARRAY
+ || zend_hash_index_find(Z_ARRVAL_P(value), 0, (void**)&item) == FAILURE
+ || Z_TYPE_PP(item) != IS_STRING
+ || zend_lookup_class(Z_STRVAL_PP(item), Z_STRLEN_PP(item), &pce TSRMLS_CC) == FAILURE
+ ) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); "
+ "the classname must be a string specifying an existing class"
+ TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ return FAILURE;
+ }
+ if (!instanceof_function(*pce, pdo_dbstmt_ce TSRMLS_CC)) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "user-supplied statement class must be derived from PDOStatement" TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ return FAILURE;
+ }
+ if ((*pce)->constructor && !((*pce)->constructor->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED))) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "user-supplied statement class cannot have a public constructor" TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ return FAILURE;
+ }
+ dbh->def_stmt_ce = *pce;
+ if (dbh->def_stmt_ctor_args) {
+ zval_ptr_dtor(&dbh->def_stmt_ctor_args);
+ dbh->def_stmt_ctor_args = NULL;
+ }
+ if (zend_hash_index_find(Z_ARRVAL_P(value), 1, (void**)&item) == SUCCESS) {
+ if (Z_TYPE_PP(item) != IS_ARRAY) {
+ pdo_raise_impl_error(dbh, NULL, "HY000",
+ "PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); "
+ "ctor_args must be an array"
+ TSRMLS_CC);
+ PDO_HANDLE_DBH_ERR();
+ return FAILURE;
+ }
+ Z_ADDREF_PP(item);
+ dbh->def_stmt_ctor_args = *item;
+ }
+ return SUCCESS;
+ }
+
+ default:
+ ;
+ }
+
+ if (!dbh->methods->set_attribute) {
+ goto fail;
+ }
+
+ PDO_DBH_CLEAR_ERR();
+ if (dbh->methods->set_attribute(dbh, attr, value TSRMLS_CC)) {
+ return SUCCESS;
+ }
+
+fail:
+ if (attr == PDO_ATTR_AUTOCOMMIT) {
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "The auto-commit mode cannot be changed for this driver");
+ } else if (!dbh->methods->set_attribute) {
+ pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support setting attributes" TSRMLS_CC);
+ } else {
+ PDO_HANDLE_DBH_ERR();
+ }
+ return FAILURE;
+}
+/* }}} */
+
+/* {{{ proto bool PDO::setAttribute(long attribute, mixed value)
+ Set an attribute */
+static PHP_METHOD(PDO, setAttribute)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+ long attr;
+ zval *value;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &attr, &value)) {
+ RETURN_FALSE;
+ }
+
+ PDO_DBH_CLEAR_ERR();
+ PDO_CONSTRUCT_CHECK;
+
+ if (pdo_dbh_attribute_set(dbh, attr, value TSRMLS_CC) != FAILURE) {
+ RETURN_TRUE;
+ }
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto mixed PDO::getAttribute(long attribute)
+ Get an attribute */
+static PHP_METHOD(PDO, getAttribute)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+ long attr;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &attr)) {
+ RETURN_FALSE;
+ }
+
+ PDO_DBH_CLEAR_ERR();
+ PDO_CONSTRUCT_CHECK;
+
+ /* handle generic PDO-level atributes */
+ switch (attr) {
+ case PDO_ATTR_PERSISTENT:
+ RETURN_BOOL(dbh->is_persistent);
+
+ case PDO_ATTR_CASE:
+ RETURN_LONG(dbh->desired_case);
+
+ case PDO_ATTR_ORACLE_NULLS:
+ RETURN_LONG(dbh->oracle_nulls);
+
+ case PDO_ATTR_ERRMODE:
+ RETURN_LONG(dbh->error_mode);
+
+ case PDO_ATTR_DRIVER_NAME:
+ RETURN_STRINGL((char*)dbh->driver->driver_name, dbh->driver->driver_name_len, 1);
+
+ case PDO_ATTR_STATEMENT_CLASS:
+ array_init(return_value);
+ add_next_index_string(return_value, dbh->def_stmt_ce->name, 1);
+ if (dbh->def_stmt_ctor_args) {
+ Z_ADDREF_P(dbh->def_stmt_ctor_args);
+ add_next_index_zval(return_value, dbh->def_stmt_ctor_args);
+ }
+ return;
+ case PDO_ATTR_DEFAULT_FETCH_MODE:
+ RETURN_LONG(dbh->default_fetch_type);
+
+ }
+
+ if (!dbh->methods->get_attribute) {
+ pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support getting attributes" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+
+ switch (dbh->methods->get_attribute(dbh, attr, return_value TSRMLS_CC)) {
+ case -1:
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+
+ case 0:
+ pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support that attribute" TSRMLS_CC);
+ RETURN_FALSE;
+
+ default:
+ return;
+ }
+}
+/* }}} */
+
+/* {{{ proto long PDO::exec(string query)
+ Execute a query that does not return a row set, returning the number of affected rows */
+static PHP_METHOD(PDO, exec)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+ char *statement;
+ int statement_len;
+ long ret;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &statement, &statement_len)) {
+ RETURN_FALSE;
+ }
+
+ if (!statement_len) {
+ pdo_raise_impl_error(dbh, NULL, "HY000", "trying to execute an empty query" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+ PDO_DBH_CLEAR_ERR();
+ PDO_CONSTRUCT_CHECK;
+ ret = dbh->methods->doer(dbh, statement, statement_len TSRMLS_CC);
+ if(ret == -1) {
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+ } else {
+ RETURN_LONG(ret);
+ }
+}
+/* }}} */
+
+
+/* {{{ proto string PDO::lastInsertId([string seqname])
+ Returns the id of the last row that we affected on this connection. Some databases require a sequence or table name to be passed in. Not always meaningful. */
+static PHP_METHOD(PDO, lastInsertId)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+ char *name = NULL;
+ int namelen;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &name, &namelen)) {
+ RETURN_FALSE;
+ }
+
+ PDO_DBH_CLEAR_ERR();
+ PDO_CONSTRUCT_CHECK;
+ if (!dbh->methods->last_id) {
+ pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support lastInsertId()" TSRMLS_CC);
+ RETURN_FALSE;
+ } else {
+ Z_STRVAL_P(return_value) = dbh->methods->last_id(dbh, name, &Z_STRLEN_P(return_value) TSRMLS_CC);
+ if (!Z_STRVAL_P(return_value)) {
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+ } else {
+ Z_TYPE_P(return_value) = IS_STRING;
+ }
+ }
+}
+/* }}} */
+
+/* {{{ proto string PDO::errorCode()
+ Fetch the error code associated with the last operation on the database handle */
+static PHP_METHOD(PDO, errorCode)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+ PDO_CONSTRUCT_CHECK;
+
+ if (dbh->query_stmt) {
+ RETURN_STRING(dbh->query_stmt->error_code, 1);
+ }
+
+ if (dbh->error_code[0] == '\0') {
+ RETURN_NULL();
+ }
+
+ /**
+ * Making sure that we fallback to the default implementation
+ * if the dbh->error_code is not null.
+ */
+ RETURN_STRING(dbh->error_code, 1);
+}
+/* }}} */
+
+/* {{{ proto int PDO::errorInfo()
+ Fetch extended error information associated with the last operation on the database handle */
+static PHP_METHOD(PDO, errorInfo)
+{
+ int error_count;
+ int error_count_diff = 0;
+ int error_expected_count = 3;
+
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ PDO_CONSTRUCT_CHECK;
+
+ array_init(return_value);
+
+ if (dbh->query_stmt) {
+ add_next_index_string(return_value, dbh->query_stmt->error_code, 1);
+ } else {
+ add_next_index_string(return_value, dbh->error_code, 1);
+ }
+
+ if (dbh->methods->fetch_err) {
+ dbh->methods->fetch_err(dbh, dbh->query_stmt, return_value TSRMLS_CC);
+ }
+
+ /**
+ * In order to be consistent, we have to make sure we add the good amount
+ * of nulls depending on the current number of elements. We make a simple
+ * difference and add the needed elements
+ */
+ error_count = zend_hash_num_elements(Z_ARRVAL_P(return_value));
+
+ if (error_expected_count > error_count) {
+ int current_index;
+
+ error_count_diff = error_expected_count - error_count;
+ for (current_index = 0; current_index < error_count_diff; current_index++) {
+ add_next_index_null(return_value);
+ }
+ }
+}
+/* }}} */
+
+/* {{{ proto object PDO::query(string sql [, PDOStatement::setFetchMode() args])
+ Prepare and execute $sql; returns the statement object for iteration */
+static PHP_METHOD(PDO, query)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+ pdo_stmt_t *stmt;
+ char *statement;
+ int statement_len;
+
+ /* Return a meaningful error when no parameters were passed */
+ if (!ZEND_NUM_ARGS()) {
+ zend_parse_parameters(0 TSRMLS_CC, "z|z", NULL, NULL);
+ RETURN_FALSE;
+ }
+
+ if (FAILURE == zend_parse_parameters(1 TSRMLS_CC, "s", &statement,
+ &statement_len)) {
+ RETURN_FALSE;
+ }
+
+ PDO_DBH_CLEAR_ERR();
+ PDO_CONSTRUCT_CHECK;
+
+ if (!pdo_stmt_instantiate(dbh, return_value, dbh->def_stmt_ce, dbh->def_stmt_ctor_args TSRMLS_CC)) {
+ pdo_raise_impl_error(dbh, NULL, "HY000", "failed to instantiate user supplied statement class" TSRMLS_CC);
+ return;
+ }
+ stmt = (pdo_stmt_t*)zend_object_store_get_object(return_value TSRMLS_CC);
+
+ /* unconditionally keep this for later reference */
+ stmt->query_string = estrndup(statement, statement_len);
+ stmt->query_stringlen = statement_len;
+
+ stmt->default_fetch_type = dbh->default_fetch_type;
+ stmt->active_query_string = stmt->query_string;
+ stmt->active_query_stringlen = statement_len;
+ stmt->dbh = dbh;
+ /* give it a reference to me */
+ zend_objects_store_add_ref(getThis() TSRMLS_CC);
+ php_pdo_dbh_addref(dbh TSRMLS_CC);
+ stmt->database_object_handle = *getThis();
+ /* we haven't created a lazy object yet */
+ ZVAL_NULL(&stmt->lazy_object_ref);
+
+ if (dbh->methods->preparer(dbh, statement, statement_len, stmt, NULL TSRMLS_CC)) {
+ PDO_STMT_CLEAR_ERR();
+ if (ZEND_NUM_ARGS() == 1 || SUCCESS == pdo_stmt_setup_fetch_mode(INTERNAL_FUNCTION_PARAM_PASSTHRU, stmt, 1)) {
+
+ /* now execute the statement */
+ PDO_STMT_CLEAR_ERR();
+ if (stmt->methods->executer(stmt TSRMLS_CC)) {
+ int ret = 1;
+ if (!stmt->executed) {
+ if (stmt->dbh->alloc_own_columns) {
+ ret = pdo_stmt_describe_columns(stmt TSRMLS_CC);
+ }
+ stmt->executed = 1;
+ }
+ if (ret) {
+ pdo_stmt_construct(stmt, return_value, dbh->def_stmt_ce, dbh->def_stmt_ctor_args TSRMLS_CC);
+ return;
+ }
+ }
+ }
+ /* something broke */
+ dbh->query_stmt = stmt;
+ dbh->query_stmt_zval = *return_value;
+ PDO_HANDLE_STMT_ERR();
+ } else {
+ PDO_HANDLE_DBH_ERR();
+ zval_dtor(return_value);
+ }
+
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto string PDO::quote(string string [, int paramtype])
+ quotes string for use in a query. The optional paramtype acts as a hint for drivers that have alternate quoting styles. The default value is PDO_PARAM_STR */
+static PHP_METHOD(PDO, quote)
+{
+ pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC);
+ char *str;
+ int str_len;
+ long paramtype = PDO_PARAM_STR;
+ char *qstr;
+ int qlen;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, &paramtype)) {
+ RETURN_FALSE;
+ }
+
+ PDO_DBH_CLEAR_ERR();
+ PDO_CONSTRUCT_CHECK;
+ if (!dbh->methods->quoter) {
+ pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support quoting" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+
+ if (dbh->methods->quoter(dbh, str, str_len, &qstr, &qlen, paramtype TSRMLS_CC)) {
+ RETURN_STRINGL(qstr, qlen, 0);
+ }
+ PDO_HANDLE_DBH_ERR();
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto int PDO::__wakeup()
+ Prevents use of a PDO instance that has been unserialized */
+static PHP_METHOD(PDO, __wakeup)
+{
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "You cannot serialize or unserialize PDO instances");
+}
+/* }}} */
+
+/* {{{ proto int PDO::__sleep()
+ Prevents serialization of a PDO instance */
+static PHP_METHOD(PDO, __sleep)
+{
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "You cannot serialize or unserialize PDO instances");
+}
+/* }}} */
+
+/* {{{ proto array PDO::getAvailableDrivers()
+ Return array of available PDO drivers */
+static PHP_METHOD(PDO, getAvailableDrivers)
+{
+ HashPosition pos;
+ pdo_driver_t **pdriver;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ array_init(return_value);
+
+ zend_hash_internal_pointer_reset_ex(&pdo_driver_hash, &pos);
+ while (SUCCESS == zend_hash_get_current_data_ex(&pdo_driver_hash, (void**)&pdriver, &pos)) {
+ add_next_index_stringl(return_value, (char*)(*pdriver)->driver_name, (*pdriver)->driver_name_len, 1);
+ zend_hash_move_forward_ex(&pdo_driver_hash, &pos);
+ }
+}
+/* }}} */
+
+/* {{{ arginfo */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdo___construct, 0, 0, 3)
+ ZEND_ARG_INFO(0, dsn)
+ ZEND_ARG_INFO(0, username)
+ ZEND_ARG_INFO(0, passwd)
+ ZEND_ARG_INFO(0, options) /* array */
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdo_prepare, 0, 0, 1)
+ ZEND_ARG_INFO(0, statement)
+ ZEND_ARG_INFO(0, options) /* array */
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_pdo_setattribute, 0)
+ ZEND_ARG_INFO(0, attribute)
+ ZEND_ARG_INFO(0, value)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_pdo_getattribute, 0)
+ ZEND_ARG_INFO(0, attribute)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_pdo_exec, 0)
+ ZEND_ARG_INFO(0, query)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdo_lastinsertid, 0, 0, 0)
+ ZEND_ARG_INFO(0, seqname)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdo_quote, 0, 0, 1)
+ ZEND_ARG_INFO(0, string)
+ ZEND_ARG_INFO(0, paramtype)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_pdo__void, 0)
+ZEND_END_ARG_INFO()
+/* }}} */
+
+const zend_function_entry pdo_dbh_functions[] = {
+ ZEND_MALIAS(PDO, __construct, dbh_constructor, arginfo_pdo___construct, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, prepare, arginfo_pdo_prepare, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, beginTransaction, arginfo_pdo__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, commit, arginfo_pdo__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, rollBack, arginfo_pdo__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, inTransaction, arginfo_pdo__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, setAttribute, arginfo_pdo_setattribute, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, exec, arginfo_pdo_exec, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, query, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, lastInsertId, arginfo_pdo_lastinsertid, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, errorCode, arginfo_pdo__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, errorInfo, arginfo_pdo__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, getAttribute, arginfo_pdo_getattribute, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, quote, arginfo_pdo_quote, ZEND_ACC_PUBLIC)
+ PHP_ME(PDO, __wakeup, arginfo_pdo__void, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
+ PHP_ME(PDO, __sleep, arginfo_pdo__void, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
+ PHP_ME(PDO, getAvailableDrivers, arginfo_pdo__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ {NULL, NULL, NULL}
+};
+
+/* {{{ overloaded object handlers for PDO class */
+int pdo_hash_methods(pdo_dbh_t *dbh, int kind TSRMLS_DC)
+{
+ const zend_function_entry *funcs;
+ zend_function func;
+ zend_internal_function *ifunc = (zend_internal_function*)&func;
+ int namelen;
+ char *lc_name;
+
+ if (!dbh || !dbh->methods || !dbh->methods->get_driver_methods) {
+ return 0;
+ }
+ funcs = dbh->methods->get_driver_methods(dbh, kind TSRMLS_CC);
+ if (!funcs) {
+ return 0;
+ }
+
+ if (!(dbh->cls_methods[kind] = pemalloc(sizeof(HashTable), dbh->is_persistent))) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "out of memory while allocating PDO methods.");
+ }
+ zend_hash_init_ex(dbh->cls_methods[kind], 8, NULL, NULL, dbh->is_persistent, 0);
+
+ while (funcs->fname) {
+ ifunc->type = ZEND_INTERNAL_FUNCTION;
+ ifunc->handler = funcs->handler;
+ ifunc->function_name = (char*)funcs->fname;
+ ifunc->scope = dbh->std.ce;
+ ifunc->prototype = NULL;
+ if (funcs->flags) {
+ ifunc->fn_flags = funcs->flags | ZEND_ACC_NEVER_CACHE;
+ } else {
+ ifunc->fn_flags = ZEND_ACC_PUBLIC | ZEND_ACC_NEVER_CACHE;
+ }
+ if (funcs->arg_info) {
+ zend_internal_function_info *info = (zend_internal_function_info*)funcs->arg_info;
+
+ ifunc->arg_info = (zend_arg_info*)funcs->arg_info + 1;
+ ifunc->num_args = funcs->num_args;
+ if (info->required_num_args == -1) {
+ ifunc->required_num_args = funcs->num_args;
+ } else {
+ ifunc->required_num_args = info->required_num_args;
+ }
+ if (info->pass_rest_by_reference) {
+ if (info->pass_rest_by_reference == ZEND_SEND_PREFER_REF) {
+ ifunc->fn_flags |= ZEND_ACC_PASS_REST_PREFER_REF;
+ } else {
+ ifunc->fn_flags |= ZEND_ACC_PASS_REST_BY_REFERENCE;
+ }
+ }
+ if (info->return_reference) {
+ ifunc->fn_flags |= ZEND_ACC_RETURN_REFERENCE;
+ }
+ } else {
+ ifunc->arg_info = NULL;
+ ifunc->num_args = 0;
+ ifunc->required_num_args = 0;
+ }
+ namelen = strlen(funcs->fname);
+ lc_name = emalloc(namelen+1);
+ zend_str_tolower_copy(lc_name, funcs->fname, namelen);
+ zend_hash_add(dbh->cls_methods[kind], lc_name, namelen+1, &func, sizeof(func), NULL);
+ efree(lc_name);
+ funcs++;
+ }
+
+ return 1;
+}
+
+static union _zend_function *dbh_method_get(
+#if PHP_API_VERSION >= 20041225
+ zval **object_pp,
+#else
+ zval *object,
+#endif
+ char *method_name, int method_len, const zend_literal *key TSRMLS_DC)
+{
+ zend_function *fbc = NULL;
+ char *lc_method_name;
+#if PHP_API_VERSION >= 20041225
+ zval *object = *object_pp;
+#endif
+ pdo_dbh_t *dbh = zend_object_store_get_object(object TSRMLS_CC);
+
+ lc_method_name = emalloc(method_len + 1);
+ zend_str_tolower_copy(lc_method_name, method_name, method_len);
+
+ if ((fbc = std_object_handlers.get_method(object_pp, method_name, method_len, key TSRMLS_CC)) == NULL) {
+ /* not a pre-defined method, nor a user-defined method; check
+ * the driver specific methods */
+ if (!dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH]) {
+ if (!pdo_hash_methods(dbh,
+ PDO_DBH_DRIVER_METHOD_KIND_DBH TSRMLS_CC)
+ || !dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH]) {
+ goto out;
+ }
+ }
+
+ if (zend_hash_find(dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH],
+ lc_method_name, method_len+1, (void**)&fbc) == FAILURE) {
+ if (!fbc) {
+ fbc = NULL;
+ }
+ }
+ }
+
+out:
+ efree(lc_method_name);
+ return fbc;
+}
+
+static int dbh_compare(zval *object1, zval *object2 TSRMLS_DC)
+{
+ return -1;
+}
+
+static zend_object_handlers pdo_dbh_object_handlers;
+
+void pdo_dbh_init(TSRMLS_D)
+{
+ zend_class_entry ce;
+
+ INIT_CLASS_ENTRY(ce, "PDO", pdo_dbh_functions);
+ pdo_dbh_ce = zend_register_internal_class(&ce TSRMLS_CC);
+ pdo_dbh_ce->create_object = pdo_dbh_new;
+
+ memcpy(&pdo_dbh_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
+ pdo_dbh_object_handlers.get_method = dbh_method_get;
+ pdo_dbh_object_handlers.compare_objects = dbh_compare;
+
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_BOOL", (long)PDO_PARAM_BOOL);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_NULL", (long)PDO_PARAM_NULL);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_INT", (long)PDO_PARAM_INT);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_STR", (long)PDO_PARAM_STR);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_LOB", (long)PDO_PARAM_LOB);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_STMT", (long)PDO_PARAM_STMT);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_INPUT_OUTPUT", (long)PDO_PARAM_INPUT_OUTPUT);
+
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_ALLOC", (long)PDO_PARAM_EVT_ALLOC);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_FREE", (long)PDO_PARAM_EVT_FREE);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_EXEC_PRE", (long)PDO_PARAM_EVT_EXEC_PRE);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_EXEC_POST", (long)PDO_PARAM_EVT_EXEC_POST);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_FETCH_PRE", (long)PDO_PARAM_EVT_FETCH_PRE);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_FETCH_POST", (long)PDO_PARAM_EVT_FETCH_POST);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_NORMALIZE", (long)PDO_PARAM_EVT_NORMALIZE);
+
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_LAZY", (long)PDO_FETCH_LAZY);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_ASSOC",(long)PDO_FETCH_ASSOC);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_NUM", (long)PDO_FETCH_NUM);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_BOTH", (long)PDO_FETCH_BOTH);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_OBJ", (long)PDO_FETCH_OBJ);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_BOUND",(long)PDO_FETCH_BOUND);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_COLUMN",(long)PDO_FETCH_COLUMN);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_CLASS",(long)PDO_FETCH_CLASS);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_INTO", (long)PDO_FETCH_INTO);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_FUNC", (long)PDO_FETCH_FUNC);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_GROUP",(long)PDO_FETCH_GROUP);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_UNIQUE",(long)PDO_FETCH_UNIQUE);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_KEY_PAIR",(long)PDO_FETCH_KEY_PAIR);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_CLASSTYPE",(long)PDO_FETCH_CLASSTYPE);
+#if PHP_MAJOR_VERSION > 5 || PHP_MINOR_VERSION >= 1
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_SERIALIZE",(long)PDO_FETCH_SERIALIZE);
+#endif
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_PROPS_LATE",(long)PDO_FETCH_PROPS_LATE);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_NAMED",(long)PDO_FETCH_NAMED);
+
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_AUTOCOMMIT", (long)PDO_ATTR_AUTOCOMMIT);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_PREFETCH", (long)PDO_ATTR_PREFETCH);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_TIMEOUT", (long)PDO_ATTR_TIMEOUT);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_ERRMODE", (long)PDO_ATTR_ERRMODE);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_SERVER_VERSION", (long)PDO_ATTR_SERVER_VERSION);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_CLIENT_VERSION", (long)PDO_ATTR_CLIENT_VERSION);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_SERVER_INFO", (long)PDO_ATTR_SERVER_INFO);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_CONNECTION_STATUS", (long)PDO_ATTR_CONNECTION_STATUS);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_CASE", (long)PDO_ATTR_CASE);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_CURSOR_NAME", (long)PDO_ATTR_CURSOR_NAME);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_CURSOR", (long)PDO_ATTR_CURSOR);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_ORACLE_NULLS", (long)PDO_ATTR_ORACLE_NULLS);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_PERSISTENT", (long)PDO_ATTR_PERSISTENT);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_STATEMENT_CLASS", (long)PDO_ATTR_STATEMENT_CLASS);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_FETCH_TABLE_NAMES", (long)PDO_ATTR_FETCH_TABLE_NAMES);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_FETCH_CATALOG_NAMES", (long)PDO_ATTR_FETCH_CATALOG_NAMES);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_DRIVER_NAME", (long)PDO_ATTR_DRIVER_NAME);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_STRINGIFY_FETCHES",(long)PDO_ATTR_STRINGIFY_FETCHES);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_MAX_COLUMN_LEN",(long)PDO_ATTR_MAX_COLUMN_LEN);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_EMULATE_PREPARES",(long)PDO_ATTR_EMULATE_PREPARES);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_DEFAULT_FETCH_MODE",(long)PDO_ATTR_DEFAULT_FETCH_MODE);
+
+ REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_SILENT", (long)PDO_ERRMODE_SILENT);
+ REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_WARNING", (long)PDO_ERRMODE_WARNING);
+ REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_EXCEPTION", (long)PDO_ERRMODE_EXCEPTION);
+
+ REGISTER_PDO_CLASS_CONST_LONG("CASE_NATURAL", (long)PDO_CASE_NATURAL);
+ REGISTER_PDO_CLASS_CONST_LONG("CASE_LOWER", (long)PDO_CASE_LOWER);
+ REGISTER_PDO_CLASS_CONST_LONG("CASE_UPPER", (long)PDO_CASE_UPPER);
+
+ REGISTER_PDO_CLASS_CONST_LONG("NULL_NATURAL", (long)PDO_NULL_NATURAL);
+ REGISTER_PDO_CLASS_CONST_LONG("NULL_EMPTY_STRING", (long)PDO_NULL_EMPTY_STRING);
+ REGISTER_PDO_CLASS_CONST_LONG("NULL_TO_STRING", (long)PDO_NULL_TO_STRING);
+
+ REGISTER_PDO_CLASS_CONST_STRING("ERR_NONE", PDO_ERR_NONE);
+
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_ORI_NEXT", (long)PDO_FETCH_ORI_NEXT);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_ORI_PRIOR", (long)PDO_FETCH_ORI_PRIOR);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_ORI_FIRST", (long)PDO_FETCH_ORI_FIRST);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_ORI_LAST", (long)PDO_FETCH_ORI_LAST);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_ORI_ABS", (long)PDO_FETCH_ORI_ABS);
+ REGISTER_PDO_CLASS_CONST_LONG("FETCH_ORI_REL", (long)PDO_FETCH_ORI_REL);
+
+ REGISTER_PDO_CLASS_CONST_LONG("CURSOR_FWDONLY", (long)PDO_CURSOR_FWDONLY);
+ REGISTER_PDO_CLASS_CONST_LONG("CURSOR_SCROLL", (long)PDO_CURSOR_SCROLL);
+
+#if 0
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_CANT_MAP", (long)PDO_ERR_CANT_MAP);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_SYNTAX", (long)PDO_ERR_SYNTAX);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_CONSTRAINT", (long)PDO_ERR_CONSTRAINT);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_NOT_FOUND", (long)PDO_ERR_NOT_FOUND);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_ALREADY_EXISTS", (long)PDO_ERR_ALREADY_EXISTS);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_NOT_IMPLEMENTED", (long)PDO_ERR_NOT_IMPLEMENTED);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_MISMATCH", (long)PDO_ERR_MISMATCH);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_TRUNCATED", (long)PDO_ERR_TRUNCATED);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_DISCONNECTED", (long)PDO_ERR_DISCONNECTED);
+ REGISTER_PDO_CLASS_CONST_LONG("ERR_NO_PERM", (long)PDO_ERR_NO_PERM);
+#endif
+
+}
+
+static void dbh_free(pdo_dbh_t *dbh TSRMLS_DC)
+{
+ int i;
+
+ if (--dbh->refcount)
+ return;
+
+ if (dbh->query_stmt) {
+ zval_dtor(&dbh->query_stmt_zval);
+ dbh->query_stmt = NULL;
+ }
+
+ if (dbh->methods) {
+ dbh->methods->closer(dbh TSRMLS_CC);
+ }
+
+ if (dbh->data_source) {
+ pefree((char *)dbh->data_source, dbh->is_persistent);
+ }
+ if (dbh->username) {
+ pefree(dbh->username, dbh->is_persistent);
+ }
+ if (dbh->password) {
+ pefree(dbh->password, dbh->is_persistent);
+ }
+
+ if (dbh->persistent_id) {
+ pefree((char *)dbh->persistent_id, dbh->is_persistent);
+ }
+
+ if (dbh->def_stmt_ctor_args) {
+ zval_ptr_dtor(&dbh->def_stmt_ctor_args);
+ }
+
+ for (i = 0; i < PDO_DBH_DRIVER_METHOD_KIND__MAX; i++) {
+ if (dbh->cls_methods[i]) {
+ zend_hash_destroy(dbh->cls_methods[i]);
+ pefree(dbh->cls_methods[i], dbh->is_persistent);
+ }
+ }
+
+ pefree(dbh, dbh->is_persistent);
+}
+
+PDO_API void php_pdo_dbh_addref(pdo_dbh_t *dbh TSRMLS_DC)
+{
+ dbh->refcount++;
+}
+
+PDO_API void php_pdo_dbh_delref(pdo_dbh_t *dbh TSRMLS_DC)
+{
+ dbh_free(dbh TSRMLS_CC);
+}
+
+static void pdo_dbh_free_storage(pdo_dbh_t *dbh TSRMLS_DC)
+{
+ if (dbh->in_txn && dbh->methods && dbh->methods->rollback) {
+ dbh->methods->rollback(dbh TSRMLS_CC);
+ dbh->in_txn = 0;
+ }
+
+ if (dbh->is_persistent && dbh->methods && dbh->methods->persistent_shutdown) {
+ dbh->methods->persistent_shutdown(dbh TSRMLS_CC);
+ }
+ zend_object_std_dtor(&dbh->std TSRMLS_CC);
+ dbh->std.properties = NULL;
+ dbh_free(dbh TSRMLS_CC);
+}
+
+zend_object_value pdo_dbh_new(zend_class_entry *ce TSRMLS_DC)
+{
+ zend_object_value retval;
+ pdo_dbh_t *dbh;
+
+ dbh = emalloc(sizeof(*dbh));
+ memset(dbh, 0, sizeof(*dbh));
+ zend_object_std_init(&dbh->std, ce TSRMLS_CC);
+ object_properties_init(&dbh->std, ce);
+ rebuild_object_properties(&dbh->std);
+ dbh->refcount = 1;
+ dbh->def_stmt_ce = pdo_dbstmt_ce;
+
+ retval.handle = zend_objects_store_put(dbh, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)pdo_dbh_free_storage, NULL TSRMLS_CC);
+ retval.handlers = &pdo_dbh_object_handlers;
+
+ return retval;
+}
+
+/* }}} */
+
+ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor)
+{
+ if (rsrc->ptr) {
+ pdo_dbh_t *dbh = (pdo_dbh_t*)rsrc->ptr;
+ dbh_free(dbh TSRMLS_CC);
+ rsrc->ptr = NULL;
+ }
+}
+
+/*
+ * 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/pdo_sql_parser.c b/ext/pdo/pdo_sql_parser.c
new file mode 100644
index 0000000..6aee275
--- /dev/null
+++ b/ext/pdo/pdo_sql_parser.c
@@ -0,0 +1,853 @@
+/* Generated by re2c 0.13.5 on Thu Apr 19 12:42:11 2012 */
+/*
+ +----------------------------------------------------------------------+
+ | 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: George Schlossnagle <george@omniti.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_pdo_driver.h"
+#include "php_pdo_int.h"
+
+#define PDO_PARSER_TEXT 1
+#define PDO_PARSER_BIND 2
+#define PDO_PARSER_BIND_POS 3
+#define PDO_PARSER_EOI 4
+
+#define RET(i) {s->cur = cursor; return i; }
+#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
+
+#define YYCTYPE unsigned char
+#define YYCURSOR cursor
+#define YYLIMIT s->end
+#define YYMARKER s->ptr
+#define YYFILL(n) { RET(PDO_PARSER_EOI); }
+
+typedef struct Scanner {
+ char *ptr, *cur, *tok, *end;
+} Scanner;
+
+static int scan(Scanner *s)
+{
+ char *cursor = s->cur;
+
+ s->tok = cursor;
+
+
+
+{
+ YYCTYPE yych;
+ unsigned int yyaccept = 0;
+
+ if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case 0x00: goto yy2;
+ case '"': goto yy3;
+ case '\'': goto yy5;
+ case '-': goto yy11;
+ case '/': goto yy9;
+ case ':': goto yy6;
+ case '?': goto yy7;
+ default: goto yy12;
+ }
+yy2:
+ YYCURSOR = YYMARKER;
+ switch (yyaccept) {
+ case 0: goto yy4;
+ case 1: goto yy10;
+ }
+yy3:
+ yyaccept = 0;
+ yych = *(YYMARKER = ++YYCURSOR);
+ if (yych >= 0x01) goto yy41;
+yy4:
+ { SKIP_ONE(PDO_PARSER_TEXT); }
+yy5:
+ yyaccept = 0;
+ yych = *(YYMARKER = ++YYCURSOR);
+ if (yych <= 0x00) goto yy4;
+ goto yy36;
+yy6:
+ yych = *++YYCURSOR;
+ switch (yych) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case '_':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z': goto yy32;
+ case ':':
+ case '?': goto yy29;
+ default: goto yy4;
+ }
+yy7:
+ ++YYCURSOR;
+ switch ((yych = *YYCURSOR)) {
+ case ':':
+ case '?': goto yy29;
+ default: goto yy8;
+ }
+yy8:
+ { RET(PDO_PARSER_BIND_POS); }
+yy9:
+ ++YYCURSOR;
+ switch ((yych = *YYCURSOR)) {
+ case '*': goto yy19;
+ default: goto yy13;
+ }
+yy10:
+ { RET(PDO_PARSER_TEXT); }
+yy11:
+ yych = *++YYCURSOR;
+ switch (yych) {
+ case '-': goto yy14;
+ default: goto yy13;
+ }
+yy12:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+yy13:
+ switch (yych) {
+ case 0x00:
+ case '"':
+ case '\'':
+ case ':':
+ case '?': goto yy10;
+ default: goto yy12;
+ }
+yy14:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case 0x00:
+ case '"':
+ case '\'':
+ case ':':
+ case '?': goto yy17;
+ case '\n':
+ case '\r': goto yy12;
+ default: goto yy14;
+ }
+yy16:
+ { RET(PDO_PARSER_TEXT); }
+yy17:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case '\n':
+ case '\r': goto yy16;
+ default: goto yy17;
+ }
+yy19:
+ yyaccept = 1;
+ YYMARKER = ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case 0x00:
+ case '"':
+ case '\'':
+ case ':':
+ case '?': goto yy21;
+ case '*': goto yy23;
+ default: goto yy19;
+ }
+yy21:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case '*': goto yy26;
+ default: goto yy21;
+ }
+yy23:
+ yyaccept = 1;
+ YYMARKER = ++YYCURSOR;
+ if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case 0x00:
+ case '"':
+ case '\'':
+ case ':':
+ case '?': goto yy21;
+ case '*': goto yy23;
+ case '/': goto yy25;
+ default: goto yy19;
+ }
+yy25:
+ yych = *++YYCURSOR;
+ switch (yych) {
+ case 0x00:
+ case '"':
+ case '\'':
+ case ':':
+ case '?': goto yy16;
+ default: goto yy12;
+ }
+yy26:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case '*': goto yy26;
+ case '/': goto yy28;
+ default: goto yy21;
+ }
+yy28:
+ yych = *++YYCURSOR;
+ goto yy16;
+yy29:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case ':':
+ case '?': goto yy29;
+ default: goto yy31;
+ }
+yy31:
+ { RET(PDO_PARSER_TEXT); }
+yy32:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ switch (yych) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case '_':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z': goto yy32;
+ default: goto yy34;
+ }
+yy34:
+ { RET(PDO_PARSER_BIND); }
+yy35:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+yy36:
+ switch (yych) {
+ case 0x00: goto yy2;
+ case '\'': goto yy38;
+ case '\\': goto yy37;
+ default: goto yy35;
+ }
+yy37:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ if (yych <= 0x00) goto yy2;
+ goto yy35;
+yy38:
+ ++YYCURSOR;
+ { RET(PDO_PARSER_TEXT); }
+yy40:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+yy41:
+ switch (yych) {
+ case 0x00: goto yy2;
+ case '"': goto yy43;
+ case '\\': goto yy42;
+ default: goto yy40;
+ }
+yy42:
+ ++YYCURSOR;
+ if (YYLIMIT <= YYCURSOR) YYFILL(1);
+ yych = *YYCURSOR;
+ if (yych <= 0x00) goto yy2;
+ goto yy40;
+yy43:
+ ++YYCURSOR;
+ { RET(PDO_PARSER_TEXT); }
+}
+
+}
+
+struct placeholder {
+ char *pos;
+ int len;
+ int bindno;
+ int qlen; /* quoted length of value */
+ char *quoted; /* quoted value */
+ int freeq;
+ struct placeholder *next;
+};
+
+PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len,
+ char **outquery, int *outquery_len TSRMLS_DC)
+{
+ Scanner s;
+ char *ptr, *newbuffer;
+ int t;
+ int bindno = 0;
+ int ret = 0;
+ int newbuffer_len;
+ HashTable *params;
+ struct pdo_bound_param_data *param;
+ int query_type = PDO_PLACEHOLDER_NONE;
+ struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
+
+ ptr = *outquery;
+ s.cur = inquery;
+ s.end = inquery + inquery_len + 1;
+
+ /* phase 1: look for args */
+ while((t = scan(&s)) != PDO_PARSER_EOI) {
+ if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
+ if (t == PDO_PARSER_BIND) {
+ int len = s.cur - s.tok;
+ if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
+ continue;
+ }
+ query_type |= PDO_PLACEHOLDER_NAMED;
+ } else {
+ query_type |= PDO_PLACEHOLDER_POSITIONAL;
+ }
+
+ plc = emalloc(sizeof(*plc));
+ memset(plc, 0, sizeof(*plc));
+ plc->next = NULL;
+ plc->pos = s.tok;
+ plc->len = s.cur - s.tok;
+ plc->bindno = bindno++;
+
+ if (placetail) {
+ placetail->next = plc;
+ } else {
+ placeholders = plc;
+ }
+ placetail = plc;
+ }
+ }
+
+ if (bindno == 0) {
+ /* nothing to do; good! */
+ return 0;
+ }
+
+ /* did the query make sense to me? */
+ if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
+ /* they mixed both types; punt */
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters" TSRMLS_CC);
+ ret = -1;
+ goto clean_up;
+ }
+
+ if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
+ /* query matches native syntax */
+ ret = 0;
+ goto clean_up;
+ }
+
+ if (stmt->named_rewrite_template) {
+ /* magic/hack.
+ * We we pretend that the query was positional even if
+ * it was named so that we fall into the
+ * named rewrite case below. Not too pretty,
+ * but it works. */
+ query_type = PDO_PLACEHOLDER_POSITIONAL;
+ }
+
+ params = stmt->bound_params;
+
+ /* Do we have placeholders but no bound params */
+ if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound" TSRMLS_CC);
+ ret = -1;
+ goto clean_up;
+ }
+
+ if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
+ /* extra bit of validation for instances when same params are bound more then once */
+ if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
+ int ok = 1;
+ for (plc = placeholders; plc; plc = plc->next) {
+ if (zend_hash_find(params, plc->pos, plc->len, (void**) &param) == FAILURE) {
+ ok = 0;
+ break;
+ }
+ }
+ if (ok) {
+ goto safe;
+ }
+ }
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens" TSRMLS_CC);
+ ret = -1;
+ goto clean_up;
+ }
+safe:
+ /* what are we going to do ? */
+ if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
+ /* query generation */
+
+ newbuffer_len = inquery_len;
+
+ /* let's quote all the values */
+ for (plc = placeholders; plc; plc = plc->next) {
+ if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
+ ret = zend_hash_index_find(params, plc->bindno, (void**) &param);
+ } else {
+ ret = zend_hash_find(params, plc->pos, plc->len, (void**) &param);
+ }
+ if (ret == FAILURE) {
+ /* parameter was not defined */
+ ret = -1;
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined" TSRMLS_CC);
+ goto clean_up;
+ }
+ if (stmt->dbh->methods->quoter) {
+ if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(param->parameter) == IS_RESOURCE) {
+ php_stream *stm;
+
+ php_stream_from_zval_no_verify(stm, &param->parameter);
+ if (stm) {
+ size_t len;
+ char *buf = NULL;
+
+ len = php_stream_copy_to_mem(stm, &buf, PHP_STREAM_COPY_ALL, 0);
+ if (!stmt->dbh->methods->quoter(stmt->dbh, buf, len, &plc->quoted, &plc->qlen,
+ param->param_type TSRMLS_CC)) {
+ /* bork */
+ ret = -1;
+ strncpy(stmt->error_code, stmt->dbh->error_code, 6);
+ if (buf) {
+ efree(buf);
+ }
+ goto clean_up;
+ }
+ if (buf) {
+ efree(buf);
+ }
+ } else {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource" TSRMLS_CC);
+ ret = -1;
+ goto clean_up;
+ }
+ plc->freeq = 1;
+ } else {
+ switch (Z_TYPE_P(param->parameter)) {
+ case IS_NULL:
+ plc->quoted = "NULL";
+ plc->qlen = sizeof("NULL")-1;
+ plc->freeq = 0;
+ break;
+
+ case IS_BOOL:
+ convert_to_long(param->parameter);
+
+ case IS_LONG:
+ case IS_DOUBLE:
+ convert_to_string(param->parameter);
+ plc->qlen = Z_STRLEN_P(param->parameter);
+ plc->quoted = Z_STRVAL_P(param->parameter);
+ plc->freeq = 0;
+ break;
+
+ default:
+ convert_to_string(param->parameter);
+ if (!stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
+ Z_STRLEN_P(param->parameter), &plc->quoted, &plc->qlen,
+ param->param_type TSRMLS_CC)) {
+ /* bork */
+ ret = -1;
+ strncpy(stmt->error_code, stmt->dbh->error_code, 6);
+ goto clean_up;
+ }
+ plc->freeq = 1;
+ }
+ }
+ } else {
+ plc->quoted = Z_STRVAL_P(param->parameter);
+ plc->qlen = Z_STRLEN_P(param->parameter);
+ }
+ newbuffer_len += plc->qlen;
+ }
+
+rewrite:
+ /* allocate output buffer */
+ newbuffer = emalloc(newbuffer_len + 1);
+ *outquery = newbuffer;
+
+ /* and build the query */
+ plc = placeholders;
+ ptr = inquery;
+
+ do {
+ t = plc->pos - ptr;
+ if (t) {
+ memcpy(newbuffer, ptr, t);
+ newbuffer += t;
+ }
+ memcpy(newbuffer, plc->quoted, plc->qlen);
+ newbuffer += plc->qlen;
+ ptr = plc->pos + plc->len;
+
+ plc = plc->next;
+ } while (plc);
+
+ t = (inquery + inquery_len) - ptr;
+ if (t) {
+ memcpy(newbuffer, ptr, t);
+ newbuffer += t;
+ }
+ *newbuffer = '\0';
+ *outquery_len = newbuffer - *outquery;
+
+ ret = 1;
+ goto clean_up;
+
+ } else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
+ /* rewrite ? to :pdoX */
+ char *name, *idxbuf;
+ const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
+ int bind_no = 1;
+
+ newbuffer_len = inquery_len;
+
+ if (stmt->bound_param_map == NULL) {
+ ALLOC_HASHTABLE(stmt->bound_param_map);
+ zend_hash_init(stmt->bound_param_map, 13, NULL, NULL, 0);
+ }
+
+ for (plc = placeholders; plc; plc = plc->next) {
+ int skip_map = 0;
+ char *p;
+ name = estrndup(plc->pos, plc->len);
+
+ /* check if bound parameter is already available */
+ if (!strcmp(name, "?") || zend_hash_find(stmt->bound_param_map, name, plc->len + 1, (void**) &p) == FAILURE) {
+ spprintf(&idxbuf, 0, tmpl, bind_no++);
+ } else {
+ idxbuf = estrdup(p);
+ skip_map = 1;
+ }
+
+ plc->quoted = idxbuf;
+ plc->qlen = strlen(plc->quoted);
+ plc->freeq = 1;
+ newbuffer_len += plc->qlen;
+
+ if (!skip_map && stmt->named_rewrite_template) {
+ /* create a mapping */
+ zend_hash_update(stmt->bound_param_map, name, plc->len + 1, idxbuf, plc->qlen + 1, NULL);
+ }
+
+ /* map number to name */
+ zend_hash_index_update(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1, NULL);
+
+ efree(name);
+ }
+
+ goto rewrite;
+
+ } else {
+ /* rewrite :name to ? */
+
+ newbuffer_len = inquery_len;
+
+ if (stmt->bound_param_map == NULL) {
+ ALLOC_HASHTABLE(stmt->bound_param_map);
+ zend_hash_init(stmt->bound_param_map, 13, NULL, NULL, 0);
+ }
+
+ for (plc = placeholders; plc; plc = plc->next) {
+ char *name;
+
+ name = estrndup(plc->pos, plc->len);
+ zend_hash_index_update(stmt->bound_param_map, plc->bindno, name, plc->len + 1, NULL);
+ efree(name);
+ plc->quoted = "?";
+ plc->qlen = 1;
+ }
+
+ goto rewrite;
+ }
+
+clean_up:
+
+ while (placeholders) {
+ plc = placeholders;
+ placeholders = plc->next;
+
+ if (plc->freeq) {
+ efree(plc->quoted);
+ }
+
+ efree(plc);
+ }
+
+ return ret;
+}
+
+#if 0
+int old_pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len, char **outquery,
+ int *outquery_len TSRMLS_DC)
+{
+ Scanner s;
+ char *ptr;
+ int t;
+ int bindno = 0;
+ int newbuffer_len;
+ int padding;
+ HashTable *params = stmt->bound_params;
+ struct pdo_bound_param_data *param;
+ /* allocate buffer for query with expanded binds, ptr is our writing pointer */
+ newbuffer_len = inquery_len;
+
+ /* calculate the possible padding factor due to quoting */
+ if(stmt->dbh->max_escaped_char_length) {
+ padding = stmt->dbh->max_escaped_char_length;
+ } else {
+ padding = 3;
+ }
+ if(params) {
+ zend_hash_internal_pointer_reset(params);
+ while (SUCCESS == zend_hash_get_current_data(params, (void**)&param)) {
+ if(param->parameter) {
+ convert_to_string(param->parameter);
+ /* accomodate a string that needs to be fully quoted
+ bind placeholders are at least 2 characters, so
+ the accomodate their own "'s
+ */
+ newbuffer_len += padding * Z_STRLEN_P(param->parameter);
+ }
+ zend_hash_move_forward(params);
+ }
+ }
+ *outquery = (char *) emalloc(newbuffer_len + 1);
+ *outquery_len = 0;
+
+ ptr = *outquery;
+ s.cur = inquery;
+ while((t = scan(&s)) != PDO_PARSER_EOI) {
+ if(t == PDO_PARSER_TEXT) {
+ memcpy(ptr, s.tok, s.cur - s.tok);
+ ptr += (s.cur - s.tok);
+ *outquery_len += (s.cur - s.tok);
+ }
+ else if(t == PDO_PARSER_BIND) {
+ if(!params) {
+ /* error */
+ efree(*outquery);
+ *outquery = NULL;
+ return (int) (s.cur - inquery);
+ }
+ /* lookup bind first via hash and then index */
+ /* stupid keys need to be null-terminated, even though we know their length */
+ if((SUCCESS == zend_hash_find(params, s.tok, s.cur-s.tok,(void **)&param))
+ ||
+ (SUCCESS == zend_hash_index_find(params, bindno, (void **)&param)))
+ {
+ char *quotedstr;
+ int quotedstrlen;
+ /* restore the in-string key, doesn't need null-termination here */
+ /* currently everything is a string here */
+
+ /* quote the bind value if necessary */
+ if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
+ Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen TSRMLS_CC))
+ {
+ memcpy(ptr, quotedstr, quotedstrlen);
+ ptr += quotedstrlen;
+ *outquery_len += quotedstrlen;
+ efree(quotedstr);
+ } else {
+ memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
+ ptr += Z_STRLEN_P(param->parameter);
+ *outquery_len += (Z_STRLEN_P(param->parameter));
+ }
+ }
+ else {
+ /* error and cleanup */
+ efree(*outquery);
+ *outquery = NULL;
+ return (int) (s.cur - inquery);
+ }
+ bindno++;
+ }
+ else if(t == PDO_PARSER_BIND_POS) {
+ if(!params) {
+ /* error */
+ efree(*outquery);
+ *outquery = NULL;
+ return (int) (s.cur - inquery);
+ }
+ /* lookup bind by index */
+ if(SUCCESS == zend_hash_index_find(params, bindno, (void **)&param))
+ {
+ char *quotedstr;
+ int quotedstrlen;
+ /* currently everything is a string here */
+
+ /* quote the bind value if necessary */
+ if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
+ Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen TSRMLS_CC))
+ {
+ memcpy(ptr, quotedstr, quotedstrlen);
+ ptr += quotedstrlen;
+ *outquery_len += quotedstrlen;
+ efree(quotedstr);
+ } else {
+ memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
+ ptr += Z_STRLEN_P(param->parameter);
+ *outquery_len += (Z_STRLEN_P(param->parameter));
+ }
+ }
+ else {
+ /* error and cleanup */
+ efree(*outquery);
+ *outquery = NULL;
+ return (int) (s.cur - inquery);
+ }
+ bindno++;
+ }
+ }
+ *ptr = '\0';
+ return 0;
+}
+#endif
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker ft=c
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/ext/pdo/pdo_sql_parser.re b/ext/pdo/pdo_sql_parser.re
new file mode 100644
index 0000000..d4272d6
--- /dev/null
+++ b/ext/pdo/pdo_sql_parser.re
@@ -0,0 +1,520 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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: George Schlossnagle <george@omniti.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_pdo_driver.h"
+#include "php_pdo_int.h"
+
+#define PDO_PARSER_TEXT 1
+#define PDO_PARSER_BIND 2
+#define PDO_PARSER_BIND_POS 3
+#define PDO_PARSER_EOI 4
+
+#define RET(i) {s->cur = cursor; return i; }
+#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
+
+#define YYCTYPE unsigned char
+#define YYCURSOR cursor
+#define YYLIMIT s->end
+#define YYMARKER s->ptr
+#define YYFILL(n) { RET(PDO_PARSER_EOI); }
+
+typedef struct Scanner {
+ char *ptr, *cur, *tok, *end;
+} Scanner;
+
+static int scan(Scanner *s)
+{
+ char *cursor = s->cur;
+
+ s->tok = cursor;
+ /*!re2c
+ BINDCHR = [:][a-zA-Z0-9_]+;
+ QUESTION = [?];
+ COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*);
+ SPECIALS = [:?"'];
+ MULTICHAR = [:?];
+ ANYNOEOF = [\001-\377];
+ */
+
+ /*!re2c
+ (["](([\\]ANYNOEOF)|ANYNOEOF\["\\])*["]) { RET(PDO_PARSER_TEXT); }
+ (['](([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); }
+ MULTICHAR{2,} { RET(PDO_PARSER_TEXT); }
+ BINDCHR { RET(PDO_PARSER_BIND); }
+ QUESTION { RET(PDO_PARSER_BIND_POS); }
+ SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); }
+ COMMENTS { RET(PDO_PARSER_TEXT); }
+ (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); }
+ */
+}
+
+struct placeholder {
+ char *pos;
+ int len;
+ int bindno;
+ int qlen; /* quoted length of value */
+ char *quoted; /* quoted value */
+ int freeq;
+ struct placeholder *next;
+};
+
+PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len,
+ char **outquery, int *outquery_len TSRMLS_DC)
+{
+ Scanner s;
+ char *ptr, *newbuffer;
+ int t;
+ int bindno = 0;
+ int ret = 0;
+ int newbuffer_len;
+ HashTable *params;
+ struct pdo_bound_param_data *param;
+ int query_type = PDO_PLACEHOLDER_NONE;
+ struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
+
+ ptr = *outquery;
+ s.cur = inquery;
+ s.end = inquery + inquery_len + 1;
+
+ /* phase 1: look for args */
+ while((t = scan(&s)) != PDO_PARSER_EOI) {
+ if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
+ if (t == PDO_PARSER_BIND) {
+ int len = s.cur - s.tok;
+ if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
+ continue;
+ }
+ query_type |= PDO_PLACEHOLDER_NAMED;
+ } else {
+ query_type |= PDO_PLACEHOLDER_POSITIONAL;
+ }
+
+ plc = emalloc(sizeof(*plc));
+ memset(plc, 0, sizeof(*plc));
+ plc->next = NULL;
+ plc->pos = s.tok;
+ plc->len = s.cur - s.tok;
+ plc->bindno = bindno++;
+
+ if (placetail) {
+ placetail->next = plc;
+ } else {
+ placeholders = plc;
+ }
+ placetail = plc;
+ }
+ }
+
+ if (bindno == 0) {
+ /* nothing to do; good! */
+ return 0;
+ }
+
+ /* did the query make sense to me? */
+ if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
+ /* they mixed both types; punt */
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters" TSRMLS_CC);
+ ret = -1;
+ goto clean_up;
+ }
+
+ if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
+ /* query matches native syntax */
+ ret = 0;
+ goto clean_up;
+ }
+
+ if (stmt->named_rewrite_template) {
+ /* magic/hack.
+ * We we pretend that the query was positional even if
+ * it was named so that we fall into the
+ * named rewrite case below. Not too pretty,
+ * but it works. */
+ query_type = PDO_PLACEHOLDER_POSITIONAL;
+ }
+
+ params = stmt->bound_params;
+
+ /* Do we have placeholders but no bound params */
+ if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound" TSRMLS_CC);
+ ret = -1;
+ goto clean_up;
+ }
+
+ if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
+ /* extra bit of validation for instances when same params are bound more then once */
+ if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
+ int ok = 1;
+ for (plc = placeholders; plc; plc = plc->next) {
+ if (zend_hash_find(params, plc->pos, plc->len, (void**) &param) == FAILURE) {
+ ok = 0;
+ break;
+ }
+ }
+ if (ok) {
+ goto safe;
+ }
+ }
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens" TSRMLS_CC);
+ ret = -1;
+ goto clean_up;
+ }
+safe:
+ /* what are we going to do ? */
+ if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
+ /* query generation */
+
+ newbuffer_len = inquery_len;
+
+ /* let's quote all the values */
+ for (plc = placeholders; plc; plc = plc->next) {
+ if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
+ ret = zend_hash_index_find(params, plc->bindno, (void**) &param);
+ } else {
+ ret = zend_hash_find(params, plc->pos, plc->len, (void**) &param);
+ }
+ if (ret == FAILURE) {
+ /* parameter was not defined */
+ ret = -1;
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined" TSRMLS_CC);
+ goto clean_up;
+ }
+ if (stmt->dbh->methods->quoter) {
+ if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(param->parameter) == IS_RESOURCE) {
+ php_stream *stm;
+
+ php_stream_from_zval_no_verify(stm, &param->parameter);
+ if (stm) {
+ size_t len;
+ char *buf = NULL;
+
+ len = php_stream_copy_to_mem(stm, &buf, PHP_STREAM_COPY_ALL, 0);
+ if (!stmt->dbh->methods->quoter(stmt->dbh, buf, len, &plc->quoted, &plc->qlen,
+ param->param_type TSRMLS_CC)) {
+ /* bork */
+ ret = -1;
+ strncpy(stmt->error_code, stmt->dbh->error_code, 6);
+ if (buf) {
+ efree(buf);
+ }
+ goto clean_up;
+ }
+ if (buf) {
+ efree(buf);
+ }
+ } else {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource" TSRMLS_CC);
+ ret = -1;
+ goto clean_up;
+ }
+ plc->freeq = 1;
+ } else {
+ switch (Z_TYPE_P(param->parameter)) {
+ case IS_NULL:
+ plc->quoted = "NULL";
+ plc->qlen = sizeof("NULL")-1;
+ plc->freeq = 0;
+ break;
+
+ case IS_BOOL:
+ convert_to_long(param->parameter);
+
+ case IS_LONG:
+ case IS_DOUBLE:
+ convert_to_string(param->parameter);
+ plc->qlen = Z_STRLEN_P(param->parameter);
+ plc->quoted = Z_STRVAL_P(param->parameter);
+ plc->freeq = 0;
+ break;
+
+ default:
+ convert_to_string(param->parameter);
+ if (!stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
+ Z_STRLEN_P(param->parameter), &plc->quoted, &plc->qlen,
+ param->param_type TSRMLS_CC)) {
+ /* bork */
+ ret = -1;
+ strncpy(stmt->error_code, stmt->dbh->error_code, 6);
+ goto clean_up;
+ }
+ plc->freeq = 1;
+ }
+ }
+ } else {
+ plc->quoted = Z_STRVAL_P(param->parameter);
+ plc->qlen = Z_STRLEN_P(param->parameter);
+ }
+ newbuffer_len += plc->qlen;
+ }
+
+rewrite:
+ /* allocate output buffer */
+ newbuffer = emalloc(newbuffer_len + 1);
+ *outquery = newbuffer;
+
+ /* and build the query */
+ plc = placeholders;
+ ptr = inquery;
+
+ do {
+ t = plc->pos - ptr;
+ if (t) {
+ memcpy(newbuffer, ptr, t);
+ newbuffer += t;
+ }
+ memcpy(newbuffer, plc->quoted, plc->qlen);
+ newbuffer += plc->qlen;
+ ptr = plc->pos + plc->len;
+
+ plc = plc->next;
+ } while (plc);
+
+ t = (inquery + inquery_len) - ptr;
+ if (t) {
+ memcpy(newbuffer, ptr, t);
+ newbuffer += t;
+ }
+ *newbuffer = '\0';
+ *outquery_len = newbuffer - *outquery;
+
+ ret = 1;
+ goto clean_up;
+
+ } else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
+ /* rewrite ? to :pdoX */
+ char *name, *idxbuf;
+ const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
+ int bind_no = 1;
+
+ newbuffer_len = inquery_len;
+
+ if (stmt->bound_param_map == NULL) {
+ ALLOC_HASHTABLE(stmt->bound_param_map);
+ zend_hash_init(stmt->bound_param_map, 13, NULL, NULL, 0);
+ }
+
+ for (plc = placeholders; plc; plc = plc->next) {
+ int skip_map = 0;
+ char *p;
+ name = estrndup(plc->pos, plc->len);
+
+ /* check if bound parameter is already available */
+ if (!strcmp(name, "?") || zend_hash_find(stmt->bound_param_map, name, plc->len + 1, (void**) &p) == FAILURE) {
+ spprintf(&idxbuf, 0, tmpl, bind_no++);
+ } else {
+ idxbuf = estrdup(p);
+ skip_map = 1;
+ }
+
+ plc->quoted = idxbuf;
+ plc->qlen = strlen(plc->quoted);
+ plc->freeq = 1;
+ newbuffer_len += plc->qlen;
+
+ if (!skip_map && stmt->named_rewrite_template) {
+ /* create a mapping */
+ zend_hash_update(stmt->bound_param_map, name, plc->len + 1, idxbuf, plc->qlen + 1, NULL);
+ }
+
+ /* map number to name */
+ zend_hash_index_update(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1, NULL);
+
+ efree(name);
+ }
+
+ goto rewrite;
+
+ } else {
+ /* rewrite :name to ? */
+
+ newbuffer_len = inquery_len;
+
+ if (stmt->bound_param_map == NULL) {
+ ALLOC_HASHTABLE(stmt->bound_param_map);
+ zend_hash_init(stmt->bound_param_map, 13, NULL, NULL, 0);
+ }
+
+ for (plc = placeholders; plc; plc = plc->next) {
+ char *name;
+
+ name = estrndup(plc->pos, plc->len);
+ zend_hash_index_update(stmt->bound_param_map, plc->bindno, name, plc->len + 1, NULL);
+ efree(name);
+ plc->quoted = "?";
+ plc->qlen = 1;
+ }
+
+ goto rewrite;
+ }
+
+clean_up:
+
+ while (placeholders) {
+ plc = placeholders;
+ placeholders = plc->next;
+
+ if (plc->freeq) {
+ efree(plc->quoted);
+ }
+
+ efree(plc);
+ }
+
+ return ret;
+}
+
+#if 0
+int old_pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len, char **outquery,
+ int *outquery_len TSRMLS_DC)
+{
+ Scanner s;
+ char *ptr;
+ int t;
+ int bindno = 0;
+ int newbuffer_len;
+ int padding;
+ HashTable *params = stmt->bound_params;
+ struct pdo_bound_param_data *param;
+ /* allocate buffer for query with expanded binds, ptr is our writing pointer */
+ newbuffer_len = inquery_len;
+
+ /* calculate the possible padding factor due to quoting */
+ if(stmt->dbh->max_escaped_char_length) {
+ padding = stmt->dbh->max_escaped_char_length;
+ } else {
+ padding = 3;
+ }
+ if(params) {
+ zend_hash_internal_pointer_reset(params);
+ while (SUCCESS == zend_hash_get_current_data(params, (void**)&param)) {
+ if(param->parameter) {
+ convert_to_string(param->parameter);
+ /* accomodate a string that needs to be fully quoted
+ bind placeholders are at least 2 characters, so
+ the accomodate their own "'s
+ */
+ newbuffer_len += padding * Z_STRLEN_P(param->parameter);
+ }
+ zend_hash_move_forward(params);
+ }
+ }
+ *outquery = (char *) emalloc(newbuffer_len + 1);
+ *outquery_len = 0;
+
+ ptr = *outquery;
+ s.cur = inquery;
+ while((t = scan(&s)) != PDO_PARSER_EOI) {
+ if(t == PDO_PARSER_TEXT) {
+ memcpy(ptr, s.tok, s.cur - s.tok);
+ ptr += (s.cur - s.tok);
+ *outquery_len += (s.cur - s.tok);
+ }
+ else if(t == PDO_PARSER_BIND) {
+ if(!params) {
+ /* error */
+ efree(*outquery);
+ *outquery = NULL;
+ return (int) (s.cur - inquery);
+ }
+ /* lookup bind first via hash and then index */
+ /* stupid keys need to be null-terminated, even though we know their length */
+ if((SUCCESS == zend_hash_find(params, s.tok, s.cur-s.tok,(void **)&param))
+ ||
+ (SUCCESS == zend_hash_index_find(params, bindno, (void **)&param)))
+ {
+ char *quotedstr;
+ int quotedstrlen;
+ /* restore the in-string key, doesn't need null-termination here */
+ /* currently everything is a string here */
+
+ /* quote the bind value if necessary */
+ if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
+ Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen TSRMLS_CC))
+ {
+ memcpy(ptr, quotedstr, quotedstrlen);
+ ptr += quotedstrlen;
+ *outquery_len += quotedstrlen;
+ efree(quotedstr);
+ } else {
+ memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
+ ptr += Z_STRLEN_P(param->parameter);
+ *outquery_len += (Z_STRLEN_P(param->parameter));
+ }
+ }
+ else {
+ /* error and cleanup */
+ efree(*outquery);
+ *outquery = NULL;
+ return (int) (s.cur - inquery);
+ }
+ bindno++;
+ }
+ else if(t == PDO_PARSER_BIND_POS) {
+ if(!params) {
+ /* error */
+ efree(*outquery);
+ *outquery = NULL;
+ return (int) (s.cur - inquery);
+ }
+ /* lookup bind by index */
+ if(SUCCESS == zend_hash_index_find(params, bindno, (void **)&param))
+ {
+ char *quotedstr;
+ int quotedstrlen;
+ /* currently everything is a string here */
+
+ /* quote the bind value if necessary */
+ if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
+ Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen TSRMLS_CC))
+ {
+ memcpy(ptr, quotedstr, quotedstrlen);
+ ptr += quotedstrlen;
+ *outquery_len += quotedstrlen;
+ efree(quotedstr);
+ } else {
+ memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
+ ptr += Z_STRLEN_P(param->parameter);
+ *outquery_len += (Z_STRLEN_P(param->parameter));
+ }
+ }
+ else {
+ /* error and cleanup */
+ efree(*outquery);
+ *outquery = NULL;
+ return (int) (s.cur - inquery);
+ }
+ bindno++;
+ }
+ }
+ *ptr = '\0';
+ return 0;
+}
+#endif
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker ft=c
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/ext/pdo/pdo_sqlstate.c b/ext/pdo/pdo_sqlstate.c
new file mode 100644
index 0000000..7c92c1c
--- /dev/null
+++ b/ext/pdo/pdo_sqlstate.c
@@ -0,0 +1,340 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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: Wez Furlong <wez@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_pdo.h"
+#include "php_pdo_driver.h"
+
+struct pdo_sqlstate_info {
+ const char state[5];
+ const char *desc;
+};
+
+static HashTable err_hash;
+
+static const struct pdo_sqlstate_info err_initializer[] = {
+ { "00000", "No error" },
+ { "01000", "Warning" },
+ { "01001", "Cursor operation conflict" },
+ { "01002", "Disconnect error" },
+ { "01003", "NULL value eliminated in set function" },
+ { "01004", "String data, right truncated" },
+ { "01006", "Privilege not revoked" },
+ { "01007", "Privilege not granted" },
+ { "01008", "Implicit zero bit padding" },
+ { "0100C", "Dynamic result sets returned" },
+ { "01P01", "Deprecated feature" },
+ { "01S00", "Invalid connection string attribute" },
+ { "01S01", "Error in row" },
+ { "01S02", "Option value changed" },
+ { "01S06", "Attempt to fetch before the result set returned the first rowset" },
+ { "01S07", "Fractional truncation" },
+ { "01S08", "Error saving File DSN" },
+ { "01S09", "Invalid keyword" },
+ { "02000", "No data" },
+ { "02001", "No additional dynamic result sets returned" },
+ { "03000", "Sql statement not yet complete" },
+ { "07002", "COUNT field incorrect" },
+ { "07005", "Prepared statement not a cursor-specification" },
+ { "07006", "Restricted data type attribute violation" },
+ { "07009", "Invalid descriptor index" },
+ { "07S01", "Invalid use of default parameter" },
+ { "08000", "Connection exception" },
+ { "08001", "Client unable to establish connection" },
+ { "08002", "Connection name in use" },
+ { "08003", "Connection does not exist" },
+ { "08004", "Server rejected the connection" },
+ { "08006", "Connection failure" },
+ { "08007", "Connection failure during transaction" },
+ { "08S01", "Communication link failure" },
+ { "09000", "Triggered action exception" },
+ { "0A000", "Feature not supported" },
+ { "0B000", "Invalid transaction initiation" },
+ { "0F000", "Locator exception" },
+ { "0F001", "Invalid locator specification" },
+ { "0L000", "Invalid grantor" },
+ { "0LP01", "Invalid grant operation" },
+ { "0P000", "Invalid role specification" },
+ { "21000", "Cardinality violation" },
+ { "21S01", "Insert value list does not match column list" },
+ { "21S02", "Degree of derived table does not match column list" },
+ { "22000", "Data exception" },
+ { "22001", "String data, right truncated" },
+ { "22002", "Indicator variable required but not supplied" },
+ { "22003", "Numeric value out of range" },
+ { "22004", "Null value not allowed" },
+ { "22005", "Error in assignment" },
+ { "22007", "Invalid datetime format" },
+ { "22008", "Datetime field overflow" },
+ { "22009", "Invalid time zone displacement value" },
+ { "2200B", "Escape character conflict" },
+ { "2200C", "Invalid use of escape character" },
+ { "2200D", "Invalid escape octet" },
+ { "2200F", "Zero length character string" },
+ { "2200G", "Most specific type mismatch" },
+ { "22010", "Invalid indicator parameter value" },
+ { "22011", "Substring error" },
+ { "22012", "Division by zero" },
+ { "22015", "Interval field overflow" },
+ { "22018", "Invalid character value for cast specification" },
+ { "22019", "Invalid escape character" },
+ { "2201B", "Invalid regular expression" },
+ { "2201E", "Invalid argument for logarithm" },
+ { "2201F", "Invalid argument for power function" },
+ { "2201G", "Invalid argument for width bucket function" },
+ { "22020", "Invalid limit value" },
+ { "22021", "Character not in repertoire" },
+ { "22022", "Indicator overflow" },
+ { "22023", "Invalid parameter value" },
+ { "22024", "Unterminated c string" },
+ { "22025", "Invalid escape sequence" },
+ { "22026", "String data, length mismatch" },
+ { "22027", "Trim error" },
+ { "2202E", "Array subscript error" },
+ { "22P01", "Floating point exception" },
+ { "22P02", "Invalid text representation" },
+ { "22P03", "Invalid binary representation" },
+ { "22P04", "Bad copy file format" },
+ { "22P05", "Untranslatable character" },
+ { "23000", "Integrity constraint violation" },
+ { "23001", "Restrict violation" },
+ { "23502", "Not null violation" },
+ { "23503", "Foreign key violation" },
+ { "23505", "Unique violation" },
+ { "23514", "Check violation" },
+ { "24000", "Invalid cursor state" },
+ { "25000", "Invalid transaction state" },
+ { "25001", "Active sql transaction" },
+ { "25002", "Branch transaction already active" },
+ { "25003", "Inappropriate access mode for branch transaction" },
+ { "25004", "Inappropriate isolation level for branch transaction" },
+ { "25005", "No active sql transaction for branch transaction" },
+ { "25006", "Read only sql transaction" },
+ { "25007", "Schema and data statement mixing not supported" },
+ { "25008", "Held cursor requires same isolation level" },
+ { "25P01", "No active sql transaction" },
+ { "25P02", "In failed sql transaction" },
+ { "25S01", "Transaction state" },
+ { "25S02", "Transaction is still active" },
+ { "25S03", "Transaction is rolled back" },
+ { "26000", "Invalid sql statement name" },
+ { "27000", "Triggered data change violation" },
+ { "28000", "Invalid authorization specification" },
+ { "2B000", "Dependent privilege descriptors still exist" },
+ { "2BP01", "Dependent objects still exist" },
+ { "2D000", "Invalid transaction termination" },
+ { "2F000", "Sql routine exception" },
+ { "2F002", "Modifying sql data not permitted" },
+ { "2F003", "Prohibited sql statement attempted" },
+ { "2F004", "Reading sql data not permitted" },
+ { "2F005", "Function executed no return statement" },
+ { "34000", "Invalid cursor name" },
+ { "38000", "External routine exception" },
+ { "38001", "Containing sql not permitted" },
+ { "38002", "Modifying sql data not permitted" },
+ { "38003", "Prohibited sql statement attempted" },
+ { "38004", "Reading sql data not permitted" },
+ { "39000", "External routine invocation exception" },
+ { "39001", "Invalid sqlstate returned" },
+ { "39004", "Null value not allowed" },
+ { "39P01", "Trigger protocol violated" },
+ { "39P02", "Srf protocol violated" },
+ { "3B000", "Savepoint exception" },
+ { "3B001", "Invalid savepoint specification" },
+ { "3C000", "Duplicate cursor name" },
+ { "3D000", "Invalid catalog name" },
+ { "3F000", "Invalid schema name" },
+ { "40000", "Transaction rollback" },
+ { "40001", "Serialization failure" },
+ { "40002", "Transaction integrity constraint violation" },
+ { "40003", "Statement completion unknown" },
+ { "40P01", "Deadlock detected" },
+ { "42000", "Syntax error or access violation" },
+ { "42501", "Insufficient privilege" },
+ { "42601", "Syntax error" },
+ { "42602", "Invalid name" },
+ { "42611", "Invalid column definition" },
+ { "42622", "Name too long" },
+ { "42701", "Duplicate column" },
+ { "42702", "Ambiguous column" },
+ { "42703", "Undefined column" },
+ { "42704", "Undefined object" },
+ { "42710", "Duplicate object" },
+ { "42712", "Duplicate alias" },
+ { "42723", "Duplicate function" },
+ { "42725", "Ambiguous function" },
+ { "42803", "Grouping error" },
+ { "42804", "Datatype mismatch" },
+ { "42809", "Wrong object type" },
+ { "42830", "Invalid foreign key" },
+ { "42846", "Cannot coerce" },
+ { "42883", "Undefined function" },
+ { "42939", "Reserved name" },
+ { "42P01", "Undefined table" },
+ { "42P02", "Undefined parameter" },
+ { "42P03", "Duplicate cursor" },
+ { "42P04", "Duplicate database" },
+ { "42P05", "Duplicate prepared statement" },
+ { "42P06", "Duplicate schema" },
+ { "42P07", "Duplicate table" },
+ { "42P08", "Ambiguous parameter" },
+ { "42P09", "Ambiguous alias" },
+ { "42P10", "Invalid column reference" },
+ { "42P11", "Invalid cursor definition" },
+ { "42P12", "Invalid database definition" },
+ { "42P13", "Invalid function definition" },
+ { "42P14", "Invalid prepared statement definition" },
+ { "42P15", "Invalid schema definition" },
+ { "42P16", "Invalid table definition" },
+ { "42P17", "Invalid object definition" },
+ { "42P18", "Indeterminate datatype" },
+ { "42S01", "Base table or view already exists" },
+ { "42S02", "Base table or view not found" },
+ { "42S11", "Index already exists" },
+ { "42S12", "Index not found" },
+ { "42S21", "Column already exists" },
+ { "42S22", "Column not found" },
+ { "44000", "WITH CHECK OPTION violation" },
+ { "53000", "Insufficient resources" },
+ { "53100", "Disk full" },
+ { "53200", "Out of memory" },
+ { "53300", "Too many connections" },
+ { "54000", "Program limit exceeded" },
+ { "54001", "Statement too complex" },
+ { "54011", "Too many columns" },
+ { "54023", "Too many arguments" },
+ { "55000", "Object not in prerequisite state" },
+ { "55006", "Object in use" },
+ { "55P02", "Cant change runtime param" },
+ { "55P03", "Lock not available" },
+ { "57000", "Operator intervention" },
+ { "57014", "Query canceled" },
+ { "57P01", "Admin shutdown" },
+ { "57P02", "Crash shutdown" },
+ { "57P03", "Cannot connect now" },
+ { "58030", "Io error" },
+ { "58P01", "Undefined file" },
+ { "58P02", "Duplicate file" },
+ { "F0000", "Config file error" },
+ { "F0001", "Lock file exists" },
+ { "HY000", "General error" },
+ { "HY001", "Memory allocation error" },
+ { "HY003", "Invalid application buffer type" },
+ { "HY004", "Invalid SQL data type" },
+ { "HY007", "Associated statement is not prepared" },
+ { "HY008", "Operation canceled" },
+ { "HY009", "Invalid use of null pointer" },
+ { "HY010", "Function sequence error" },
+ { "HY011", "Attribute cannot be set now" },
+ { "HY012", "Invalid transaction operation code" },
+ { "HY013", "Memory management error" },
+ { "HY014", "Limit on the number of handles exceeded" },
+ { "HY015", "No cursor name available" },
+ { "HY016", "Cannot modify an implementation row descriptor" },
+ { "HY017", "Invalid use of an automatically allocated descriptor handle" },
+ { "HY018", "Server declined cancel request" },
+ { "HY019", "Non-character and non-binary data sent in pieces" },
+ { "HY020", "Attempt to concatenate a null value" },
+ { "HY021", "Inconsistent descriptor information" },
+ { "HY024", "Invalid attribute value" },
+ { "HY090", "Invalid string or buffer length" },
+ { "HY091", "Invalid descriptor field identifier" },
+ { "HY092", "Invalid attribute/option identifier" },
+ { "HY093", "Invalid parameter number" },
+ { "HY095", "Function type out of range" },
+ { "HY096", "Invalid information type" },
+ { "HY097", "Column type out of range" },
+ { "HY098", "Scope type out of range" },
+ { "HY099", "Nullable type out of range" },
+ { "HY100", "Uniqueness option type out of range" },
+ { "HY101", "Accuracy option type out of range" },
+ { "HY103", "Invalid retrieval code" },
+ { "HY104", "Invalid precision or scale value" },
+ { "HY105", "Invalid parameter type" },
+ { "HY106", "Fetch type out of range" },
+ { "HY107", "Row value out of range" },
+ { "HY109", "Invalid cursor position" },
+ { "HY110", "Invalid driver completion" },
+ { "HY111", "Invalid bookmark value" },
+ { "HYC00", "Optional feature not implemented" },
+ { "HYT00", "Timeout expired" },
+ { "HYT01", "Connection timeout expired" },
+ { "IM001", "Driver does not support this function" },
+ { "IM002", "Data source name not found and no default driver specified" },
+ { "IM003", "Specified driver could not be loaded" },
+ { "IM004", "Driver's SQLAllocHandle on SQL_HANDLE_ENV failed" },
+ { "IM005", "Driver's SQLAllocHandle on SQL_HANDLE_DBC failed" },
+ { "IM006", "Driver's SQLSetConnectAttr failed" },
+ { "IM007", "No data source or driver specified; dialog prohibited" },
+ { "IM008", "Dialog failed" },
+ { "IM009", "Unable to load translation DLL" },
+ { "IM010", "Data source name too long" },
+ { "IM011", "Driver name too long" },
+ { "IM012", "DRIVER keyword syntax error" },
+ { "IM013", "Trace file error" },
+ { "IM014", "Invalid name of File DSN" },
+ { "IM015", "Corrupt file data source" },
+ { "P0000", "Plpgsql error" },
+ { "P0001", "Raise exception" },
+ { "XX000", "Internal error" },
+ { "XX001", "Data corrupted" },
+ { "XX002", "Index corrupted" }
+};
+
+void pdo_sqlstate_fini_error_table(void)
+{
+ zend_hash_destroy(&err_hash);
+}
+
+int pdo_sqlstate_init_error_table(void)
+{
+ int i;
+ const struct pdo_sqlstate_info *info;
+
+ if (FAILURE == zend_hash_init(&err_hash,
+ sizeof(err_initializer)/sizeof(err_initializer[0]), NULL, NULL, 1)) {
+ return FAILURE;
+ }
+
+ for (i = 0; i < sizeof(err_initializer)/sizeof(err_initializer[0]); i++) {
+ info = &err_initializer[i];
+
+ zend_hash_add(&err_hash, info->state, sizeof(info->state), &info, sizeof(info), NULL);
+ }
+
+ return SUCCESS;
+}
+
+const char *pdo_sqlstate_state_to_description(char *state)
+{
+ const struct pdo_sqlstate_info **info;
+ if (SUCCESS == zend_hash_find(&err_hash, state, sizeof(err_initializer[0].state),
+ (void**)&info)) {
+ return (*info)->desc;
+ }
+ return NULL;
+}
+
diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c
new file mode 100644
index 0000000..a469d09
--- /dev/null
+++ b/ext/pdo/pdo_stmt.c
@@ -0,0 +1,2822 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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: Wez Furlong <wez@php.net> |
+ | Marcus Boerger <helly@php.net> |
+ | Sterling Hughes <sterling@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+/* The PDO Statement Handle Class */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "ext/standard/php_var.h"
+#include "php_pdo.h"
+#include "php_pdo_driver.h"
+#include "php_pdo_int.h"
+#include "zend_exceptions.h"
+#include "zend_interfaces.h"
+#include "php_memory_streams.h"
+
+/* {{{ arginfo */
+ZEND_BEGIN_ARG_INFO(arginfo_pdostatement__void, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_execute, 0, 0, 0)
+ ZEND_ARG_INFO(0, bound_input_params) /* array */
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_fetch, 0, 0, 0)
+ ZEND_ARG_INFO(0, how)
+ ZEND_ARG_INFO(0, orientation)
+ ZEND_ARG_INFO(0, offset)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_fetchobject, 0, 0, 0)
+ ZEND_ARG_INFO(0, class_name)
+ ZEND_ARG_INFO(0, ctor_args) /* array */
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_fetchcolumn, 0, 0, 0)
+ ZEND_ARG_INFO(0, column_number)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_fetchall, 0, 0, 0)
+ ZEND_ARG_INFO(0, how)
+ ZEND_ARG_INFO(0, class_name)
+ ZEND_ARG_INFO(0, ctor_args) /* array */
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_bindvalue, 0, 0, 2)
+ ZEND_ARG_INFO(0, paramno)
+ ZEND_ARG_INFO(0, param)
+ ZEND_ARG_INFO(0, type)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_bindparam, 0, 0, 2)
+ ZEND_ARG_INFO(0, paramno)
+ ZEND_ARG_INFO(1, param)
+ ZEND_ARG_INFO(0, type)
+ ZEND_ARG_INFO(0, maxlen)
+ ZEND_ARG_INFO(0, driverdata)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_bindcolumn, 0, 0, 2)
+ ZEND_ARG_INFO(0, column)
+ ZEND_ARG_INFO(1, param)
+ ZEND_ARG_INFO(0, type)
+ ZEND_ARG_INFO(0, maxlen)
+ ZEND_ARG_INFO(0, driverdata)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_pdostatement_setattribute, 0)
+ ZEND_ARG_INFO(0, attribute)
+ ZEND_ARG_INFO(0, value)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_pdostatement_getattribute, 0)
+ ZEND_ARG_INFO(0, attribute)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_pdostatement_getcolumnmeta, 0)
+ ZEND_ARG_INFO(0, column)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pdostatement_setfetchmode, 0, 0, 1)
+ ZEND_ARG_INFO(0, mode)
+ ZEND_ARG_INFO(0, params)
+ZEND_END_ARG_INFO()
+/* }}} */
+
+#define PHP_STMT_GET_OBJ \
+ pdo_stmt_t *stmt = (pdo_stmt_t*)zend_object_store_get_object(getThis() TSRMLS_CC); \
+ if (!stmt->dbh) { \
+ RETURN_FALSE; \
+ } \
+
+static PHP_FUNCTION(dbstmt_constructor) /* {{{ */
+{
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "You should not create a PDOStatement manually");
+}
+/* }}} */
+
+static inline int rewrite_name_to_position(pdo_stmt_t *stmt, struct pdo_bound_param_data *param TSRMLS_DC) /* {{{ */
+{
+ if (stmt->bound_param_map) {
+ /* rewriting :name to ? style.
+ * We need to fixup the parameter numbers on the parameters.
+ * If we find that a given named parameter has been used twice,
+ * we will raise an error, as we can't be sure that it is safe
+ * to bind multiple parameters onto the same zval in the underlying
+ * driver */
+ char *name;
+ int position = 0;
+
+ if (stmt->named_rewrite_template) {
+ /* this is not an error here */
+ return 1;
+ }
+ if (!param->name) {
+ /* do the reverse; map the parameter number to the name */
+ if (SUCCESS == zend_hash_index_find(stmt->bound_param_map, param->paramno, (void**)&name)) {
+ param->name = estrdup(name);
+ param->namelen = strlen(param->name);
+ return 1;
+ }
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined" TSRMLS_CC);
+ return 0;
+ }
+
+ zend_hash_internal_pointer_reset(stmt->bound_param_map);
+ while (SUCCESS == zend_hash_get_current_data(stmt->bound_param_map, (void**)&name)) {
+ if (strcmp(name, param->name)) {
+ position++;
+ zend_hash_move_forward(stmt->bound_param_map);
+ continue;
+ }
+ if (param->paramno >= 0) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "PDO refuses to handle repeating the same :named parameter for multiple positions with this driver, as it might be unsafe to do so. Consider using a separate name for each parameter instead" TSRMLS_CC);
+ return -1;
+ }
+ param->paramno = position;
+ return 1;
+ }
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined" TSRMLS_CC);
+ return 0;
+ }
+ return 1;
+}
+/* }}} */
+
+/* trigger callback hook for parameters */
+static int dispatch_param_event(pdo_stmt_t *stmt, enum pdo_param_event event_type TSRMLS_DC) /* {{{ */
+{
+ int ret = 1, is_param = 1;
+ struct pdo_bound_param_data *param;
+ HashTable *ht;
+
+ if (!stmt->methods->param_hook) {
+ return 1;
+ }
+
+ ht = stmt->bound_params;
+
+iterate:
+ if (ht) {
+ zend_hash_internal_pointer_reset(ht);
+ while (SUCCESS == zend_hash_get_current_data(ht, (void**)&param)) {
+ if (!stmt->methods->param_hook(stmt, param, event_type TSRMLS_CC)) {
+ ret = 0;
+ break;
+ }
+
+ zend_hash_move_forward(ht);
+ }
+ }
+ if (ret && is_param) {
+ ht = stmt->bound_columns;
+ is_param = 0;
+ goto iterate;
+ }
+
+ return ret;
+}
+/* }}} */
+
+int pdo_stmt_describe_columns(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
+{
+ int col;
+
+ stmt->columns = ecalloc(stmt->column_count, sizeof(struct pdo_column_data));
+
+ for (col = 0; col < stmt->column_count; col++) {
+ if (!stmt->methods->describer(stmt, col TSRMLS_CC)) {
+ return 0;
+ }
+
+ /* if we are applying case conversions on column names, do so now */
+ if (stmt->dbh->native_case != stmt->dbh->desired_case && stmt->dbh->desired_case != PDO_CASE_NATURAL) {
+ char *s = stmt->columns[col].name;
+
+ switch (stmt->dbh->desired_case) {
+ case PDO_CASE_UPPER:
+ while (*s != '\0') {
+ *s = toupper(*s);
+ s++;
+ }
+ break;
+ case PDO_CASE_LOWER:
+ while (*s != '\0') {
+ *s = tolower(*s);
+ s++;
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+#if 0
+ /* update the column index on named bound parameters */
+ if (stmt->bound_params) {
+ struct pdo_bound_param_data *param;
+
+ if (SUCCESS == zend_hash_find(stmt->bound_params, stmt->columns[col].name,
+ stmt->columns[col].namelen, (void**)&param)) {
+ param->paramno = col;
+ }
+ }
+#endif
+ if (stmt->bound_columns) {
+ struct pdo_bound_param_data *param;
+
+ if (SUCCESS == zend_hash_find(stmt->bound_columns, stmt->columns[col].name,
+ stmt->columns[col].namelen, (void**)&param)) {
+ param->paramno = col;
+ }
+ }
+
+ }
+ return 1;
+}
+/* }}} */
+
+static void get_lazy_object(pdo_stmt_t *stmt, zval *return_value TSRMLS_DC) /* {{{ */
+{
+ if (Z_TYPE(stmt->lazy_object_ref) == IS_NULL) {
+ Z_TYPE(stmt->lazy_object_ref) = IS_OBJECT;
+ Z_OBJ_HANDLE(stmt->lazy_object_ref) = zend_objects_store_put(stmt, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)pdo_row_free_storage, NULL TSRMLS_CC);
+ Z_OBJ_HT(stmt->lazy_object_ref) = &pdo_row_object_handlers;
+ stmt->refcount++;
+ }
+ Z_TYPE_P(return_value) = IS_OBJECT;
+ Z_OBJ_HANDLE_P(return_value) = Z_OBJ_HANDLE(stmt->lazy_object_ref);
+ Z_OBJ_HT_P(return_value) = Z_OBJ_HT(stmt->lazy_object_ref);
+ zend_objects_store_add_ref(return_value TSRMLS_CC);
+}
+/* }}} */
+
+static void param_dtor(void *data) /* {{{ */
+{
+ struct pdo_bound_param_data *param = (struct pdo_bound_param_data *)data;
+ TSRMLS_FETCH();
+
+ /* tell the driver that it is going away */
+ if (param->stmt->methods->param_hook) {
+ param->stmt->methods->param_hook(param->stmt, param, PDO_PARAM_EVT_FREE TSRMLS_CC);
+ }
+
+ if (param->name) {
+ efree(param->name);
+ }
+
+ if (param->parameter) {
+ zval_ptr_dtor(&(param->parameter));
+ param->parameter = NULL;
+ }
+ if (param->driver_params) {
+ zval_ptr_dtor(&(param->driver_params));
+ }
+}
+/* }}} */
+
+static int really_register_bound_param(struct pdo_bound_param_data *param, pdo_stmt_t *stmt, int is_param TSRMLS_DC) /* {{{ */
+{
+ HashTable *hash;
+ struct pdo_bound_param_data *pparam = NULL;
+
+ hash = is_param ? stmt->bound_params : stmt->bound_columns;
+
+ if (!hash) {
+ ALLOC_HASHTABLE(hash);
+ zend_hash_init(hash, 13, NULL, param_dtor, 0);
+
+ if (is_param) {
+ stmt->bound_params = hash;
+ } else {
+ stmt->bound_columns = hash;
+ }
+ }
+
+ if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR && param->max_value_len <= 0 && ! ZVAL_IS_NULL(param->parameter)) {
+ if (Z_TYPE_P(param->parameter) == IS_DOUBLE) {
+ char *p;
+ int len = spprintf(&p, 0, "%.*H", (int) EG(precision), Z_DVAL_P(param->parameter));
+ ZVAL_STRINGL(param->parameter, p, len, 0);
+ } else {
+ convert_to_string(param->parameter);
+ }
+ } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_INT && Z_TYPE_P(param->parameter) == IS_BOOL) {
+ convert_to_long(param->parameter);
+ } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && Z_TYPE_P(param->parameter) == IS_LONG) {
+ convert_to_boolean(param->parameter);
+ }
+
+ param->stmt = stmt;
+ param->is_param = is_param;
+
+ if (param->driver_params) {
+ Z_ADDREF_P(param->driver_params);
+ }
+
+ if (!is_param && param->name && stmt->columns) {
+ /* try to map the name to the column */
+ int i;
+
+ for (i = 0; i < stmt->column_count; i++) {
+ if (strcmp(stmt->columns[i].name, param->name) == 0) {
+ param->paramno = i;
+ break;
+ }
+ }
+
+ /* if you prepare and then execute passing an array of params keyed by names,
+ * then this will trigger, and we don't want that */
+ if (param->paramno == -1) {
+ char *tmp;
+ spprintf(&tmp, 0, "Did not find column name '%s' in the defined columns; it will not be bound", param->name);
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", tmp TSRMLS_CC);
+ efree(tmp);
+ }
+ }
+
+ if (param->name) {
+ if (is_param && param->name[0] != ':') {
+ char *temp = emalloc(++param->namelen + 1);
+ temp[0] = ':';
+ memmove(temp+1, param->name, param->namelen);
+ param->name = temp;
+ } else {
+ param->name = estrndup(param->name, param->namelen);
+ }
+ }
+
+ if (is_param && !rewrite_name_to_position(stmt, param TSRMLS_CC)) {
+ if (param->name) {
+ efree(param->name);
+ param->name = NULL;
+ }
+ return 0;
+ }
+
+ /* ask the driver to perform any normalization it needs on the
+ * parameter name. Note that it is illegal for the driver to take
+ * a reference to param, as it resides in transient storage only
+ * at this time. */
+ if (stmt->methods->param_hook) {
+ if (!stmt->methods->param_hook(stmt, param, PDO_PARAM_EVT_NORMALIZE
+ TSRMLS_CC)) {
+ if (param->name) {
+ efree(param->name);
+ param->name = NULL;
+ }
+ return 0;
+ }
+ }
+
+ /* delete any other parameter registered with this number.
+ * If the parameter is named, it will be removed and correctly
+ * disposed of by the hash_update call that follows */
+ if (param->paramno >= 0) {
+ zend_hash_index_del(hash, param->paramno);
+ }
+
+ /* allocate storage for the parameter, keyed by its "canonical" name */
+ if (param->name) {
+ zend_hash_update(hash, param->name, param->namelen, param,
+ sizeof(*param), (void**)&pparam);
+ } else {
+ zend_hash_index_update(hash, param->paramno, param, sizeof(*param),
+ (void**)&pparam);
+ }
+
+ /* tell the driver we just created a parameter */
+ if (stmt->methods->param_hook) {
+ if (!stmt->methods->param_hook(stmt, pparam, PDO_PARAM_EVT_ALLOC
+ TSRMLS_CC)) {
+ /* undo storage allocation; the hash will free the parameter
+ * name if required */
+ if (pparam->name) {
+ zend_hash_del(hash, pparam->name, pparam->namelen);
+ } else {
+ zend_hash_index_del(hash, pparam->paramno);
+ }
+ /* param->parameter is freed by hash dtor */
+ param->parameter = NULL;
+ return 0;
+ }
+ }
+ return 1;
+}
+/* }}} */
+
+/* {{{ proto bool PDOStatement::execute([array $bound_input_params])
+ Execute a prepared statement, optionally binding parameters */
+static PHP_METHOD(PDOStatement, execute)
+{
+ zval *input_params = NULL;
+ int ret = 1;
+ PHP_STMT_GET_OBJ;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &input_params)) {
+ RETURN_FALSE;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+
+ if (input_params) {
+ struct pdo_bound_param_data param;
+ zval **tmp;
+ uint str_length;
+ ulong num_index;
+
+ if (stmt->bound_params) {
+ zend_hash_destroy(stmt->bound_params);
+ FREE_HASHTABLE(stmt->bound_params);
+ stmt->bound_params = NULL;
+ }
+
+ zend_hash_internal_pointer_reset(Z_ARRVAL_P(input_params));
+ while (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(input_params), (void*)&tmp)) {
+ memset(&param, 0, sizeof(param));
+
+ if (HASH_KEY_IS_STRING == zend_hash_get_current_key_ex(Z_ARRVAL_P(input_params),
+ &param.name, &str_length, &num_index, 0, NULL)) {
+ /* yes this is correct. we don't want to count the null byte. ask wez */
+ param.namelen = str_length - 1;
+ param.paramno = -1;
+ } else {
+ /* we're okay to be zero based here */
+ if (num_index < 0) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", NULL TSRMLS_CC);
+ RETURN_FALSE;
+ }
+ param.paramno = num_index;
+ }
+
+ param.param_type = PDO_PARAM_STR;
+ MAKE_STD_ZVAL(param.parameter);
+ MAKE_COPY_ZVAL(tmp, param.parameter);
+
+ if (!really_register_bound_param(&param, stmt, 1 TSRMLS_CC)) {
+ if (param.parameter) {
+ zval_ptr_dtor(&param.parameter);
+ }
+ RETURN_FALSE;
+ }
+
+ zend_hash_move_forward(Z_ARRVAL_P(input_params));
+ }
+ }
+
+ if (PDO_PLACEHOLDER_NONE == stmt->supports_placeholders) {
+ /* handle the emulated parameter binding,
+ * stmt->active_query_string holds the query with binds expanded and
+ * quoted.
+ */
+
+ ret = pdo_parse_params(stmt, stmt->query_string, stmt->query_stringlen,
+ &stmt->active_query_string, &stmt->active_query_stringlen TSRMLS_CC);
+
+ if (ret == 0) {
+ /* no changes were made */
+ stmt->active_query_string = stmt->query_string;
+ stmt->active_query_stringlen = stmt->query_stringlen;
+ ret = 1;
+ } else if (ret == -1) {
+ /* something broke */
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+ }
+ } else if (!dispatch_param_event(stmt, PDO_PARAM_EVT_EXEC_PRE TSRMLS_CC)) {
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+ }
+ if (stmt->methods->executer(stmt TSRMLS_CC)) {
+ if (stmt->active_query_string && stmt->active_query_string != stmt->query_string) {
+ efree(stmt->active_query_string);
+ }
+ stmt->active_query_string = NULL;
+ if (!stmt->executed) {
+ /* this is the first execute */
+
+ if (stmt->dbh->alloc_own_columns && !stmt->columns) {
+ /* for "big boy" drivers, we need to allocate memory to fetch
+ * the results into, so lets do that now */
+ ret = pdo_stmt_describe_columns(stmt TSRMLS_CC);
+ }
+
+ stmt->executed = 1;
+ }
+
+ if (ret && !dispatch_param_event(stmt, PDO_PARAM_EVT_EXEC_POST TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+
+ RETURN_BOOL(ret);
+ }
+ if (stmt->active_query_string && stmt->active_query_string != stmt->query_string) {
+ efree(stmt->active_query_string);
+ }
+ stmt->active_query_string = NULL;
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+}
+/* }}} */
+
+static inline void fetch_value(pdo_stmt_t *stmt, zval *dest, int colno, int *type_override TSRMLS_DC) /* {{{ */
+{
+ struct pdo_column_data *col;
+ char *value = NULL;
+ unsigned long value_len = 0;
+ int caller_frees = 0;
+ int type, new_type;
+
+ col = &stmt->columns[colno];
+ type = PDO_PARAM_TYPE(col->param_type);
+ new_type = type_override ? PDO_PARAM_TYPE(*type_override) : type;
+
+ value = NULL;
+ value_len = 0;
+
+ stmt->methods->get_col(stmt, colno, &value, &value_len, &caller_frees TSRMLS_CC);
+
+ switch (type) {
+ case PDO_PARAM_ZVAL:
+ if (value && value_len == sizeof(zval)) {
+ int need_copy = (new_type != PDO_PARAM_ZVAL || stmt->dbh->stringify) ? 1 : 0;
+ zval *zv = *(zval**)value;
+ ZVAL_ZVAL(dest, zv, need_copy, 1);
+ } else {
+ ZVAL_NULL(dest);
+ }
+
+ if (Z_TYPE_P(dest) == IS_NULL) {
+ type = new_type;
+ }
+ break;
+
+ case PDO_PARAM_INT:
+ if (value && value_len == sizeof(long)) {
+ ZVAL_LONG(dest, *(long*)value);
+ break;
+ }
+ ZVAL_NULL(dest);
+ break;
+
+ case PDO_PARAM_BOOL:
+ if (value && value_len == sizeof(zend_bool)) {
+ ZVAL_BOOL(dest, *(zend_bool*)value);
+ break;
+ }
+ ZVAL_NULL(dest);
+ break;
+
+ case PDO_PARAM_LOB:
+ if (value == NULL) {
+ ZVAL_NULL(dest);
+ } else if (value_len == 0) {
+ /* Warning, empty strings need to be passed as stream */
+ if (stmt->dbh->stringify || new_type == PDO_PARAM_STR) {
+ char *buf = NULL;
+ size_t len;
+ len = php_stream_copy_to_mem((php_stream*)value, &buf, PHP_STREAM_COPY_ALL, 0);
+ if(buf == NULL) {
+ ZVAL_EMPTY_STRING(dest);
+ } else {
+ ZVAL_STRINGL(dest, buf, len, 0);
+ }
+ php_stream_close((php_stream*)value);
+ } else {
+ php_stream_to_zval((php_stream*)value, dest);
+ }
+ } else if (!stmt->dbh->stringify && new_type != PDO_PARAM_STR) {
+ /* they gave us a string, but LOBs are represented as streams in PDO */
+ php_stream *stm;
+#ifdef TEMP_STREAM_TAKE_BUFFER
+ if (caller_frees) {
+ stm = php_stream_memory_open(TEMP_STREAM_TAKE_BUFFER, value, value_len);
+ if (stm) {
+ caller_frees = 0;
+ }
+ } else
+#endif
+ {
+ stm = php_stream_memory_open(TEMP_STREAM_READONLY, value, value_len);
+ }
+ if (stm) {
+ php_stream_to_zval(stm, dest);
+ } else {
+ ZVAL_NULL(dest);
+ }
+ } else {
+ ZVAL_STRINGL(dest, value, value_len, !caller_frees);
+ if (caller_frees) {
+ caller_frees = 0;
+ }
+ }
+ break;
+
+ case PDO_PARAM_STR:
+ if (value && !(value_len == 0 && stmt->dbh->oracle_nulls == PDO_NULL_EMPTY_STRING)) {
+ ZVAL_STRINGL(dest, value, value_len, !caller_frees);
+ if (caller_frees) {
+ caller_frees = 0;
+ }
+ break;
+ }
+ default:
+ ZVAL_NULL(dest);
+ }
+
+ if (type != new_type) {
+ switch (new_type) {
+ case PDO_PARAM_INT:
+ convert_to_long_ex(&dest);
+ break;
+ case PDO_PARAM_BOOL:
+ convert_to_boolean_ex(&dest);
+ break;
+ case PDO_PARAM_STR:
+ convert_to_string_ex(&dest);
+ break;
+ case PDO_PARAM_NULL:
+ convert_to_null_ex(&dest);
+ break;
+ default:
+ ;
+ }
+ }
+
+ if (caller_frees && value) {
+ efree(value);
+ }
+
+ if (stmt->dbh->stringify) {
+ switch (Z_TYPE_P(dest)) {
+ case IS_LONG:
+ case IS_DOUBLE:
+ convert_to_string(dest);
+ break;
+ }
+ }
+
+ if (Z_TYPE_P(dest) == IS_NULL && stmt->dbh->oracle_nulls == PDO_NULL_TO_STRING) {
+ ZVAL_EMPTY_STRING(dest);
+ }
+}
+/* }}} */
+
+static int do_fetch_common(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori,
+ long offset, int do_bind TSRMLS_DC) /* {{{ */
+{
+ if (!stmt->executed) {
+ return 0;
+ }
+
+ if (!dispatch_param_event(stmt, PDO_PARAM_EVT_FETCH_PRE TSRMLS_CC)) {
+ return 0;
+ }
+
+ if (!stmt->methods->fetcher(stmt, ori, offset TSRMLS_CC)) {
+ return 0;
+ }
+
+ /* some drivers might need to describe the columns now */
+ if (!stmt->columns && !pdo_stmt_describe_columns(stmt TSRMLS_CC)) {
+ return 0;
+ }
+
+ if (!dispatch_param_event(stmt, PDO_PARAM_EVT_FETCH_POST TSRMLS_CC)) {
+ return 0;
+ }
+
+ if (do_bind && stmt->bound_columns) {
+ /* update those bound column variables now */
+ struct pdo_bound_param_data *param;
+
+ zend_hash_internal_pointer_reset(stmt->bound_columns);
+ while (SUCCESS == zend_hash_get_current_data(stmt->bound_columns, (void**)&param)) {
+ if (param->paramno >= 0) {
+ convert_to_string(param->parameter);
+
+ /* delete old value */
+ zval_dtor(param->parameter);
+
+ /* set new value */
+ fetch_value(stmt, param->parameter, param->paramno, (int *)&param->param_type TSRMLS_CC);
+
+ /* TODO: some smart thing that avoids duplicating the value in the
+ * general loop below. For now, if you're binding output columns,
+ * it's better to use LAZY or BOUND fetches if you want to shave
+ * off those cycles */
+ }
+
+ zend_hash_move_forward(stmt->bound_columns);
+ }
+ }
+
+ return 1;
+}
+/* }}} */
+
+static int do_fetch_class_prepare(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
+{
+ zend_class_entry * ce = stmt->fetch.cls.ce;
+ zend_fcall_info * fci = &stmt->fetch.cls.fci;
+ zend_fcall_info_cache * fcc = &stmt->fetch.cls.fcc;
+
+ fci->size = sizeof(zend_fcall_info);
+
+ if (!ce) {
+ stmt->fetch.cls.ce = ZEND_STANDARD_CLASS_DEF_PTR;
+ ce = ZEND_STANDARD_CLASS_DEF_PTR;
+ }
+
+ if (ce->constructor) {
+ fci->function_table = &ce->function_table;
+ fci->function_name = NULL;
+ fci->symbol_table = NULL;
+ fci->retval_ptr_ptr = &stmt->fetch.cls.retval_ptr;
+ if (stmt->fetch.cls.ctor_args) {
+ HashTable *ht = Z_ARRVAL_P(stmt->fetch.cls.ctor_args);
+ Bucket *p;
+
+ fci->param_count = 0;
+ fci->params = safe_emalloc(sizeof(zval**), ht->nNumOfElements, 0);
+ p = ht->pListHead;
+ while (p != NULL) {
+ fci->params[fci->param_count++] = (zval**)p->pData;
+ p = p->pListNext;
+ }
+ } else {
+ fci->param_count = 0;
+ fci->params = NULL;
+ }
+ fci->no_separation = 1;
+
+ fcc->initialized = 1;
+ fcc->function_handler = ce->constructor;
+ fcc->calling_scope = EG(scope);
+ fcc->called_scope = ce;
+ return 1;
+ } else if (stmt->fetch.cls.ctor_args) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "user-supplied class does not have a constructor, use NULL for the ctor_params parameter, or simply omit it" TSRMLS_CC);
+ return 0;
+ } else {
+ return 1; /* no ctor no args is also ok */
+ }
+}
+/* }}} */
+
+static int make_callable_ex(pdo_stmt_t *stmt, zval *callable, zend_fcall_info * fci, zend_fcall_info_cache * fcc, int num_args TSRMLS_DC) /* {{{ */
+{
+ char *is_callable_error = NULL;
+
+ if (zend_fcall_info_init(callable, 0, fci, fcc, NULL, &is_callable_error TSRMLS_CC) == FAILURE) {
+ if (is_callable_error) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", is_callable_error TSRMLS_CC);
+ efree(is_callable_error);
+ } else {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "user-supplied function must be a valid callback" TSRMLS_CC);
+ }
+ return 0;
+ }
+ if (is_callable_error) {
+ /* Possible E_STRICT error message */
+ efree(is_callable_error);
+ }
+
+ fci->param_count = num_args; /* probably less */
+ fci->params = safe_emalloc(sizeof(zval**), num_args, 0);
+
+ return 1;
+}
+/* }}} */
+
+static int do_fetch_func_prepare(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
+{
+ zend_fcall_info * fci = &stmt->fetch.cls.fci;
+ zend_fcall_info_cache * fcc = &stmt->fetch.cls.fcc;
+
+ if (!make_callable_ex(stmt, stmt->fetch.func.function, fci, fcc, stmt->column_count TSRMLS_CC)) {
+ return 0;
+ } else {
+ stmt->fetch.func.values = safe_emalloc(sizeof(zval*), stmt->column_count, 0);
+ return 1;
+ }
+}
+/* }}} */
+
+static int do_fetch_opt_finish(pdo_stmt_t *stmt, int free_ctor_agrs TSRMLS_DC) /* {{{ */
+{
+ /* fci.size is used to check if it is valid */
+ if (stmt->fetch.cls.fci.size && stmt->fetch.cls.fci.params) {
+ efree(stmt->fetch.cls.fci.params);
+ stmt->fetch.cls.fci.params = NULL;
+ }
+ stmt->fetch.cls.fci.size = 0;
+ if (stmt->fetch.cls.ctor_args && free_ctor_agrs) {
+ zval_ptr_dtor(&stmt->fetch.cls.ctor_args);
+ stmt->fetch.cls.ctor_args = NULL;
+ stmt->fetch.cls.fci.param_count = 0;
+ }
+ if (stmt->fetch.func.values) {
+ efree(stmt->fetch.func.values);
+ stmt->fetch.func.values = NULL;
+ }
+ return 1;
+}
+/* }}} */
+
+/* perform a fetch. If do_bind is true, update any bound columns.
+ * If return_value is not null, store values into it according to HOW. */
+static int do_fetch(pdo_stmt_t *stmt, int do_bind, zval *return_value,
+ enum pdo_fetch_type how, enum pdo_fetch_orientation ori, long offset, zval *return_all TSRMLS_DC) /* {{{ */
+{
+ int flags, idx, old_arg_count = 0;
+ zend_class_entry *ce = NULL, *old_ce = NULL;
+ zval grp_val, *grp, **pgrp, *retval, *old_ctor_args = NULL;
+ int colno;
+
+ if (how == PDO_FETCH_USE_DEFAULT) {
+ how = stmt->default_fetch_type;
+ }
+ flags = how & PDO_FETCH_FLAGS;
+ how = how & ~PDO_FETCH_FLAGS;
+
+ if (!do_fetch_common(stmt, ori, offset, do_bind TSRMLS_CC)) {
+ return 0;
+ }
+
+ if (how == PDO_FETCH_BOUND) {
+ RETVAL_TRUE;
+ return 1;
+ }
+
+ if (flags & PDO_FETCH_GROUP && stmt->fetch.column == -1) {
+ colno = 1;
+ } else {
+ colno = stmt->fetch.column;
+ }
+
+ if (return_value) {
+ int i = 0;
+
+ if (how == PDO_FETCH_LAZY) {
+ get_lazy_object(stmt, return_value TSRMLS_CC);
+ return 1;
+ }
+
+ RETVAL_FALSE;
+
+ switch (how) {
+ case PDO_FETCH_USE_DEFAULT:
+ case PDO_FETCH_ASSOC:
+ case PDO_FETCH_BOTH:
+ case PDO_FETCH_NUM:
+ case PDO_FETCH_NAMED:
+ if (!return_all) {
+ ALLOC_HASHTABLE(return_value->value.ht);
+ zend_hash_init(return_value->value.ht, stmt->column_count, NULL, ZVAL_PTR_DTOR, 0);
+ Z_TYPE_P(return_value) = IS_ARRAY;
+ } else {
+ array_init(return_value);
+ }
+ break;
+
+ case PDO_FETCH_KEY_PAIR:
+ if (stmt->column_count != 2) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain extactly 2 columns." TSRMLS_CC);
+ return 0;
+ }
+ if (!return_all) {
+ array_init(return_value);
+ }
+ break;
+
+ case PDO_FETCH_COLUMN:
+ if (colno >= 0 && colno < stmt->column_count) {
+ if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) {
+ fetch_value(stmt, return_value, 1, NULL TSRMLS_CC);
+ } else if (flags == PDO_FETCH_GROUP && colno) {
+ fetch_value(stmt, return_value, 0, NULL TSRMLS_CC);
+ } else {
+ fetch_value(stmt, return_value, colno, NULL TSRMLS_CC);
+ }
+ if (!return_all) {
+ return 1;
+ } else {
+ break;
+ }
+ } else {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Invalid column index" TSRMLS_CC);
+ }
+ return 0;
+
+ case PDO_FETCH_OBJ:
+ object_init_ex(return_value, ZEND_STANDARD_CLASS_DEF_PTR);
+ break;
+
+ case PDO_FETCH_CLASS:
+ if (flags & PDO_FETCH_CLASSTYPE) {
+ zval val;
+ zend_class_entry **cep;
+
+ old_ce = stmt->fetch.cls.ce;
+ old_ctor_args = stmt->fetch.cls.ctor_args;
+ old_arg_count = stmt->fetch.cls.fci.param_count;
+ do_fetch_opt_finish(stmt, 0 TSRMLS_CC);
+
+ INIT_PZVAL(&val);
+ fetch_value(stmt, &val, i++, NULL TSRMLS_CC);
+ if (Z_TYPE(val) != IS_NULL) {
+ convert_to_string(&val);
+ if (zend_lookup_class(Z_STRVAL(val), Z_STRLEN(val), &cep TSRMLS_CC) == FAILURE) {
+ stmt->fetch.cls.ce = ZEND_STANDARD_CLASS_DEF_PTR;
+ } else {
+ stmt->fetch.cls.ce = *cep;
+ }
+ }
+
+ do_fetch_class_prepare(stmt TSRMLS_CC);
+ zval_dtor(&val);
+ }
+ ce = stmt->fetch.cls.ce;
+ if (!ce) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch class specified" TSRMLS_CC);
+ return 0;
+ }
+ if ((flags & PDO_FETCH_SERIALIZE) == 0) {
+ object_init_ex(return_value, ce);
+ if (!stmt->fetch.cls.fci.size) {
+ if (!do_fetch_class_prepare(stmt TSRMLS_CC))
+ {
+ return 0;
+ }
+ }
+ if (ce->constructor && (flags & PDO_FETCH_PROPS_LATE)) {
+ stmt->fetch.cls.fci.object_ptr = return_value;
+ stmt->fetch.cls.fcc.object_ptr = return_value;
+ if (zend_call_function(&stmt->fetch.cls.fci, &stmt->fetch.cls.fcc TSRMLS_CC) == FAILURE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call class constructor" TSRMLS_CC);
+ return 0;
+ } else {
+ if (stmt->fetch.cls.retval_ptr) {
+ zval_ptr_dtor(&stmt->fetch.cls.retval_ptr);
+ }
+ }
+ }
+ }
+ break;
+
+ case PDO_FETCH_INTO:
+ if (!stmt->fetch.into) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch-into object specified." TSRMLS_CC);
+ return 0;
+ break;
+ }
+
+ Z_TYPE_P(return_value) = IS_OBJECT;
+ Z_OBJ_HANDLE_P(return_value) = Z_OBJ_HANDLE_P(stmt->fetch.into);
+ Z_OBJ_HT_P(return_value) = Z_OBJ_HT_P(stmt->fetch.into);
+ zend_objects_store_add_ref(stmt->fetch.into TSRMLS_CC);
+
+ if (zend_get_class_entry(return_value TSRMLS_CC) == ZEND_STANDARD_CLASS_DEF_PTR) {
+ how = PDO_FETCH_OBJ;
+ }
+ break;
+
+ case PDO_FETCH_FUNC:
+ if (!stmt->fetch.func.function) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch function specified" TSRMLS_CC);
+ return 0;
+ }
+ if (!stmt->fetch.func.fci.size) {
+ if (!do_fetch_func_prepare(stmt TSRMLS_CC))
+ {
+ return 0;
+ }
+ }
+ break;
+
+
+ default:
+ /* shouldn't happen */
+ return 0;
+ }
+
+ if (return_all && how != PDO_FETCH_KEY_PAIR) {
+ INIT_PZVAL(&grp_val);
+ if (flags == PDO_FETCH_GROUP && how == PDO_FETCH_COLUMN && stmt->fetch.column > 0) {
+ fetch_value(stmt, &grp_val, colno, NULL TSRMLS_CC);
+ } else {
+ fetch_value(stmt, &grp_val, i, NULL TSRMLS_CC);
+ }
+ convert_to_string(&grp_val);
+ if (how == PDO_FETCH_COLUMN) {
+ i = stmt->column_count; /* no more data to fetch */
+ } else {
+ i++;
+ }
+ }
+
+ for (idx = 0; i < stmt->column_count; i++, idx++) {
+ zval *val;
+ MAKE_STD_ZVAL(val);
+ fetch_value(stmt, val, i, NULL TSRMLS_CC);
+
+ switch (how) {
+ case PDO_FETCH_ASSOC:
+ add_assoc_zval(return_value, stmt->columns[i].name, val);
+ break;
+
+ case PDO_FETCH_KEY_PAIR:
+ {
+ zval *tmp;
+ MAKE_STD_ZVAL(tmp);
+ fetch_value(stmt, tmp, ++i, NULL TSRMLS_CC);
+
+ if (Z_TYPE_P(val) == IS_LONG) {
+ zend_hash_index_update((return_all ? Z_ARRVAL_P(return_all) : Z_ARRVAL_P(return_value)), Z_LVAL_P(val), &tmp, sizeof(zval *), NULL);
+ } else {
+ convert_to_string(val);
+ zend_symtable_update((return_all ? Z_ARRVAL_P(return_all) : Z_ARRVAL_P(return_value)), Z_STRVAL_P(val), Z_STRLEN_P(val) + 1, &tmp, sizeof(zval *), NULL);
+ }
+ zval_ptr_dtor(&val);
+ return 1;
+ }
+ break;
+
+ case PDO_FETCH_USE_DEFAULT:
+ case PDO_FETCH_BOTH:
+ add_assoc_zval(return_value, stmt->columns[i].name, val);
+ Z_ADDREF_P(val);
+ add_next_index_zval(return_value, val);
+ break;
+
+ case PDO_FETCH_NAMED:
+ /* already have an item with this name? */
+ {
+ zval **curr_val = NULL;
+ if (zend_hash_find(Z_ARRVAL_P(return_value), stmt->columns[i].name,
+ strlen(stmt->columns[i].name)+1,
+ (void**)&curr_val) == SUCCESS) {
+ zval *arr;
+ if (Z_TYPE_PP(curr_val) != IS_ARRAY) {
+ /* a little bit of black magic here:
+ * we're creating a new array and swapping it for the
+ * zval that's already stored in the hash under the name
+ * we want. We then add that zval to the array.
+ * This is effectively the same thing as:
+ * if (!is_array($hash[$name])) {
+ * $hash[$name] = array($hash[$name]);
+ * }
+ * */
+ zval *cur;
+
+ MAKE_STD_ZVAL(arr);
+ array_init(arr);
+
+ cur = *curr_val;
+ *curr_val = arr;
+
+ add_next_index_zval(arr, cur);
+ } else {
+ arr = *curr_val;
+ }
+ add_next_index_zval(arr, val);
+ } else {
+ add_assoc_zval(return_value, stmt->columns[i].name, val);
+ }
+ }
+ break;
+
+ case PDO_FETCH_NUM:
+ add_next_index_zval(return_value, val);
+ break;
+
+ case PDO_FETCH_OBJ:
+ case PDO_FETCH_INTO:
+ zend_update_property(NULL, return_value,
+ stmt->columns[i].name, stmt->columns[i].namelen,
+ val TSRMLS_CC);
+ zval_ptr_dtor(&val);
+ break;
+
+ case PDO_FETCH_CLASS:
+ if ((flags & PDO_FETCH_SERIALIZE) == 0 || idx) {
+ zend_update_property(ce, return_value,
+ stmt->columns[i].name, stmt->columns[i].namelen,
+ val TSRMLS_CC);
+ zval_ptr_dtor(&val);
+ } else {
+#ifdef MBO_0
+ php_unserialize_data_t var_hash;
+
+ PHP_VAR_UNSERIALIZE_INIT(var_hash);
+ if (php_var_unserialize(&return_value, (const unsigned char**)&Z_STRVAL_P(val), Z_STRVAL_P(val)+Z_STRLEN_P(val), NULL TSRMLS_CC) == FAILURE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "cannot unserialize data" TSRMLS_CC);
+ PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
+ return 0;
+ }
+ PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
+#endif
+#if PHP_MAJOR_VERSION > 5 || PHP_MINOR_VERSION >= 1
+ if (!ce->unserialize) {
+ zval_ptr_dtor(&val);
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "cannot unserialize class" TSRMLS_CC);
+ return 0;
+ } else if (ce->unserialize(&return_value, ce, Z_TYPE_P(val) == IS_STRING ? Z_STRVAL_P(val) : "", Z_TYPE_P(val) == IS_STRING ? Z_STRLEN_P(val) : 0, NULL TSRMLS_CC) == FAILURE) {
+ zval_ptr_dtor(&val);
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "cannot unserialize class" TSRMLS_CC);
+ zval_dtor(return_value);
+ ZVAL_NULL(return_value);
+ return 0;
+ } else {
+ zval_ptr_dtor(&val);
+ }
+#endif
+ }
+ break;
+
+ case PDO_FETCH_FUNC:
+ stmt->fetch.func.values[idx] = val;
+ stmt->fetch.cls.fci.params[idx] = &stmt->fetch.func.values[idx];
+ break;
+
+ default:
+ zval_ptr_dtor(&val);
+ pdo_raise_impl_error(stmt->dbh, stmt, "22003", "mode is out of range" TSRMLS_CC);
+ return 0;
+ break;
+ }
+ }
+
+ switch (how) {
+ case PDO_FETCH_CLASS:
+ if (ce->constructor && !(flags & (PDO_FETCH_PROPS_LATE | PDO_FETCH_SERIALIZE))) {
+ stmt->fetch.cls.fci.object_ptr = return_value;
+ stmt->fetch.cls.fcc.object_ptr = return_value;
+ if (zend_call_function(&stmt->fetch.cls.fci, &stmt->fetch.cls.fcc TSRMLS_CC) == FAILURE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call class constructor" TSRMLS_CC);
+ return 0;
+ } else {
+ if (stmt->fetch.cls.retval_ptr) {
+ zval_ptr_dtor(&stmt->fetch.cls.retval_ptr);
+ }
+ }
+ }
+ if (flags & PDO_FETCH_CLASSTYPE) {
+ do_fetch_opt_finish(stmt, 0 TSRMLS_CC);
+ stmt->fetch.cls.ce = old_ce;
+ stmt->fetch.cls.ctor_args = old_ctor_args;
+ stmt->fetch.cls.fci.param_count = old_arg_count;
+ }
+ break;
+
+ case PDO_FETCH_FUNC:
+ stmt->fetch.func.fci.param_count = idx;
+ stmt->fetch.func.fci.retval_ptr_ptr = &retval;
+ if (zend_call_function(&stmt->fetch.func.fci, &stmt->fetch.func.fcc TSRMLS_CC) == FAILURE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call user-supplied function" TSRMLS_CC);
+ return 0;
+ } else {
+ if (return_all) {
+ zval_ptr_dtor(&return_value); /* we don't need that */
+ return_value = retval;
+ } else if (retval) {
+ MAKE_COPY_ZVAL(&retval, return_value);
+ zval_ptr_dtor(&retval);
+ }
+ }
+ while(idx--) {
+ zval_ptr_dtor(&stmt->fetch.func.values[idx]);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (return_all) {
+ if ((flags & PDO_FETCH_UNIQUE) == PDO_FETCH_UNIQUE) {
+ add_assoc_zval(return_all, Z_STRVAL(grp_val), return_value);
+ } else {
+ if (zend_symtable_find(Z_ARRVAL_P(return_all), Z_STRVAL(grp_val), Z_STRLEN(grp_val)+1, (void**)&pgrp) == FAILURE) {
+ MAKE_STD_ZVAL(grp);
+ array_init(grp);
+ add_assoc_zval(return_all, Z_STRVAL(grp_val), grp);
+ } else {
+ grp = *pgrp;
+ }
+ add_next_index_zval(grp, return_value);
+ }
+ zval_dtor(&grp_val);
+ }
+
+ }
+
+ return 1;
+}
+/* }}} */
+
+static int pdo_stmt_verify_mode(pdo_stmt_t *stmt, long mode, int fetch_all TSRMLS_DC) /* {{{ */
+{
+ int flags = mode & PDO_FETCH_FLAGS;
+
+ mode = mode & ~PDO_FETCH_FLAGS;
+
+ if (mode < 0 || mode > PDO_FETCH__MAX) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "invalid fetch mode" TSRMLS_CC);
+ return 0;
+ }
+
+ if (mode == PDO_FETCH_USE_DEFAULT) {
+ flags = stmt->default_fetch_type & PDO_FETCH_FLAGS;
+ mode = stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
+ }
+
+#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 1
+ if ((flags & PDO_FETCH_SERIALIZE) == PDO_FETCH_SERIALIZE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "PDO::FETCH_SERIALIZE is not supported in this PHP version" TSRMLS_CC);
+ return 0;
+ }
+#endif
+
+ switch(mode) {
+ case PDO_FETCH_FUNC:
+ if (!fetch_all) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_FUNC is only allowed in PDOStatement::fetchAll()" TSRMLS_CC);
+ return 0;
+ }
+ return 1;
+
+ case PDO_FETCH_LAZY:
+ if (fetch_all) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_LAZY can't be used with PDOStatement::fetchAll()" TSRMLS_CC);
+ return 0;
+ }
+ /* fall through */
+
+ default:
+ if ((flags & PDO_FETCH_SERIALIZE) == PDO_FETCH_SERIALIZE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_SERIALIZE can only be used together with PDO::FETCH_CLASS" TSRMLS_CC);
+ return 0;
+ }
+ if ((flags & PDO_FETCH_CLASSTYPE) == PDO_FETCH_CLASSTYPE) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_CLASSTYPE can only be used together with PDO::FETCH_CLASS" TSRMLS_CC);
+ return 0;
+ }
+ if (mode >= PDO_FETCH__MAX) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "invalid fetch mode" TSRMLS_CC);
+ return 0;
+ }
+ /* no break; */
+
+ case PDO_FETCH_CLASS:
+ return 1;
+ }
+}
+/* }}} */
+
+/* {{{ proto mixed PDOStatement::fetch([int $how = PDO_FETCH_BOTH [, int $orientation [, int $offset]]])
+ Fetches the next row and returns it, or false if there are no more rows */
+static PHP_METHOD(PDOStatement, fetch)
+{
+ long how = PDO_FETCH_USE_DEFAULT;
+ long ori = PDO_FETCH_ORI_NEXT;
+ long off = 0;
+ PHP_STMT_GET_OBJ;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|lll", &how,
+ &ori, &off)) {
+ RETURN_FALSE;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+
+ if (!pdo_stmt_verify_mode(stmt, how, 0 TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+
+ if (!do_fetch(stmt, TRUE, return_value, how, ori, off, 0 TSRMLS_CC)) {
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+ }
+}
+/* }}} */
+
+/* {{{ proto mixed PDOStatement::fetchObject([string class_name [, NULL|array ctor_args]])
+ Fetches the next row and returns it as an object. */
+static PHP_METHOD(PDOStatement, fetchObject)
+{
+ long how = PDO_FETCH_CLASS;
+ long ori = PDO_FETCH_ORI_NEXT;
+ long off = 0;
+ char *class_name = NULL;
+ int class_name_len;
+ zend_class_entry *old_ce;
+ zval *old_ctor_args, *ctor_args = NULL;
+ int error = 0, old_arg_count;
+
+ PHP_STMT_GET_OBJ;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!a", &class_name, &class_name_len, &ctor_args)) {
+ RETURN_FALSE;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+
+ if (!pdo_stmt_verify_mode(stmt, how, 0 TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+
+ old_ce = stmt->fetch.cls.ce;
+ old_ctor_args = stmt->fetch.cls.ctor_args;
+ old_arg_count = stmt->fetch.cls.fci.param_count;
+
+ do_fetch_opt_finish(stmt, 0 TSRMLS_CC);
+
+ if (ctor_args) {
+ if (Z_TYPE_P(ctor_args) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(ctor_args))) {
+ ALLOC_ZVAL(stmt->fetch.cls.ctor_args);
+ *stmt->fetch.cls.ctor_args = *ctor_args;
+ zval_copy_ctor(stmt->fetch.cls.ctor_args);
+ } else {
+ stmt->fetch.cls.ctor_args = NULL;
+ }
+ }
+ if (class_name && !error) {
+ stmt->fetch.cls.ce = zend_fetch_class(class_name, class_name_len, ZEND_FETCH_CLASS_AUTO TSRMLS_CC);
+
+ if (!stmt->fetch.cls.ce) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Could not find user-supplied class" TSRMLS_CC);
+ error = 1;
+ }
+ } else if (!error) {
+ stmt->fetch.cls.ce = zend_standard_class_def;
+ }
+
+ if (!error && !do_fetch(stmt, TRUE, return_value, how, ori, off, 0 TSRMLS_CC)) {
+ error = 1;
+ }
+ if (error) {
+ PDO_HANDLE_STMT_ERR();
+ }
+ do_fetch_opt_finish(stmt, 1 TSRMLS_CC);
+
+ stmt->fetch.cls.ce = old_ce;
+ stmt->fetch.cls.ctor_args = old_ctor_args;
+ stmt->fetch.cls.fci.param_count = old_arg_count;
+ if (error) {
+ RETURN_FALSE;
+ }
+}
+/* }}} */
+
+/* {{{ proto string PDOStatement::fetchColumn([int column_number])
+ Returns a data of the specified column in the result set. */
+static PHP_METHOD(PDOStatement, fetchColumn)
+{
+ long col_n = 0;
+ PHP_STMT_GET_OBJ;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &col_n)) {
+ RETURN_FALSE;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+
+ if (!do_fetch_common(stmt, PDO_FETCH_ORI_NEXT, 0, TRUE TSRMLS_CC)) {
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+ }
+
+ fetch_value(stmt, return_value, col_n, NULL TSRMLS_CC);
+}
+/* }}} */
+
+/* {{{ proto array PDOStatement::fetchAll([int $how = PDO_FETCH_BOTH [, string class_name [, NULL|array ctor_args]]])
+ Returns an array of all of the results. */
+static PHP_METHOD(PDOStatement, fetchAll)
+{
+ long how = PDO_FETCH_USE_DEFAULT;
+ zval *data, *return_all;
+ zval *arg2;
+ zend_class_entry *old_ce;
+ zval *old_ctor_args, *ctor_args = NULL;
+ int error = 0, flags, old_arg_count;
+ PHP_STMT_GET_OBJ;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|lzz", &how, &arg2, &ctor_args)) {
+ RETURN_FALSE;
+ }
+
+ if (!pdo_stmt_verify_mode(stmt, how, 1 TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+
+ old_ce = stmt->fetch.cls.ce;
+ old_ctor_args = stmt->fetch.cls.ctor_args;
+ old_arg_count = stmt->fetch.cls.fci.param_count;
+
+ do_fetch_opt_finish(stmt, 0 TSRMLS_CC);
+
+ switch(how & ~PDO_FETCH_FLAGS) {
+ case PDO_FETCH_CLASS:
+ switch(ZEND_NUM_ARGS()) {
+ case 0:
+ case 1:
+ stmt->fetch.cls.ce = zend_standard_class_def;
+ break;
+ case 3:
+ if (Z_TYPE_P(ctor_args) != IS_NULL && Z_TYPE_P(ctor_args) != IS_ARRAY) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "ctor_args must be either NULL or an array" TSRMLS_CC);
+ error = 1;
+ break;
+ }
+ if (Z_TYPE_P(ctor_args) != IS_ARRAY || !zend_hash_num_elements(Z_ARRVAL_P(ctor_args))) {
+ ctor_args = NULL;
+ }
+ /* no break */
+ case 2:
+ stmt->fetch.cls.ctor_args = ctor_args; /* we're not going to free these */
+ if (Z_TYPE_P(arg2) != IS_STRING) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Invalid class name (should be a string)" TSRMLS_CC);
+ error = 1;
+ break;
+ } else {
+ stmt->fetch.cls.ce = zend_fetch_class(Z_STRVAL_P(arg2), Z_STRLEN_P(arg2), ZEND_FETCH_CLASS_AUTO TSRMLS_CC);
+ if (!stmt->fetch.cls.ce) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not find user-specified class" TSRMLS_CC);
+ error = 1;
+ break;
+ }
+ }
+ }
+ if (!error) {
+ do_fetch_class_prepare(stmt TSRMLS_CC);
+ }
+ break;
+
+ case PDO_FETCH_FUNC:
+ switch(ZEND_NUM_ARGS()) {
+ case 0:
+ case 1:
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "no fetch function specified" TSRMLS_CC);
+ error = 1;
+ break;
+ case 3:
+ case 2:
+ stmt->fetch.func.function = arg2;
+ if (do_fetch_func_prepare(stmt TSRMLS_CC) == 0) {
+ error = 1;
+ }
+ break;
+ }
+ break;
+
+ case PDO_FETCH_COLUMN:
+ switch(ZEND_NUM_ARGS()) {
+ case 0:
+ case 1:
+ stmt->fetch.column = how & PDO_FETCH_GROUP ? -1 : 0;
+ break;
+ case 2:
+ convert_to_long(arg2);
+ stmt->fetch.column = Z_LVAL_P(arg2);
+ break;
+ case 3:
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Third parameter not allowed for PDO::FETCH_COLUMN" TSRMLS_CC);
+ error = 1;
+ }
+ break;
+
+ default:
+ if (ZEND_NUM_ARGS() > 1) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Extraneous additional parameters" TSRMLS_CC);
+ error = 1;
+ }
+ }
+
+ flags = how & PDO_FETCH_FLAGS;
+
+ if ((how & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) {
+ flags |= stmt->default_fetch_type & PDO_FETCH_FLAGS;
+ how |= stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
+ }
+
+ if (!error) {
+ PDO_STMT_CLEAR_ERR();
+ MAKE_STD_ZVAL(data);
+ if ( (how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
+ (how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)
+ ) {
+ array_init(return_value);
+ return_all = return_value;
+ } else {
+ return_all = 0;
+ }
+ if (!do_fetch(stmt, TRUE, data, how | flags, PDO_FETCH_ORI_NEXT, 0, return_all TSRMLS_CC)) {
+ FREE_ZVAL(data);
+ error = 2;
+ }
+ }
+ if (!error) {
+ if ((how & PDO_FETCH_GROUP)) {
+ do {
+ MAKE_STD_ZVAL(data);
+ } while (do_fetch(stmt, TRUE, data, how | flags, PDO_FETCH_ORI_NEXT, 0, return_all TSRMLS_CC));
+ } else if (how == PDO_FETCH_KEY_PAIR || (how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)) {
+ while (do_fetch(stmt, TRUE, data, how | flags, PDO_FETCH_ORI_NEXT, 0, return_all TSRMLS_CC));
+ } else {
+ array_init(return_value);
+ do {
+ add_next_index_zval(return_value, data);
+ MAKE_STD_ZVAL(data);
+ } while (do_fetch(stmt, TRUE, data, how | flags, PDO_FETCH_ORI_NEXT, 0, 0 TSRMLS_CC));
+ }
+ FREE_ZVAL(data);
+ }
+
+ do_fetch_opt_finish(stmt, 0 TSRMLS_CC);
+
+ stmt->fetch.cls.ce = old_ce;
+ stmt->fetch.cls.ctor_args = old_ctor_args;
+ stmt->fetch.cls.fci.param_count = old_arg_count;
+
+ if (error) {
+ PDO_HANDLE_STMT_ERR();
+ if (error != 2) {
+ RETURN_FALSE;
+ } else { /* on no results, return an empty array */
+ if (Z_TYPE_P(return_value) != IS_ARRAY) {
+ array_init(return_value);
+ }
+ return;
+ }
+ }
+}
+/* }}} */
+
+static int register_bound_param(INTERNAL_FUNCTION_PARAMETERS, pdo_stmt_t *stmt, int is_param) /* {{{ */
+{
+ struct pdo_bound_param_data param = {0};
+ long param_type = PDO_PARAM_STR;
+
+ param.paramno = -1;
+
+ if (FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC,
+ "lz|llz!", &param.paramno, &param.parameter, &param_type, &param.max_value_len,
+ &param.driver_params)) {
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|llz!", &param.name,
+ &param.namelen, &param.parameter, &param_type, &param.max_value_len,
+ &param.driver_params)) {
+ return 0;
+ }
+ }
+
+ param.param_type = (int) param_type;
+
+ if (param.paramno > 0) {
+ --param.paramno; /* make it zero-based internally */
+ } else if (!param.name) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "Columns/Parameters are 1-based" TSRMLS_CC);
+ return 0;
+ }
+
+ Z_ADDREF_P(param.parameter);
+ if (!really_register_bound_param(&param, stmt, is_param TSRMLS_CC)) {
+ if (param.parameter) {
+ zval_ptr_dtor(&(param.parameter));
+ param.parameter = NULL;
+ }
+ return 0;
+ }
+ return 1;
+} /* }}} */
+
+/* {{{ proto bool PDOStatement::bindValue(mixed $paramno, mixed $param [, int $type ])
+ bind an input parameter to the value of a PHP variable. $paramno is the 1-based position of the placeholder in the SQL statement (but can be the parameter name for drivers that support named placeholders). It should be called prior to execute(). */
+static PHP_METHOD(PDOStatement, bindValue)
+{
+ struct pdo_bound_param_data param = {0};
+ long param_type = PDO_PARAM_STR;
+ PHP_STMT_GET_OBJ;
+
+ param.paramno = -1;
+
+ if (FAILURE == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC,
+ "lz/|l", &param.paramno, &param.parameter, &param_type)) {
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz/|l", &param.name,
+ &param.namelen, &param.parameter, &param_type)) {
+ RETURN_FALSE;
+ }
+ }
+
+ param.param_type = (int) param_type;
+
+ if (param.paramno > 0) {
+ --param.paramno; /* make it zero-based internally */
+ } else if (!param.name) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "Columns/Parameters are 1-based" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+
+ Z_ADDREF_P(param.parameter);
+ if (!really_register_bound_param(&param, stmt, TRUE TSRMLS_CC)) {
+ if (param.parameter) {
+ zval_ptr_dtor(&(param.parameter));
+ param.parameter = NULL;
+ }
+ RETURN_FALSE;
+ }
+ RETURN_TRUE;
+}
+/* }}} */
+
+
+/* {{{ proto bool PDOStatement::bindParam(mixed $paramno, mixed &$param [, int $type [, int $maxlen [, mixed $driverdata]]])
+ bind a parameter to a PHP variable. $paramno is the 1-based position of the placeholder in the SQL statement (but can be the parameter name for drivers that support named placeholders). This isn't supported by all drivers. It should be called prior to execute(). */
+static PHP_METHOD(PDOStatement, bindParam)
+{
+ PHP_STMT_GET_OBJ;
+ RETURN_BOOL(register_bound_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, stmt, TRUE));
+}
+/* }}} */
+
+/* {{{ proto bool PDOStatement::bindColumn(mixed $column, mixed &$param [, int $type [, int $maxlen [, mixed $driverdata]]])
+ bind a column to a PHP variable. On each row fetch $param will contain the value of the corresponding column. $column is the 1-based offset of the column, or the column name. For portability, don't call this before execute(). */
+static PHP_METHOD(PDOStatement, bindColumn)
+{
+ PHP_STMT_GET_OBJ;
+ RETURN_BOOL(register_bound_param(INTERNAL_FUNCTION_PARAM_PASSTHRU, stmt, FALSE));
+}
+/* }}} */
+
+/* {{{ proto int PDOStatement::rowCount()
+ Returns the number of rows in a result set, or the number of rows affected by the last execute(). It is not always meaningful. */
+static PHP_METHOD(PDOStatement, rowCount)
+{
+ PHP_STMT_GET_OBJ;
+
+ RETURN_LONG(stmt->row_count);
+}
+/* }}} */
+
+/* {{{ proto string PDOStatement::errorCode()
+ Fetch the error code associated with the last operation on the statement handle */
+static PHP_METHOD(PDOStatement, errorCode)
+{
+ PHP_STMT_GET_OBJ;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ if (stmt->error_code[0] == '\0') {
+ RETURN_NULL();
+ }
+
+ RETURN_STRING(stmt->error_code, 1);
+}
+/* }}} */
+
+/* {{{ proto array PDOStatement::errorInfo()
+ Fetch extended error information associated with the last operation on the statement handle */
+static PHP_METHOD(PDOStatement, errorInfo)
+{
+ int error_count;
+ int error_count_diff = 0;
+ int error_expected_count = 3;
+
+ PHP_STMT_GET_OBJ;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ array_init(return_value);
+ add_next_index_string(return_value, stmt->error_code, 1);
+
+ if (stmt->dbh->methods->fetch_err) {
+ stmt->dbh->methods->fetch_err(stmt->dbh, stmt, return_value TSRMLS_CC);
+ }
+
+ error_count = zend_hash_num_elements(Z_ARRVAL_P(return_value));
+
+ if (error_expected_count > error_count) {
+ int current_index;
+
+ error_count_diff = error_expected_count - error_count;
+ for (current_index = 0; current_index < error_count_diff; current_index++) {
+ add_next_index_null(return_value);
+ }
+ }
+}
+/* }}} */
+
+/* {{{ proto bool PDOStatement::setAttribute(long attribute, mixed value)
+ Set an attribute */
+static PHP_METHOD(PDOStatement, setAttribute)
+{
+ long attr;
+ zval *value = NULL;
+ PHP_STMT_GET_OBJ;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz!", &attr, &value)) {
+ RETURN_FALSE;
+ }
+
+ if (!stmt->methods->set_attribute) {
+ goto fail;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+ if (stmt->methods->set_attribute(stmt, attr, value TSRMLS_CC)) {
+ RETURN_TRUE;
+ }
+
+fail:
+ if (!stmt->methods->set_attribute) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "This driver doesn't support setting attributes" TSRMLS_CC);
+ } else {
+ PDO_HANDLE_STMT_ERR();
+ }
+ RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto mixed PDOStatement::getAttribute(long attribute)
+ Get an attribute */
+
+static int generic_stmt_attr_get(pdo_stmt_t *stmt, zval *return_value, long attr)
+{
+ switch (attr) {
+ case PDO_ATTR_EMULATE_PREPARES:
+ RETVAL_BOOL(stmt->supports_placeholders == PDO_PLACEHOLDER_NONE);
+ return 1;
+ }
+ return 0;
+}
+
+static PHP_METHOD(PDOStatement, getAttribute)
+{
+ long attr;
+ PHP_STMT_GET_OBJ;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &attr)) {
+ RETURN_FALSE;
+ }
+
+ if (!stmt->methods->get_attribute) {
+ if (!generic_stmt_attr_get(stmt, return_value, attr)) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "IM001",
+ "This driver doesn't support getting attributes" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+ return;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+ switch (stmt->methods->get_attribute(stmt, attr, return_value TSRMLS_CC)) {
+ case -1:
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+
+ case 0:
+ if (!generic_stmt_attr_get(stmt, return_value, attr)) {
+ /* XXX: should do something better here */
+ pdo_raise_impl_error(stmt->dbh, stmt, "IM001",
+ "driver doesn't support getting that attribute" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+ return;
+
+ default:
+ return;
+ }
+}
+/* }}} */
+
+/* {{{ proto int PDOStatement::columnCount()
+ Returns the number of columns in the result set */
+static PHP_METHOD(PDOStatement, columnCount)
+{
+ PHP_STMT_GET_OBJ;
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+ RETURN_LONG(stmt->column_count);
+}
+/* }}} */
+
+/* {{{ proto array PDOStatement::getColumnMeta(int $column)
+ Returns meta data for a numbered column */
+static PHP_METHOD(PDOStatement, getColumnMeta)
+{
+ long colno;
+ struct pdo_column_data *col;
+ PHP_STMT_GET_OBJ;
+
+ if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &colno)) {
+ RETURN_FALSE;
+ }
+ if(colno < 0) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "42P10", "column number must be non-negative" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+
+ if (!stmt->methods->get_column_meta) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "driver doesn't support meta data" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+ if (FAILURE == stmt->methods->get_column_meta(stmt, colno, return_value TSRMLS_CC)) {
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+ }
+
+ /* add stock items */
+ col = &stmt->columns[colno];
+ add_assoc_string(return_value, "name", col->name, 1);
+ add_assoc_long(return_value, "len", col->maxlen); /* FIXME: unsigned ? */
+ add_assoc_long(return_value, "precision", col->precision);
+ if (col->param_type != PDO_PARAM_ZVAL) {
+ /* if param_type is PDO_PARAM_ZVAL the driver has to provide correct data */
+ add_assoc_long(return_value, "pdo_type", col->param_type);
+ }
+}
+/* }}} */
+
+/* {{{ proto bool PDOStatement::setFetchMode(int mode [mixed* params])
+ Changes the default fetch mode for subsequent fetches (params have different meaning for different fetch modes) */
+
+int pdo_stmt_setup_fetch_mode(INTERNAL_FUNCTION_PARAMETERS, pdo_stmt_t *stmt, int skip)
+{
+ long mode = PDO_FETCH_BOTH;
+ int flags, argc = ZEND_NUM_ARGS() - skip;
+ zval ***args;
+ zend_class_entry **cep;
+ int retval;
+
+ do_fetch_opt_finish(stmt, 1 TSRMLS_CC);
+
+ switch (stmt->default_fetch_type) {
+ case PDO_FETCH_INTO:
+ if (stmt->fetch.into) {
+ zval_ptr_dtor(&stmt->fetch.into);
+ stmt->fetch.into = NULL;
+ }
+ break;
+ default:
+ ;
+ }
+
+ stmt->default_fetch_type = PDO_FETCH_BOTH;
+
+ if (argc == 0) {
+ return SUCCESS;
+ }
+
+ args = safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval*), 0);
+
+ retval = zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args);
+
+ if (SUCCESS == retval) {
+ if (Z_TYPE_PP(args[skip]) != IS_LONG) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "mode must be an integer" TSRMLS_CC);
+ retval = FAILURE;
+ } else {
+ mode = Z_LVAL_PP(args[skip]);
+ flags = mode & PDO_FETCH_FLAGS;
+
+ retval = pdo_stmt_verify_mode(stmt, mode, 0 TSRMLS_CC);
+ }
+ }
+
+ if (FAILURE == retval) {
+ PDO_STMT_CLEAR_ERR();
+ efree(args);
+ return FAILURE;
+ }
+
+ retval = FAILURE;
+ switch (mode & ~PDO_FETCH_FLAGS) {
+ case PDO_FETCH_USE_DEFAULT:
+ case PDO_FETCH_LAZY:
+ case PDO_FETCH_ASSOC:
+ case PDO_FETCH_NUM:
+ case PDO_FETCH_BOTH:
+ case PDO_FETCH_OBJ:
+ case PDO_FETCH_BOUND:
+ case PDO_FETCH_NAMED:
+ case PDO_FETCH_KEY_PAIR:
+ if (argc != 1) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode doesn't allow any extra arguments" TSRMLS_CC);
+ } else {
+ retval = SUCCESS;
+ }
+ break;
+
+ case PDO_FETCH_COLUMN:
+ if (argc != 2) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode requires the colno argument" TSRMLS_CC);
+ } else if (Z_TYPE_PP(args[skip+1]) != IS_LONG) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "colno must be an integer" TSRMLS_CC);
+ } else {
+ stmt->fetch.column = Z_LVAL_PP(args[skip+1]);
+ retval = SUCCESS;
+ }
+ break;
+
+ case PDO_FETCH_CLASS:
+ /* Gets its class name from 1st column */
+ if ((flags & PDO_FETCH_CLASSTYPE) == PDO_FETCH_CLASSTYPE) {
+ if (argc != 1) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode doesn't allow any extra arguments" TSRMLS_CC);
+ } else {
+ stmt->fetch.cls.ce = NULL;
+ retval = SUCCESS;
+ }
+ } else {
+ if (argc < 2) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode requires the classname argument" TSRMLS_CC);
+ } else if (argc > 3) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "too many arguments" TSRMLS_CC);
+ } else if (Z_TYPE_PP(args[skip+1]) != IS_STRING) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "classname must be a string" TSRMLS_CC);
+ } else {
+ retval = zend_lookup_class(Z_STRVAL_PP(args[skip+1]),
+ Z_STRLEN_PP(args[skip+1]), &cep TSRMLS_CC);
+
+ if (SUCCESS == retval && cep && *cep) {
+ stmt->fetch.cls.ce = *cep;
+ }
+ }
+ }
+
+ if (SUCCESS == retval) {
+ stmt->fetch.cls.ctor_args = NULL;
+#ifdef ilia_0 /* we'll only need this when we have persistent statements, if ever */
+ if (stmt->dbh->is_persistent) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "PHP might crash if you don't call $stmt->setFetchMode() to reset to defaults on this persistent statement. This will be fixed in a later release");
+ }
+#endif
+ if (argc == 3) {
+ if (Z_TYPE_PP(args[skip+2]) != IS_NULL && Z_TYPE_PP(args[skip+2]) != IS_ARRAY) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "ctor_args must be either NULL or an array" TSRMLS_CC);
+ retval = FAILURE;
+ } else if (Z_TYPE_PP(args[skip+2]) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_PP(args[skip+2]))) {
+ ALLOC_ZVAL(stmt->fetch.cls.ctor_args);
+ *stmt->fetch.cls.ctor_args = **args[skip+2];
+ zval_copy_ctor(stmt->fetch.cls.ctor_args);
+ }
+ }
+
+ if (SUCCESS == retval) {
+ do_fetch_class_prepare(stmt TSRMLS_CC);
+ }
+ }
+
+ break;
+
+ case PDO_FETCH_INTO:
+ if (argc != 2) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode requires the object parameter" TSRMLS_CC);
+ } else if (Z_TYPE_PP(args[skip+1]) != IS_OBJECT) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "object must be an object" TSRMLS_CC);
+ } else {
+ retval = SUCCESS;
+ }
+
+ if (SUCCESS == retval) {
+#ifdef ilia_0 /* we'll only need this when we have persistent statements, if ever */
+ if (stmt->dbh->is_persistent) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "PHP might crash if you don't call $stmt->setFetchMode() to reset to defaults on this persistent statement. This will be fixed in a later release");
+ }
+#endif
+ MAKE_STD_ZVAL(stmt->fetch.into);
+
+ Z_TYPE_P(stmt->fetch.into) = IS_OBJECT;
+ Z_OBJ_HANDLE_P(stmt->fetch.into) = Z_OBJ_HANDLE_PP(args[skip+1]);
+ Z_OBJ_HT_P(stmt->fetch.into) = Z_OBJ_HT_PP(args[skip+1]);
+ zend_objects_store_add_ref(stmt->fetch.into TSRMLS_CC);
+ }
+
+ break;
+
+ default:
+ pdo_raise_impl_error(stmt->dbh, stmt, "22003", "Invalid fetch mode specified" TSRMLS_CC);
+ }
+
+ if (SUCCESS == retval) {
+ stmt->default_fetch_type = mode;
+ }
+
+ /*
+ * PDO error (if any) has already been raised at this point.
+ *
+ * The error_code is cleared, otherwise the caller will read the
+ * last error message from the driver.
+ *
+ */
+ PDO_STMT_CLEAR_ERR();
+
+ efree(args);
+
+ return retval;
+}
+
+static PHP_METHOD(PDOStatement, setFetchMode)
+{
+ PHP_STMT_GET_OBJ;
+
+ RETVAL_BOOL(
+ pdo_stmt_setup_fetch_mode(INTERNAL_FUNCTION_PARAM_PASSTHRU,
+ stmt, 0) == SUCCESS ? 1 : 0
+ );
+}
+/* }}} */
+
+/* {{{ proto bool PDOStatement::nextRowset()
+ Advances to the next rowset in a multi-rowset statement handle. Returns true if it succeded, false otherwise */
+
+static int pdo_stmt_do_next_rowset(pdo_stmt_t *stmt TSRMLS_DC)
+{
+ /* un-describe */
+ if (stmt->columns) {
+ int i;
+ struct pdo_column_data *cols = stmt->columns;
+
+ for (i = 0; i < stmt->column_count; i++) {
+ efree(cols[i].name);
+ }
+ efree(stmt->columns);
+ stmt->columns = NULL;
+ stmt->column_count = 0;
+ }
+
+ if (!stmt->methods->next_rowset(stmt TSRMLS_CC)) {
+ /* Set the executed flag to 0 to reallocate columns on next execute */
+ stmt->executed = 0;
+ return 0;
+ }
+
+ pdo_stmt_describe_columns(stmt TSRMLS_CC);
+
+ return 1;
+}
+
+static PHP_METHOD(PDOStatement, nextRowset)
+{
+ PHP_STMT_GET_OBJ;
+
+ if (!stmt->methods->next_rowset) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "driver does not support multiple rowsets" TSRMLS_CC);
+ RETURN_FALSE;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+
+ if (!pdo_stmt_do_next_rowset(stmt TSRMLS_CC)) {
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+ }
+
+ RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto bool PDOStatement::closeCursor()
+ Closes the cursor, leaving the statement ready for re-execution. */
+static PHP_METHOD(PDOStatement, closeCursor)
+{
+ PHP_STMT_GET_OBJ;
+
+ if (!stmt->methods->cursor_closer) {
+ /* emulate it by fetching and discarding rows */
+ do {
+ while (stmt->methods->fetcher(stmt, PDO_FETCH_ORI_NEXT, 0 TSRMLS_CC))
+ ;
+ if (!stmt->methods->next_rowset) {
+ break;
+ }
+
+ if (!pdo_stmt_do_next_rowset(stmt TSRMLS_CC)) {
+ break;
+ }
+
+ } while (1);
+ stmt->executed = 0;
+ RETURN_TRUE;
+ }
+
+ PDO_STMT_CLEAR_ERR();
+
+ if (!stmt->methods->cursor_closer(stmt TSRMLS_CC)) {
+ PDO_HANDLE_STMT_ERR();
+ RETURN_FALSE;
+ }
+ stmt->executed = 0;
+ RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto void PDOStatement::debugDumpParams()
+ A utility for internals hackers to debug parameter internals */
+static PHP_METHOD(PDOStatement, debugDumpParams)
+{
+ php_stream *out = php_stream_open_wrapper("php://output", "w", 0, NULL);
+ HashPosition pos;
+ struct pdo_bound_param_data *param;
+ PHP_STMT_GET_OBJ;
+
+ if (out == NULL) {
+ RETURN_FALSE;
+ }
+
+ php_stream_printf(out TSRMLS_CC, "SQL: [%d] %.*s\n",
+ stmt->query_stringlen,
+ stmt->query_stringlen, stmt->query_string);
+
+ php_stream_printf(out TSRMLS_CC, "Params: %d\n",
+ stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0);
+
+ if (stmt->bound_params) {
+ zend_hash_internal_pointer_reset_ex(stmt->bound_params, &pos);
+ while (SUCCESS == zend_hash_get_current_data_ex(stmt->bound_params,
+ (void**)&param, &pos)) {
+ char *str;
+ uint len;
+ ulong num;
+ int res;
+
+ res = zend_hash_get_current_key_ex(stmt->bound_params, &str, &len, &num, 0, &pos);
+ if (res == HASH_KEY_IS_LONG) {
+ php_stream_printf(out TSRMLS_CC, "Key: Position #%ld:\n", num);
+ } else if (res == HASH_KEY_IS_STRING) {
+ php_stream_printf(out TSRMLS_CC, "Key: Name: [%d] %.*s\n", len, len, str);
+ }
+
+ php_stream_printf(out TSRMLS_CC, "paramno=%ld\nname=[%d] \"%.*s\"\nis_param=%d\nparam_type=%d\n",
+ param->paramno, param->namelen, param->namelen, param->name ? param->name : "",
+ param->is_param,
+ param->param_type);
+
+ zend_hash_move_forward_ex(stmt->bound_params, &pos);
+ }
+ }
+
+ php_stream_close(out);
+}
+/* }}} */
+
+/* {{{ proto int PDOStatement::__wakeup()
+ Prevents use of a PDOStatement instance that has been unserialized */
+static PHP_METHOD(PDOStatement, __wakeup)
+{
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "You cannot serialize or unserialize PDOStatement instances");
+}
+/* }}} */
+
+/* {{{ proto int PDOStatement::__sleep()
+ Prevents serialization of a PDOStatement instance */
+static PHP_METHOD(PDOStatement, __sleep)
+{
+ zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "You cannot serialize or unserialize PDOStatement instances");
+}
+/* }}} */
+
+const zend_function_entry pdo_dbstmt_functions[] = {
+ PHP_ME(PDOStatement, execute, arginfo_pdostatement_execute, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, fetch, arginfo_pdostatement_fetch, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, bindParam, arginfo_pdostatement_bindparam, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, bindColumn, arginfo_pdostatement_bindcolumn, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, bindValue, arginfo_pdostatement_bindvalue, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, rowCount, arginfo_pdostatement__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, fetchColumn, arginfo_pdostatement_fetchcolumn, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, fetchAll, arginfo_pdostatement_fetchall, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, fetchObject, arginfo_pdostatement_fetchobject, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, errorCode, arginfo_pdostatement__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, errorInfo, arginfo_pdostatement__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, setAttribute, arginfo_pdostatement_setattribute, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, getAttribute, arginfo_pdostatement_getattribute, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, columnCount, arginfo_pdostatement__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, getColumnMeta, arginfo_pdostatement_getcolumnmeta, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, setFetchMode, arginfo_pdostatement_setfetchmode, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, nextRowset, arginfo_pdostatement__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, closeCursor, arginfo_pdostatement__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, debugDumpParams, arginfo_pdostatement__void, ZEND_ACC_PUBLIC)
+ PHP_ME(PDOStatement, __wakeup, arginfo_pdostatement__void, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
+ PHP_ME(PDOStatement, __sleep, arginfo_pdostatement__void, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL)
+ {NULL, NULL, NULL}
+};
+
+/* {{{ overloaded handlers for PDOStatement class */
+static void dbstmt_prop_write(zval *object, zval *member, zval *value, const zend_literal *key TSRMLS_DC)
+{
+ pdo_stmt_t * stmt = (pdo_stmt_t *) zend_object_store_get_object(object TSRMLS_CC);
+
+ convert_to_string(member);
+
+ if(strcmp(Z_STRVAL_P(member), "queryString") == 0) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "property queryString is read only" TSRMLS_CC);
+ } else {
+ std_object_handlers.write_property(object, member, value, key TSRMLS_CC);
+ }
+}
+
+static void dbstmt_prop_delete(zval *object, zval *member, const zend_literal *key TSRMLS_DC)
+{
+ pdo_stmt_t * stmt = (pdo_stmt_t *) zend_object_store_get_object(object TSRMLS_CC);
+
+ convert_to_string(member);
+
+ if(strcmp(Z_STRVAL_P(member), "queryString") == 0) {
+ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "property queryString is read only" TSRMLS_CC);
+ } else {
+ std_object_handlers.unset_property(object, member, key TSRMLS_CC);
+ }
+}
+
+static union _zend_function *dbstmt_method_get(
+#if PHP_API_VERSION >= 20041225
+ zval **object_pp,
+#else
+ zval *object,
+#endif
+ char *method_name, int method_len, const zend_literal *key TSRMLS_DC)
+{
+ zend_function *fbc = NULL;
+ char *lc_method_name;
+#if PHP_API_VERSION >= 20041225
+ zval *object = *object_pp;
+#endif
+
+ lc_method_name = emalloc(method_len + 1);
+ zend_str_tolower_copy(lc_method_name, method_name, method_len);
+
+ if (zend_hash_find(&Z_OBJCE_P(object)->function_table, lc_method_name,
+ method_len+1, (void**)&fbc) == FAILURE) {
+ pdo_stmt_t *stmt = (pdo_stmt_t*)zend_object_store_get_object(object TSRMLS_CC);
+ /* instance not created by PDO object */
+ if (!stmt->dbh) {
+ goto out;
+ }
+ /* not a pre-defined method, nor a user-defined method; check
+ * the driver specific methods */
+ if (!stmt->dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_STMT]) {
+ if (!pdo_hash_methods(stmt->dbh,
+ PDO_DBH_DRIVER_METHOD_KIND_STMT TSRMLS_CC)
+ || !stmt->dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_STMT]) {
+ goto out;
+ }
+ }
+
+ if (zend_hash_find(stmt->dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_STMT],
+ lc_method_name, method_len+1, (void**)&fbc) == FAILURE) {
+ fbc = NULL;
+ goto out;
+ }
+ /* got it */
+ }
+
+out:
+ efree(lc_method_name);
+ return fbc;
+}
+
+static int dbstmt_compare(zval *object1, zval *object2 TSRMLS_DC)
+{
+ return -1;
+}
+
+static zend_object_value dbstmt_clone_obj(zval *zobject TSRMLS_DC)
+{
+ zend_object_value retval;
+ pdo_stmt_t *stmt;
+ pdo_stmt_t *old_stmt;
+ zend_object_handle handle = Z_OBJ_HANDLE_P(zobject);
+
+ stmt = ecalloc(1, sizeof(*stmt));
+ zend_object_std_init(&stmt->std, Z_OBJCE_P(zobject) TSRMLS_CC);
+ object_properties_init(&stmt->std, Z_OBJCE_P(zobject));
+ stmt->refcount = 1;
+
+ old_stmt = (pdo_stmt_t *)zend_object_store_get_object(zobject TSRMLS_CC);
+
+ retval.handle = zend_objects_store_put(stmt, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)pdo_dbstmt_free_storage, (zend_objects_store_clone_t)dbstmt_clone_obj TSRMLS_CC);
+ retval.handlers = Z_OBJ_HT_P(zobject);
+
+ zend_objects_clone_members((zend_object *)stmt, retval, (zend_object *)old_stmt, handle TSRMLS_CC);
+
+ zend_objects_store_add_ref(&old_stmt->database_object_handle TSRMLS_CC);
+ stmt->database_object_handle = old_stmt->database_object_handle;
+
+ return retval;
+}
+
+zend_object_handlers pdo_dbstmt_object_handlers;
+static int pdo_row_serialize(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC);
+
+void pdo_stmt_init(TSRMLS_D)
+{
+ zend_class_entry ce;
+
+ INIT_CLASS_ENTRY(ce, "PDOStatement", pdo_dbstmt_functions);
+ pdo_dbstmt_ce = zend_register_internal_class(&ce TSRMLS_CC);
+ pdo_dbstmt_ce->get_iterator = pdo_stmt_iter_get;
+ pdo_dbstmt_ce->create_object = pdo_dbstmt_new;
+ zend_class_implements(pdo_dbstmt_ce TSRMLS_CC, 1, zend_ce_traversable);
+ zend_declare_property_null(pdo_dbstmt_ce, "queryString", sizeof("queryString")-1, ZEND_ACC_PUBLIC TSRMLS_CC);
+
+ memcpy(&pdo_dbstmt_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
+ pdo_dbstmt_object_handlers.write_property = dbstmt_prop_write;
+ pdo_dbstmt_object_handlers.unset_property = dbstmt_prop_delete;
+ pdo_dbstmt_object_handlers.get_method = dbstmt_method_get;
+ pdo_dbstmt_object_handlers.compare_objects = dbstmt_compare;
+ pdo_dbstmt_object_handlers.clone_obj = dbstmt_clone_obj;
+
+ INIT_CLASS_ENTRY(ce, "PDORow", pdo_row_functions);
+ pdo_row_ce = zend_register_internal_class(&ce TSRMLS_CC);
+ pdo_row_ce->ce_flags |= ZEND_ACC_FINAL_CLASS; /* when removing this a lot of handlers need to be redone */
+ pdo_row_ce->create_object = pdo_row_new;
+ pdo_row_ce->serialize = pdo_row_serialize;
+}
+
+static void free_statement(pdo_stmt_t *stmt TSRMLS_DC)
+{
+ if (stmt->bound_params) {
+ zend_hash_destroy(stmt->bound_params);
+ FREE_HASHTABLE(stmt->bound_params);
+ stmt->bound_params = NULL;
+ }
+ if (stmt->bound_param_map) {
+ zend_hash_destroy(stmt->bound_param_map);
+ FREE_HASHTABLE(stmt->bound_param_map);
+ stmt->bound_param_map = NULL;
+ }
+ if (stmt->bound_columns) {
+ zend_hash_destroy(stmt->bound_columns);
+ FREE_HASHTABLE(stmt->bound_columns);
+ stmt->bound_columns = NULL;
+ }
+
+ if (stmt->methods && stmt->methods->dtor) {
+ stmt->methods->dtor(stmt TSRMLS_CC);
+ }
+ if (stmt->query_string) {
+ efree(stmt->query_string);
+ }
+
+ if (stmt->columns) {
+ int i;
+ struct pdo_column_data *cols = stmt->columns;
+
+ for (i = 0; i < stmt->column_count; i++) {
+ if (cols[i].name) {
+ efree(cols[i].name);
+ cols[i].name = NULL;
+ }
+ }
+ efree(stmt->columns);
+ stmt->columns = NULL;
+ }
+
+ if (stmt->fetch.into && stmt->default_fetch_type == PDO_FETCH_INTO) {
+ FREE_ZVAL(stmt->fetch.into);
+ stmt->fetch.into = NULL;
+ }
+
+ do_fetch_opt_finish(stmt, 1 TSRMLS_CC);
+
+ zend_objects_store_del_ref(&stmt->database_object_handle TSRMLS_CC);
+ if (stmt->dbh) {
+ php_pdo_dbh_delref(stmt->dbh TSRMLS_CC);
+ }
+ zend_object_std_dtor(&stmt->std TSRMLS_CC);
+ efree(stmt);
+}
+
+PDO_API void php_pdo_stmt_addref(pdo_stmt_t *stmt TSRMLS_DC)
+{
+ stmt->refcount++;
+}
+
+PDO_API void php_pdo_stmt_delref(pdo_stmt_t *stmt TSRMLS_DC)
+{
+ if (--stmt->refcount == 0) {
+ free_statement(stmt TSRMLS_CC);
+ }
+}
+
+void pdo_dbstmt_free_storage(pdo_stmt_t *stmt TSRMLS_DC)
+{
+ php_pdo_stmt_delref(stmt TSRMLS_CC);
+}
+
+zend_object_value pdo_dbstmt_new(zend_class_entry *ce TSRMLS_DC)
+{
+ zend_object_value retval;
+
+ pdo_stmt_t *stmt;
+ stmt = emalloc(sizeof(*stmt));
+ memset(stmt, 0, sizeof(*stmt));
+ zend_object_std_init(&stmt->std, ce TSRMLS_CC);
+ object_properties_init(&stmt->std, ce);
+ stmt->refcount = 1;
+
+ retval.handle = zend_objects_store_put(stmt, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)pdo_dbstmt_free_storage, (zend_objects_store_clone_t)dbstmt_clone_obj TSRMLS_CC);
+ retval.handlers = &pdo_dbstmt_object_handlers;
+
+ return retval;
+}
+/* }}} */
+
+/* {{{ statement iterator */
+
+struct php_pdo_iterator {
+ zend_object_iterator iter;
+ pdo_stmt_t *stmt;
+ ulong key;
+ zval *fetch_ahead;
+};
+
+static void pdo_stmt_iter_dtor(zend_object_iterator *iter TSRMLS_DC)
+{
+ struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter->data;
+
+ if (--I->stmt->refcount == 0) {
+ free_statement(I->stmt TSRMLS_CC);
+ }
+
+ if (I->fetch_ahead) {
+ zval_ptr_dtor(&I->fetch_ahead);
+ }
+
+ efree(I);
+}
+
+static int pdo_stmt_iter_valid(zend_object_iterator *iter TSRMLS_DC)
+{
+ struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter->data;
+
+ return I->fetch_ahead ? SUCCESS : FAILURE;
+}
+
+static void pdo_stmt_iter_get_data(zend_object_iterator *iter, zval ***data TSRMLS_DC)
+{
+ struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter->data;
+
+ /* sanity */
+ if (!I->fetch_ahead) {
+ *data = NULL;
+ return;
+ }
+
+ *data = &I->fetch_ahead;
+}
+
+static int pdo_stmt_iter_get_key(zend_object_iterator *iter, char **str_key, uint *str_key_len,
+ ulong *int_key TSRMLS_DC)
+{
+ struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter->data;
+
+ if (I->key == (ulong)-1) {
+ return HASH_KEY_NON_EXISTANT;
+ }
+ *int_key = I->key;
+ return HASH_KEY_IS_LONG;
+}
+
+static void pdo_stmt_iter_move_forwards(zend_object_iterator *iter TSRMLS_DC)
+{
+ struct php_pdo_iterator *I = (struct php_pdo_iterator*)iter->data;
+
+ if (I->fetch_ahead) {
+ zval_ptr_dtor(&I->fetch_ahead);
+ I->fetch_ahead = NULL;
+ }
+
+ MAKE_STD_ZVAL(I->fetch_ahead);
+
+ if (!do_fetch(I->stmt, TRUE, I->fetch_ahead, PDO_FETCH_USE_DEFAULT,
+ PDO_FETCH_ORI_NEXT, 0, 0 TSRMLS_CC)) {
+ pdo_stmt_t *stmt = I->stmt; /* for PDO_HANDLE_STMT_ERR() */
+
+ PDO_HANDLE_STMT_ERR();
+ I->key = (ulong)-1;
+ FREE_ZVAL(I->fetch_ahead);
+ I->fetch_ahead = NULL;
+
+ return;
+ }
+
+ I->key++;
+}
+
+static zend_object_iterator_funcs pdo_stmt_iter_funcs = {
+ pdo_stmt_iter_dtor,
+ pdo_stmt_iter_valid,
+ pdo_stmt_iter_get_data,
+ pdo_stmt_iter_get_key,
+ pdo_stmt_iter_move_forwards,
+ NULL
+};
+
+zend_object_iterator *pdo_stmt_iter_get(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC)
+{
+ pdo_stmt_t *stmt = (pdo_stmt_t*)zend_object_store_get_object(object TSRMLS_CC);
+ struct php_pdo_iterator *I;
+
+ if (by_ref) {
+ zend_error(E_ERROR, "An iterator cannot be used with foreach by reference");
+ }
+
+ I = ecalloc(1, sizeof(*I));
+ I->iter.funcs = &pdo_stmt_iter_funcs;
+ I->iter.data = I;
+ I->stmt = stmt;
+ stmt->refcount++;
+
+ MAKE_STD_ZVAL(I->fetch_ahead);
+ if (!do_fetch(I->stmt, TRUE, I->fetch_ahead, PDO_FETCH_USE_DEFAULT,
+ PDO_FETCH_ORI_NEXT, 0, 0 TSRMLS_CC)) {
+ PDO_HANDLE_STMT_ERR();
+ I->key = (ulong)-1;
+ FREE_ZVAL(I->fetch_ahead);
+ I->fetch_ahead = NULL;
+ }
+
+ return &I->iter;
+}
+
+/* }}} */
+
+/* {{{ overloaded handlers for PDORow class (used by PDO_FETCH_LAZY) */
+
+const zend_function_entry pdo_row_functions[] = {
+ {NULL, NULL, NULL}
+};
+
+static zval *row_prop_read(zval *object, zval *member, int type, const zend_literal *key TSRMLS_DC)
+{
+ zval *return_value;
+ pdo_stmt_t * stmt = (pdo_stmt_t *) zend_object_store_get_object(object TSRMLS_CC);
+ int colno = -1;
+
+ MAKE_STD_ZVAL(return_value);
+ RETVAL_NULL();
+
+ if (stmt) {
+ if (Z_TYPE_P(member) == IS_LONG) {
+ if (Z_LVAL_P(member) >= 0 && Z_LVAL_P(member) < stmt->column_count) {
+ fetch_value(stmt, return_value, Z_LVAL_P(member), NULL TSRMLS_CC);
+ }
+ } else {
+ convert_to_string(member);
+ /* TODO: replace this with a hash of available column names to column
+ * numbers */
+ for (colno = 0; colno < stmt->column_count; colno++) {
+ if (strcmp(stmt->columns[colno].name, Z_STRVAL_P(member)) == 0) {
+ fetch_value(stmt, return_value, colno, NULL TSRMLS_CC);
+ Z_SET_REFCOUNT_P(return_value, 0);
+ Z_UNSET_ISREF_P(return_value);
+ return return_value;
+ }
+ }
+ if (strcmp(Z_STRVAL_P(member), "queryString") == 0) {
+ zval_ptr_dtor(&return_value);
+ return std_object_handlers.read_property(object, member, type, key TSRMLS_CC);
+ }
+ }
+ }
+
+ Z_SET_REFCOUNT_P(return_value, 0);
+ Z_UNSET_ISREF_P(return_value);
+
+ return return_value;
+}
+
+static zval *row_dim_read(zval *object, zval *member, int type TSRMLS_DC)
+{
+ return row_prop_read(object, member, type, NULL TSRMLS_CC);
+}
+
+static void row_prop_write(zval *object, zval *member, zval *value, const zend_literal *key TSRMLS_DC)
+{
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "This PDORow is not from a writable result set");
+}
+
+static void row_dim_write(zval *object, zval *member, zval *value TSRMLS_DC)
+{
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "This PDORow is not from a writable result set");
+}
+
+static int row_prop_exists(zval *object, zval *member, int check_empty, const zend_literal *key TSRMLS_DC)
+{
+ pdo_stmt_t * stmt = (pdo_stmt_t *) zend_object_store_get_object(object TSRMLS_CC);
+ int colno = -1;
+
+ if (stmt) {
+ if (Z_TYPE_P(member) == IS_LONG) {
+ return Z_LVAL_P(member) >= 0 && Z_LVAL_P(member) < stmt->column_count;
+ } else {
+ convert_to_string(member);
+
+ /* TODO: replace this with a hash of available column names to column
+ * numbers */
+ for (colno = 0; colno < stmt->column_count; colno++) {
+ if (strcmp(stmt->columns[colno].name, Z_STRVAL_P(member)) == 0) {
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int row_dim_exists(zval *object, zval *member, int check_empty TSRMLS_DC)
+{
+ return row_prop_exists(object, member, check_empty, NULL TSRMLS_CC);
+}
+
+static void row_prop_delete(zval *object, zval *offset, const zend_literal *key TSRMLS_DC)
+{
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot delete properties from a PDORow");
+}
+
+static void row_dim_delete(zval *object, zval *offset TSRMLS_DC)
+{
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot delete properties from a PDORow");
+}
+
+static HashTable *row_get_properties(zval *object TSRMLS_DC)
+{
+ pdo_stmt_t * stmt = (pdo_stmt_t *) zend_object_store_get_object(object TSRMLS_CC);
+ int i;
+
+ if (stmt == NULL) {
+ return NULL;
+ }
+
+ if (!stmt->std.properties) {
+ rebuild_object_properties(&stmt->std);
+ }
+ for (i = 0; i < stmt->column_count; i++) {
+ zval *val;
+ MAKE_STD_ZVAL(val);
+ fetch_value(stmt, val, i, NULL TSRMLS_CC);
+
+ zend_hash_update(stmt->std.properties, stmt->columns[i].name, stmt->columns[i].namelen + 1, (void *)&val, sizeof(zval *), NULL);
+ }
+
+ return stmt->std.properties;
+}
+
+static union _zend_function *row_method_get(
+#if PHP_API_VERSION >= 20041225
+ zval **object_pp,
+#else
+ zval *object,
+#endif
+ char *method_name, int method_len, const zend_literal *key TSRMLS_DC)
+{
+ zend_function *fbc;
+ char *lc_method_name;
+
+ lc_method_name = emalloc(method_len + 1);
+ zend_str_tolower_copy(lc_method_name, method_name, method_len);
+
+ if (zend_hash_find(&pdo_row_ce->function_table, lc_method_name, method_len+1, (void**)&fbc) == FAILURE) {
+ efree(lc_method_name);
+ return NULL;
+ }
+
+ efree(lc_method_name);
+ return fbc;
+}
+
+static int row_call_method(const char *method, INTERNAL_FUNCTION_PARAMETERS)
+{
+ return FAILURE;
+}
+
+static union _zend_function *row_get_ctor(zval *object TSRMLS_DC)
+{
+ static zend_internal_function ctor = {0};
+
+ ctor.type = ZEND_INTERNAL_FUNCTION;
+ ctor.function_name = "__construct";
+ ctor.scope = pdo_row_ce;
+ ctor.handler = ZEND_FN(dbstmt_constructor);
+
+ return (union _zend_function*)&ctor;
+}
+
+static zend_class_entry *row_get_ce(const zval *object TSRMLS_DC)
+{
+ return pdo_row_ce;
+}
+
+static int row_get_classname(const zval *object, const char **class_name, zend_uint *class_name_len, int parent TSRMLS_DC)
+{
+ if (parent) {
+ return FAILURE;
+ } else {
+ *class_name = estrndup("PDORow", sizeof("PDORow")-1);
+ *class_name_len = sizeof("PDORow")-1;
+ return SUCCESS;
+ }
+}
+
+static int row_compare(zval *object1, zval *object2 TSRMLS_DC)
+{
+ return -1;
+}
+
+zend_object_handlers pdo_row_object_handlers = {
+ zend_objects_store_add_ref,
+ zend_objects_store_del_ref,
+ NULL,
+ row_prop_read,
+ row_prop_write,
+ row_dim_read,
+ row_dim_write,
+ NULL,
+ NULL,
+ NULL,
+ row_prop_exists,
+ row_prop_delete,
+ row_dim_exists,
+ row_dim_delete,
+ row_get_properties,
+ row_method_get,
+ row_call_method,
+ row_get_ctor,
+ row_get_ce,
+ row_get_classname,
+ row_compare,
+ NULL, /* cast */
+ NULL
+};
+
+void pdo_row_free_storage(pdo_stmt_t *stmt TSRMLS_DC)
+{
+ if (stmt) {
+ ZVAL_NULL(&stmt->lazy_object_ref);
+
+ if (--stmt->refcount == 0) {
+ free_statement(stmt TSRMLS_CC);
+ }
+ }
+}
+
+zend_object_value pdo_row_new(zend_class_entry *ce TSRMLS_DC)
+{
+ zend_object_value retval;
+
+ retval.handle = zend_objects_store_put(NULL, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)pdo_row_free_storage, NULL TSRMLS_CC);
+ retval.handlers = &pdo_row_object_handlers;
+
+ return retval;
+}
+
+static int pdo_row_serialize(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC)
+{
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "PDORow instances may not be serialized");
+ return FAILURE;
+}
+/* }}} */
+
+/*
+ * 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/php_pdo.h b/ext/pdo/php_pdo.h
new file mode 100644
index 0000000..f8a8ce3
--- /dev/null
+++ b/ext/pdo/php_pdo.h
@@ -0,0 +1,94 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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: Wez Furlong <wez@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifndef PHP_PDO_H
+#define PHP_PDO_H
+
+#include "zend.h"
+
+#if PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 1)
+#define can_handle_soft_dependency_on_SPL 1
+#endif
+
+extern zend_module_entry pdo_module_entry;
+#define phpext_pdo_ptr &pdo_module_entry
+
+#ifdef PHP_WIN32
+# if defined(PDO_EXPORTS) || (!defined(COMPILE_DL_PDO))
+# define PDO_API __declspec(dllexport)
+# elif defined(COMPILE_DL_PDO)
+# define PDO_API __declspec(dllimport)
+# else
+# define PDO_API /* nothing special */
+# endif
+#elif defined(__GNUC__) && __GNUC__ >= 4
+# define PDO_API __attribute__ ((visibility("default")))
+#else
+# define PDO_API /* nothing special */
+#endif
+
+#ifdef ZTS
+# include "TSRM.h"
+#endif
+
+PHP_MINIT_FUNCTION(pdo);
+PHP_MSHUTDOWN_FUNCTION(pdo);
+PHP_MINFO_FUNCTION(pdo);
+
+ZEND_BEGIN_MODULE_GLOBALS(pdo)
+ long global_value;
+ZEND_END_MODULE_GLOBALS(pdo)
+
+#ifdef ZTS
+# define PDOG(v) TSRMG(pdo_globals_id, zend_pdo_globals *, v)
+#else
+# define PDOG(v) (pdo_globals.v)
+#endif
+
+#define REGISTER_PDO_CLASS_CONST_LONG(const_name, value) \
+ zend_declare_class_constant_long(php_pdo_get_dbh_ce(), const_name, sizeof(const_name)-1, (long)value TSRMLS_CC);
+
+#define REGISTER_PDO_CONST_LONG(const_name, value) { \
+ zend_class_entry **pce; \
+ if (zend_hash_find(CG(class_table), "pdo", sizeof("pdo"), (void **) &pce) != FAILURE) \
+ zend_declare_class_constant_long(*pce, const_name, sizeof(const_name)-1, (long)value TSRMLS_CC); \
+} \
+
+#define REGISTER_PDO_CLASS_CONST_STRING(const_name, value) \
+ zend_declare_class_constant_stringl(php_pdo_get_dbh_ce(), const_name, sizeof(const_name)-1, value, sizeof(value)-1 TSRMLS_CC);
+
+#define PDO_CONSTRUCT_CHECK \
+ if (!dbh->driver) { \
+ pdo_raise_impl_error(dbh, NULL, "00000", "PDO constructor was not called" TSRMLS_CC); \
+ return; \
+ } \
+
+
+#endif /* PHP_PDO_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/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h
new file mode 100644
index 0000000..f38e7b5
--- /dev/null
+++ b/ext/pdo/php_pdo_driver.h
@@ -0,0 +1,673 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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: Wez Furlong <wez@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifndef PHP_PDO_DRIVER_H
+#define PHP_PDO_DRIVER_H
+
+#include "php_pdo.h"
+
+/* forward declarations */
+typedef struct _pdo_dbh_t pdo_dbh_t;
+typedef struct _pdo_stmt_t pdo_stmt_t;
+struct pdo_bound_param_data;
+
+#ifdef PHP_WIN32
+typedef __int64 pdo_int64_t;
+typedef unsigned __int64 pdo_uint64_t;
+#else
+typedef long long int pdo_int64_t;
+typedef unsigned long long int pdo_uint64_t;
+#endif
+PDO_API char *php_pdo_int64_to_str(pdo_int64_t i64 TSRMLS_DC);
+
+#ifndef TRUE
+# define TRUE 1
+#endif
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+#define PDO_DRIVER_API 20080721
+
+enum pdo_param_type {
+ PDO_PARAM_NULL,
+
+ /* int as in long (the php native int type).
+ * If you mark a column as an int, PDO expects get_col to return
+ * a pointer to a long */
+ PDO_PARAM_INT,
+
+ /* get_col ptr should point to start of the string buffer */
+ PDO_PARAM_STR,
+
+ /* get_col: when len is 0 ptr should point to a php_stream *,
+ * otherwise it should behave like a string. Indicate a NULL field
+ * value by setting the ptr to NULL */
+ PDO_PARAM_LOB,
+
+ /* get_col: will expect the ptr to point to a new PDOStatement object handle,
+ * but this isn't wired up yet */
+ PDO_PARAM_STMT, /* hierarchical result set */
+
+ /* get_col ptr should point to a zend_bool */
+ PDO_PARAM_BOOL,
+
+ /* get_col ptr should point to a zval*
+ and the driver is responsible for adding correct type information to get_column_meta()
+ */
+ PDO_PARAM_ZVAL
+};
+
+/* magic flag to denote a parameter as being input/output */
+#define PDO_PARAM_INPUT_OUTPUT 0x80000000
+
+#define PDO_PARAM_FLAGS 0xFFFF0000
+
+#define PDO_PARAM_TYPE(x) ((x) & ~PDO_PARAM_FLAGS)
+
+enum pdo_fetch_type {
+ PDO_FETCH_USE_DEFAULT,
+ PDO_FETCH_LAZY,
+ PDO_FETCH_ASSOC,
+ PDO_FETCH_NUM,
+ PDO_FETCH_BOTH,
+ PDO_FETCH_OBJ,
+ PDO_FETCH_BOUND, /* return true/false only; rely on bound columns */
+ PDO_FETCH_COLUMN, /* fetch a numbered column only */
+ PDO_FETCH_CLASS, /* create an instance of named class, call ctor and set properties */
+ PDO_FETCH_INTO, /* fetch row into an existing object */
+ PDO_FETCH_FUNC, /* fetch into function and return its result */
+ PDO_FETCH_NAMED, /* like PDO_FETCH_ASSOC, but can handle duplicate names */
+ PDO_FETCH_KEY_PAIR, /* fetch into an array where the 1st column is a key and all subsequent columns are values */
+ PDO_FETCH__MAX /* must be last */
+};
+
+#define PDO_FETCH_FLAGS 0xFFFF0000 /* fetchAll() modes or'd to PDO_FETCH_XYZ */
+#define PDO_FETCH_GROUP 0x00010000 /* fetch into groups */
+#define PDO_FETCH_UNIQUE 0x00030000 /* fetch into groups assuming first col is unique */
+#define PDO_FETCH_CLASSTYPE 0x00040000 /* fetch class gets its class name from 1st column */
+#define PDO_FETCH_SERIALIZE 0x00080000 /* fetch class instances by calling serialize */
+#define PDO_FETCH_PROPS_LATE 0x00100000 /* fetch props after calling ctor */
+
+/* fetch orientation for scrollable cursors */
+enum pdo_fetch_orientation {
+ PDO_FETCH_ORI_NEXT, /* default: fetch the next available row */
+ PDO_FETCH_ORI_PRIOR, /* scroll back to prior row and fetch that */
+ PDO_FETCH_ORI_FIRST, /* scroll to the first row and fetch that */
+ PDO_FETCH_ORI_LAST, /* scroll to the last row and fetch that */
+ PDO_FETCH_ORI_ABS, /* scroll to an absolute numbered row and fetch that */
+ PDO_FETCH_ORI_REL /* scroll relative to the current row, and fetch that */
+};
+
+enum pdo_attribute_type {
+ PDO_ATTR_AUTOCOMMIT, /* use to turn on or off auto-commit mode */
+ PDO_ATTR_PREFETCH, /* configure the prefetch size for drivers that support it. Size is in KB */
+ PDO_ATTR_TIMEOUT, /* connection timeout in seconds */
+ PDO_ATTR_ERRMODE, /* control how errors are handled */
+ PDO_ATTR_SERVER_VERSION, /* database server version */
+ PDO_ATTR_CLIENT_VERSION, /* client library version */
+ PDO_ATTR_SERVER_INFO, /* server information */
+ PDO_ATTR_CONNECTION_STATUS, /* connection status */
+ PDO_ATTR_CASE, /* control case folding for portability */
+ PDO_ATTR_CURSOR_NAME, /* name a cursor for use in "WHERE CURRENT OF <name>" */
+ PDO_ATTR_CURSOR, /* cursor type */
+ PDO_ATTR_ORACLE_NULLS, /* convert empty strings to NULL */
+ PDO_ATTR_PERSISTENT, /* pconnect style connection */
+ PDO_ATTR_STATEMENT_CLASS, /* array(classname, array(ctor_args)) to specify the class of the constructed statement */
+ PDO_ATTR_FETCH_TABLE_NAMES, /* include table names in the column names, where available */
+ PDO_ATTR_FETCH_CATALOG_NAMES, /* include the catalog/db name names in the column names, where available */
+ PDO_ATTR_DRIVER_NAME, /* name of the driver (as used in the constructor) */
+ PDO_ATTR_STRINGIFY_FETCHES, /* converts integer/float types to strings during fetch */
+ PDO_ATTR_MAX_COLUMN_LEN, /* make database calculate maximum length of data found in a column */
+ PDO_ATTR_DEFAULT_FETCH_MODE, /* Set the default fetch mode */
+ PDO_ATTR_EMULATE_PREPARES, /* use query emulation rather than native */
+
+ /* this defines the start of the range for driver specific options.
+ * Drivers should define their own attribute constants beginning with this
+ * value. */
+ PDO_ATTR_DRIVER_SPECIFIC = 1000
+};
+
+enum pdo_cursor_type {
+ PDO_CURSOR_FWDONLY, /* forward only cursor (default) */
+ PDO_CURSOR_SCROLL /* scrollable cursor */
+};
+
+/* SQL-92 SQLSTATE error codes.
+
+The character string value returned for an SQLSTATE consists of a two-character
+class value followed by a three-character subclass value. A class value of 01
+indicates a warning and is accompanied by a return code of
+SQL_SUCCESS_WITH_INFO.
+
+Class values other than '01', except for the class 'IM',
+indicate an error and are accompanied by a return code of SQL_ERROR. The class
+'IM' is specific to warnings and errors that derive from the implementation of
+ODBC itself.
+
+The subclass value '000' in any class indicates that there is no
+subclass for that SQLSTATE. The assignment of class and subclass values is
+defined by SQL-92.
+*/
+
+typedef char pdo_error_type[6]; /* SQLSTATE */
+
+
+#define PDO_ERR_NONE "00000"
+
+enum pdo_error_mode {
+ PDO_ERRMODE_SILENT, /* just set error codes */
+ PDO_ERRMODE_WARNING, /* raise E_WARNING */
+ PDO_ERRMODE_EXCEPTION /* throw exceptions */
+};
+
+enum pdo_case_conversion {
+ PDO_CASE_NATURAL,
+ PDO_CASE_UPPER,
+ PDO_CASE_LOWER
+};
+
+/* oracle interop settings */
+enum pdo_null_handling {
+ PDO_NULL_NATURAL = 0,
+ PDO_NULL_EMPTY_STRING = 1,
+ PDO_NULL_TO_STRING = 2
+};
+
+/* {{{ utils for reading attributes set as driver_options */
+static inline long pdo_attr_lval(zval *options, enum pdo_attribute_type option_name, long defval TSRMLS_DC)
+{
+ zval **v;
+
+ if (options && SUCCESS == zend_hash_index_find(Z_ARRVAL_P(options), option_name, (void**)&v)) {
+ convert_to_long_ex(v);
+ return Z_LVAL_PP(v);
+ }
+ return defval;
+}
+static inline char *pdo_attr_strval(zval *options, enum pdo_attribute_type option_name, char *defval TSRMLS_DC)
+{
+ zval **v;
+
+ if (options && SUCCESS == zend_hash_index_find(Z_ARRVAL_P(options), option_name, (void**)&v)) {
+ convert_to_string_ex(v);
+ return estrndup(Z_STRVAL_PP(v), Z_STRLEN_PP(v));
+ }
+ return defval ? estrdup(defval) : NULL;
+}
+/* }}} */
+
+/* This structure is registered with PDO when a PDO driver extension is
+ * initialized */
+typedef struct {
+ const char *driver_name;
+ unsigned long driver_name_len;
+ unsigned long api_version; /* needs to be compatible with PDO */
+
+#define PDO_DRIVER_HEADER(name) \
+ #name, sizeof(#name)-1, \
+ PDO_DRIVER_API
+
+ /* create driver specific portion of the database handle and stash it into
+ * the dbh. dbh contains the data source string and flags for this
+ * instance. You MUST respect dbh->is_persistent and pass that flag to
+ * pemalloc() for all allocations that are stored in the dbh or your instance
+ * data in the db, otherwise you will crash PHP when persistent connections
+ * are used.
+ */
+ int (*db_handle_factory)(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC);
+
+} pdo_driver_t;
+
+/* {{{ methods for a database handle */
+
+/* close or otherwise disconnect the database */
+typedef int (*pdo_dbh_close_func)(pdo_dbh_t *dbh TSRMLS_DC);
+
+/* prepare a statement and stash driver specific portion into stmt */
+typedef int (*pdo_dbh_prepare_func)(pdo_dbh_t *dbh, const char *sql, long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC);
+
+/* execute a statement (that does not return a result set) */
+typedef long (*pdo_dbh_do_func)(pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC);
+
+/* quote a string */
+typedef int (*pdo_dbh_quote_func)(pdo_dbh_t *dbh, const char *unquoted, int unquotedlen, char **quoted, int *quotedlen, enum pdo_param_type paramtype TSRMLS_DC);
+
+/* transaction related */
+typedef int (*pdo_dbh_txn_func)(pdo_dbh_t *dbh TSRMLS_DC);
+
+/* setting of attributes */
+typedef int (*pdo_dbh_set_attr_func)(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC);
+
+/* return last insert id. NULL indicates error condition, otherwise, the return value
+ * MUST be an emalloc'd NULL terminated string. */
+typedef char *(*pdo_dbh_last_id_func)(pdo_dbh_t *dbh, const char *name, unsigned int *len TSRMLS_DC);
+
+/* fetch error information. if stmt is not null, fetch information pertaining
+ * to the statement, otherwise fetch global error information. The driver
+ * should add the following information to the array "info" in this order:
+ * - native error code
+ * - string representation of the error code ... any other optional driver
+ * specific data ... */
+typedef int (*pdo_dbh_fetch_error_func)(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info TSRMLS_DC);
+
+/* fetching of attributes */
+typedef int (*pdo_dbh_get_attr_func)(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC);
+
+/* checking/pinging persistent connections; return SUCCESS if the connection
+ * is still alive and ready to be used, FAILURE otherwise.
+ * You may set this handler to NULL, which is equivalent to returning SUCCESS. */
+typedef int (*pdo_dbh_check_liveness_func)(pdo_dbh_t *dbh TSRMLS_DC);
+
+/* called at request end for each persistent dbh; this gives the driver
+ * the opportunity to safely release resources that only have per-request
+ * scope */
+typedef void (*pdo_dbh_request_shutdown)(pdo_dbh_t *dbh TSRMLS_DC);
+
+/* for adding methods to the dbh or stmt objects
+pointer to a list of driver specific functions. The convention is
+to prefix the function names using the PDO driver name; this will
+reduce the chance of collisions with future functionality in the
+PDO class or in user code (they can extend the PDO object).
+*/
+enum {
+ PDO_DBH_DRIVER_METHOD_KIND_DBH = 0,
+ PDO_DBH_DRIVER_METHOD_KIND_STMT,
+ PDO_DBH_DRIVER_METHOD_KIND__MAX
+};
+
+typedef const zend_function_entry *(*pdo_dbh_get_driver_methods_func)(pdo_dbh_t *dbh, int kind TSRMLS_DC);
+
+struct pdo_dbh_methods {
+ pdo_dbh_close_func closer;
+ pdo_dbh_prepare_func preparer;
+ pdo_dbh_do_func doer;
+ pdo_dbh_quote_func quoter;
+ pdo_dbh_txn_func begin;
+ pdo_dbh_txn_func commit;
+ pdo_dbh_txn_func rollback;
+ pdo_dbh_set_attr_func set_attribute;
+ pdo_dbh_last_id_func last_id;
+ pdo_dbh_fetch_error_func fetch_err;
+ pdo_dbh_get_attr_func get_attribute;
+ pdo_dbh_check_liveness_func check_liveness;
+ pdo_dbh_get_driver_methods_func get_driver_methods;
+ pdo_dbh_request_shutdown persistent_shutdown;
+ pdo_dbh_txn_func in_transaction;
+};
+
+/* }}} */
+
+/* {{{ methods for a statement handle */
+
+/* free the statement handle */
+typedef int (*pdo_stmt_dtor_func)(pdo_stmt_t *stmt TSRMLS_DC);
+
+/* start the query */
+typedef int (*pdo_stmt_execute_func)(pdo_stmt_t *stmt TSRMLS_DC);
+
+/* causes the next row in the set to be fetched; indicates if there are no
+ * more rows. The ori and offset params modify which row should be returned,
+ * if the stmt represents a scrollable cursor */
+typedef int (*pdo_stmt_fetch_func)(pdo_stmt_t *stmt,
+ enum pdo_fetch_orientation ori, long offset TSRMLS_DC);
+
+/* queries information about the type of a column, by index (0 based).
+ * Driver should populate stmt->columns[colno] with appropriate info */
+typedef int (*pdo_stmt_describe_col_func)(pdo_stmt_t *stmt, int colno TSRMLS_DC);
+
+/* retrieves pointer and size of the value for a column.
+ * Note that PDO expects the driver to manage the lifetime of this data;
+ * it will copy the value into a zval on behalf of the script.
+ * If the driver sets caller_frees, ptr should point to emalloc'd memory
+ * and PDO will free it as soon as it is done using it.
+ */
+typedef int (*pdo_stmt_get_col_data_func)(pdo_stmt_t *stmt, int colno, char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC);
+
+/* hook for bound params */
+enum pdo_param_event {
+ PDO_PARAM_EVT_ALLOC,
+ PDO_PARAM_EVT_FREE,
+ PDO_PARAM_EVT_EXEC_PRE,
+ PDO_PARAM_EVT_EXEC_POST,
+ PDO_PARAM_EVT_FETCH_PRE,
+ PDO_PARAM_EVT_FETCH_POST,
+ PDO_PARAM_EVT_NORMALIZE
+};
+
+typedef int (*pdo_stmt_param_hook_func)(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, enum pdo_param_event event_type TSRMLS_DC);
+
+/* setting of attributes */
+typedef int (*pdo_stmt_set_attr_func)(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC);
+
+/* fetching of attributes */
+typedef int (*pdo_stmt_get_attr_func)(pdo_stmt_t *stmt, long attr, zval *val TSRMLS_DC);
+
+/* retrieves meta data for a numbered column.
+ * Returns SUCCESS/FAILURE.
+ * On SUCCESS, fill in return_value with an array with the following fields.
+ * If a particular field is not supported, then the driver simply does not add it to
+ * the array, so that scripts can use isset() to check for it.
+ *
+ * ### this is just a rough first cut, and subject to change ###
+ *
+ * these are added by PDO itself, based on data from the describe handler:
+ * name => the column name
+ * len => the length/size of the column
+ * precision => precision of the column
+ * pdo_type => an integer, one of the PDO_PARAM_XXX values
+ *
+ * scale => the floating point scale
+ * table => the table for that column
+ * type => a string representation of the type, mapped to the PHP equivalent type name
+ * native_type => a string representation of the type, native style, if different from
+ * the mapped name.
+ * flags => an array of flags including zero or more of the following:
+ * primary_key, not_null, unique_key, multiple_key, unsigned, auto_increment, blob
+ *
+ * Any driver specific data should be returned using a prefixed key or value.
+ * Eg: custom data for the mysql driver would use either
+ * 'mysql:foobar' => 'some data' // to add a new key to the array
+ * or
+ * 'flags' => array('not_null', 'mysql:some_flag'); // to add data to an existing key
+ */
+typedef int (*pdo_stmt_get_column_meta_func)(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC);
+
+/* advances the statement to the next rowset of the batch.
+ * If it returns 1, PDO will tear down its idea of columns
+ * and meta data. If it returns 0, PDO will indicate an error
+ * to the caller. */
+typedef int (*pdo_stmt_next_rowset_func)(pdo_stmt_t *stmt TSRMLS_DC);
+
+/* closes the active cursor on a statement, leaving the prepared
+ * statement ready for re-execution. Useful to explicitly state
+ * that you are done with a given rowset, without having to explicitly
+ * fetch all the rows. */
+typedef int (*pdo_stmt_cursor_closer_func)(pdo_stmt_t *stmt TSRMLS_DC);
+
+struct pdo_stmt_methods {
+ pdo_stmt_dtor_func dtor;
+ pdo_stmt_execute_func executer;
+ pdo_stmt_fetch_func fetcher;
+ pdo_stmt_describe_col_func describer;
+ pdo_stmt_get_col_data_func get_col;
+ pdo_stmt_param_hook_func param_hook;
+ pdo_stmt_set_attr_func set_attribute;
+ pdo_stmt_get_attr_func get_attribute;
+ pdo_stmt_get_column_meta_func get_column_meta;
+ pdo_stmt_next_rowset_func next_rowset;
+ pdo_stmt_cursor_closer_func cursor_closer;
+};
+
+/* }}} */
+
+enum pdo_placeholder_support {
+ PDO_PLACEHOLDER_NONE=0,
+ PDO_PLACEHOLDER_NAMED=1,
+ PDO_PLACEHOLDER_POSITIONAL=2
+};
+
+/* represents a connection to a database */
+struct _pdo_dbh_t {
+ /* these items must appear in this order at the beginning of the
+ struct so that this can be cast as a zend_object. we need this
+ to allow the extending class to escape all the custom handlers
+ that PDO declares.
+ */
+ zend_object std;
+
+ /* driver specific methods */
+ struct pdo_dbh_methods *methods;
+ /* driver specific data */
+ void *driver_data;
+
+ /* credentials */
+ char *username, *password;
+
+ /* if true, then data stored and pointed at by this handle must all be
+ * persistently allocated */
+ unsigned is_persistent:1;
+
+ /* if true, driver should act as though a COMMIT were executed between
+ * each executed statement; otherwise, COMMIT must be carried out manually
+ * */
+ unsigned auto_commit:1;
+
+ /* if true, the handle has been closed and will not function anymore */
+ unsigned is_closed:1;
+
+ /* if true, the driver requires that memory be allocated explicitly for
+ * the columns that are returned */
+ unsigned alloc_own_columns:1;
+
+ /* if true, commit or rollBack is allowed to be called */
+ unsigned in_txn:1;
+
+ /* max length a single character can become after correct quoting */
+ unsigned max_escaped_char_length:3;
+
+ /* oracle compat; see enum pdo_null_handling */
+ unsigned oracle_nulls:2;
+
+ /* when set, convert int/floats to strings */
+ unsigned stringify:1;
+
+ /* the sum of the number of bits here and the bit fields preceeding should
+ * equal 32 */
+ unsigned _reserved_flags:21;
+
+ /* data source string used to open this handle */
+ const char *data_source;
+ unsigned long data_source_len;
+
+ /* the global error code. */
+ pdo_error_type error_code;
+
+ enum pdo_error_mode error_mode;
+
+ enum pdo_case_conversion native_case, desired_case;
+
+ /* persistent hash key associated with this handle */
+ const char *persistent_id;
+ int persistent_id_len;
+ unsigned int refcount;
+
+ /* driver specific "class" methods for the dbh and stmt */
+ HashTable *cls_methods[PDO_DBH_DRIVER_METHOD_KIND__MAX];
+
+ pdo_driver_t *driver;
+
+ zend_class_entry *def_stmt_ce;
+
+ zval *def_stmt_ctor_args;
+
+ /* when calling PDO::query(), we need to keep the error
+ * context from the statement around until we next clear it.
+ * This will allow us to report the correct error message
+ * when PDO::query() fails */
+ pdo_stmt_t *query_stmt;
+ zval query_stmt_zval;
+
+ /* defaults for fetches */
+ enum pdo_fetch_type default_fetch_type;
+};
+
+/* describes a column */
+struct pdo_column_data {
+ char *name;
+ int namelen;
+ unsigned long maxlen;
+ enum pdo_param_type param_type;
+ unsigned long precision;
+
+ /* don't touch this unless your name is dbdo */
+ void *dbdo_data;
+};
+
+/* describes a bound parameter */
+struct pdo_bound_param_data {
+ long paramno; /* if -1, then it has a name, and we don't know the index *yet* */
+ char *name;
+ int namelen;
+
+ long max_value_len; /* as a hint for pre-allocation */
+
+ zval *parameter; /* the variable itself */
+ enum pdo_param_type param_type; /* desired or suggested type */
+
+ zval *driver_params; /* optional parameter(s) for the driver */
+ void *driver_data;
+
+ pdo_stmt_t *stmt; /* for convenience in dtor */
+ int is_param; /* parameter or column ? */
+};
+
+/* represents a prepared statement */
+struct _pdo_stmt_t {
+ /* these items must appear in this order at the beginning of the
+ struct so that this can be cast as a zend_object. we need this
+ to allow the extending class to escape all the custom handlers
+ that PDO declares.
+ */
+ zend_object std;
+
+ /* driver specifics */
+ struct pdo_stmt_methods *methods;
+ void *driver_data;
+
+ /* if true, we've already successfully executed this statement at least
+ * once */
+ unsigned executed:1;
+ /* if true, the statement supports placeholders and can implement
+ * bindParam() for its prepared statements, if false, PDO should
+ * emulate prepare and bind on its behalf */
+ unsigned supports_placeholders:2;
+
+ unsigned _reserved:29;
+
+ /* the number of columns in the result set; not valid until after
+ * the statement has been executed at least once. In some cases, might
+ * not be valid until fetch (at the driver level) has been called at least once.
+ * */
+ int column_count;
+ struct pdo_column_data *columns;
+
+ /* we want to keep the dbh alive while we live, so we own a reference */
+ zval database_object_handle;
+ pdo_dbh_t *dbh;
+
+ /* keep track of bound input parameters. Some drivers support
+ * input/output parameters, but you can't rely on that working */
+ HashTable *bound_params;
+ /* When rewriting from named to positional, this maps positions to names */
+ HashTable *bound_param_map;
+ /* keep track of PHP variables bound to named (or positional) columns
+ * in the result set */
+ HashTable *bound_columns;
+
+ /* not always meaningful */
+ long row_count;
+
+ /* used to hold the statement's current query */
+ char *query_string;
+ int query_stringlen;
+
+ /* the copy of the query with expanded binds ONLY for emulated-prepare drivers */
+ char *active_query_string;
+ int active_query_stringlen;
+
+ /* the cursor specific error code. */
+ pdo_error_type error_code;
+
+ /* for lazy fetches, we always return the same lazy object handle.
+ * Let's keep it here. */
+ zval lazy_object_ref;
+ unsigned long refcount;
+
+ /* defaults for fetches */
+ enum pdo_fetch_type default_fetch_type;
+ union {
+ int column;
+ struct {
+ zend_class_entry *ce;
+ zval *ctor_args; /* freed */
+ zval *retval_ptr;
+ zend_fcall_info fci;
+ zend_fcall_info_cache fcc;
+ } cls;
+ struct {
+ zval *function;
+ zval *fetch_args; /* freed */
+ zval *object;
+ zend_fcall_info fci;
+ zend_fcall_info_cache fcc;
+ zval **values; /* freed */
+ } func;
+ zval *into;
+ } fetch;
+
+ /* used by the query parser for driver specific
+ * parameter naming (see pgsql driver for example) */
+ const char *named_rewrite_template;
+};
+
+/* call this in MINIT to register your PDO driver */
+PDO_API int php_pdo_register_driver(pdo_driver_t *driver);
+/* call this in MSHUTDOWN to unregister your PDO driver */
+PDO_API void php_pdo_unregister_driver(pdo_driver_t *driver);
+
+/* For the convenience of drivers, this function will parse a data source
+ * string, of the form "name=value; name2=value2" and populate variables
+ * according to the data you pass in and array of pdo_data_src_parser structures */
+struct pdo_data_src_parser {
+ const char *optname;
+ char *optval;
+ int freeme;
+};
+
+PDO_API int php_pdo_parse_data_source(const char *data_source,
+ unsigned long data_source_len, struct pdo_data_src_parser *parsed,
+ int nparams);
+
+PDO_API zend_class_entry *php_pdo_get_dbh_ce(void);
+PDO_API zend_class_entry *php_pdo_get_exception(void);
+
+PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len,
+ char **outquery, int *outquery_len TSRMLS_DC);
+
+PDO_API void pdo_raise_impl_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt,
+ const char *sqlstate, const char *supp TSRMLS_DC);
+
+PDO_API void php_pdo_dbh_addref(pdo_dbh_t *dbh TSRMLS_DC);
+PDO_API void php_pdo_dbh_delref(pdo_dbh_t *dbh TSRMLS_DC);
+
+PDO_API void php_pdo_stmt_addref(pdo_stmt_t *stmt TSRMLS_DC);
+PDO_API void php_pdo_stmt_delref(pdo_stmt_t *stmt TSRMLS_DC);
+
+
+#endif /* PHP_PDO_DRIVER_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/php_pdo_int.h b/ext/pdo/php_pdo_int.h
new file mode 100644
index 0000000..57c683a
--- /dev/null
+++ b/ext/pdo/php_pdo_int.h
@@ -0,0 +1,84 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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: Wez Furlong <wez@php.net> |
+ | Marcus Boerger <helly@php.net> |
+ | Sterling Hughes <sterling@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+/* Stuff private to the PDO extension and not for consumption by PDO drivers
+ * */
+
+extern HashTable pdo_driver_hash;
+extern zend_class_entry *pdo_exception_ce;
+PDO_API zend_class_entry *php_pdo_get_exception_base(int root TSRMLS_DC);
+int php_pdo_list_entry(void);
+
+void pdo_dbh_init(TSRMLS_D);
+void pdo_stmt_init(TSRMLS_D);
+
+extern zend_object_value pdo_dbh_new(zend_class_entry *ce TSRMLS_DC);
+extern const zend_function_entry pdo_dbh_functions[];
+extern zend_class_entry *pdo_dbh_ce;
+extern ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor);
+
+extern zend_object_value pdo_dbstmt_new(zend_class_entry *ce TSRMLS_DC);
+extern const zend_function_entry pdo_dbstmt_functions[];
+extern zend_class_entry *pdo_dbstmt_ce;
+void pdo_dbstmt_free_storage(pdo_stmt_t *stmt TSRMLS_DC);
+zend_object_iterator *pdo_stmt_iter_get(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC);
+extern zend_object_handlers pdo_dbstmt_object_handlers;
+int pdo_stmt_describe_columns(pdo_stmt_t *stmt TSRMLS_DC);
+int pdo_stmt_setup_fetch_mode(INTERNAL_FUNCTION_PARAMETERS, pdo_stmt_t *stmt, int skip_first_arg);
+
+extern zend_object_value pdo_row_new(zend_class_entry *ce TSRMLS_DC);
+extern const zend_function_entry pdo_row_functions[];
+extern zend_class_entry *pdo_row_ce;
+void pdo_row_free_storage(pdo_stmt_t *stmt TSRMLS_DC);
+extern zend_object_handlers pdo_row_object_handlers;
+
+zend_object_iterator *php_pdo_dbstmt_iter_get(zend_class_entry *ce, zval *object TSRMLS_DC);
+
+extern pdo_driver_t *pdo_find_driver(const char *name, int namelen);
+
+extern void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC);
+
+#define PDO_DBH_CLEAR_ERR() do { \
+ strncpy(dbh->error_code, PDO_ERR_NONE, sizeof(PDO_ERR_NONE)); \
+ if (dbh->query_stmt) { \
+ dbh->query_stmt = NULL; \
+ zend_objects_store_del_ref(&dbh->query_stmt_zval TSRMLS_CC); \
+ } \
+} while (0)
+#define PDO_STMT_CLEAR_ERR() strcpy(stmt->error_code, PDO_ERR_NONE)
+#define PDO_HANDLE_DBH_ERR() if (strcmp(dbh->error_code, PDO_ERR_NONE)) { pdo_handle_error(dbh, NULL TSRMLS_CC); }
+#define PDO_HANDLE_STMT_ERR() if (strcmp(stmt->error_code, PDO_ERR_NONE)) { pdo_handle_error(stmt->dbh, stmt TSRMLS_CC); }
+
+int pdo_sqlstate_init_error_table(void);
+void pdo_sqlstate_fini_error_table(void);
+const char *pdo_sqlstate_state_to_description(char *state);
+int pdo_hash_methods(pdo_dbh_t *dbh, int kind TSRMLS_DC);
+
+
+/*
+ * 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/tests/bug47769.phpt b/ext/pdo/tests/bug47769.phpt
new file mode 100644
index 0000000..3b2f1e8
--- /dev/null
+++ b/ext/pdo/tests/bug47769.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Bug #47769 (Strange extends PDO)
+--SKIPIF--
+<?php
+if (!extension_loaded("pdo_sqlite"))
+ die("skip: PDO_SQLite not available");
+?>
+--FILE--
+<?php
+
+class test extends PDO
+{
+ protected function isProtected() {
+ echo "this is a protected method.\n";
+ }
+ private function isPrivate() {
+ echo "this is a private method.\n";
+ }
+
+ public function quote($str, $paramtype = NULL) {
+ $this->isProtected();
+ $this->isPrivate();
+ print $str ."\n";
+ }
+}
+
+$test = new test('sqlite::memory:');
+$test->quote('foo');
+$test->isProtected();
+
+?>
+--EXPECTF--
+this is a protected method.
+this is a private method.
+foo
+
+Fatal error: Call to protected method test::isProtected() from context '' in %s on line %d
diff --git a/ext/pdo/tests/bug61292.phpt b/ext/pdo/tests/bug61292.phpt
new file mode 100644
index 0000000..2381fd1
--- /dev/null
+++ b/ext/pdo/tests/bug61292.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Bug #61292 (Segfault while calling a method on an overloaded PDO object)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+class Database_SQL extends PDO
+{
+ function __construct()
+ {
+ $options = array(PDO::ATTR_PERSISTENT => TRUE);
+ parent::__construct(getenv("PDOTEST_DSN"), getenv("PDOTEST_USER"), getenv("PDOTEST_PASS"), $options);
+ }
+
+ var $bar = array();
+
+ public function foo()
+ {
+ var_dump($this->bar);
+ }
+}
+
+(new Database_SQL)->foo();
+?>
+--EXPECTF--
+array(0) {
+}
diff --git a/ext/pdo/tests/bug_34630.phpt b/ext/pdo/tests/bug_34630.phpt
new file mode 100644
index 0000000..070217b
--- /dev/null
+++ b/ext/pdo/tests/bug_34630.phpt
@@ -0,0 +1,57 @@
+--TEST--
+PDO Common: Bug #34630 (inserting streams as LOBs)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$driver = $db->getAttribute(PDO::ATTR_DRIVER_NAME);
+$is_oci = $driver == 'oci';
+
+if ($is_oci) {
+ $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val BLOB)');
+} else {
+ $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val VARCHAR(256))');
+}
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+$fp = tmpfile();
+fwrite($fp, "I am the LOB data");
+rewind($fp);
+
+if ($is_oci) {
+ /* oracle is a bit different; you need to initiate a transaction otherwise
+ * the empty blob will be committed implicitly when the statement is
+ * executed */
+ $db->beginTransaction();
+ $insert = $db->prepare("insert into test (id, val) values (1, EMPTY_BLOB()) RETURNING val INTO :blob");
+} else {
+ $insert = $db->prepare("insert into test (id, val) values (1, :blob)");
+}
+$insert->bindValue(':blob', $fp, PDO::PARAM_LOB);
+$insert->execute();
+$insert = null;
+
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+var_dump($db->query("SELECT * from test")->fetchAll(PDO::FETCH_ASSOC));
+
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ array(2) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(17) "I am the LOB data"
+ }
+}
diff --git a/ext/pdo/tests/bug_34687.phpt b/ext/pdo/tests/bug_34687.phpt
new file mode 100644
index 0000000..dbb1471
--- /dev/null
+++ b/ext/pdo/tests/bug_34687.phpt
@@ -0,0 +1,32 @@
+--TEST--
+PDO Common: Bug #34687 (query doesn't return error information)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
+$x = $db->query("UPDATE non_existent_pdo_test_table set foo = 'bar'");
+
+var_dump($x);
+$code = $db->errorCode();
+if ($code !== '00000' && strlen($code)) {
+ echo "OK: $code\n";
+} else {
+ echo "ERR: $code\n";
+ print_r($db->errorInfo());
+}
+
+?>
+--EXPECTF--
+bool(false)
+OK: %s
diff --git a/ext/pdo/tests/bug_35671.phpt b/ext/pdo/tests/bug_35671.phpt
new file mode 100644
index 0000000..8d1e114
--- /dev/null
+++ b/ext/pdo/tests/bug_35671.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PDO Common: Bug #35671 (binding by name breakage)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test (field1 VARCHAR(32), field2 VARCHAR(32), field3 VARCHAR(32))');
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+$insert = $db->prepare("insert into test (field1, field2, field3) values (:value1, :value2, :value3)");
+
+$parm = array(
+ ":value1" => 15,
+ ":value2" => 20,
+ ":value3" => 25
+);
+
+$insert->execute($parm);
+$insert = null;
+
+var_dump($db->query("SELECT * from test")->fetchAll(PDO::FETCH_ASSOC));
+
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ array(3) {
+ ["field1"]=>
+ string(2) "15"
+ ["field2"]=>
+ string(2) "20"
+ ["field3"]=>
+ string(2) "25"
+ }
+}
diff --git a/ext/pdo/tests/bug_36428.phpt b/ext/pdo/tests/bug_36428.phpt
new file mode 100644
index 0000000..703b9d4
--- /dev/null
+++ b/ext/pdo/tests/bug_36428.phpt
@@ -0,0 +1,33 @@
+--TEST--
+PDO Common: Bug #36428 (Incorrect error message for PDO::fetchAll())
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+if (!extension_loaded('simplexml')) die('skip SimpleXML not loaded');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+$db = PDOTest::factory();
+$db->exec("CREATE TABLE test (a VARCHAR(10))");
+$db->exec("INSERT INTO test (a) VALUES ('xyz')");
+$res = $db->query("SELECT a FROM test");
+var_dump($res->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'SimpleXMLElement', array('<root/>')));
+
+?>
+===DONE===
+--EXPECTF--
+array(1) {
+ [0]=>
+ object(SimpleXMLElement)#%d (1) {
+ ["a"]=>
+ string(3) "xyz"
+ }
+}
+===DONE===
diff --git a/ext/pdo/tests/bug_36798.phpt b/ext/pdo/tests/bug_36798.phpt
new file mode 100644
index 0000000..9da703e
--- /dev/null
+++ b/ext/pdo/tests/bug_36798.phpt
@@ -0,0 +1,39 @@
+--TEST--
+PDO Common: Bug #36798 (Error parsing named parameters with queries containing high-ascii chars)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+
+if (!strncasecmp(getenv('PDOTEST_DSN'), 'oci', strlen('oci'))){
+ if (!strpos(strtolower(getenv('PDOTEST_DSN')), 'charset=we8mswin1252')) die('skip expected output valid for Oracle with WE8MSWIN1252 character set');
+
+}
+
+?>
+--FILE--
+<?php
+
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+@$db->exec("SET NAMES 'LATIN1'"); // needed for PostgreSQL
+$db->exec("CREATE TABLE test (id INTEGER)");
+$db->exec("INSERT INTO test (id) VALUES (1)");
+
+$stmt = $db->prepare("SELECT 'Ã' as test FROM test WHERE id = :id");
+$stmt->execute(array(":id" => 1));
+
+$row = $stmt->fetch(PDO::FETCH_NUM);
+var_dump( $row );
+
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ string(1) "Ã"
+}
diff --git a/ext/pdo/tests/bug_38253.phpt b/ext/pdo/tests/bug_38253.phpt
new file mode 100644
index 0000000..4453c3b
--- /dev/null
+++ b/ext/pdo/tests/bug_38253.phpt
@@ -0,0 +1,53 @@
+--TEST--
+PDO Common: Bug #38253 (PDO produces segfault with default fetch mode)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$pdo = PDOTest::factory();
+
+$pdo->exec ("create table test (id integer primary key, n varchar(255))");
+$pdo->exec ("INSERT INTO test (id, n) VALUES (1, 'hi')");
+
+$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS);
+$stmt = $pdo->prepare ("SELECT * FROM test");
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+$pdo = PDOTest::factory();
+
+if ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) == 'oci') {
+ $type = "clob";
+} else{
+ $type = "text";
+}
+
+$pdo->exec ("create table test2 (id integer primary key, n $type)");
+$pdo->exec ("INSERT INTO test2 (id, n) VALUES (1,'hi')");
+
+$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_FUNC);
+$stmt = $pdo->prepare ("SELECT * FROM test2");
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+?>
+--EXPECTF--
+Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: No fetch class specified in %s on line %d
+
+Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error%s on line %d
+array(0) {
+}
+
+Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: No fetch function specified in %s on line %d
+
+Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error%s on line %d
+array(0) {
+}
diff --git a/ext/pdo/tests/bug_38394.phpt b/ext/pdo/tests/bug_38394.phpt
new file mode 100644
index 0000000..be990b3
--- /dev/null
+++ b/ext/pdo/tests/bug_38394.phpt
@@ -0,0 +1,51 @@
+--TEST--
+PDO Common: Bug #38394 (Prepared statement error stops subsequent statements)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+if (!strncasecmp(getenv('PDOTEST_DSN'), 'sqlite2', strlen('sqlite2'))) die('skip not relevant for pdo_sqlite2 driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+$db = PDOTest::factory();
+$db->exec("CREATE TABLE test (a INT, b INT, c INT)");
+$s = $db->prepare("INSERT INTO test (a,b,c) VALUES (:a,:b,:c)");
+
+$s->execute(array('a' => 1, 'b' => 2, 'c' => 3));
+
+@$s->execute(array('a' => 5, 'b' => 6, 'c' => 7, 'd' => 8));
+
+$s->execute(array('a' => 9, 'b' => 10, 'c' => 11));
+
+var_dump($db->query("SELECT * FROM test")->fetchAll(PDO::FETCH_ASSOC));
+?>
+===DONE===
+--EXPECTF--
+array(2) {
+ [0]=>
+ array(3) {
+ ["a"]=>
+ string(1) "1"
+ ["b"]=>
+ string(1) "2"
+ ["c"]=>
+ string(1) "3"
+ }
+ [1]=>
+ array(3) {
+ ["a"]=>
+ string(1) "9"
+ ["b"]=>
+ string(2) "10"
+ ["c"]=>
+ string(2) "11"
+ }
+}
+===DONE===
diff --git a/ext/pdo/tests/bug_39398.phpt b/ext/pdo/tests/bug_39398.phpt
new file mode 100644
index 0000000..633771a
--- /dev/null
+++ b/ext/pdo/tests/bug_39398.phpt
@@ -0,0 +1,35 @@
+--TEST--
+PDO Common: Bug #39398 (Booleans are not automatically translated to integers)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+$db = PDOTest::factory();
+$db->exec("CREATE TABLE test (test INT)");
+
+$boolean = 1;
+$stmt = $db->prepare('INSERT INTO test VALUES (:boolean)');
+$stmt->bindValue(':boolean', isset($boolean), PDO::PARAM_INT);
+$stmt->execute();
+
+var_dump($db->query("SELECT * FROM test")->fetchAll(PDO::FETCH_ASSOC));
+?>
+===DONE===
+--EXPECT--
+array(1) {
+ [0]=>
+ array(1) {
+ ["test"]=>
+ string(1) "1"
+ }
+}
+===DONE===
diff --git a/ext/pdo/tests/bug_39656.phpt b/ext/pdo/tests/bug_39656.phpt
new file mode 100644
index 0000000..7d113ef
--- /dev/null
+++ b/ext/pdo/tests/bug_39656.phpt
@@ -0,0 +1,51 @@
+--TEST--
+PDO Common: Bug #39656 (Crash when calling fetch() on a PDO statment object after closeCursor())
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+@$db->exec("DROP TABLE test");
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+$db->exec("CREATE TABLE test (id INTEGER NOT NULL PRIMARY KEY, usr VARCHAR( 256 ) NOT NULL)");
+$db->exec("INSERT INTO test (id, usr) VALUES (1, 'user')");
+
+$stmt = $db->prepare("SELECT * FROM test WHERE id = ?");
+$stmt->bindValue(1, 1, PDO::PARAM_INT );
+$stmt->execute();
+$row = $stmt->fetch();
+var_dump( $row );
+
+$stmt->execute();
+$stmt->closeCursor();
+$row = $stmt->fetch(); // this line will crash CLI
+var_dump( $row );
+
+@$db->exec("DROP TABLE test");
+echo "Done\n";
+?>
+--EXPECT--
+array(4) {
+ ["id"]=>
+ string(1) "1"
+ [0]=>
+ string(1) "1"
+ ["usr"]=>
+ string(4) "user"
+ [1]=>
+ string(4) "user"
+}
+bool(false)
+Done
+
diff --git a/ext/pdo/tests/bug_40285.phpt b/ext/pdo/tests/bug_40285.phpt
new file mode 100644
index 0000000..c0a5674
--- /dev/null
+++ b/ext/pdo/tests/bug_40285.phpt
@@ -0,0 +1,27 @@
+--TEST--
+PDO Common: Bug #40285 (The prepare parser goes into an infinite loop on ': or ":)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test (field1 VARCHAR(32), field2 VARCHAR(32), field3 VARCHAR(32), field4 INT)');
+
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
+$s = $db->prepare("INSERT INTO test VALUES( ':id', 'name', 'section', 22)" );
+$s->execute();
+
+echo "Done\n";
+?>
+--EXPECT--
+Done
diff --git a/ext/pdo/tests/bug_42917.phpt b/ext/pdo/tests/bug_42917.phpt
new file mode 100644
index 0000000..d4b71f5
--- /dev/null
+++ b/ext/pdo/tests/bug_42917.phpt
@@ -0,0 +1,40 @@
+--TEST--
+PDO Common: Bug #42917 (PDO::FETCH_KEY_PAIR doesn't work with setFetchMode)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec("CREATE TABLE test (a varchar(100), b varchar(100), c varchar(100))");
+
+for ($i = 0; $i < 5; $i++) {
+ $db->exec("INSERT INTO test (a,b,c) VALUES('test".$i."','".$i."','".$i."')");
+}
+
+$res = $db->query("SELECT a,b FROM test");
+$res->setFetchMode(PDO::FETCH_KEY_PAIR);
+var_dump($res->fetchAll());
+
+?>
+--EXPECT--
+array(5) {
+ ["test0"]=>
+ string(1) "0"
+ ["test1"]=>
+ string(1) "1"
+ ["test2"]=>
+ string(1) "2"
+ ["test3"]=>
+ string(1) "3"
+ ["test4"]=>
+ string(1) "4"
+}
diff --git a/ext/pdo/tests/bug_43130.phpt b/ext/pdo/tests/bug_43130.phpt
new file mode 100644
index 0000000..70f8887
--- /dev/null
+++ b/ext/pdo/tests/bug_43130.phpt
@@ -0,0 +1,40 @@
+--TEST--
+PDO Common: Bug #43130 (Bound parameters cannot have - in their name)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+if (!strncasecmp(getenv('PDOTEST_DSN'), 'sqlite', strlen('sqlite'))) die('skip not relevant for sqlite driver');
+if (!strncasecmp(getenv('PDOTEST_DSN'), 'pgsql', strlen('pgsql'))) die('skip not relevant for pgsql driver');
+if (!strncasecmp(getenv('PDOTEST_DSN'), 'oci', strlen('oci'))) die('skip not relevant for oci driver - Hyphen is not legal for bind names in Oracle DB');
+if (!strncasecmp(getenv('PDOTEST_DSN'), 'firebird', strlen('firebird'))) die('skip not relevant for firebird driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql')
+ $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+
+$db->exec("CREATE TABLE test (a varchar(100), b varchar(100), c varchar(100))");
+
+for ($i = 0; $i < 5; $i++) {
+ $db->exec("INSERT INTO test (a,b,c) VALUES('test".$i."','".$i."','".$i."')");
+}
+
+$stmt = $db->prepare("SELECT a FROM test WHERE b=:id-value");
+$stmt->bindParam(':id-value', $id);
+$id = '1';
+$stmt->execute();
+var_dump($stmt->fetch(PDO::FETCH_COLUMN));
+?>
+--EXPECTF--
+Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: parameter was not defined in %s on line %d
+
+Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number in %s on line %d
+bool(false)
diff --git a/ext/pdo/tests/bug_43139.phpt b/ext/pdo/tests/bug_43139.phpt
new file mode 100644
index 0000000..87bfd4e
--- /dev/null
+++ b/ext/pdo/tests/bug_43139.phpt
@@ -0,0 +1,41 @@
+--TEST--
+PDO Common: Bug #43139 (PDO ignore ATTR_DEFAULT_FETCH_MODE in some cases with fetchAll())
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+
+$from = '';
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'oci') {
+ $from = 'from dual';
+} else if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'firebird') {
+ $from = 'FROM RDB$DATABASE';
+}
+
+var_dump($db->query("select 0 as abc, 1 as xyz, 2 as def $from")->fetchAll(PDO::FETCH_GROUP));
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ array(1) {
+ [0]=>
+ array(2) {
+ ["xyz"]=>
+ string(1) "1"
+ ["def"]=>
+ string(1) "2"
+ }
+ }
+}
diff --git a/ext/pdo/tests/bug_43663.phpt b/ext/pdo/tests/bug_43663.phpt
new file mode 100644
index 0000000..f8e968f
--- /dev/null
+++ b/ext/pdo/tests/bug_43663.phpt
@@ -0,0 +1,31 @@
+--TEST--
+PDO Common: Bug #43663 (__call on classes derived from PDO)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+if (!extension_loaded('pdo_sqlite')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+class test extends PDO{
+ function __call($name, array $args) {
+ echo "Called $name in ".__CLASS__."\n";
+ }
+ function foo() {
+ echo "Called foo in ".__CLASS__."\n";
+ }
+}
+
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+$a = new test('sqlite::memory:');
+$a->foo();
+$a->bar();
+--EXPECT--
+Called foo in test
+Called bar in test
diff --git a/ext/pdo/tests/bug_44159.phpt b/ext/pdo/tests/bug_44159.phpt
new file mode 100644
index 0000000..9f1961c
--- /dev/null
+++ b/ext/pdo/tests/bug_44159.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Bug #44159 (Crash: $pdo->setAttribute(PDO::STATEMENT_ATTR_CLASS, NULL))
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip PDO not available');
+try {
+ $pdo = new PDO("sqlite:".__DIR__."/foo.db");
+} catch (Exception $e) {
+ die("skip PDP_SQLITE not available");
+}
+?>
+--FILE--
+<?php
+$pdo = new PDO("sqlite:".__DIR__."/foo.db");
+
+$attrs = array(PDO::ATTR_STATEMENT_CLASS, PDO::ATTR_STRINGIFY_FETCHES, PDO::NULL_TO_STRING);
+
+foreach ($attrs as $attr) {
+ var_dump($pdo->setAttribute($attr, NULL));
+ var_dump($pdo->setAttribute($attr, 1));
+ var_dump($pdo->setAttribute($attr, 'nonsense'));
+}
+
+@unlink(__DIR__."/foo.db");
+
+?>
+--EXPECTF--
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d
+bool(false)
+
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d
+bool(false)
+
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d
+bool(false)
+
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: attribute value must be an integer in %s on line %d
+bool(false)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
diff --git a/ext/pdo/tests/bug_44173.phpt b/ext/pdo/tests/bug_44173.phpt
new file mode 100644
index 0000000..f13abaa
--- /dev/null
+++ b/ext/pdo/tests/bug_44173.phpt
@@ -0,0 +1,78 @@
+--TEST--
+PDO Common: Bug #44173 (PDO->query() parameter parsing/checking needs an update)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec("CREATE TABLE test (x int)");
+$db->exec("INSERT INTO test VALUES (1)");
+
+
+// Bug entry [1]
+$stmt = $db->query();
+var_dump($stmt);
+
+
+// Bug entry [2] -- 1 is PDO::FETCH_LAZY
+$stmt = $db->query("SELECT * FROM test", PDO::FETCH_LAZY, 0, 0);
+var_dump($stmt);
+
+
+// Bug entry [3]
+$stmt = $db->query("SELECT * FROM test", 'abc');
+var_dump($stmt);
+
+
+// Bug entry [4]
+$stmt = $db->query("SELECT * FROM test", PDO::FETCH_CLASS, 0, 0, 0);
+var_dump($stmt);
+
+
+// Bug entry [5]
+$stmt = $db->query("SELECT * FROM test", PDO::FETCH_INTO);
+var_dump($stmt);
+
+
+// Bug entry [6]
+$stmt = $db->query("SELECT * FROM test", PDO::FETCH_COLUMN);
+var_dump($stmt);
+
+
+// Bug entry [7]
+$stmt = $db->query("SELECT * FROM test", PDO::FETCH_CLASS);
+var_dump($stmt);
+
+
+?>
+--EXPECTF--
+Warning: PDO::query() expects at least 1 parameter, 0 given in %s
+bool(false)
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: fetch mode doesn't allow any extra arguments in %s
+bool(false)
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: mode must be an integer in %s
+bool(false)
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: too many arguments in %s
+bool(false)
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: fetch mode requires the object parameter in %s
+bool(false)
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: fetch mode requires the colno argument in %s
+bool(false)
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: fetch mode requires the classname argument in %s
+bool(false)
+
diff --git a/ext/pdo/tests/bug_44409.phpt b/ext/pdo/tests/bug_44409.phpt
new file mode 100644
index 0000000..fe24fdf
--- /dev/null
+++ b/ext/pdo/tests/bug_44409.phpt
@@ -0,0 +1,51 @@
+--TEST--
+PDO Common: Bug #44409 (PDO::FETCH_SERIALIZE calls __construct())
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec("CREATE TABLE test (dat varchar(100))");
+$db->exec("INSERT INTO test (dat) VALUES ('Data from DB')");
+
+class bug44409 implements Serializable
+{
+ public function __construct()
+ {
+ printf("Method called: %s()\n", __METHOD__);
+ }
+
+ public function serialize()
+ {
+ return "any data from serizalize()";
+ }
+
+ public function unserialize($dat)
+ {
+ printf("Method called: %s(%s)\n", __METHOD__, var_export($dat, true));
+ }
+}
+
+$stmt = $db->query("SELECT * FROM test");
+
+print_r($stmt->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE, "bug44409"));
+
+?>
+--EXPECT--
+Method called: bug44409::unserialize('Data from DB')
+Array
+(
+ [0] => bug44409 Object
+ (
+ )
+
+)
diff --git a/ext/pdo/tests/bug_44861.phpt b/ext/pdo/tests/bug_44861.phpt
new file mode 100644
index 0000000..cb8e657
--- /dev/null
+++ b/ext/pdo/tests/bug_44861.phpt
@@ -0,0 +1,106 @@
+--TEST--
+PDO Common: Bug #44861 (scrollable cursor don't work with pgsql)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+$allowed = array('oci', 'pgsql');
+$ok = false;
+foreach ($allowed as $driver) {
+ if (!strncasecmp(getenv('PDOTEST_DSN'), $driver, strlen($driver))) {
+ $ok = true;
+ }
+}
+if (!$ok) {
+ die("skip Scrollable cursors not supported");
+}
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'oci') {
+ $from = 'FROM DUAL';
+ $ob = '1';
+} else {
+ $from = '';
+ $ob = 'r';
+}
+
+$query = "SELECT 'row1' AS r $from UNION SELECT 'row2' $from UNION SELECT 'row3' $from UNION SELECT 'row4' $from ORDER BY $ob";
+$aParams = array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL);
+
+$res = $db->prepare($query, $aParams);
+$res->execute();
+var_dump($res->fetchColumn());
+var_dump($res->fetchColumn());
+var_dump($res->fetchColumn());
+var_dump($res->fetchColumn());
+var_dump($res->fetchColumn());
+
+var_dump($res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 3));
+var_dump($res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_PRIOR));
+var_dump($res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_FIRST));
+var_dump($res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_LAST));
+var_dump($res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_REL, -1));
+
+var_dump($res->fetchAll(PDO::FETCH_ASSOC));
+
+// Test binding params via emulated prepared query
+$res = $db->prepare("SELECT ? $from", $aParams);
+$res->execute(array("it's working"));
+var_dump($res->fetch(PDO::FETCH_NUM));
+
+
+// Test bug #48188, trying to execute again
+$res->execute(array("try again"));
+var_dump($res->fetchColumn());
+var_dump($res->fetchColumn());
+
+?>
+--EXPECT--
+string(4) "row1"
+string(4) "row2"
+string(4) "row3"
+string(4) "row4"
+bool(false)
+array(1) {
+ [0]=>
+ string(4) "row3"
+}
+array(1) {
+ [0]=>
+ string(4) "row2"
+}
+array(1) {
+ [0]=>
+ string(4) "row1"
+}
+array(1) {
+ [0]=>
+ string(4) "row4"
+}
+array(1) {
+ [0]=>
+ string(4) "row3"
+}
+array(1) {
+ [0]=>
+ array(1) {
+ ["r"]=>
+ string(4) "row4"
+ }
+}
+array(1) {
+ [0]=>
+ string(12) "it's working"
+}
+string(9) "try again"
+bool(false)
diff --git a/ext/pdo/tests/bug_50458.phpt b/ext/pdo/tests/bug_50458.phpt
new file mode 100644
index 0000000..3deb289
--- /dev/null
+++ b/ext/pdo/tests/bug_50458.phpt
@@ -0,0 +1,29 @@
+--TEST--
+PDO Common: Bug #50458 (PDO::FETCH_FUNC fails with Closures)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+$db = PDOTest::factory();
+$db->exec("CREATE TABLE test (a VARCHAR(10))");
+$db->exec("INSERT INTO test (a) VALUES ('xyz')");
+$res = $db->query("SELECT a FROM test");
+var_dump($res->fetchAll(PDO::FETCH_FUNC, function($a) { return strtoupper($a); }));
+
+?>
+===DONE===
+--EXPECTF--
+array(1) {
+ [0]=>
+ string(3) "XYZ"
+}
+===DONE===
diff --git a/ext/pdo/tests/pdo.inc b/ext/pdo/tests/pdo.inc
new file mode 100644
index 0000000..8089236
--- /dev/null
+++ b/ext/pdo/tests/pdo.inc
@@ -0,0 +1,10 @@
+<?php
+
+function set_sql($name, $query)
+{
+ if (empty($GLOBALS['SQL'][$name]))
+ {
+ $GLOBALS['SQL'][$name] = $query;
+ }
+}
+?>
diff --git a/ext/pdo/tests/pdo_001.phpt b/ext/pdo/tests/pdo_001.phpt
new file mode 100644
index 0000000..3b79a09
--- /dev/null
+++ b/ext/pdo/tests/pdo_001.phpt
@@ -0,0 +1,50 @@
+--TEST--
+PDO Common: PDO::FETCH_ASSOC
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES(1, 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'B')");
+$db->exec("INSERT INTO test VALUES(3, 'C')");
+
+$stmt = $db->prepare('SELECT * from test');
+$stmt->execute();
+
+var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+?>
+--EXPECT--
+array(3) {
+ [0]=>
+ array(2) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(2) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(2) {
+ ["id"]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ }
+}
diff --git a/ext/pdo/tests/pdo_002.phpt b/ext/pdo/tests/pdo_002.phpt
new file mode 100644
index 0000000..047fd59
--- /dev/null
+++ b/ext/pdo/tests/pdo_002.phpt
@@ -0,0 +1,50 @@
+--TEST--
+PDO Common: PDO::FETCH_NUM
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES(1, 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'B')");
+$db->exec("INSERT INTO test VALUES(3, 'C')");
+
+$stmt = $db->prepare('SELECT * from test');
+$stmt->execute();
+
+var_dump($stmt->fetchAll(PDO::FETCH_NUM));
+?>
+--EXPECT--
+array(3) {
+ [0]=>
+ array(2) {
+ [0]=>
+ string(1) "1"
+ [1]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(2) {
+ [0]=>
+ string(1) "2"
+ [1]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(2) {
+ [0]=>
+ string(1) "3"
+ [1]=>
+ string(1) "C"
+ }
+}
diff --git a/ext/pdo/tests/pdo_003.phpt b/ext/pdo/tests/pdo_003.phpt
new file mode 100644
index 0000000..ccd23c8
--- /dev/null
+++ b/ext/pdo/tests/pdo_003.phpt
@@ -0,0 +1,62 @@
+--TEST--
+PDO Common: PDO::FETCH_BOTH
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES(1, 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'B')");
+$db->exec("INSERT INTO test VALUES(3, 'C')");
+
+$stmt = $db->prepare('SELECT * from test');
+$stmt->execute();
+
+var_dump($stmt->fetchAll(PDO::FETCH_BOTH));
+?>
+--EXPECT--
+array(3) {
+ [0]=>
+ array(4) {
+ ["id"]=>
+ string(1) "1"
+ [0]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ [1]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(4) {
+ ["id"]=>
+ string(1) "2"
+ [0]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ [1]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(4) {
+ ["id"]=>
+ string(1) "3"
+ [0]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ [1]=>
+ string(1) "C"
+ }
+}
diff --git a/ext/pdo/tests/pdo_004.phpt b/ext/pdo/tests/pdo_004.phpt
new file mode 100644
index 0000000..933629e
--- /dev/null
+++ b/ext/pdo/tests/pdo_004.phpt
@@ -0,0 +1,50 @@
+--TEST--
+PDO Common: PDO::FETCH_OBJ
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES(1, 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'B')");
+$db->exec("INSERT INTO test VALUES(3, 'C')");
+
+$stmt = $db->prepare('SELECT * from test');
+$stmt->execute();
+
+var_dump($stmt->fetchAll(PDO::FETCH_OBJ));
+?>
+--EXPECTF--
+array(3) {
+ [0]=>
+ object(stdClass)#%d (2) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ }
+ [1]=>
+ object(stdClass)#%d (2) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ }
+ [2]=>
+ object(stdClass)#%d (2) {
+ ["id"]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ }
+}
diff --git a/ext/pdo/tests/pdo_005.phpt b/ext/pdo/tests/pdo_005.phpt
new file mode 100644
index 0000000..1ea558b
--- /dev/null
+++ b/ext/pdo/tests/pdo_005.phpt
@@ -0,0 +1,154 @@
+--TEST--
+PDO Common: PDO::FETCH_CLASS
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES(1, 'A', 'AA')");
+$db->exec("INSERT INTO test VALUES(2, 'B', 'BB')");
+$db->exec("INSERT INTO test VALUES(3, 'C', 'CC')");
+
+$stmt = $db->prepare('SELECT id, val, val2 from test');
+
+class TestBase
+{
+ public $id;
+ protected $val;
+ private $val2;
+}
+
+class TestDerived extends TestBase
+{
+ protected $row;
+
+ public function __construct(&$row)
+ {
+ echo __METHOD__ . "($row,{$this->id})\n";
+ $this->row = $row++;
+ }
+}
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_CLASS));
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_CLASS, 'TestBase'));
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_CLASS, 'TestDerived', array(0)));
+
+?>
+--EXPECTF--
+array(3) {
+ [0]=>
+ object(stdClass)#%d (3) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ ["val2"]=>
+ string(2) "AA"
+ }
+ [1]=>
+ object(stdClass)#%d (3) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ ["val2"]=>
+ string(2) "BB"
+ }
+ [2]=>
+ object(stdClass)#%d (3) {
+ ["id"]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ ["val2"]=>
+ string(2) "CC"
+ }
+}
+array(3) {
+ [0]=>
+ object(TestBase)#%d (3) {
+ ["id"]=>
+ string(1) "1"
+ ["val":protected]=>
+ string(1) "A"
+ ["val2":"TestBase":private]=>
+ string(2) "AA"
+ }
+ [1]=>
+ object(TestBase)#%d (3) {
+ ["id"]=>
+ string(1) "2"
+ ["val":protected]=>
+ string(1) "B"
+ ["val2":"TestBase":private]=>
+ string(2) "BB"
+ }
+ [2]=>
+ object(TestBase)#%d (3) {
+ ["id"]=>
+ string(1) "3"
+ ["val":protected]=>
+ string(1) "C"
+ ["val2":"TestBase":private]=>
+ string(2) "CC"
+ }
+}
+TestDerived::__construct(0,1)
+TestDerived::__construct(1,2)
+TestDerived::__construct(2,3)
+array(3) {
+ [0]=>
+ object(TestDerived)#%d (5) {
+ ["row":protected]=>
+ int(0)
+ ["id"]=>
+ string(1) "1"
+ ["val":protected]=>
+ string(1) "A"
+ ["val2":"TestBase":private]=>
+ NULL
+ ["val2"]=>
+ string(2) "AA"
+ }
+ [1]=>
+ object(TestDerived)#%d (5) {
+ ["row":protected]=>
+ int(1)
+ ["id"]=>
+ string(1) "2"
+ ["val":protected]=>
+ string(1) "B"
+ ["val2":"TestBase":private]=>
+ NULL
+ ["val2"]=>
+ string(2) "BB"
+ }
+ [2]=>
+ object(TestDerived)#%d (5) {
+ ["row":protected]=>
+ int(2)
+ ["id"]=>
+ string(1) "3"
+ ["val":protected]=>
+ string(1) "C"
+ ["val2":"TestBase":private]=>
+ NULL
+ ["val2"]=>
+ string(2) "CC"
+ }
+} \ No newline at end of file
diff --git a/ext/pdo/tests/pdo_006.phpt b/ext/pdo/tests/pdo_006.phpt
new file mode 100644
index 0000000..fc83301
--- /dev/null
+++ b/ext/pdo/tests/pdo_006.phpt
@@ -0,0 +1,77 @@
+--TEST--
+PDO Common: PDO::FETCH_GROUP
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES(1, 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'A')");
+$db->exec("INSERT INTO test VALUES(3, 'C')");
+
+$stmt = $db->prepare('SELECT val, id from test');
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_NUM|PDO::FETCH_GROUP));
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_ASSOC|PDO::FETCH_GROUP));
+
+?>
+--EXPECT--
+array(2) {
+ ["A"]=>
+ array(2) {
+ [0]=>
+ array(1) {
+ [0]=>
+ string(1) "1"
+ }
+ [1]=>
+ array(1) {
+ [0]=>
+ string(1) "2"
+ }
+ }
+ ["C"]=>
+ array(1) {
+ [0]=>
+ array(1) {
+ [0]=>
+ string(1) "3"
+ }
+ }
+}
+array(2) {
+ ["A"]=>
+ array(2) {
+ [0]=>
+ array(1) {
+ ["id"]=>
+ string(1) "1"
+ }
+ [1]=>
+ array(1) {
+ ["id"]=>
+ string(1) "2"
+ }
+ }
+ ["C"]=>
+ array(1) {
+ [0]=>
+ array(1) {
+ ["id"]=>
+ string(1) "3"
+ }
+ }
+}
diff --git a/ext/pdo/tests/pdo_007.phpt b/ext/pdo/tests/pdo_007.phpt
new file mode 100644
index 0000000..291e863
--- /dev/null
+++ b/ext/pdo/tests/pdo_007.phpt
@@ -0,0 +1,65 @@
+--TEST--
+PDO Common: PDO::FETCH_UNIQUE
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id CHAR(1) NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES('A', 'A')");
+$db->exec("INSERT INTO test VALUES('B', 'A')");
+$db->exec("INSERT INTO test VALUES('C', 'C')");
+
+$stmt = $db->prepare('SELECT id, val from test');
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_NUM|PDO::FETCH_UNIQUE));
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_ASSOC|PDO::FETCH_UNIQUE));
+
+?>
+--EXPECT--
+array(3) {
+ ["A"]=>
+ array(1) {
+ [0]=>
+ string(1) "A"
+ }
+ ["B"]=>
+ array(1) {
+ [0]=>
+ string(1) "A"
+ }
+ ["C"]=>
+ array(1) {
+ [0]=>
+ string(1) "C"
+ }
+}
+array(3) {
+ ["A"]=>
+ array(1) {
+ ["val"]=>
+ string(1) "A"
+ }
+ ["B"]=>
+ array(1) {
+ ["val"]=>
+ string(1) "A"
+ }
+ ["C"]=>
+ array(1) {
+ ["val"]=>
+ string(1) "C"
+ }
+}
diff --git a/ext/pdo/tests/pdo_008.phpt b/ext/pdo/tests/pdo_008.phpt
new file mode 100644
index 0000000..3e9e12e
--- /dev/null
+++ b/ext/pdo/tests/pdo_008.phpt
@@ -0,0 +1,40 @@
+--TEST--
+PDO Common: PDO::FETCH_UNIQUE conflict
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id CHAR(1) NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES('A', 'A')");
+$db->exec("INSERT INTO test VALUES('B', 'A')");
+$db->exec("INSERT INTO test VALUES('C', 'C')");
+
+$stmt = $db->prepare('SELECT val, id from test');
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_NUM|PDO::FETCH_UNIQUE));
+
+?>
+--EXPECT--
+array(2) {
+ ["A"]=>
+ array(1) {
+ [0]=>
+ string(1) "B"
+ }
+ ["C"]=>
+ array(1) {
+ [0]=>
+ string(1) "C"
+ }
+}
diff --git a/ext/pdo/tests/pdo_009.phpt b/ext/pdo/tests/pdo_009.phpt
new file mode 100644
index 0000000..0e21177
--- /dev/null
+++ b/ext/pdo/tests/pdo_009.phpt
@@ -0,0 +1,131 @@
+--TEST--
+PDO Common: PDO::FETCH_CLASSTYPE
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE classtypes(id int NOT NULL PRIMARY KEY, name VARCHAR(10) NOT NULL UNIQUE)');
+$db->exec('INSERT INTO classtypes VALUES(0, \'stdClass\')');
+$db->exec('INSERT INTO classtypes VALUES(1, \'Test1\')');
+$db->exec('INSERT INTO classtypes VALUES(2, \'Test2\')');
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, classtype int, val VARCHAR(10))');
+$db->exec('INSERT INTO test VALUES(1, 0, \'A\')');
+$db->exec('INSERT INTO test VALUES(2, 1, \'B\')');
+$db->exec('INSERT INTO test VALUES(3, 2, \'C\')');
+$db->exec('INSERT INTO test VALUES(4, 3, \'D\')');
+
+$stmt = $db->prepare('SELECT classtypes.name, test.id AS id, test.val AS val FROM test LEFT JOIN classtypes ON test.classtype=classtypes.id');
+
+class Test1
+{
+ public function __construct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+}
+
+class Test2
+{
+ public function __construct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+}
+
+class Test3
+{
+ public function __construct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+}
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_NUM));
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_CLASSTYPE, 'Test3'));
+
+?>
+--EXPECTF--
+array(4) {
+ [0]=>
+ array(3) {
+ [0]=>
+ string(8) "stdClass"
+ [1]=>
+ string(1) "1"
+ [2]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(3) {
+ [0]=>
+ string(5) "Test1"
+ [1]=>
+ string(1) "2"
+ [2]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(3) {
+ [0]=>
+ string(5) "Test2"
+ [1]=>
+ string(1) "3"
+ [2]=>
+ string(1) "C"
+ }
+ [3]=>
+ array(3) {
+ [0]=>
+ NULL
+ [1]=>
+ string(1) "4"
+ [2]=>
+ string(1) "D"
+ }
+}
+Test1::__construct()
+Test2::__construct()
+Test3::__construct()
+array(4) {
+ [0]=>
+ object(stdClass)#%d (2) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ }
+ [1]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ }
+ [2]=>
+ object(Test2)#%d (2) {
+ ["id"]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ }
+ [3]=>
+ object(Test3)#%d (2) {
+ ["id"]=>
+ string(1) "4"
+ ["val"]=>
+ string(1) "D"
+ }
+}
diff --git a/ext/pdo/tests/pdo_010.phpt b/ext/pdo/tests/pdo_010.phpt
new file mode 100644
index 0000000..812b868
--- /dev/null
+++ b/ext/pdo/tests/pdo_010.phpt
@@ -0,0 +1,119 @@
+--TEST--
+PDO Common: PDO::FETCH_CLASSTYPE and GROUP/UNIQUE
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE classtypes(id int NOT NULL PRIMARY KEY, name VARCHAR(10) NOT NULL UNIQUE)');
+$db->exec('INSERT INTO classtypes VALUES(0, \'stdClass\')');
+$db->exec('INSERT INTO classtypes VALUES(1, \'Test1\')');
+$db->exec('INSERT INTO classtypes VALUES(2, \'Test2\')');
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, classtype int, val VARCHAR(10), grp VARCHAR(10))');
+$db->exec('INSERT INTO test VALUES(1, 0, \'A\', \'Group1\')');
+$db->exec('INSERT INTO test VALUES(2, 1, \'B\', \'Group1\')');
+$db->exec('INSERT INTO test VALUES(3, 2, \'C\', \'Group2\')');
+$db->exec('INSERT INTO test VALUES(4, 3, \'D\', \'Group2\')');
+
+$stmt = $db->prepare('SELECT classtypes.name, test.grp AS grp, test.id AS id, test.val AS val FROM test LEFT JOIN classtypes ON test.classtype=classtypes.id');
+
+class Test1
+{
+ public function __construct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+}
+
+class Test2
+{
+ public function __construct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+}
+
+class Test3
+{
+ public function __construct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+}
+
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_CLASSTYPE|PDO::FETCH_GROUP, 'Test3'));
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_CLASSTYPE|PDO::FETCH_UNIQUE, 'Test3'));
+
+?>
+--EXPECTF--
+Test1::__construct()
+Test2::__construct()
+Test3::__construct()
+array(2) {
+ ["Group1"]=>
+ array(2) {
+ [0]=>
+ object(stdClass)#%d (2) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ }
+ [1]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ }
+ }
+ ["Group2"]=>
+ array(2) {
+ [0]=>
+ object(Test2)#%d (2) {
+ ["id"]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ }
+ [1]=>
+ object(Test3)#%d (2) {
+ ["id"]=>
+ string(1) "4"
+ ["val"]=>
+ string(1) "D"
+ }
+ }
+}
+Test1::__construct()
+Test2::__construct()
+Test3::__construct()
+array(2) {
+ ["Group1"]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ }
+ ["Group2"]=>
+ object(Test3)#%d (2) {
+ ["id"]=>
+ string(1) "4"
+ ["val"]=>
+ string(1) "D"
+ }
+}
diff --git a/ext/pdo/tests/pdo_011.phpt b/ext/pdo/tests/pdo_011.phpt
new file mode 100644
index 0000000..4a24042
--- /dev/null
+++ b/ext/pdo/tests/pdo_011.phpt
@@ -0,0 +1,301 @@
+--TEST--
+PDO Common: PDO::FETCH_FUNC and statement overloading
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10), grp VARCHAR(10))');
+$db->exec('INSERT INTO test VALUES(1, \'A\', \'Group1\')');
+$db->exec('INSERT INTO test VALUES(2, \'B\', \'Group1\')');
+$db->exec('INSERT INTO test VALUES(3, \'C\', \'Group2\')');
+$db->exec('INSERT INTO test VALUES(4, \'D\', \'Group2\')');
+
+class DerivedStatement extends PDOStatement
+{
+ private function __construct($name, $db)
+ {
+ $this->name = $name;
+ echo __METHOD__ . "($name)\n";
+ }
+
+ function reTrieve($id, $val) {
+ echo __METHOD__ . "($id,$val)\n";
+ return array($id=>$val);
+ }
+}
+
+$select1 = $db->prepare('SELECT grp, id FROM test');
+$select2 = $db->prepare('SELECT id, val FROM test');
+$derived = $db->prepare('SELECT id, val FROM test', array(PDO::ATTR_STATEMENT_CLASS=>array('DerivedStatement', array('Overloaded', $db))));
+
+class Test1
+{
+ public function __construct($id, $val)
+ {
+ echo __METHOD__ . "($id,$val)\n";
+ $this->id = $id;
+ $this->val = $val;
+ }
+
+ static public function factory($id, $val)
+ {
+ echo __METHOD__ . "($id,$val)\n";
+ return new self($id, $val);
+ }
+}
+
+function test($id,$val='N/A')
+{
+ echo __METHOD__ . "($id,$val)\n";
+ return array($id=>$val);
+}
+
+$f = new Test1(0,0);
+
+$select1->execute();
+var_dump($select1->fetchAll(PDO::FETCH_FUNC|PDO::FETCH_GROUP, 'test'));
+
+$select2->execute();
+var_dump($select2->fetchAll(PDO::FETCH_FUNC, 'test'));
+
+$select2->execute();
+var_dump($select2->fetchAll(PDO::FETCH_FUNC, array('Test1','factory')));
+
+$select2->execute();
+var_dump($select2->fetchAll(PDO::FETCH_FUNC, array($f, 'factory')));
+
+var_dump(get_class($derived));
+$derived->execute();
+var_dump($derived->fetchAll(PDO::FETCH_FUNC, array($derived, 'retrieve')));
+$derived->execute();
+var_dump($derived->fetchAll(PDO::FETCH_FUNC, array($derived, 'reTrieve')));
+$derived->execute();
+var_dump($derived->fetchAll(PDO::FETCH_FUNC, array($derived, 'RETRIEVE')));
+
+?>
+--EXPECTF--
+DerivedStatement::__construct(Overloaded)
+Test1::__construct(0,0)
+test(1,N/A)
+test(2,N/A)
+test(3,N/A)
+test(4,N/A)
+array(2) {
+ ["Group1"]=>
+ array(2) {
+ [0]=>
+ array(1) {
+ [1]=>
+ string(3) "N/A"
+ }
+ [1]=>
+ array(1) {
+ [2]=>
+ string(3) "N/A"
+ }
+ }
+ ["Group2"]=>
+ array(2) {
+ [0]=>
+ array(1) {
+ [3]=>
+ string(3) "N/A"
+ }
+ [1]=>
+ array(1) {
+ [4]=>
+ string(3) "N/A"
+ }
+ }
+}
+test(1,A)
+test(2,B)
+test(3,C)
+test(4,D)
+array(4) {
+ [0]=>
+ array(1) {
+ [1]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(1) {
+ [2]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(1) {
+ [3]=>
+ string(1) "C"
+ }
+ [3]=>
+ array(1) {
+ [4]=>
+ string(1) "D"
+ }
+}
+Test1::factory(1,A)
+Test1::__construct(1,A)
+Test1::factory(2,B)
+Test1::__construct(2,B)
+Test1::factory(3,C)
+Test1::__construct(3,C)
+Test1::factory(4,D)
+Test1::__construct(4,D)
+array(4) {
+ [0]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ }
+ [1]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ }
+ [2]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ }
+ [3]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "4"
+ ["val"]=>
+ string(1) "D"
+ }
+}
+Test1::factory(1,A)
+Test1::__construct(1,A)
+Test1::factory(2,B)
+Test1::__construct(2,B)
+Test1::factory(3,C)
+Test1::__construct(3,C)
+Test1::factory(4,D)
+Test1::__construct(4,D)
+array(4) {
+ [0]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ }
+ [1]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ }
+ [2]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ }
+ [3]=>
+ object(Test1)#%d (2) {
+ ["id"]=>
+ string(1) "4"
+ ["val"]=>
+ string(1) "D"
+ }
+}
+string(16) "DerivedStatement"
+DerivedStatement::reTrieve(1,A)
+DerivedStatement::reTrieve(2,B)
+DerivedStatement::reTrieve(3,C)
+DerivedStatement::reTrieve(4,D)
+array(4) {
+ [0]=>
+ array(1) {
+ [1]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(1) {
+ [2]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(1) {
+ [3]=>
+ string(1) "C"
+ }
+ [3]=>
+ array(1) {
+ [4]=>
+ string(1) "D"
+ }
+}
+DerivedStatement::reTrieve(1,A)
+DerivedStatement::reTrieve(2,B)
+DerivedStatement::reTrieve(3,C)
+DerivedStatement::reTrieve(4,D)
+array(4) {
+ [0]=>
+ array(1) {
+ [1]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(1) {
+ [2]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(1) {
+ [3]=>
+ string(1) "C"
+ }
+ [3]=>
+ array(1) {
+ [4]=>
+ string(1) "D"
+ }
+}
+DerivedStatement::reTrieve(1,A)
+DerivedStatement::reTrieve(2,B)
+DerivedStatement::reTrieve(3,C)
+DerivedStatement::reTrieve(4,D)
+array(4) {
+ [0]=>
+ array(1) {
+ [1]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(1) {
+ [2]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(1) {
+ [3]=>
+ string(1) "C"
+ }
+ [3]=>
+ array(1) {
+ [4]=>
+ string(1) "D"
+ }
+}
diff --git a/ext/pdo/tests/pdo_012.phpt b/ext/pdo/tests/pdo_012.phpt
new file mode 100644
index 0000000..471c397
--- /dev/null
+++ b/ext/pdo/tests/pdo_012.phpt
@@ -0,0 +1,98 @@
+--TEST--
+PDO Common: PDOStatement::setFetchMode
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10), grp VARCHAR(10))');
+$db->exec('INSERT INTO test VALUES(1, \'A\', \'Group1\')');
+$db->exec('INSERT INTO test VALUES(2, \'B\', \'Group2\')');
+
+$SELECT = 'SELECT val, grp FROM test';
+
+$stmt = $db->query($SELECT, PDO::FETCH_NUM);
+var_dump($stmt->fetchAll());
+
+class Test
+{
+ function __construct($name = 'N/A')
+ {
+ echo __METHOD__ . "($name)\n";
+ }
+}
+
+unset($stmt);
+
+$stmt = $db->query($SELECT, PDO::FETCH_CLASS, 'Test');
+var_dump($stmt->fetchAll());
+
+unset($stmt);
+
+$stmt = $db->query($SELECT, PDO::FETCH_NUM);
+$stmt->setFetchMode(PDO::FETCH_CLASS, 'Test', array('Changed'));
+var_dump($stmt->fetchAll());
+
+?>
+--EXPECTF--
+array(2) {
+ [0]=>
+ array(2) {
+ [0]=>
+ string(1) "A"
+ [1]=>
+ string(6) "Group1"
+ }
+ [1]=>
+ array(2) {
+ [0]=>
+ string(1) "B"
+ [1]=>
+ string(6) "Group2"
+ }
+}
+Test::__construct(N/A)
+Test::__construct(N/A)
+array(2) {
+ [0]=>
+ object(Test)#%d (2) {
+ ["val"]=>
+ string(1) "A"
+ ["grp"]=>
+ string(6) "Group1"
+ }
+ [1]=>
+ object(Test)#%d (2) {
+ ["val"]=>
+ string(1) "B"
+ ["grp"]=>
+ string(6) "Group2"
+ }
+}
+Test::__construct(Changed)
+Test::__construct(Changed)
+array(2) {
+ [0]=>
+ object(Test)#%d (2) {
+ ["val"]=>
+ string(1) "A"
+ ["grp"]=>
+ string(6) "Group1"
+ }
+ [1]=>
+ object(Test)#%d (2) {
+ ["val"]=>
+ string(1) "B"
+ ["grp"]=>
+ string(6) "Group2"
+ }
+}
diff --git a/ext/pdo/tests/pdo_013.phpt b/ext/pdo/tests/pdo_013.phpt
new file mode 100644
index 0000000..009c7e2
--- /dev/null
+++ b/ext/pdo/tests/pdo_013.phpt
@@ -0,0 +1,96 @@
+--TEST--
+PDO Common: PDOStatement iterator
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10), grp VARCHAR(10))');
+$db->exec('INSERT INTO test VALUES(1, \'A\', \'Group1\')');
+$db->exec('INSERT INTO test VALUES(2, \'B\', \'Group2\')');
+
+$SELECT = 'SELECT val, grp FROM test';
+
+$stmt = $db->prepare($SELECT);
+
+$stmt->execute();
+$stmt->setFetchMode(PDO::FETCH_NUM);
+foreach ($stmt as $data)
+{
+ var_dump($data);
+}
+
+class Test
+{
+ function __construct($name = 'N/A')
+ {
+ echo __METHOD__ . "($name)\n";
+ }
+}
+
+unset($stmt);
+
+foreach ($db->query($SELECT, PDO::FETCH_CLASS, 'Test') as $data)
+{
+ var_dump($data);
+}
+
+unset($stmt);
+
+$stmt = $db->query($SELECT, PDO::FETCH_CLASS, 'Test', array('WOW'));
+
+foreach($stmt as $data)
+{
+ var_dump($data);
+}
+?>
+--EXPECTF--
+array(2) {
+ [0]=>
+ string(1) "A"
+ [1]=>
+ string(6) "Group1"
+}
+array(2) {
+ [0]=>
+ string(1) "B"
+ [1]=>
+ string(6) "Group2"
+}
+Test::__construct(N/A)
+object(Test)#%d (2) {
+ ["val"]=>
+ string(1) "A"
+ ["grp"]=>
+ string(6) "Group1"
+}
+Test::__construct(N/A)
+object(Test)#%d (2) {
+ ["val"]=>
+ string(1) "B"
+ ["grp"]=>
+ string(6) "Group2"
+}
+Test::__construct(WOW)
+object(Test)#%d (2) {
+ ["val"]=>
+ string(1) "A"
+ ["grp"]=>
+ string(6) "Group1"
+}
+Test::__construct(WOW)
+object(Test)#%d (2) {
+ ["val"]=>
+ string(1) "B"
+ ["grp"]=>
+ string(6) "Group2"
+}
diff --git a/ext/pdo/tests/pdo_014.phpt b/ext/pdo/tests/pdo_014.phpt
new file mode 100644
index 0000000..794fdae
--- /dev/null
+++ b/ext/pdo/tests/pdo_014.phpt
@@ -0,0 +1,102 @@
+--TEST--
+PDO Common: PDOStatement SPL iterator
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+if (!extension_loaded('SPL')) die('skip SPL not available');
+if (!class_exists('IteratorIterator', false)) die('skip IteratorIterator class not present');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10), grp VARCHAR(10))');
+$db->exec('INSERT INTO test VALUES(1, \'A\', \'Group1\')');
+$db->exec('INSERT INTO test VALUES(2, \'B\', \'Group2\')');
+$SELECT = 'SELECT val, grp FROM test';
+
+class Test
+{
+ function __construct($name = 'N/A')
+ {
+ echo __METHOD__ . "($name)\n";
+ }
+}
+
+$stmt = $db->query($SELECT, PDO::FETCH_CLASS, 'Test', array('WOW'));
+
+$it = new IteratorIterator($stmt); /* check if we can convert that thing */
+
+/*** HINT: If YOU plan to do so remember not to call rewind() -> see below ***/
+
+foreach($it as $data)
+{
+ var_dump($data);
+}
+
+$it->next(); /* must be allowed */
+var_dump($it->current()); /* must return NULL */
+var_dump($it->valid()); /* must return false */
+
+class PDOStatementAggregate extends PDOStatement implements IteratorAggregate
+{
+ private function __construct()
+ {
+ echo __METHOD__ . "\n";
+ $this->setFetchMode(PDO::FETCH_NUM);
+ /* default fetch mode is BOTH, so we see if the ctor can overwrite that */
+ }
+
+ function getIterator()
+ {
+ echo __METHOD__ . "\n";
+ $this->execute();
+ return new IteratorIterator($this, 'PDOStatement');
+ }
+}
+
+$stmt = $db->prepare($SELECT, array(PDO::ATTR_STATEMENT_CLASS=>array('PDOStatementAggregate')));
+
+foreach($stmt as $data)
+{
+ var_dump($data);
+}
+
+?>
+--EXPECTF--
+Test::__construct(WOW)
+object(Test)#4 (2) {
+ ["val"]=>
+ string(1) "A"
+ ["grp"]=>
+ string(6) "Group1"
+}
+Test::__construct(WOW)
+object(Test)#6 (2) {
+ ["val"]=>
+ string(1) "B"
+ ["grp"]=>
+ string(6) "Group2"
+}
+NULL
+bool(false)
+PDOStatementAggregate::__construct
+PDOStatementAggregate::getIterator
+array(2) {
+ [0]=>
+ string(1) "A"
+ [1]=>
+ string(6) "Group1"
+}
+array(2) {
+ [0]=>
+ string(1) "B"
+ [1]=>
+ string(6) "Group2"
+}
diff --git a/ext/pdo/tests/pdo_015.phpt b/ext/pdo/tests/pdo_015.phpt
new file mode 100644
index 0000000..aea292b
--- /dev/null
+++ b/ext/pdo/tests/pdo_015.phpt
@@ -0,0 +1,100 @@
+--TEST--
+PDO Common: PDO::FETCH_COLUMN
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(20))');
+$db->exec('INSERT INTO test VALUES(1, \'A\', \'A2\')');
+$db->exec('INSERT INTO test VALUES(2, \'A\', \'B2\')');
+
+$select1 = $db->prepare('SELECT id, val, val2 FROM test');
+$select2 = $db->prepare('SELECT val, val2 FROM test');
+
+$select1->execute();
+var_dump($select1->fetchAll(PDO::FETCH_COLUMN));
+$select1->execute();
+var_dump($select1->fetchAll(PDO::FETCH_COLUMN, 2));
+$select1->execute();
+var_dump($select1->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP));
+$select1->execute();
+var_dump($select1->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE));
+$select1->execute();
+var_dump($select1->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE, 0));
+$select1->execute();
+var_dump($select1->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE, 1));
+$select1->execute();
+var_dump($select1->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE, 2));
+
+$select2->execute();
+var_dump($select2->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP));
+
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ string(1) "1"
+ [1]=>
+ string(1) "2"
+}
+array(2) {
+ [0]=>
+ string(2) "A2"
+ [1]=>
+ string(2) "B2"
+}
+array(2) {
+ [1]=>
+ array(1) {
+ [0]=>
+ string(1) "A"
+ }
+ [2]=>
+ array(1) {
+ [0]=>
+ string(1) "A"
+ }
+}
+array(2) {
+ [1]=>
+ string(1) "A"
+ [2]=>
+ string(1) "A"
+}
+array(2) {
+ [1]=>
+ string(1) "1"
+ [2]=>
+ string(1) "2"
+}
+array(2) {
+ [1]=>
+ string(1) "A"
+ [2]=>
+ string(1) "A"
+}
+array(2) {
+ [1]=>
+ string(2) "A2"
+ [2]=>
+ string(2) "B2"
+}
+array(1) {
+ ["A"]=>
+ array(2) {
+ [0]=>
+ string(2) "A2"
+ [1]=>
+ string(2) "B2"
+ }
+}
diff --git a/ext/pdo/tests/pdo_016.phpt b/ext/pdo/tests/pdo_016.phpt
new file mode 100644
index 0000000..12c9517
--- /dev/null
+++ b/ext/pdo/tests/pdo_016.phpt
@@ -0,0 +1,204 @@
+--TEST--
+PDO Common: PDO::FETCH_BOUND
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+if (!strncasecmp(getenv('PDOTEST_DSN'), 'oci', strlen('oci'))) die('skip not relevant for oci driver - cannot reexecute after closing cursors without reparse');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
+ $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+}
+
+$db->exec('CREATE TABLE test(idx int NOT NULL PRIMARY KEY, txt VARCHAR(20))');
+$db->exec('INSERT INTO test VALUES(0, \'String0\')');
+$db->exec('INSERT INTO test VALUES(1, \'String1\')');
+$db->exec('INSERT INTO test VALUES(2, \'String2\')');
+
+$stmt1 = $db->prepare('SELECT COUNT(idx) FROM test');
+$stmt2 = $db->prepare('SELECT idx, txt FROM test ORDER by idx');
+
+$stmt1->execute();
+var_dump($stmt1->fetchColumn());
+$stmt1 = null;
+
+$stmt2->execute();
+$cont = $stmt2->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE);
+var_dump($cont);
+
+echo "===WHILE===\n";
+
+$stmt2->bindColumn('idx', $idx);
+$stmt2->bindColumn('txt', $txt);
+$stmt2->execute();
+
+while($stmt2->fetch(PDO::FETCH_BOUND)) {
+ var_dump(array($idx=>$txt));
+}
+
+echo "===ALONE===\n";
+
+$stmt3 = $db->prepare('SELECT txt FROM test WHERE idx=:inp');
+$stmt3->bindParam(':inp', $idx); /* by foreign name */
+
+$stmt4 = $db->prepare('SELECT idx FROM test WHERE txt=:txt');
+$stmt4->bindParam(':txt', $txt); /* using same name */
+
+foreach($cont as $idx => $txt)
+{
+ var_dump(array($idx=>$txt));
+ var_dump($stmt3->execute());
+
+ if ($idx == 0) {
+ /* portability-wise, you may only bindColumn()s
+ * after execute() has been called at least once */
+ $stmt3->bindColumn('txt', $col1);
+ }
+ var_dump($stmt3->fetch(PDO::FETCH_BOUND));
+ $stmt3->closeCursor();
+
+ var_dump($stmt4->execute());
+ if ($idx == 0) {
+ /* portability-wise, you may only bindColumn()s
+ * after execute() has been called at least once */
+ $stmt4->bindColumn('idx', $col2);
+ }
+ var_dump($stmt4->fetch(PDO::FETCH_BOUND));
+ $stmt4->closeCursor();
+ var_dump(array($col2=>$col1));
+}
+
+echo "===REBIND/SAME===\n";
+
+$stmt4->bindColumn('idx', $col1);
+
+foreach($cont as $idx => $txt)
+{
+ var_dump(array($idx=>$txt));
+ var_dump($stmt3->execute());
+ var_dump($stmt3->fetch(PDO::FETCH_BOUND));
+ $stmt3->closeCursor();
+ var_dump($col1);
+ var_dump($stmt4->execute());
+ var_dump($stmt4->fetch(PDO::FETCH_BOUND));
+ $stmt4->closeCursor();
+ var_dump($col1);
+}
+
+echo "===REBIND/CONFLICT===\n";
+
+$stmt2->bindColumn('idx', $col1);
+$stmt2->bindColumn('txt', $col1);
+$stmt2->execute();
+
+while($stmt2->fetch(PDO::FETCH_BOUND))
+{
+ var_dump($col1);
+}
+
+
+?>
+--EXPECT--
+string(1) "3"
+array(3) {
+ [0]=>
+ string(7) "String0"
+ [1]=>
+ string(7) "String1"
+ [2]=>
+ string(7) "String2"
+}
+===WHILE===
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+===ALONE===
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+===REBIND/SAME===
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+bool(true)
+bool(true)
+string(7) "String0"
+bool(true)
+bool(true)
+string(1) "0"
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+bool(true)
+bool(true)
+string(7) "String1"
+bool(true)
+bool(true)
+string(1) "1"
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+bool(true)
+bool(true)
+string(7) "String2"
+bool(true)
+bool(true)
+string(1) "2"
+===REBIND/CONFLICT===
+string(7) "String0"
+string(7) "String1"
+string(7) "String2"
diff --git a/ext/pdo/tests/pdo_016a.phpt b/ext/pdo/tests/pdo_016a.phpt
new file mode 100644
index 0000000..72f4b42
--- /dev/null
+++ b/ext/pdo/tests/pdo_016a.phpt
@@ -0,0 +1,204 @@
+--TEST--
+PDO Common: PDO::FETCH_BOUND w/o :
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+if (!strncasecmp(getenv('PDOTEST_DSN'), 'oci', strlen('oci'))) die('skip not relevant for oci driver - cannot reexecute after closing cursors without reparse');
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
+ $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+}
+
+$db->exec('CREATE TABLE test(idx int NOT NULL PRIMARY KEY, txt VARCHAR(20))');
+$db->exec('INSERT INTO test VALUES(0, \'String0\')');
+$db->exec('INSERT INTO test VALUES(1, \'String1\')');
+$db->exec('INSERT INTO test VALUES(2, \'String2\')');
+
+$stmt1 = $db->prepare('SELECT COUNT(idx) FROM test');
+$stmt2 = $db->prepare('SELECT idx, txt FROM test ORDER by idx');
+
+$stmt1->execute();
+var_dump($stmt1->fetchColumn());
+$stmt1 = null;
+
+$stmt2->execute();
+$cont = $stmt2->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE);
+var_dump($cont);
+
+echo "===WHILE===\n";
+
+$stmt2->bindColumn('idx', $idx);
+$stmt2->bindColumn('txt', $txt);
+$stmt2->execute();
+
+while($stmt2->fetch(PDO::FETCH_BOUND)) {
+ var_dump(array($idx=>$txt));
+}
+
+echo "===ALONE===\n";
+
+$stmt3 = $db->prepare('SELECT txt FROM test WHERE idx=:inp');
+$stmt3->bindParam('inp', $idx); /* by foreign name */
+
+$stmt4 = $db->prepare('SELECT idx FROM test WHERE txt=:txt');
+$stmt4->bindParam('txt', $txt); /* using same name */
+
+foreach($cont as $idx => $txt)
+{
+ var_dump(array($idx=>$txt));
+ var_dump($stmt3->execute());
+
+ if ($idx == 0) {
+ /* portability-wise, you may only bindColumn()s
+ * after execute() has been called at least once */
+ $stmt3->bindColumn('txt', $col1);
+ }
+ var_dump($stmt3->fetch(PDO::FETCH_BOUND));
+ $stmt3->closeCursor();
+
+ var_dump($stmt4->execute());
+ if ($idx == 0) {
+ /* portability-wise, you may only bindColumn()s
+ * after execute() has been called at least once */
+ $stmt4->bindColumn('idx', $col2);
+ }
+ var_dump($stmt4->fetch(PDO::FETCH_BOUND));
+ $stmt4->closeCursor();
+ var_dump(array($col2=>$col1));
+}
+
+echo "===REBIND/SAME===\n";
+
+$stmt4->bindColumn('idx', $col1);
+
+foreach($cont as $idx => $txt)
+{
+ var_dump(array($idx=>$txt));
+ var_dump($stmt3->execute());
+ var_dump($stmt3->fetch(PDO::FETCH_BOUND));
+ $stmt3->closeCursor();
+ var_dump($col1);
+ var_dump($stmt4->execute());
+ var_dump($stmt4->fetch(PDO::FETCH_BOUND));
+ $stmt4->closeCursor();
+ var_dump($col1);
+}
+
+echo "===REBIND/CONFLICT===\n";
+
+$stmt2->bindColumn('idx', $col1);
+$stmt2->bindColumn('txt', $col1);
+$stmt2->execute();
+
+while($stmt2->fetch(PDO::FETCH_BOUND))
+{
+ var_dump($col1);
+}
+
+
+?>
+--EXPECT--
+string(1) "3"
+array(3) {
+ [0]=>
+ string(7) "String0"
+ [1]=>
+ string(7) "String1"
+ [2]=>
+ string(7) "String2"
+}
+===WHILE===
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+===ALONE===
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+===REBIND/SAME===
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+bool(true)
+bool(true)
+string(7) "String0"
+bool(true)
+bool(true)
+string(1) "0"
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+bool(true)
+bool(true)
+string(7) "String1"
+bool(true)
+bool(true)
+string(1) "1"
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+bool(true)
+bool(true)
+string(7) "String2"
+bool(true)
+bool(true)
+string(1) "2"
+===REBIND/CONFLICT===
+string(7) "String0"
+string(7) "String1"
+string(7) "String2"
diff --git a/ext/pdo/tests/pdo_017.phpt b/ext/pdo/tests/pdo_017.phpt
new file mode 100644
index 0000000..31ee88b
--- /dev/null
+++ b/ext/pdo/tests/pdo_017.phpt
@@ -0,0 +1,74 @@
+--TEST--
+PDO Common: transactions
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+
+$db = PDOTest::factory();
+try {
+ $db->beginTransaction();
+} catch (PDOException $e) {
+ die('skip no working transactions: ' . $e->getMessage());
+}
+
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
+ require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+ if (false === MySQLPDOTest::detect_transactional_mysql_engine($db)) {
+ die('skip your mysql configuration does not support working transactions');
+ }
+}
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
+ require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+ $suf = ' ENGINE=' . MySQLPDOTest::detect_transactional_mysql_engine($db);
+} else {
+ $suf = '';
+}
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10))'.$suf);
+$db->exec("INSERT INTO test VALUES(1, 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'B')");
+$db->exec("INSERT INTO test VALUES(3, 'C')");
+$delete = $db->prepare('DELETE FROM test');
+
+function countRows($action) {
+ global $db;
+ $select = $db->prepare('SELECT COUNT(*) FROM test');
+ $select->execute();
+ $res = $select->fetchColumn();
+ return "Counted $res rows after $action.\n";
+}
+
+echo countRows('insert');
+
+$db->beginTransaction();
+$delete->execute();
+echo countRows('delete');
+$db->rollBack();
+
+echo countRows('rollback');
+
+$db->beginTransaction();
+$delete->execute();
+echo countRows('delete');
+$db->commit();
+
+echo countRows('commit');
+
+?>
+--EXPECT--
+Counted 3 rows after insert.
+Counted 0 rows after delete.
+Counted 3 rows after rollback.
+Counted 0 rows after delete.
+Counted 0 rows after commit.
diff --git a/ext/pdo/tests/pdo_018.phpt b/ext/pdo/tests/pdo_018.phpt
new file mode 100644
index 0000000..7f27ce3
--- /dev/null
+++ b/ext/pdo/tests/pdo_018.phpt
@@ -0,0 +1,276 @@
+--TEST--
+PDO Common: serializing
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+if (!interface_exists('Serializable')) die('skip no Serializable interface');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+class TestBase implements Serializable
+{
+ public $BasePub = 'Public';
+ protected $BasePro = 'Protected';
+ private $BasePri = 'Private';
+
+ function serialize()
+ {
+ $serialized = array();
+ foreach($this as $prop => $val) {
+ $serialized[$prop] = $val;
+ }
+ $serialized = serialize($serialized);
+ echo __METHOD__ . "() = '$serialized'\n";
+ return $serialized;
+ }
+
+ function unserialize($serialized)
+ {
+ echo __METHOD__ . "($serialized)\n";
+ foreach(unserialize($serialized) as $prop => $val) {
+ $this->$prop = '#'.$val;
+ }
+ return true;
+ }
+}
+
+class TestDerived extends TestBase
+{
+ public $BasePub = 'DerivedPublic';
+ protected $BasePro = 'DerivdeProtected';
+ public $DerivedPub = 'Public';
+ protected $DerivedPro = 'Protected';
+ private $DerivedPri = 'Private';
+
+ function serialize()
+ {
+ echo __METHOD__ . "()\n";
+ return TestBase::serialize();
+ }
+
+ function unserialize($serialized)
+ {
+ echo __METHOD__ . "()\n";
+ return TestBase::unserialize($serialized);
+ }
+}
+
+class TestLeaf extends TestDerived
+{
+}
+
+$db->exec('CREATE TABLE classtypes(id int NOT NULL PRIMARY KEY, name VARCHAR(20) NOT NULL UNIQUE)');
+$db->exec('INSERT INTO classtypes VALUES(0, \'stdClass\')');
+$db->exec('INSERT INTO classtypes VALUES(1, \'TestBase\')');
+$db->exec('INSERT INTO classtypes VALUES(2, \'TestDerived\')');
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, classtype int, val VARCHAR(255))');
+
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+var_dump($db->query('SELECT COUNT(*) FROM classtypes')->fetchColumn());
+var_dump($db->query('SELECT id, name FROM classtypes ORDER by id')->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE));
+
+$objs = array();
+$objs[0] = new stdClass;
+$objs[1] = new TestBase;
+$objs[2] = new TestDerived;
+$objs[3] = new TestLeaf;
+
+$stmt = $db->prepare('SELECT id FROM classtypes WHERE name=:cname');
+$stmt->bindParam(':cname', $cname);
+
+$ctypes = array();
+
+foreach($objs as $obj)
+{
+ $cname = get_class($obj);
+ $ctype = NULL; /* set default for non stored class name */
+ $stmt->execute();
+ $stmt->bindColumn('id', $ctype);
+ $stmt->fetch(PDO::FETCH_BOUND);
+ $ctypes[$cname] = $ctype;
+}
+
+echo "===TYPES===\n";
+var_dump($ctypes);
+
+unset($stmt);
+
+echo "===INSERT===\n";
+$stmt = $db->prepare('INSERT INTO test VALUES(:id, :classtype, :val)');
+$stmt->bindParam(':id', $idx);
+$stmt->bindParam(':classtype', $ctype);
+$stmt->bindParam(':val', $val);
+
+foreach($objs as $idx => $obj)
+{
+ $ctype = $ctypes[get_class($obj)];
+ if (method_exists($obj, 'serialize'))
+ {
+ $val = $obj->serialize();
+ }
+ else
+ {
+ $val = '';
+ }
+ $stmt->execute();
+}
+
+unset($stmt);
+
+echo "===DATA===\n";
+$res = $db->query('SELECT test.val FROM test')->fetchAll(PDO::FETCH_COLUMN);
+
+// For Oracle map NULL to empty string so the test doesn't diff
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'oci' && $res[0] === null) {
+ $res[0] = "";
+}
+var_dump($res);
+
+echo "===FAILURE===\n";
+try
+{
+ $db->query('SELECT classtypes.name AS name, test.val AS val FROM test LEFT JOIN classtypes ON test.classtype=classtypes.id')->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_CLASSTYPE|PDO::FETCH_SERIALIZE, 'TestLeaf', array());
+}
+catch (PDOException $e)
+{
+ echo 'Exception:';
+ echo $e->getMessage()."\n";
+}
+
+echo "===COUNT===\n";
+var_dump($db->query('SELECT COUNT(*) FROM test LEFT JOIN classtypes ON test.classtype=classtypes.id WHERE (classtypes.id IS NULL OR classtypes.id > 0)')->fetchColumn());
+
+echo "===DATABASE===\n";
+$stmt = $db->prepare('SELECT classtypes.name AS name, test.val AS val FROM test LEFT JOIN classtypes ON test.classtype=classtypes.id WHERE (classtypes.id IS NULL OR classtypes.id > 0)');
+
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+echo "===FETCHCLASS===\n";
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_CLASSTYPE|PDO::FETCH_SERIALIZE, 'TestLeaf'));
+
+
+?>
+--EXPECTF--
+string(1) "3"
+array(3) {
+ [0]=>
+ string(8) "stdClass"
+ [1]=>
+ string(8) "TestBase"
+ [2]=>
+ string(11) "TestDerived"
+}
+===TYPES===
+array(4) {
+ ["stdClass"]=>
+ string(1) "0"
+ ["TestBase"]=>
+ string(1) "1"
+ ["TestDerived"]=>
+ string(1) "2"
+ ["TestLeaf"]=>
+ NULL
+}
+===INSERT===
+TestBase::serialize() = 'a:3:{s:7:"BasePub";s:6:"Public";s:7:"BasePro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}'
+TestDerived::serialize()
+TestBase::serialize() = 'a:5:{s:7:"BasePub";s:13:"DerivedPublic";s:7:"BasePro";s:16:"DerivdeProtected";s:10:"DerivedPub";s:6:"Public";s:10:"DerivedPro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}'
+TestDerived::serialize()
+TestBase::serialize() = 'a:5:{s:7:"BasePub";s:13:"DerivedPublic";s:7:"BasePro";s:16:"DerivdeProtected";s:10:"DerivedPub";s:6:"Public";s:10:"DerivedPro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}'
+===DATA===
+array(4) {
+ [0]=>
+ string(0) ""
+ [1]=>
+ string(91) "a:3:{s:7:"BasePub";s:6:"Public";s:7:"BasePro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}"
+ [2]=>
+ string(172) "a:5:{s:7:"BasePub";s:13:"DerivedPublic";s:7:"BasePro";s:16:"DerivdeProtected";s:10:"DerivedPub";s:6:"Public";s:10:"DerivedPro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}"
+ [3]=>
+ string(172) "a:5:{s:7:"BasePub";s:13:"DerivedPublic";s:7:"BasePro";s:16:"DerivdeProtected";s:10:"DerivedPub";s:6:"Public";s:10:"DerivedPro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}"
+}
+===FAILURE===
+Exception:SQLSTATE[HY000]: General error: cannot unserialize class
+===COUNT===
+string(1) "3"
+===DATABASE===
+array(3) {
+ [0]=>
+ array(2) {
+ ["name"]=>
+ string(8) "TestBase"
+ ["val"]=>
+ string(91) "a:3:{s:7:"BasePub";s:6:"Public";s:7:"BasePro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}"
+ }
+ [1]=>
+ array(2) {
+ ["name"]=>
+ string(11) "TestDerived"
+ ["val"]=>
+ string(172) "a:5:{s:7:"BasePub";s:13:"DerivedPublic";s:7:"BasePro";s:16:"DerivdeProtected";s:10:"DerivedPub";s:6:"Public";s:10:"DerivedPro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}"
+ }
+ [2]=>
+ array(2) {
+ ["name"]=>
+ NULL
+ ["val"]=>
+ string(172) "a:5:{s:7:"BasePub";s:13:"DerivedPublic";s:7:"BasePro";s:16:"DerivdeProtected";s:10:"DerivedPub";s:6:"Public";s:10:"DerivedPro";s:9:"Protected";s:7:"BasePri";s:7:"Private";}"
+ }
+}
+===FETCHCLASS===
+TestBase::unserialize(a:3:{s:7:"BasePub";s:6:"Public";s:7:"BasePro";s:9:"Protected";s:7:"BasePri";s:7:"Private";})
+TestDerived::unserialize()
+TestBase::unserialize(a:5:{s:7:"BasePub";s:13:"DerivedPublic";s:7:"BasePro";s:16:"DerivdeProtected";s:10:"DerivedPub";s:6:"Public";s:10:"DerivedPro";s:9:"Protected";s:7:"BasePri";s:7:"Private";})
+TestDerived::unserialize()
+TestBase::unserialize(a:5:{s:7:"BasePub";s:13:"DerivedPublic";s:7:"BasePro";s:16:"DerivdeProtected";s:10:"DerivedPub";s:6:"Public";s:10:"DerivedPro";s:9:"Protected";s:7:"BasePri";s:7:"Private";})
+array(3) {
+ [0]=>
+ object(TestBase)#%d (3) {
+ ["BasePub"]=>
+ string(7) "#Public"
+ ["BasePro":protected]=>
+ string(10) "#Protected"
+ ["BasePri":"TestBase":private]=>
+ string(8) "#Private"
+ }
+ [1]=>
+ object(TestDerived)#%d (6) {
+ ["BasePub"]=>
+ string(14) "#DerivedPublic"
+ ["BasePro":protected]=>
+ string(17) "#DerivdeProtected"
+ ["DerivedPub"]=>
+ string(7) "#Public"
+ ["DerivedPro":protected]=>
+ string(10) "#Protected"
+ ["DerivedPri":"TestDerived":private]=>
+ string(7) "Private"
+ ["BasePri":"TestBase":private]=>
+ string(8) "#Private"
+ }
+ [2]=>
+ object(TestLeaf)#%d (6) {
+ ["BasePub"]=>
+ string(14) "#DerivedPublic"
+ ["BasePro":protected]=>
+ string(17) "#DerivdeProtected"
+ ["DerivedPub"]=>
+ string(7) "#Public"
+ ["DerivedPro":protected]=>
+ string(10) "#Protected"
+ ["DerivedPri":"TestDerived":private]=>
+ string(7) "Private"
+ ["BasePri":"TestBase":private]=>
+ string(8) "#Private"
+ }
+}
diff --git a/ext/pdo/tests/pdo_019.phpt b/ext/pdo/tests/pdo_019.phpt
new file mode 100644
index 0000000..f6d75bb
--- /dev/null
+++ b/ext/pdo/tests/pdo_019.phpt
@@ -0,0 +1,71 @@
+--TEST--
+PDO Common: fetch() and while()
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(idx int NOT NULL PRIMARY KEY, txt VARCHAR(20))');
+$db->exec('INSERT INTO test VALUES(0, \'String0\')');
+$db->exec('INSERT INTO test VALUES(1, \'String1\')');
+$db->exec('INSERT INTO test VALUES(2, \'String2\')');
+$db->exec('INSERT INTO test VALUES(3, \'String3\')');
+
+
+var_dump($db->query('SELECT COUNT(*) FROM test')->fetchColumn());
+
+$stmt = $db->prepare('SELECT idx, txt FROM test ORDER by idx');
+
+$stmt->execute();
+$cont = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE);
+var_dump($cont);
+
+echo "===WHILE===\n";
+
+$stmt->bindColumn('idx', $idx);
+$stmt->bindColumn('txt', $txt);
+$stmt->execute();
+
+while($stmt->fetch(PDO::FETCH_BOUND)) {
+ var_dump(array($idx=>$txt));
+}
+
+?>
+--EXPECT--
+string(1) "4"
+array(4) {
+ [0]=>
+ string(7) "String0"
+ [1]=>
+ string(7) "String1"
+ [2]=>
+ string(7) "String2"
+ [3]=>
+ string(7) "String3"
+}
+===WHILE===
+array(1) {
+ [0]=>
+ string(7) "String0"
+}
+array(1) {
+ [1]=>
+ string(7) "String1"
+}
+array(1) {
+ [2]=>
+ string(7) "String2"
+}
+array(1) {
+ [3]=>
+ string(7) "String3"
+}
diff --git a/ext/pdo/tests/pdo_020.phpt b/ext/pdo/tests/pdo_020.phpt
new file mode 100644
index 0000000..dc7e4d1
--- /dev/null
+++ b/ext/pdo/tests/pdo_020.phpt
@@ -0,0 +1,34 @@
+--TEST--
+PDO Common: PDOStatement::columnCount
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');
+$db->exec("INSERT INTO test VALUES(1, 'A', 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'B', 'B')");
+$db->exec("INSERT INTO test VALUES(3, 'C', 'C')");
+
+foreach (array('SELECT id, val FROM test', 'SELECT id, val, val2 FROM test', 'SELECT COUNT(*) FROM test') as $sql) {
+
+ $stmt = $db->query($sql);
+ $res = $stmt->columnCount();
+ echo "Counted $res columns after $sql.\n";
+ $stmt = null;
+}
+
+?>
+--EXPECT--
+Counted 2 columns after SELECT id, val FROM test.
+Counted 3 columns after SELECT id, val, val2 FROM test.
+Counted 1 columns after SELECT COUNT(*) FROM test.
diff --git a/ext/pdo/tests/pdo_021.phpt b/ext/pdo/tests/pdo_021.phpt
new file mode 100644
index 0000000..fb25fc5
--- /dev/null
+++ b/ext/pdo/tests/pdo_021.phpt
@@ -0,0 +1,59 @@
+--TEST--
+PDO Common: PDOStatement::execute with parameters
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
+ $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+}
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');
+
+$select = $db->prepare('SELECT COUNT(id) FROM test');
+
+$data = array(
+ array('10', 'Abc', 'zxy'),
+ array('20', 'Def', 'wvu'),
+ array('30', 'Ghi', 'tsr'),
+ array('40', 'Jkl', 'qpo'),
+ array('50', 'Mno', 'nml'),
+ array('60', 'Pqr', 'kji'),
+);
+
+
+// Insert using question mark placeholders
+$stmt = $db->prepare("INSERT INTO test VALUES(?, ?, ?)");
+foreach ($data as $row) {
+ $stmt->execute($row);
+}
+$select->execute();
+$num = $select->fetchColumn();
+echo 'There are ' . $num . " rows in the table.\n";
+
+// Insert using named parameters
+$stmt2 = $db->prepare("INSERT INTO test VALUES(:first, :second, :third)");
+foreach ($data as $row) {
+ $stmt2->execute(array(':first'=>($row[0] + 5), ':second'=>$row[1],
+ ':third'=>$row[2]));
+}
+
+$select->execute();
+$num = $select->fetchColumn();
+echo 'There are ' . $num . " rows in the table.\n";
+
+
+?>
+--EXPECT--
+There are 6 rows in the table.
+There are 12 rows in the table.
diff --git a/ext/pdo/tests/pdo_022.phpt b/ext/pdo/tests/pdo_022.phpt
new file mode 100644
index 0000000..daec14f
--- /dev/null
+++ b/ext/pdo/tests/pdo_022.phpt
@@ -0,0 +1,62 @@
+--TEST--
+PDO Common: PDOStatement::getColumnMeta
+--SKIPIF--
+<?php # vim:ft=php
+die('skip this feature is not yet finalized, no test makes sense');
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+/*
+ * Note well: meta information is a nightmare to handle portably.
+ * it's not really PDOs job.
+ * We've not yet defined exactly what makes sense for getColumnMeta,
+ * so no tests make any sense to anyone. When they do, we can enable
+ * this test file.
+ * TODO: filter out driver dependent components from this common core
+ * test file.
+ */
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');
+$db->exec('insert2', "INSERT INTO test VALUES(:first, :second, :third)");
+
+$data = array(
+ array('10', 'Abc', 'zxy'),
+ array('20', 'Def', 'wvu'),
+ array('30', 'Ghi', 'tsr'),
+ array('40', 'Jkl', 'qpo'),
+ array('50', 'Mno', 'nml'),
+ array('60', 'Pqr', 'kji'),
+);
+
+
+// Insert using question mark placeholders
+$stmt = $db->prepare("INSERT INTO test VALUES(?, ?, ?)");
+foreach ($data as $row) {
+ $stmt->execute($row);
+}
+
+// Retrieve column metadata for a result set returned by explicit SELECT
+$select = $db->query('SELECT id, val, val2 FROM test');
+$meta = $select->getColumnMeta(0);
+var_dump($meta);
+$meta = $select->getColumnMeta(1);
+var_dump($meta);
+$meta = $select->getColumnMeta(2);
+var_dump($meta);
+
+// Retrieve column metadata for a result set returned by a function
+$select = $db->query('SELECT COUNT(*) FROM test');
+$meta = $select->getColumnMeta(0);
+var_dump($meta);
+
+?>
+--EXPECT--
+The unexpected!
diff --git a/ext/pdo/tests/pdo_023.phpt b/ext/pdo/tests/pdo_023.phpt
new file mode 100644
index 0000000..564bcf8
--- /dev/null
+++ b/ext/pdo/tests/pdo_023.phpt
@@ -0,0 +1,112 @@
+--TEST--
+PDO Common: extending PDO
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+class PDOStatementX extends PDOStatement
+{
+ public $test1 = 1;
+
+ protected function __construct()
+ {
+ $this->test2 = 2;
+ $this->test2 = 22;
+ echo __METHOD__ . "()\n";
+ }
+
+ function __destruct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+}
+
+class PDODatabaseX extends PDO
+{
+ public $test1 = 1;
+
+ function __destruct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+
+ function test()
+ {
+ $this->test2 = 2;
+ var_dump($this->test1);
+ var_dump($this->test2);
+ $this->test2 = 22;
+ }
+
+ function query($sql)
+ {
+ echo __METHOD__ . "()\n";
+ $stmt = parent::prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('PDOStatementx')));
+ $stmt->execute();
+ return $stmt;
+ }
+}
+
+$db = PDOTest::factory('PDODatabaseX');
+$db->test();
+var_dump($db);
+
+$db->query('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->query('INSERT INTO test VALUES(0, \'A\')');
+$db->query('INSERT INTO test VALUES(1, \'B\')');
+
+
+$stmt = $db->query('SELECT val, id FROM test');
+var_dump($stmt);
+var_dump($stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_UNIQUE));
+
+$stmt = NULL;
+$db = NULL;
+
+
+?>
+--EXPECTF--
+int(1)
+int(2)
+object(PDODatabaseX)#%d (2) {
+ ["test1"]=>
+ int(1)
+ ["test2"]=>
+ int(22)
+}
+PDODatabaseX::query()
+PDOStatementX::__construct()
+PDOStatementX::__destruct()
+PDODatabaseX::query()
+PDOStatementX::__construct()
+PDOStatementX::__destruct()
+PDODatabaseX::query()
+PDOStatementX::__construct()
+PDOStatementX::__destruct()
+PDODatabaseX::query()
+PDOStatementX::__construct()
+object(PDOStatementX)#%d (3) {
+ ["test1"]=>
+ int(1)
+ ["queryString"]=>
+ string(24) "SELECT val, id FROM test"
+ ["test2"]=>
+ int(22)
+}
+array(2) {
+ ["A"]=>
+ string(1) "0"
+ ["B"]=>
+ string(1) "1"
+}
+PDOStatementX::__destruct()
+PDODatabaseX::__destruct()
diff --git a/ext/pdo/tests/pdo_024.phpt b/ext/pdo/tests/pdo_024.phpt
new file mode 100644
index 0000000..1b4f841
--- /dev/null
+++ b/ext/pdo/tests/pdo_024.phpt
@@ -0,0 +1,35 @@
+--TEST--
+PDO Common: assert that bindParam does not modify parameter
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('create table test (id int, name varchar(10))');
+
+$stmt = $db->prepare('insert into test (id, name) values(0, :name)');
+$name = NULL;
+$before_bind = $name;
+$stmt->bindParam(':name', $name);
+if ($name !== $before_bind) {
+ echo "bind: fail\n";
+} else {
+ echo "bind: success\n";
+}
+var_dump($stmt->execute());
+var_dump($db->query('select name from test where id=0')->fetchColumn());
+
+?>
+--EXPECT--
+bind: success
+bool(true)
+NULL
diff --git a/ext/pdo/tests/pdo_025.phpt b/ext/pdo/tests/pdo_025.phpt
new file mode 100644
index 0000000..d16d8dd
--- /dev/null
+++ b/ext/pdo/tests/pdo_025.phpt
@@ -0,0 +1,113 @@
+--TEST--
+PDO Common: PDO::FETCH_INTO
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');
+
+$data = array(
+ array('10', 'Abc', 'zxy'),
+ array('20', 'Def', 'wvu'),
+ array('30', 'Ghi', 'tsr'),
+ array('40', 'Jkl', 'qpo'),
+ array('50', 'Mno', 'nml'),
+ array('60', 'Pqr', 'kji'),
+);
+
+
+// Insert using question mark placeholders
+$stmt = $db->prepare("INSERT INTO test VALUES(?, ?, ?)");
+foreach ($data as $row) {
+ $stmt->execute($row);
+}
+
+class Test {
+ public $id, $val, $val2;
+}
+
+$stmt = $db->prepare('SELECT * FROM test');
+$stmt->setFetchMode(PDO::FETCH_INTO, new Test);
+$stmt->execute();
+
+foreach($stmt as $obj) {
+ var_dump($obj);
+}
+
+echo "===FAIL===\n";
+
+class Fail {
+ protected $id;
+ public $val, $val2;
+}
+
+$stmt->setFetchMode(PDO::FETCH_INTO, new Fail);
+$stmt->execute();
+
+foreach($stmt as $obj) {
+ var_dump($obj);
+}
+
+?>
+--EXPECTF--
+object(Test)#%d (3) {
+ ["id"]=>
+ string(2) "10"
+ ["val"]=>
+ string(3) "Abc"
+ ["val2"]=>
+ string(3) "zxy"
+}
+object(Test)#%d (3) {
+ ["id"]=>
+ string(2) "20"
+ ["val"]=>
+ string(3) "Def"
+ ["val2"]=>
+ string(3) "wvu"
+}
+object(Test)#%d (3) {
+ ["id"]=>
+ string(2) "30"
+ ["val"]=>
+ string(3) "Ghi"
+ ["val2"]=>
+ string(3) "tsr"
+}
+object(Test)#%d (3) {
+ ["id"]=>
+ string(2) "40"
+ ["val"]=>
+ string(3) "Jkl"
+ ["val2"]=>
+ string(3) "qpo"
+}
+object(Test)#%d (3) {
+ ["id"]=>
+ string(2) "50"
+ ["val"]=>
+ string(3) "Mno"
+ ["val2"]=>
+ string(3) "nml"
+}
+object(Test)#%d (3) {
+ ["id"]=>
+ string(2) "60"
+ ["val"]=>
+ string(3) "Pqr"
+ ["val2"]=>
+ string(3) "kji"
+}
+===FAIL===
+
+Fatal error: Cannot access protected property Fail::$id in %spdo_025.php on line %d%A
diff --git a/ext/pdo/tests/pdo_026.phpt b/ext/pdo/tests/pdo_026.phpt
new file mode 100644
index 0000000..d993f32
--- /dev/null
+++ b/ext/pdo/tests/pdo_026.phpt
@@ -0,0 +1,111 @@
+--TEST--
+PDO Common: extending PDO (2)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+$data = array(
+ array('10', 'Abc', 'zxy'),
+ array('20', 'Def', 'wvu'),
+ array('30', 'Ghi', 'tsr'),
+);
+
+class PDOStatementX extends PDOStatement
+{
+ public $dbh;
+
+ protected function __construct($dbh)
+ {
+ $this->dbh = $dbh;
+ echo __METHOD__ . "()\n";
+ }
+
+ function __destruct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+}
+
+class PDODatabase extends PDO
+{
+ function __destruct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+
+ function query($sql)
+ {
+ echo __METHOD__ . "()\n";
+ $stmt = $this->prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('PDOStatementx', array($this))));
+ $stmt->setFetchMode(PDO::FETCH_ASSOC);
+ $stmt->execute();
+ return $stmt;
+ }
+}
+
+$db = PDOTest::factory('PDODatabase');
+var_dump(get_class($db));
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');
+
+$stmt = $db->prepare("INSERT INTO test VALUES(?, ?, ?)");
+var_dump(get_class($stmt));
+foreach ($data as $row) {
+ $stmt->execute($row);
+}
+
+unset($stmt);
+
+$stmt = $db->query('SELECT * FROM test');
+var_dump(get_class($stmt));
+var_dump(get_class($stmt->dbh));
+
+foreach($stmt as $obj) {
+ var_dump($obj);
+}
+
+echo "===DONE===\n";
+?>
+--EXPECT--
+string(11) "PDODatabase"
+string(12) "PDOStatement"
+PDODatabase::query()
+PDOStatementX::__construct()
+string(13) "PDOStatementX"
+string(11) "PDODatabase"
+array(3) {
+ ["id"]=>
+ string(2) "10"
+ ["val"]=>
+ string(3) "Abc"
+ ["val2"]=>
+ string(3) "zxy"
+}
+array(3) {
+ ["id"]=>
+ string(2) "20"
+ ["val"]=>
+ string(3) "Def"
+ ["val2"]=>
+ string(3) "wvu"
+}
+array(3) {
+ ["id"]=>
+ string(2) "30"
+ ["val"]=>
+ string(3) "Ghi"
+ ["val2"]=>
+ string(3) "tsr"
+}
+===DONE===
+PDOStatementX::__destruct()
+PDODatabase::__destruct()
diff --git a/ext/pdo/tests/pdo_027.phpt b/ext/pdo/tests/pdo_027.phpt
new file mode 100644
index 0000000..1d9990d
--- /dev/null
+++ b/ext/pdo/tests/pdo_027.phpt
@@ -0,0 +1,29 @@
+--TEST--
+PDO Common: PDO::FETCH_LAZY
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('create table test (id int, name varchar(10))');
+$db->exec("INSERT INTO test (id,name) VALUES(1,'test1')");
+$db->exec("INSERT INTO test (id,name) VALUES(2,'test2')");
+
+foreach ($db->query("SELECT * FROM test", PDO::FETCH_LAZY) as $v) {
+ echo "lazy: " . $v->id.$v->name."\n";
+}
+echo "End\n";
+?>
+--EXPECT--
+lazy: 1test1
+lazy: 2test2
+End
diff --git a/ext/pdo/tests/pdo_028.phpt b/ext/pdo/tests/pdo_028.phpt
new file mode 100644
index 0000000..0d1058d
--- /dev/null
+++ b/ext/pdo/tests/pdo_028.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PDO Common: bindValue
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val1 VARCHAR(10), val2 VARCHAR(10), val3 VARCHAR(10))');
+$stmt = $db->prepare('INSERT INTO test values (1, ?, ?, ?)');
+
+$data = array("one", "two", "three");
+
+foreach ($data as $i => $v) {
+ $stmt->bindValue($i+1, $v);
+}
+$stmt->execute();
+
+$stmt = $db->prepare('SELECT * from test');
+$stmt->execute();
+
+var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ array(4) {
+ ["id"]=>
+ string(1) "1"
+ ["val1"]=>
+ string(3) "one"
+ ["val2"]=>
+ string(3) "two"
+ ["val3"]=>
+ string(5) "three"
+ }
+}
diff --git a/ext/pdo/tests/pdo_029.phpt b/ext/pdo/tests/pdo_029.phpt
new file mode 100644
index 0000000..f3dc1f2
--- /dev/null
+++ b/ext/pdo/tests/pdo_029.phpt
@@ -0,0 +1,125 @@
+--TEST--
+PDO Common: extending PDO (3)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+$data = array(
+ array('10', 'Abc', 'zxy'),
+ array('20', 'Def', 'wvu'),
+ array('30', 'Ghi', 'tsr'),
+);
+
+class PDOStatementX extends PDOStatement
+{
+ public $dbh;
+
+ protected function __construct($dbh)
+ {
+ $this->dbh = $dbh;
+ echo __METHOD__ . "()\n";
+ }
+
+ function __destruct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+
+ function execute($params = array())
+ {
+ echo __METHOD__ . "()\n";
+ parent::execute();
+ }
+}
+
+class PDODatabase extends PDO
+{
+ function __destruct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+
+ function query($sql)
+ {
+ echo __METHOD__ . "()\n";
+ $stmt = $this->prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('PDOStatementx', array($this))));
+ $stmt->setFetchMode(PDO::FETCH_ASSOC);
+ $stmt->execute();
+ return $stmt;
+ }
+}
+
+$db = PDOTest::factory('PDODatabase');
+var_dump(get_class($db));
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');
+
+$stmt = $db->prepare("INSERT INTO test VALUES(?, ?, ?)");
+var_dump(get_class($stmt));
+foreach ($data as $row) {
+ $stmt->execute($row);
+}
+
+unset($stmt);
+
+echo "===QUERY===\n";
+
+$stmt = $db->query('SELECT * FROM test');
+var_dump(get_class($stmt));
+var_dump(get_class($stmt->dbh));
+
+echo "===FOREACH===\n";
+
+foreach($stmt as $obj) {
+ var_dump($obj);
+}
+
+echo "===DONE===\n";
+exit(0);
+?>
+--EXPECT--
+string(11) "PDODatabase"
+string(12) "PDOStatement"
+===QUERY===
+PDODatabase::query()
+PDOStatementX::__construct()
+PDOStatementX::execute()
+string(13) "PDOStatementX"
+string(11) "PDODatabase"
+===FOREACH===
+array(3) {
+ ["id"]=>
+ string(2) "10"
+ ["val"]=>
+ string(3) "Abc"
+ ["val2"]=>
+ string(3) "zxy"
+}
+array(3) {
+ ["id"]=>
+ string(2) "20"
+ ["val"]=>
+ string(3) "Def"
+ ["val2"]=>
+ string(3) "wvu"
+}
+array(3) {
+ ["id"]=>
+ string(2) "30"
+ ["val"]=>
+ string(3) "Ghi"
+ ["val2"]=>
+ string(3) "tsr"
+}
+===DONE===
+PDOStatementX::__destruct()
+PDODatabase::__destruct()
diff --git a/ext/pdo/tests/pdo_030.phpt b/ext/pdo/tests/pdo_030.phpt
new file mode 100644
index 0000000..243e94a
--- /dev/null
+++ b/ext/pdo/tests/pdo_030.phpt
@@ -0,0 +1,139 @@
+--TEST--
+PDO Common: extending PDO (4)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+
+$data = array(
+ array('10', 'Abc', 'zxy'),
+ array('20', 'Def', 'wvu'),
+ array('30', 'Ghi', 'tsr'),
+);
+
+class PDOStatementX extends PDOStatement
+{
+ public $dbh;
+
+ protected function __construct($dbh)
+ {
+ $this->dbh = $dbh;
+ $this->setFetchMode(PDO::FETCH_ASSOC);
+ echo __METHOD__ . "()\n";
+ }
+
+ function __destruct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+
+ function execute($params = array())
+ {
+ echo __METHOD__ . "()\n";
+ parent::execute();
+ }
+}
+
+class PDODatabase extends PDO
+{
+ function __destruct()
+ {
+ echo __METHOD__ . "()\n";
+ }
+
+ function query($sql)
+ {
+ echo __METHOD__ . "()\n";
+ return parent::query($sql);
+ }
+}
+
+$db = PDOTest::factory('PDODatabase');
+var_dump(get_class($db));
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');
+
+$stmt = $db->prepare("INSERT INTO test VALUES(?, ?, ?)");
+var_dump(get_class($stmt));
+foreach ($data as $row) {
+ $stmt->execute($row);
+}
+
+unset($stmt);
+
+echo "===QUERY===\n";
+
+var_dump($db->getAttribute(PDO::ATTR_STATEMENT_CLASS));
+$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatementx', array($db)));
+var_dump($db->getAttribute(PDO::ATTR_STATEMENT_CLASS));
+$stmt = $db->query('SELECT * FROM test');
+var_dump(get_class($stmt));
+var_dump(get_class($stmt->dbh));
+
+echo "===FOREACH===\n";
+
+foreach($stmt as $obj) {
+ var_dump($obj);
+}
+
+echo "===DONE===\n";
+exit(0);
+?>
+--EXPECTF--
+string(11) "PDODatabase"
+string(12) "PDOStatement"
+===QUERY===
+array(1) {
+ [0]=>
+ string(12) "PDOStatement"
+}
+array(2) {
+ [0]=>
+ string(13) "PDOStatementX"
+ [1]=>
+ array(1) {
+ [0]=>
+ object(PDODatabase)#%d (0) {
+ }
+ }
+}
+PDODatabase::query()
+PDOStatementX::__construct()
+string(13) "PDOStatementX"
+string(11) "PDODatabase"
+===FOREACH===
+array(3) {
+ ["id"]=>
+ string(2) "10"
+ ["val"]=>
+ string(3) "Abc"
+ ["val2"]=>
+ string(3) "zxy"
+}
+array(3) {
+ ["id"]=>
+ string(2) "20"
+ ["val"]=>
+ string(3) "Def"
+ ["val2"]=>
+ string(3) "wvu"
+}
+array(3) {
+ ["id"]=>
+ string(2) "30"
+ ["val"]=>
+ string(3) "Ghi"
+ ["val2"]=>
+ string(3) "tsr"
+}
+===DONE===
+PDOStatementX::__destruct()
+PDODatabase::__destruct()
diff --git a/ext/pdo/tests/pdo_031.phpt b/ext/pdo/tests/pdo_031.phpt
new file mode 100644
index 0000000..9065ce9
--- /dev/null
+++ b/ext/pdo/tests/pdo_031.phpt
@@ -0,0 +1,63 @@
+--TEST--
+PDO Common: PDOStatement SPL iterator
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+if (!extension_loaded('SPL')) die('skip SPL not available');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+if (!class_exists('RecursiveArrayIterator', false)) die('skip Class RecursiveArrayIterator missing');
+if (!class_exists('RecursiveTreeIterator', false) && !file_exists(getenv('REDIR_TEST_DIR').'../../spl/examples/recursivetreeiterator.inc')) die('skip Class RecursiveTreeIterator missing');
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+if (!class_exists('RecursiveTreeIterator', false)) require_once(getenv('REDIR_TEST_DIR').'../../spl/examples/recursivetreeiterator.inc');
+
+$data = array(
+ array('10', 'Abc', 'zxy'),
+ array('20', 'Def', 'wvu'),
+ array('30', 'Ghi', 'tsr'),
+);
+
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');
+
+$stmt = $db->prepare("INSERT INTO test VALUES(?, ?, ?)");
+foreach ($data as $row) {
+ $stmt->execute($row);
+}
+
+unset($stmt);
+
+echo "===QUERY===\n";
+
+$stmt = $db->query('SELECT * FROM test');
+
+foreach(new RecursiveTreeIterator(new RecursiveArrayIterator($stmt->fetchAll(PDO::FETCH_ASSOC)), RecursiveTreeIterator::BYPASS_KEY) as $c=>$v)
+{
+ echo "$v [$c]\n";
+}
+
+echo "===DONE===\n";
+exit(0);
+?>
+--EXPECT--
+===QUERY===
+|-Array [0]
+| |-10 [id]
+| |-Abc [val]
+| \-zxy [val2]
+|-Array [1]
+| |-20 [id]
+| |-Def [val]
+| \-wvu [val2]
+\-Array [2]
+ |-30 [id]
+ |-Ghi [val]
+ \-tsr [val2]
+===DONE===
diff --git a/ext/pdo/tests/pdo_032.phpt b/ext/pdo/tests/pdo_032.phpt
new file mode 100644
index 0000000..aed6dd3
--- /dev/null
+++ b/ext/pdo/tests/pdo_032.phpt
@@ -0,0 +1,83 @@
+--TEST--
+PDO Common: PDO::ATTR_CASE
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec('CREATE TABLE test(id int NOT NULL PRIMARY KEY, val VARCHAR(10))');
+$db->exec("INSERT INTO test VALUES(1, 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'B')");
+$db->exec("INSERT INTO test VALUES(3, 'C')");
+
+// Lower case columns
+$db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
+$stmt = $db->prepare('SELECT * from test');
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+$stmt->closeCursor();
+
+// Upper case columns
+$db->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
+$stmt = $db->prepare('SELECT * from test');
+$stmt->execute();
+var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+$stmt->closeCursor();
+
+?>
+--EXPECT--
+array(3) {
+ [0]=>
+ array(2) {
+ ["id"]=>
+ string(1) "1"
+ ["val"]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(2) {
+ ["id"]=>
+ string(1) "2"
+ ["val"]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(2) {
+ ["id"]=>
+ string(1) "3"
+ ["val"]=>
+ string(1) "C"
+ }
+}
+array(3) {
+ [0]=>
+ array(2) {
+ ["ID"]=>
+ string(1) "1"
+ ["VAL"]=>
+ string(1) "A"
+ }
+ [1]=>
+ array(2) {
+ ["ID"]=>
+ string(1) "2"
+ ["VAL"]=>
+ string(1) "B"
+ }
+ [2]=>
+ array(2) {
+ ["ID"]=>
+ string(1) "3"
+ ["VAL"]=>
+ string(1) "C"
+ }
+}
diff --git a/ext/pdo/tests/pdo_033.phpt b/ext/pdo/tests/pdo_033.phpt
new file mode 100644
index 0000000..6f78be6
--- /dev/null
+++ b/ext/pdo/tests/pdo_033.phpt
@@ -0,0 +1,44 @@
+--TEST--
+PDO Common: PDO::quote()
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$unquoted = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
+
+$quoted = $db->quote($unquoted);
+
+$len = strlen($unquoted);
+
+@$db->exec("DROP TABLE test");
+
+$db->query("CREATE TABLE test (t char($len))");
+$db->query("INSERT INTO test (t) VALUES($quoted)");
+
+$stmt = $db->prepare('SELECT * from test');
+$stmt->execute();
+
+print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+$db->exec("DROP TABLE test");
+
+?>
+--EXPECT--
+Array
+(
+ [0] => Array
+ (
+ [t] => !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
+ )
+
+)
diff --git a/ext/pdo/tests/pdo_034.phpt b/ext/pdo/tests/pdo_034.phpt
new file mode 100644
index 0000000..3803c2f
--- /dev/null
+++ b/ext/pdo/tests/pdo_034.phpt
@@ -0,0 +1,62 @@
+--TEST--
+PDO Common: PDO::FETCH_KEY_PAIR fetch mode test
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec("CREATE TABLE test (a varchar(100), b varchar(100), c varchar(100))");
+
+for ($i = 0; $i < 5; $i++) {
+ $db->exec("INSERT INTO test (a,b,c) VALUES('test".$i."','".$i."','".$i."')");
+}
+
+var_dump($db->query("SELECT a,b FROM test")->fetch(PDO::FETCH_KEY_PAIR));
+var_dump($db->query("SELECT a,b FROM test")->fetchAll(PDO::FETCH_KEY_PAIR));
+var_dump($db->query("SELECT * FROM test")->fetch(PDO::FETCH_KEY_PAIR));
+var_dump($db->query("SELECT a,a FROM test")->fetchAll(PDO::FETCH_KEY_PAIR));
+
+?>
+--EXPECTF--
+array(1) {
+ ["test0"]=>
+ string(1) "0"
+}
+array(5) {
+ ["test0"]=>
+ string(1) "0"
+ ["test1"]=>
+ string(1) "1"
+ ["test2"]=>
+ string(1) "2"
+ ["test3"]=>
+ string(1) "3"
+ ["test4"]=>
+ string(1) "4"
+}
+
+Warning: PDOStatement::fetch(): SQLSTATE[HY000]: General error: PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain extactly 2 columns. in %spdo_034.php on line %d
+
+Warning: PDOStatement::fetch(): SQLSTATE[HY000]: General error%spdo_034.php on line %d
+bool(false)
+array(5) {
+ ["test0"]=>
+ string(5) "test0"
+ ["test1"]=>
+ string(5) "test1"
+ ["test2"]=>
+ string(5) "test2"
+ ["test3"]=>
+ string(5) "test3"
+ ["test4"]=>
+ string(5) "test4"
+}
diff --git a/ext/pdo/tests/pdo_035.phpt b/ext/pdo/tests/pdo_035.phpt
new file mode 100644
index 0000000..c35c504
--- /dev/null
+++ b/ext/pdo/tests/pdo_035.phpt
@@ -0,0 +1,22 @@
+--TEST--
+PDO Common: PDORow + get_parent_class()
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo_sqlite')) die ("skip Need PDO_SQlite support");
+?>
+--FILE--
+<?php
+$db = new PDO('sqlite::memory:');
+$db->exec('CREATE TABLE test (id int)');
+$db->exec('INSERT INTO test VALUES (23)');
+
+$stmt = $db->prepare('SELECT id FROM test');
+$stmt->execute();
+$result = $stmt->fetch(PDO::FETCH_LAZY);
+
+echo get_class($result), "\n";
+var_dump(get_parent_class($result));
+?>
+--EXPECT--
+PDORow
+bool(false)
diff --git a/ext/pdo/tests/pdo_036.phpt b/ext/pdo/tests/pdo_036.phpt
new file mode 100644
index 0000000..94006c9
--- /dev/null
+++ b/ext/pdo/tests/pdo_036.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Testing PDORow and PDOStatement instances with Reflection
+--SKIPIF--
+<?php if (!extension_loaded('pdo')) die('skip'); ?>
+--FILE--
+<?php
+
+$instance = new reflectionclass('pdorow');
+$x = $instance->newInstance();
+var_dump($x);
+
+$instance = new reflectionclass('pdostatement');
+$x = $instance->newInstance();
+var_dump($x);
+
+?>
+--EXPECTF--
+object(PDORow)#%d (0) {
+}
+object(PDOStatement)#%d (1) {
+ [%u|b%"queryString"]=>
+ NULL
+}
diff --git a/ext/pdo/tests/pdo_037.phpt b/ext/pdo/tests/pdo_037.phpt
new file mode 100644
index 0000000..a0ead4b
--- /dev/null
+++ b/ext/pdo/tests/pdo_037.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Crash when calling a method of a class that inherits PDOStatement
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo')) die('skip');
+?>
+--FILE--
+<?php
+
+class MyStatement extends PDOStatement
+{
+}
+
+$obj = new MyStatement;
+var_dump($obj->foo());
+
+?>
+--EXPECTF--
+Fatal error: Call to undefined method MyStatement::foo() in %s on line %d
diff --git a/ext/pdo/tests/pdo_test.inc b/ext/pdo/tests/pdo_test.inc
new file mode 100644
index 0000000..443c8dd
--- /dev/null
+++ b/ext/pdo/tests/pdo_test.inc
@@ -0,0 +1,84 @@
+<?php
+# PDO test framework utilities
+
+if (getenv('PDOTEST_DSN') === false) {
+ $common = '';
+ $append = false;
+ foreach(file(dirname($_SERVER['PHP_SELF']).'/common.phpt') as $line) {
+ if ($append) {
+ $common .= $line;
+ } elseif (trim($line) == '--REDIRECTTEST--') {
+ $append = true;
+ }
+ }
+ $conf = eval($common);
+ foreach($conf['ENV'] as $n=>$v) putenv("$n=$v");
+}
+
+class PDOTest {
+ // create an instance of the PDO driver, based on
+ // the current environment
+ static function factory($classname = 'PDO', $drop_test_tables = true) {
+ $dsn = getenv('PDOTEST_DSN');
+ $user = getenv('PDOTEST_USER');
+ $pass = getenv('PDOTEST_PASS');
+ $attr = getenv('PDOTEST_ATTR');
+ if (is_string($attr) && strlen($attr)) {
+ $attr = unserialize($attr);
+ } else {
+ $attr = null;
+ }
+
+ if ($user === false) $user = NULL;
+ if ($pass === false) $pass = NULL;
+
+ $db = new $classname($dsn, $user, $pass, $attr);
+
+ if (!$db) {
+ die("Could not create PDO object (DSN=$dsn, user=$user)\n");
+ }
+
+ // clean up any crufty test tables we might have left behind
+ // on a previous run
+ static $test_tables = array(
+ 'test',
+ 'test2',
+ 'classtypes'
+ );
+ if ($drop_test_tables) {
+ foreach ($test_tables as $table) {
+ $db->exec("DROP TABLE $table");
+ }
+ }
+
+ $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+ $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+ return $db;
+ }
+
+ static function skip() {
+ try {
+ $db = PDOTest::factory();
+ } catch (PDOException $e) {
+ die("skip " . $e->getMessage());
+ }
+ }
+
+ static function test_factory($file) {
+ $config = self::get_config($file);
+ foreach ($config['ENV'] as $k => $v) {
+ putenv("$k=$v");
+ }
+ return self::factory();
+ }
+
+ static function get_config($file) {
+ $data = file_get_contents($file);
+ $data = preg_replace('/^.*--REDIRECTTEST--/s', '', $data);
+ $config = eval($data);
+
+ return $config;
+ }
+}
+?>
diff --git a/ext/pdo/tests/pdorow.phpt b/ext/pdo/tests/pdorow.phpt
new file mode 100644
index 0000000..bcfd8ff
--- /dev/null
+++ b/ext/pdo/tests/pdorow.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Trying instantiate a PDORow object manually
+--SKIPIF--
+<?php if (!extension_loaded('pdo')) die('skip'); ?>
+--FILE--
+<?php
+
+new PDORow;
+
+?>
+--EXPECTF--
+Fatal error: PDORow::__construct(): You should not create a PDOStatement manually in %s on line %d
diff --git a/ext/pdo/tests/pecl_bug_5217.phpt b/ext/pdo/tests/pecl_bug_5217.phpt
new file mode 100644
index 0000000..34de925
--- /dev/null
+++ b/ext/pdo/tests/pecl_bug_5217.phpt
@@ -0,0 +1,29 @@
+--TEST--
+PDO Common: PECL Bug #5217 (serialize/unserialze safety)
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+try {
+ $ser = serialize($db);
+ debug_zval_dump($ser);
+ $db = unserialize($ser);
+ $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val VARCHAR(10))');
+} catch (Exception $e) {
+ echo "Safely caught " . $e->getMessage() . "\n";
+}
+
+echo "PHP Didn't crash!\n";
+?>
+--EXPECT--
+Safely caught You cannot serialize or unserialize PDO instances
+PHP Didn't crash!
diff --git a/ext/pdo/tests/pecl_bug_5772.phpt b/ext/pdo/tests/pecl_bug_5772.phpt
new file mode 100644
index 0000000..e38c894
--- /dev/null
+++ b/ext/pdo/tests/pecl_bug_5772.phpt
@@ -0,0 +1,29 @@
+--TEST--
+PDO Common: PECL Bug #5772 (PDO::FETCH_FUNC breaks on mixed case func name)
+--SKIPIF--
+<?php # vim:ft=php:
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec("CREATE TABLE test (id int NOT NULL, PRIMARY KEY (id))");
+$db->exec("INSERT INTO test (id) VALUES (1)");
+
+function heLLO($row) {
+ return $row;
+}
+
+foreach ($db->query("SELECT * FROM test")->fetchAll(PDO::FETCH_FUNC, 'heLLO') as $row) {
+ var_dump($row);
+}
+
+--EXPECT--
+string(1) "1"
diff --git a/ext/pdo/tests/pecl_bug_5809.phpt b/ext/pdo/tests/pecl_bug_5809.phpt
new file mode 100644
index 0000000..bb41281
--- /dev/null
+++ b/ext/pdo/tests/pecl_bug_5809.phpt
@@ -0,0 +1,34 @@
+--TEST--
+PDO Common: PECL Bug #5809 (PDOStatement::execute(array()) changes param)
+--SKIPIF--
+<?php # vim:ft=php:
+if (!extension_loaded('pdo')) die('skip');
+$dir = getenv('REDIR_TEST_DIR');
+if (false == $dir) die('skip no driver');
+require_once $dir . 'pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE__) . '/../../pdo/tests/');
+require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
+$db = PDOTest::factory();
+
+$db->exec("CREATE TABLE test (id int NOT NULL, PRIMARY KEY (id))");
+$db->exec("INSERT INTO test (id) VALUES (1)");
+
+$values = array(1);
+var_dump($values);
+$stmt = $db->prepare('SELECT * FROM test WHERE id = ?');
+$stmt->execute($values);
+var_dump($values);
+
+--EXPECT--
+array(1) {
+ [0]=>
+ int(1)
+}
+array(1) {
+ [0]=>
+ int(1)
+}