]> granicus.if.org Git - php/commitdiff
MFH: Add mysqlnd support for PDO_mysql, fixes at least bug#41997,#42499,
authorJohannes Schlüter <johannes@php.net>
Mon, 21 Jul 2008 13:09:28 +0000 (13:09 +0000)
committerJohannes Schlüter <johannes@php.net>
Mon, 21 Jul 2008 13:09:28 +0000 (13:09 +0000)
     pecl#12794, pecl#12401

# Running the tests:
# (Note: Doesn't work currnetly on HEAD, see:
#  http://news.php.net/php.qa/64378)
#
#  PDO_MYSQL_TEST_DSN  - DSN
#    For example: mysql:dbname=test;host=localhost;port=3306
#
#  PDO_MYSQL_TEST_HOST    - database host
#  PDO_MYSQL_TEST_DB      - database (schema) name
#  PDO_MYSQL_TEST_SOCKET  - database server socket
#  PDO_MYSQL_TEST_ENGINE  - storage engine to use
#  PDO_MYSQL_TEST_USER    - database user
#  PDO_MYSQL_TEST_PASS    - database user password
#  PDO_MYSQL_TEST_CHARSET - database charset
#
#  NOTE: if any of PDO_MYSQL_TEST_[HOST|DB|SOCKET|ENGINE|CHARSET] is
#  part of PDO_MYSQL_TEST_DSN, the values must match. That is, for example,
#  for PDO_MYSQL_TEST_DSN = mysql:dbname=test you MUST set PDO_MYSQL_TEST_DB=test.

107 files changed:
NEWS
ext/pdo/pdo_stmt.c
ext/pdo/php_pdo_driver.h
ext/pdo/tests/bug_34630.phpt
ext/pdo/tests/bug_39656.phpt
ext/pdo/tests/bug_43130.phpt
ext/pdo/tests/bug_43663.phpt
ext/pdo/tests/bug_44159.phpt
ext/pdo/tests/pdo.inc
ext/pdo/tests/pdo_017.phpt
ext/pdo/tests/pdo_025.phpt
ext/pdo/tests/pdo_test.inc
ext/pdo_mysql/CREDITS
ext/pdo_mysql/config.m4
ext/pdo_mysql/config.w32
ext/pdo_mysql/mysql_driver.c
ext/pdo_mysql/mysql_statement.c
ext/pdo_mysql/pdo_mysql.c
ext/pdo_mysql/php_pdo_mysql.h
ext/pdo_mysql/php_pdo_mysql_int.h
ext/pdo_mysql/tests/README [new file with mode: 0644]
ext/pdo_mysql/tests/bug_33689.phpt
ext/pdo_mysql/tests/bug_39858.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_41125.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_41997.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_42499.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_43371.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_44454.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_44707.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_45120.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_pecl_12925.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_pecl_7976.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/config.inc
ext/pdo_mysql/tests/mysql_pdo_test.inc [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql___construct.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql___construct_ini.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql___construct_options.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql___construct_options_libmysql.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql___construct_uri.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_autocommit.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_case.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_client_version.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_connection_status.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_driver_name.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_errmode.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_fetch_table_names.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_init_command.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_max_buffer_size.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_oracle_nulls.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_prefetch.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_server_info.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_server_version.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_attr_statement_class.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_begintransaction.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_bit.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_class_constants.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_commit.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_errorcode.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_errorinfo.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_exec.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_exec_ddl.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_exec_load_data.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_exec_select.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_fetch_both.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_get_attribute.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_interface.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_last_insert_id.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_pconnect.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_phpinfo.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_emulated.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_anonymous.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_placeholder_everywhere.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_match_against.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_native.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_native_clear_error.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_native_column.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_native_dup_named_placeholder.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_native_mixed_style.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_native_named_placeholder.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_prepare_native_placeholder_everywhere.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_rollback.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_bindcolumn.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_bindparam.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_bindparam_types.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_bindvalue.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_blobfromsteam.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_blobs.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_closecursor.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_closecursor_empty.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_columncount.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_errorcode.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_errorinfo.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_non_select.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_serialize.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_serialize_simple.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_fetchobject.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_getcolumnmeta.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_multiquery.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_nextrowset.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_rowcount.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_unbuffered_2050.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_stmt_variable_columncount.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_subclass.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_types.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/pdo_mysql_types_zerofill.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/skipif.inc [new file with mode: 0755]
ext/pdo_mysql/tests/table.inc [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 7530cbbad6e297a8f5631a046aac3bd56a5366e2..e9fd46d3d5fb73044165ccaaf3dfa6d1b26c5440 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -206,7 +206,9 @@ PHP                                                                        NEWS
 - Fixed an issue where exec() on Windows would eat the first and last double quotes.
   (Scott)
 
+- Fixed PECL bug #12794 (PDOStatement->nextRowset() doesn¿t work). (Johannes)
 - Fixed PECL bug #12431 (OCI8 ping functionality is broken). (Oracle Corp.)
+- Fixed PECL bug #12401 (Add support for ATTR_FETCH_TABLE_NAMES). (Johannes)
 
 - Fixed bug #45571 (ReflectionClass::export() shows superclasses' private static 
   methods). (robin_fernandes at uk dot ibm dot com)
@@ -266,6 +268,8 @@ PHP                                                                        NEWS
 - Fixed bug #42637 (SoapFault : Only http and https are allowed). (Bill Moran)
 - Fixed bug #42548 (mysqli PROCEDURE calls can't return result sets). (hartmut)
 - Fixed bug #42509 (gmp leaks memory when gmp_init() not used). (Stas)
+- Fixed bug #42499 (PDO_MYSQL: multi-statement execution via PDO::exec() makes
+  connection unusable). (Johannes)
 - Fixed bug #42443 (PDO SQLite driver binds integers and booleans as strings).
   (Scott)
 - Fixed bug #42284 (duplicate of #39700). (Lars W)
@@ -274,6 +278,8 @@ PHP                                                                        NEWS
   DateTimeZone). (Derick)
 - Fixed bug #42069 (parse_ini_file() allows using some non-alpha numeric
   characters). (Jani)
+- Fixed bug #41997 (pdo_mysql: stored procedure call returning single rowset
+  blocks future queries). (Johannes)
 - Fixed bug #41599 (setTime() fails after modify() is used). (Derick)
 - Fixed bug #41522 (PDO firebird driver returns null if it fails to connect).
   (Lars W)
index d13bf3b4207d2246fd48f59824e24d5a9fdb3f90..5b676798f5a90d4aaa30ba2967b52db072a2874e 100755 (executable)
@@ -545,6 +545,20 @@ static inline void fetch_value(pdo_stmt_t *stmt, zval *dest, int colno, int *typ
        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);
@@ -1883,7 +1897,10 @@ static PHP_METHOD(PDOStatement, getColumnMeta)
        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);
-       add_assoc_long(return_value, "pdo_type", col->param_type);
+       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);
+       }
 }
 /* }}} */
 
index b252ef022e038a25f5ccdb9511bb891fc468875e..605591463e9689b2adefa94dcfbb54f1c1f14cca 100755 (executable)
@@ -44,7 +44,7 @@ PDO_API char *php_pdo_int64_to_str(pdo_int64_t i64 TSRMLS_DC);
 # define FALSE 0
 #endif
 
-#define PDO_DRIVER_API 20060511
+#define PDO_DRIVER_API 20080721
 
 enum pdo_param_type {
        PDO_PARAM_NULL,
@@ -67,7 +67,12 @@ enum pdo_param_type {
        PDO_PARAM_STMT, /* hierarchical result set */
 
        /* get_col ptr should point to a zend_bool */
-       PDO_PARAM_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 */
index 070217bafed1ee7e1465fea099743b65adda0647..22ad2e820a380f7bb6e805b35dbdeaba2f76c393 100644 (file)
@@ -45,6 +45,8 @@ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
 var_dump($db->query("SELECT * from test")->fetchAll(PDO::FETCH_ASSOC));
 
 ?>
+--XFAIL--
+This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64
 --EXPECT--
 array(1) {
   [0]=>
index e1a283ce09572640e8b816a93c7ba203095855fe..7d113ef4aab18efcc55b7852cfab22494cb37297 100644 (file)
@@ -1,7 +1,7 @@
 --TEST--
 PDO Common: Bug #39656 (Crash when calling fetch() on a PDO statment object after closeCursor())
 --SKIPIF--
-<?php  
+<?php
 if (!extension_loaded('pdo')) die('skip');
 $dir = getenv('REDIR_TEST_DIR');
 if (false == $dir) die('skip no driver');
@@ -15,13 +15,13 @@ if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE_
 require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
 $db = PDOTest::factory();
 
-@$db->exec("DROP TABLE testtable");
+@$db->exec("DROP TABLE test");
 $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
-$db->exec("CREATE TABLE testtable (id INTEGER NOT NULL PRIMARY KEY, usr VARCHAR( 256 ) NOT NULL)");
-$db->exec("INSERT INTO testtable (id, usr) VALUES (1, 'user')");
+$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 testtable WHERE id = ?");
+$stmt = $db->prepare("SELECT * FROM test WHERE id = ?");
 $stmt->bindValue(1, 1, PDO::PARAM_INT );
 $stmt->execute();
 $row = $stmt->fetch();
@@ -32,9 +32,10 @@ $stmt->closeCursor();
 $row = $stmt->fetch(); // this line will crash CLI
 var_dump( $row );
 
+@$db->exec("DROP TABLE test");
 echo "Done\n";
 ?>
---EXPECT--     
+--EXPECT--
 array(4) {
   ["id"]=>
   string(1) "1"
index dd956299e336b685079c80404933caaebddd4112..bb40f894dd81d343fd86415983c01d3ee283a9dd 100644 (file)
@@ -16,6 +16,9 @@ if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE_
 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++) {
index 2e903d788f4ab916c2bf779698078c5630872f34..f8e968f717203aafb75acd12d6a712c4ce10d604 100644 (file)
@@ -2,7 +2,12 @@
 PDO Common: Bug #43663 (__call on classes derived from PDO)
 --SKIPIF--
 <?php # vim:ft=php
-if (!extension_loaded('pdo_sqlite')) die('skip no pdo_sqlite');
+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
@@ -14,6 +19,10 @@ class test extends PDO{
         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();
index db4da4013951d140b430c28f7ad76d7123273808..d78b12fb9151c4b88caa128ef9fc920b7cf184e9 100644 (file)
@@ -1,12 +1,16 @@
 --TEST--
 Bug #44159 (Crash: $pdo->setAttribute(PDO::STATEMENT_ATTR_CLASS, NULL))
 --SKIPIF--
-<?php
-if (!extension_loaded('pdo_sqlite')) die('skip no pdo_sqlite');
+<?php # vim:ft=php
+if (!extension_loaded('pdo')) die('skip PDO not available');
+try {
+       $pdo = new PDO("sqlite:/tmp/foo.db");
+} catch (Exception $e) {
+       die("skip PDP_SQLITE not available");
+}
 ?>
 --FILE--
 <?php
-
 $pdo = new PDO("sqlite:/tmp/foo.db");
 
 $attrs = array(PDO::ATTR_STATEMENT_CLASS, PDO::ATTR_STRINGIFY_FETCHES, PDO::NULL_TO_STRING);
index 6d8fa1ea0288012cb6c855d7bb5475bf5271f311..8089236bbcdcfd67be385cbe577cd223188d1485 100755 (executable)
@@ -7,5 +7,4 @@ function set_sql($name,  $query)
                $GLOBALS['SQL'][$name] = $query;
        }
 }
-
 ?>
index 651ee2860c5969b04669896255256832405578ff..31ee88b76b4e456e00bdbc1d8c3af6417143fe19 100644 (file)
@@ -16,7 +16,8 @@ try {
 }
 
 if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
-       if (false === PDOTest::detect_transactional_mysql_engine($db)) {
+       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');
        }
 }
@@ -28,14 +29,15 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
 $db = PDOTest::factory();
 
 if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
-       $suf = ' Type=' . PDOTest::detect_transactional_mysql_engine($db);
+       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(1, 'A')");
+$db->exec("INSERT INTO test VALUES(2, 'B')");
 $db->exec("INSERT INTO test VALUES(3, 'C')");
 $delete = $db->prepare('DELETE FROM test');
 
index 2c27c489fe178a9107557ebc6f13d362535b8e5f..4f299bf1259ee7676b4975a9f56a07a459c7a3fb 100644 (file)
@@ -110,4 +110,4 @@ object(Test)#%d (3) {
 }
 ===FAIL===
 
-Fatal error: Cannot access protected property Fail::$id in %spdo_025.php on line %d
+Fatal error: Cannot access protected property Fail::$id in %spdo_025.php on line %d%a
\ No newline at end of file
index 4d14f32d46c7da2a6547f1f79647e7c0520aa6c7..bbaeb71af7e96e8843e15666158980db1a4ae996 100644 (file)
@@ -19,7 +19,7 @@ if (getenv('PDOTEST_DSN') === false) {
 class PDOTest {
        // create an instance of the PDO driver, based on
        // the current environment
-       static function factory($classname = 'PDO') {
+       static function factory($classname = 'PDO', $drop_test_tables = true) {
                $dsn = getenv('PDOTEST_DSN');
                $user = getenv('PDOTEST_USER');
                $pass = getenv('PDOTEST_PASS');
@@ -32,7 +32,7 @@ class PDOTest {
 
                if ($user === false) $user = NULL;
                if ($pass === false) $pass = NULL;
-               
+
                $db = new $classname($dsn, $user, $pass, $attr);
 
                if (!$db) {
@@ -46,10 +46,12 @@ class PDOTest {
                        'test2',
                        'classtypes'
                        );
-               foreach ($test_tables as $table) {
-                       $db->exec("DROP TABLE $table");
+               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);
@@ -64,15 +66,6 @@ class PDOTest {
                }
        }
 
-       static function detect_transactional_mysql_engine($db) {
-               foreach ($db->query("show variables like 'have%'") as $row) {
-                       if ($row[1] == 'YES' && ($row[0] == 'have_innodb' || $row[0] == 'have_bdb')) {
-                               return str_replace("have_", "", $row[0]);
-                       }
-               }
-               return false;
-       }
-
        static function test_factory($file) {
                $data = file_get_contents($file);
                $data = preg_replace('/^.*--REDIRECTTEST--/s', '', $data);
@@ -83,7 +76,4 @@ class PDOTest {
                return self::factory();
        }
 }
-
-
-
-?>
+?>
\ No newline at end of file
index 0bee1ee76e75358133949e52e7b9790c5e3b6b3c..0423cccd64fb3d7c22711bd744b2411e8dee740a 100755 (executable)
@@ -1,3 +1,3 @@
-mySQL driver for PDO
-George Schlossnagle, Wez Furlong, Ilia Alshanetsky
+MySQL driver for PDO
+George Schlossnagle, Wez Furlong, Ilia Alshanetsky, Johannes Schlueter
+
index f2352b77ca7bcbc71a835d6c1f8dbc622925fa58..5b3e5e74fbb83f296cc089493f90bf5ccb21d2d0 100755 (executable)
@@ -2,20 +2,10 @@ dnl
 dnl $Id$
 dnl
 
-if test "$PHP_PDO" != "no"; then
-
-AC_DEFUN([PDO_MYSQL_LIB_CHK], [
-  str="$PDO_MYSQL_DIR/$1/libmysqlclient*"
-  for j in `echo $str`; do
-    if test -r $j; then
-      PDO_MYSQL_LIB_DIR=$PDO_MYSQL_DIR/$1
-      break 2
-    fi
-  done
-])
-
 PHP_ARG_WITH(pdo-mysql, for MySQL support for PDO,
-[  --with-pdo-mysql[=DIR]    PDO: MySQL support. DIR is the MySQL base directory])
+[  --with-pdo-mysql[=DIR]    PDO: MySQL support. DIR is the MySQL base directoy
+                                 If mysqlnd is passed as DIR, the MySQL native
+                                 native driver will be used [/usr/local]])
 
 if test -z "$PHP_ZLIB_DIR"; then
   PHP_ARG_WITH(zlib-dir, for the location of libz,
@@ -23,118 +13,131 @@ if test -z "$PHP_ZLIB_DIR"; then
 fi
 
 if test "$PHP_PDO_MYSQL" != "no"; then
-  AC_DEFINE(HAVE_MYSQL, 1, [Whether you have MySQL])
-
-  AC_MSG_CHECKING([for mysql_config])
-
-  if test -f $PHP_PDO_MYSQL && test -x $PHP_PDO_MYSQL ; then
-    PDO_MYSQL_CONFIG=$PHP_PDO_MYSQL
-  elif test "$PHP_PDO_MYSQL" != "yes"; then
-    if test -d "$PHP_PDO_MYSQL" ; then
-      if test -x "$PHP_PDO_MYSQL/bin/mysql_config" ; then
-        PDO_MYSQL_CONFIG="$PHP_PDO_MYSQL/bin/mysql_config"
-      else
-        PDO_MYSQL_DIR="$PHP_PDO_MYSQL"
-      fi
-    else
-      AC_MSG_RESULT([$PHP_PDO_MYSQL is not a directory])
-      AC_MSG_ERROR([can not find mysql under the "$PHP_PDO_MYSQL" that you specified])
-    fi
-  else
-    for i in /usr/local /usr ; do
-      if test -x "$i/bin/mysql_config" ; then
-        PDO_MYSQL_CONFIG="$i/bin/mysql_config"
-        break;
-      fi
-      if test -r $i/include/mysql/mysql.h || test -r $i/include/mysql.h ; then
-        PDO_MYSQL_DIR="$i"
-        break;
+  PHP_MYSQLND_ENABLED=yes
+
+  AC_DEFUN([PDO_MYSQL_LIB_CHK], [
+    str="$PDO_MYSQL_DIR/$1/libmysqlclient*"
+    for j in `echo $str`; do
+      if test -r $j; then
+        PDO_MYSQL_LIB_DIR=$PDO_MYSQL_DIR/$1
+        break 2
       fi
     done
-  fi
+  ])
 
-  if test -n "$PDO_MYSQL_CONFIG" && test -x "$PDO_MYSQL_CONFIG" ; then
-    AC_MSG_RESULT($PDO_MYSQL_CONFIG)
-    if test "x$SED" = "x"; then
-      AC_PATH_PROG(SED, sed)
-    fi
-    if test "$enable_maintainer_zts" = "yes"; then
-      PDO_MYSQL_LIBNAME=mysqlclient_r
-      PDO_MYSQL_LIBS=`$PDO_MYSQL_CONFIG --libs_r | $SED -e "s/'//g"`
-    else
-      PDO_MYSQL_LIBNAME=mysqlclient
-      PDO_MYSQL_LIBS=`$PDO_MYSQL_CONFIG --libs | $SED -e "s/'//g"`
-    fi
-    PDO_MYSQL_INCLUDE=`$PDO_MYSQL_CONFIG --cflags | $SED -e "s/'//g"`
-    PDO_MYSQL_SOCKET=`$PDO_MYSQL_CONFIG --socket` 
-  elif test -z "$PDO_MYSQL_DIR"; then
-    AC_MSG_RESULT([not found])
-    AC_MSG_ERROR([Cannot find MySQL header files under $PDO_MYSQL_DIR])
+  if test "$PHP_PDO_MYSQL" = "mysqlnd"; then
+    dnl enables build of mysqnd library
+    PHP_MYSQL_ENABLED=yes
+    AC_DEFINE([PDO_USE_MYSQLND], 1, [Whether pdo_mysql uses mysqlnd])
   else
-    AC_MSG_RESULT([not found])
-    AC_MSG_CHECKING([for mysql install under $PDO_MYSQL_DIR])
-    if test -r $PDO_MYSQL_DIR/include/mysql; then
-      PDO_MYSQL_INC_DIR=$PDO_MYSQL_DIR/include/mysql
-    else
-      PDO_MYSQL_INC_DIR=$PDO_MYSQL_DIR/include
-    fi
-    if test -r $PDO_MYSQL_DIR/$PHP_LIBDIR/mysql; then
-      PDO_MYSQL_LIB_DIR=$PDO_MYSQL_DIR/$PHP_LIBDIR/mysql
+    AC_DEFINE(HAVE_MYSQL, 1, [Whether you have MySQL])
+    AC_MSG_CHECKING([for mysql_config])
+
+    if test -f $PHP_PDO_MYSQL && test -x $PHP_PDO_MYSQL ; then
+      PDO_MYSQL_CONFIG=$PHP_PDO_MYSQL
+    elif test "$PHP_PDO_MYSQL" != "yes"; then
+      if test -d "$PHP_PDO_MYSQL" ; then
+        if test -x "$PHP_PDO_MYSQL/bin/mysql_config" ; then
+          PDO_MYSQL_CONFIG="$PHP_PDO_MYSQL/bin/mysql_config"
+        else
+          PDO_MYSQL_DIR="$PHP_PDO_MYSQL"
+        fi
+      else
+        AC_MSG_RESULT([$PHP_PDO_MYSQL is not a directory])
+        AC_MSG_ERROR([can not find mysql under the "$PHP_PDO_MYSQL" that you specified])
+      fi
     else
-      PDO_MYSQL_LIB_DIR=$PDO_MYSQL_DIR/$PHP_LIBDIR
+      for i in /usr/local /usr ; do
+        if test -x "$i/bin/mysql_config" ; then
+          PDO_MYSQL_CONFIG="$i/bin/mysql_config"
+          break;
+        fi
+        if test -r $i/include/mysql/mysql.h || test -r $i/include/mysql.h ; then
+          PDO_MYSQL_DIR="$i"
+          break;
+        fi
+      done
     fi
 
-    if test -r "$PDO_MYSQL_LIB_DIR"; then
-      AC_MSG_RESULT([libs under $PDO_MYSQL_LIB_DIR; seems promising])
+    if test -n "$PDO_MYSQL_CONFIG" && test -x "$PDO_MYSQL_CONFIG" ; then
+      AC_MSG_RESULT($PDO_MYSQL_CONFIG)
+      if test "x$SED" = "x"; then
+        AC_PATH_PROG(SED, sed)
+      fi
+
+      if test "$enable_maintainer_zts" = "yes"; then
+        PDO_MYSQL_LIBNAME=mysqlclient_r
+        PDO_MYSQL_LIBS=`$PDO_MYSQL_CONFIG --libs_r | $SED -e "s/'//g"`
+      else
+        PDO_MYSQL_LIBNAME=mysqlclient
+        PDO_MYSQL_LIBS=`$PDO_MYSQL_CONFIG --libs | $SED -e "s/'//g"`
+      fi
+      PDO_MYSQL_INCLUDE=`$PDO_MYSQL_CONFIG --cflags | $SED -e "s/'//g"`
+      PDO_MYSQL_SOCKET=`$PDO_MYSQL_CONFIG --socket` 
+    elif test -z "$PDO_MYSQL_DIR"; then
+      AC_MSG_RESULT([not found])
+      AC_MSG_ERROR([Cannot find MySQL header files under $PDO_MYSQL_DIR])
     else
-      AC_MSG_RESULT([can not find it])
-      AC_MSG_ERROR([Unable to find your mysql installation])
-    fi
+      AC_MSG_RESULT([not found])
+      AC_MSG_CHECKING([for mysql install under $PDO_MYSQL_DIR])
+      if test -r $PDO_MYSQL_DIR/include/mysql; then
+        PDO_MYSQL_INC_DIR=$PDO_MYSQL_DIR/include/mysql
+      else
+        PDO_MYSQL_INC_DIR=$PDO_MYSQL_DIR/include
+      fi
+      if test -r $PDO_MYSQL_DIR/$PHP_LIBDIR/mysql; then
+        PDO_MYSQL_LIB_DIR=$PDO_MYSQL_DIR/$PHP_LIBDIR/mysql
+      else
+        PDO_MYSQL_LIB_DIR=$PDO_MYSQL_DIR/$PHP_LIBDIR
+      fi
 
-    PHP_ADD_LIBRARY_WITH_PATH($PDO_MYSQL_LIBNAME, $PDO_MYSQL_LIB_DIR, PDO_MYSQL_SHARED_LIBADD)
-    PHP_ADD_INCLUDE($PDO_MYSQL_INC_DIR)
-    PDO_MYSQL_INCLUDE=-I$PDO_MYSQL_INC_DIR
-  fi
+      if test -r "$PDO_MYSQL_LIB_DIR"; then
+        AC_MSG_RESULT([libs under $PDO_MYSQL_LIB_DIR; seems promising])
+      else
+        AC_MSG_RESULT([can not find it])
+        AC_MSG_ERROR([Unable to find your mysql installation])
+      fi
 
+      PHP_ADD_INCLUDE($PDO_MYSQL_INC_DIR)
+      PDO_MYSQL_INCLUDE=-I$PDO_MYSQL_INC_DIR
+    fi
 
-  if test "$PHP_MYSQL_SOCK" != "no" && test "$PHP_MYSQL_SOCK" != "yes"; then
-    AC_DEFINE_UNQUOTED(PDO_MYSQL_UNIX_ADDR, "$PHP_MYSQL_SOCK", [ ])  
-  else
     AC_DEFINE_UNQUOTED(PDO_MYSQL_UNIX_ADDR, "$PDO_MYSQL_SOCKET", [ ])
-  fi
 
-  PHP_CHECK_LIBRARY($PDO_MYSQL_LIBNAME, mysql_query,
-  [
-    PHP_EVAL_INCLINE($PDO_MYSQL_INCLUDE)
-    PHP_EVAL_LIBLINE($PDO_MYSQL_LIBS, PDO_MYSQL_SHARED_LIBADD)
-  ],[
-    if test "$PHP_ZLIB_DIR" != "no"; then
-      PHP_ADD_LIBRARY_WITH_PATH(z, $PHP_ZLIB_DIR, PDO_MYSQL_SHARED_LIBADD)
-      PHP_CHECK_LIBRARY($PDO_MYSQL_LIBNAME, mysql_query, [], [
-        AC_MSG_ERROR([PDO_MYSQL configure failed. Please check config.log for more information.])
-      ], [
-        -L$PHP_ZLIB_DIR/$PHP_LIBDIR -L$PDO_MYSQL_LIB_DIR 
-      ])  
-      PDO_MYSQL_LIBS="$PDO_MYSQL_LIBS -L$PHP_ZLIB_DIR/$PHP_LIBDIR -lz"
-    else
-      PHP_ADD_LIBRARY(z,, PDO_MYSQL_SHARED_LIBADD)
-      PHP_CHECK_LIBRARY($PDO_MYSQL_LIBNAME, mysql_query, [], [
-        AC_MSG_ERROR([Try adding --with-zlib-dir=<DIR>. Please check config.log for more information.])
-      ], [
-        -L$PDO_MYSQL_LIB_DIR
-      ])   
-      PDO_MYSQL_LIBS="$PDO_MYSQL_LIBS -lz"
-    fi
+    PHP_CHECK_LIBRARY($PDO_MYSQL_LIBNAME, mysql_query,
+    [
+      PHP_EVAL_INCLINE($PDO_MYSQL_INCLUDE)
+      PHP_EVAL_LIBLINE($PDO_MYSQL_LIBS, PDO_MYSQL_SHARED_LIBADD)
+    ],[
+      if test "$PHP_ZLIB_DIR" != "no"; then
+        PHP_ADD_LIBRARY_WITH_PATH(z, $PHP_ZLIB_DIR, PDO_MYSQL_SHARED_LIBADD)
+        PHP_CHECK_LIBRARY($PDO_MYSQL_LIBNAME, mysql_query, [], [
+          AC_MSG_ERROR([PDO_MYSQL configure failed. Please check config.log for more information.])
+        ], [
+          -L$PHP_ZLIB_DIR/$PHP_LIBDIR -L$PDO_MYSQL_LIB_DIR 
+        ])  
+        PDO_MYSQL_LIBS="$PDO_MYSQL_LIBS -L$PHP_ZLIB_DIR/$PHP_LIBDIR -lz"
+      else
+        PHP_ADD_LIBRARY(z,, PDO_MYSQL_SHARED_LIBADD)
+        PHP_CHECK_LIBRARY($PDO_MYSQL_LIBNAME, mysql_query, [], [
+          AC_MSG_ERROR([Try adding --with-zlib-dir=<DIR>. Please check config.log for more information.])
+        ], [
+          -L$PDO_MYSQL_LIB_DIR
+        ])   
+        PDO_MYSQL_LIBS="$PDO_MYSQL_LIBS -lz"
+      fi
 
-    PHP_EVAL_INCLINE($PDO_MYSQL_INCLUDE)
-    PHP_EVAL_LIBLINE($PDO_MYSQL_LIBS, PDO_MYSQL_SHARED_LIBADD)
-  ],[
-    $PDO_MYSQL_LIBS
-  ])
-  _SAVE_LIBS=$LIBS
-  LIBS="$LIBS $PDO_MYSQL_LIBS"
-  AC_CHECK_FUNCS([mysql_commit mysql_stmt_prepare mysql_next_result mysql_sqlstate]) 
-  LIBS=$_SAVE_LIBS
+      PHP_EVAL_INCLINE($PDO_MYSQL_INCLUDE)
+      PHP_EVAL_LIBLINE($PDO_MYSQL_LIBS, PDO_MYSQL_SHARED_LIBADD)
+    ],[
+      $PDO_MYSQL_LIBS
+    ])
+
+    _SAVE_LIBS=$LIBS
+    LIBS="$LIBS $PDO_MYSQL_LIBS"
+    AC_CHECK_FUNCS([mysql_commit mysql_stmt_prepare mysql_next_result mysql_sqlstate]) 
+    LIBS=$_SAVE_LIBS
+  fi
 
   ifdef([PHP_CHECK_PDO_INCLUDES],
   [
@@ -153,16 +156,19 @@ if test "$PHP_PDO_MYSQL" != "no"; then
     AC_MSG_RESULT($pdo_inc_path)
   ])
 
-  PHP_NEW_EXTENSION(pdo_mysql, pdo_mysql.c mysql_driver.c mysql_statement.c, $ext_shared,,-I$pdo_inc_path)
+
+  dnl fix after renaming to pdo_mysql
+  PHP_NEW_EXTENSION(pdo_mysql, pdo_mysql.c mysql_driver.c mysql_statement.c, $ext_shared,,-I$pdo_inc_path -I)
   ifdef([PHP_ADD_EXTENSION_DEP],
   [
     PHP_ADD_EXTENSION_DEP(pdo_mysql, pdo)
+    if test "$PHP_MYSQL" = "mysqlnd"; then
+      PHP_ADD_EXTENSION_DEP(pdo_mysql, mysqlnd)
+    fi
   ])
   PDO_MYSQL_MODULE_TYPE=external
 
-  PHP_SUBST(PDO_MYSQL_SHARED_LIBADD)
   PHP_SUBST_OLD(PDO_MYSQL_MODULE_TYPE)
 fi
 
-fi
 dnl vim: se ts=2 sw=2 et:
index 1fce9e5dbfb6676cbd65d64dd78299a4ccf02a8a..da085dc569e35dc167adf819c436ec179ca0c61e 100644 (file)
@@ -4,11 +4,17 @@
 ARG_WITH("pdo-mysql", "MySQL support for PDO", "no");
 
 if (PHP_PDO_MYSQL != "no") {
-       if (CHECK_LIB("libmysql.lib", "pdo_mysql", PHP_PDO_MYSQL) &&
-                       CHECK_HEADER_ADD_INCLUDE("mysql.h", "CFLAGS_PDO_MYSQL", PHP_PHP_BUILD + "\\include\\mysql;" + PHP_PDO_MYSQL)) {
+       if (PHP_PDO_MYSQL == "yes" || PHP_PDO_MYSQL == "mysqlnd") {
+               AC_DEFINE('PDO_USE_MYSQLND', 1, 'Using MySQL native driver');
+               STDOUT.WriteLine("INFO: mysqlnd build");
                EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c");
+               ADD_EXTENSION_DEP('pdo_mysql', 'pdo');
        } else {
-               WARNING("pdo_mysql not enabled; libraries and headers not found");
+               if (CHECK_LIB("libmysql.lib", "pdo_mysql", PHP_PDO_MYSQL) &&
+                               CHECK_HEADER_ADD_INCLUDE("mysql.h", "CFLAGS_PDO_MYSQL", PHP_PHP_BUILD + "\\include\\mysql;" + PHP_PDO_MYSQL)) {
+                       EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c");
+               } else {
+                       WARNING("pdo_mysql not enabled; libraries and headers not found");
+               }
        }
-       ADD_EXTENSION_DEP('pdo_mysql', 'pdo');
 }
index ba945a2c0651986d238f360fdc794c9da7346ffc..941ab4b532fc87221243f7459078e14585288d72 100755 (executable)
@@ -14,6 +14,7 @@
   +----------------------------------------------------------------------+
   | Author: George Schlossnagle <george@omniti.com>                      |
   |         Wez Furlong <wez@php.net>                                    |
+  |         Johannes Schlueter <johannes@mysql.com>                      |
   +----------------------------------------------------------------------+
 */
 
 #include "pdo/php_pdo_driver.h"
 #include "php_pdo_mysql.h"
 #include "php_pdo_mysql_int.h"
+#ifndef PDO_USE_MYSQLND
 #include <mysqld_error.h>
+#endif
 #include "zend_exceptions.h"
 
+#if PDO_USE_MYSQLND
+#      define pdo_mysql_init(persistent) mysqlnd_init(persistent)
+#else
+#      define pdo_mysql_init(persistent) mysql_init(NULL)
+#endif
 
-const char *pdo_mysql_get_sqlstate(unsigned int my_errno) {
+#if !HAVE_MYSQL_SQLSTATE && !PDO_USE_MYSQLND
+static const char *pdo_mysql_get_sqlstate(unsigned int my_errno) { /* {{{ */
        switch (my_errno) {
                /* import auto-generated case: code */
 #include "php_pdo_mysql_sqlstate.h"
        default: return "HY000";
        }
 }
+/* }}} */
+#endif
 
+/* {{{ _pdo_mysql_error */
 int _pdo_mysql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
@@ -49,6 +61,8 @@ int _pdo_mysql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int lin
        pdo_mysql_error_info *einfo;
        pdo_mysql_stmt *S = NULL;
 
+       PDO_DBG_ENTER("_pdo_mysql_error");
+       PDO_DBG_INF_FMT("file=%s line=%d", file, line);
        if (stmt) {
                S = (pdo_mysql_stmt*)stmt->driver_data;
                pdo_err = &stmt->error_code;
@@ -58,7 +72,7 @@ int _pdo_mysql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int lin
                einfo   = &H->einfo;
        }
 
-#if HAVE_MYSQL_STMT_PREPARE
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
        if (S && S->stmt) {
                einfo->errcode = mysql_stmt_errno(S->stmt);
        }
@@ -77,23 +91,29 @@ int _pdo_mysql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int lin
        }
 
        if (einfo->errcode) {
-               if (2014 != einfo->errcode) {
-                       einfo->errmsg = pestrdup(mysql_error(H->server), dbh->is_persistent);
-               } else {
+               if (einfo->errcode == 2014) {
                        einfo->errmsg = pestrdup(
                                "Cannot execute queries while other unbuffered queries are active.  "
                                "Consider using PDOStatement::fetchAll().  Alternatively, if your code "
                                "is only ever going to run against mysql, you may enable query "
                                "buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.",
                                dbh->is_persistent);
+               } else if (einfo->errcode == 2057) {
+                       einfo->errmsg = pestrdup(
+                               "A stored procedure returning result sets of different size was called. "
+                               "This is not supported by libmysql",
+                               dbh->is_persistent);
+
+               } else {
+                       einfo->errmsg = pestrdup(mysql_error(H->server), dbh->is_persistent);
                }
        } else { /* no error */
                strcpy(*pdo_err, PDO_ERR_NONE);
-               return 0;
+               PDO_DBG_RETURN(0);
        }
 
-#if HAVE_MYSQL_SQLSTATE
-# if HAVE_MYSQL_STMT_PREPARE
+#if HAVE_MYSQL_SQLSTATE || PDO_USE_MYSQLND
+# if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
        if (S && S->stmt) {
                strcpy(*pdo_err, mysql_stmt_sqlstate(S->stmt));
        } else
@@ -106,20 +126,23 @@ int _pdo_mysql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int lin
 #endif
 
        if (!dbh->methods) {
+               PDO_DBG_INF("Throwing exception");
                zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "SQLSTATE[%s] [%d] %s",
                                *pdo_err, einfo->errcode, einfo->errmsg);
        }
-/* printf("** [%s:%d] %s %s\n", file, line, *pdo_err, einfo->errmsg); */
 
-       return einfo->errcode;
+       PDO_DBG_RETURN(einfo->errcode);
 }
 /* }}} */
 
+/* {{{ pdo_mysql_fetch_error_func */
 static int pdo_mysql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info TSRMLS_DC)
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
        pdo_mysql_error_info *einfo = &H->einfo;
 
+       PDO_DBG_ENTER("pdo_mysql_fetch_error_func");
+       PDO_DBG_INF_FMT("dbh=%p stmt=%p", dbh, stmt);
        if (stmt) {
                pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
                einfo = &S->einfo;
@@ -132,13 +155,17 @@ static int pdo_mysql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *in
                add_next_index_string(info, einfo->errmsg, 1);
        }
 
-       return 1;
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
+/* {{{ mysql_handle_closer */
 static int mysql_handle_closer(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
        
+       PDO_DBG_ENTER("mysql_handle_closer");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
        if (H) {
                if (H->server) {
                        mysql_close(H->server);
@@ -151,21 +178,26 @@ static int mysql_handle_closer(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */
                pefree(H, dbh->is_persistent);
                dbh->driver_data = NULL;
        }
-       return 0;
+       PDO_DBG_RETURN(0);
 }
 /* }}} */
 
+/* {{{ mysql_handle_preparer */
 static int mysql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, pdo_stmt_t *stmt, zval *driver_options TSRMLS_DC)
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
        pdo_mysql_stmt *S = ecalloc(1, sizeof(pdo_mysql_stmt));
-#if HAVE_MYSQL_STMT_PREPARE
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
        char *nsql = NULL;
        int nsql_len = 0;
        int ret;
        int server_version;
 #endif
        
+       PDO_DBG_ENTER("mysql_handle_preparer");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+       PDO_DBG_INF_FMT("sql=%.*s", sql_len, sql);
+
        S->H = H;
        stmt->driver_data = S;
        stmt->methods = &mysql_stmt_methods;
@@ -174,7 +206,7 @@ static int mysql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len,
                goto end;
        }
 
-#if HAVE_MYSQL_STMT_PREPARE
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
        server_version = mysql_get_server_version(H->server);
        if (server_version < 40100) {
                goto fallback;
@@ -189,7 +221,7 @@ static int mysql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len,
        } else if (ret == -1) {
                /* failed to parse */
                strcpy(dbh->error_code, stmt->error_code);
-               return 0;
+               PDO_DBG_RETURN(0);
        }
 
        if (!(S->stmt = mysql_stmt_init(H->server))) {
@@ -197,7 +229,7 @@ static int mysql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len,
                if (nsql) {
                        efree(nsql);
                }
-               return 0;
+               PDO_DBG_RETURN(0);
        }
        
        if (mysql_stmt_prepare(S->stmt, sql, sql_len)) {
@@ -213,7 +245,7 @@ static int mysql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len,
                if (nsql) {
                        efree(nsql);
                }
-               return 0;
+               PDO_DBG_RETURN(0);
        }
        if (nsql) {
                efree(nsql);
@@ -222,115 +254,199 @@ static int mysql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len,
        S->num_params = mysql_stmt_param_count(S->stmt);
 
        if (S->num_params) {
+               S->params_given = 0;
+#if PDO_USE_MYSQLND
+               S->params = NULL;
+#else
                S->params = ecalloc(S->num_params, sizeof(MYSQL_BIND));
                S->in_null = ecalloc(S->num_params, sizeof(my_bool));
                S->in_length = ecalloc(S->num_params, sizeof(unsigned long));
+#endif
        }
-
        dbh->alloc_own_columns = 1;
 
        S->max_length = pdo_attr_lval(driver_options, PDO_ATTR_MAX_COLUMN_LEN, 0 TSRMLS_CC);
 
-       return 1;
+       PDO_DBG_RETURN(1);
 
 fallback:
 #endif
 end:
        stmt->supports_placeholders = PDO_PLACEHOLDER_NONE;
        
-       return 1;
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
+/* {{{ mysql_handle_doer */
 static long mysql_handle_doer(pdo_dbh_t *dbh, const char *sql, long sql_len TSRMLS_DC)
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
+       PDO_DBG_ENTER("mysql_handle_doer");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+       PDO_DBG_INF_FMT("sql=%.*s", sql_len, sql);
 
        if (mysql_real_query(H->server, sql, sql_len)) {
                pdo_mysql_error(dbh);
-               return -1;
+               PDO_DBG_RETURN(-1);
        } else {
                my_ulonglong c = mysql_affected_rows(H->server);
                if (c == (my_ulonglong) -1) {
                        pdo_mysql_error(dbh);
-                       return (H->einfo.errcode ? -1 : 0);
+                       PDO_DBG_RETURN(H->einfo.errcode ? -1 : 0);
                } else {
-                       return c;
+
+#if HAVE_MYSQL_NEXT_RESULT || PDO_USE_MYSQLND
+                       /* MULTI_QUERY support - eat up all unfetched result sets */
+                       MYSQL_RES* result;
+                       while (mysql_more_results(H->server)) {
+                               if (mysql_next_result(H->server)) {
+                                       PDO_DBG_RETURN(1);
+                               }
+                               result = mysql_store_result(H->server);
+                               if (result) {
+                                       mysql_free_result(result);
+                               }
+                       }
+#endif
+                       PDO_DBG_RETURN((int)c);
                }
        }
 }
+/* }}} */
 
+/* {{{ pdo_mysql_last_insert_id */
 static char *pdo_mysql_last_insert_id(pdo_dbh_t *dbh, const char *name, unsigned int *len TSRMLS_DC)
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
        char *id = php_pdo_int64_to_str(mysql_insert_id(H->server) TSRMLS_CC);
+       PDO_DBG_ENTER("pdo_mysql_last_insert_id");
        *len = strlen(id);
-       return id;
+       PDO_DBG_RETURN(id);
 }
 
+/* {{{ mysql_handle_quoter */
 static int mysql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, int unquotedlen, char **quoted, int *quotedlen, enum pdo_param_type paramtype  TSRMLS_DC)
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
+       PDO_DBG_ENTER("mysql_handle_quoter");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+       PDO_DBG_INF_FMT("unquoted=%.*s", unquotedlen, unquoted);
        *quoted = safe_emalloc(2, unquotedlen, 3);
        *quotedlen = mysql_real_escape_string(H->server, *quoted + 1, unquoted, unquotedlen);
        (*quoted)[0] =(*quoted)[++*quotedlen] = '\'';
        (*quoted)[++*quotedlen] = '\0';
-       return 1;
+       PDO_DBG_INF_FMT("quoted=%.*s", *quotedlen, *quoted);
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
+/* {{{ mysql_handle_begin */
 static int mysql_handle_begin(pdo_dbh_t *dbh TSRMLS_DC)
 {
-       return 0 <= mysql_handle_doer(dbh, ZEND_STRL("START TRANSACTION") TSRMLS_CC);
+       PDO_DBG_ENTER("mysql_handle_quoter");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+       PDO_DBG_RETURN(0 <= mysql_handle_doer(dbh, ZEND_STRL("START TRANSACTION") TSRMLS_CC));
 }
+/* }}} */
 
+/* {{{ mysql_handle_commit */
 static int mysql_handle_commit(pdo_dbh_t *dbh TSRMLS_DC)
 {
-       return 0 <= mysql_handle_doer(dbh, ZEND_STRL("COMMIT") TSRMLS_CC);
+       PDO_DBG_ENTER("mysql_handle_commit");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+#if MYSQL_VERSION_ID >= 40100 || defined(PDO_USE_MYSQLND)
+       PDO_DBG_RETURN(0 <= mysql_commit(((pdo_mysql_db_handle *)dbh->driver_data)->server));
+#else
+       PDO_DBG_RETURN(0 <= mysql_handle_doer(dbh, ZEND_STRL("COMMIT") TSRMLS_CC));
+#endif
 }
 
+/* {{{ mysql_handle_rollback */
 static int mysql_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC)
 {
-       return 0 <= mysql_handle_doer(dbh, ZEND_STRL("ROLLBACK") TSRMLS_CC);
+       PDO_DBG_ENTER("mysql_handle_rollback");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+#if MYSQL_VERSION_ID >= 40100 || defined(PDO_USE_MYSQLND)
+       PDO_DBG_RETURN(0 <= mysql_rollback(((pdo_mysql_db_handle *)dbh->driver_data)->server));
+#else
+       PDO_DBG_RETURN(0 <= mysql_handle_doer(dbh, ZEND_STRL("ROLLBACK") TSRMLS_CC));
+#endif
 }
+/* }}} */
 
-static int mysql_handle_autocommit(pdo_dbh_t *dbh TSRMLS_DC)
+/* {{{ mysql_handle_autocommit */
+static inline int mysql_handle_autocommit(pdo_dbh_t *dbh TSRMLS_DC)
 {
+       PDO_DBG_ENTER("mysql_handle_autocommit");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+       PDO_DBG_INF_FMT("dbh->autocommit=%d", dbh->auto_commit);
+#if MYSQL_VERSION_ID >= 40100 || defined(PDO_USE_MYSQLND)
+       PDO_DBG_RETURN(0 <= mysql_autocommit(((pdo_mysql_db_handle *)dbh->driver_data)->server, dbh->auto_commit));
+#else
        if (dbh->auto_commit) {
-               return 0 <= mysql_handle_doer(dbh, ZEND_STRL("SET AUTOCOMMIT=1") TSRMLS_CC);
+               PDO_DBG_RETURN(0 <= mysql_handle_doer(dbh, ZEND_STRL("SET AUTOCOMMIT=1") TSRMLS_CC));
        } else {
-               return 0 <= mysql_handle_doer(dbh, ZEND_STRL("SET AUTOCOMMIT=0") TSRMLS_CC);
+               PDO_DBG_RETURN(0 <= mysql_handle_doer(dbh, ZEND_STRL("SET AUTOCOMMIT=0") TSRMLS_CC));
        }
+#endif
 }
+/* }}} */
 
+/* {{{ pdo_mysql_set_attribute */
 static int pdo_mysql_set_attribute(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC)
 {
+       PDO_DBG_ENTER("pdo_mysql_set_attribute");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+       PDO_DBG_INF_FMT("attr=%l", attr);
        switch (attr) {
-       case PDO_ATTR_AUTOCOMMIT:
-               
-               convert_to_boolean(val);
+               case PDO_ATTR_AUTOCOMMIT:               
+                       convert_to_boolean(val);
        
-               /* ignore if the new value equals the old one */                        
-               if (dbh->auto_commit ^ Z_BVAL_P(val)) {
-                       dbh->auto_commit = Z_BVAL_P(val);
-                       mysql_handle_autocommit(dbh TSRMLS_CC);
-               }
-               return 1;
-
-       case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
-               ((pdo_mysql_db_handle *)dbh->driver_data)->buffered = Z_BVAL_P(val);
-               return 1;
-       case PDO_MYSQL_ATTR_DIRECT_QUERY:
-       case PDO_ATTR_EMULATE_PREPARES:
-               ((pdo_mysql_db_handle *)dbh->driver_data)->emulate_prepare = Z_BVAL_P(val);
-               return 1;
-       default:
-               return 0;
+                       /* ignore if the new value equals the old one */                        
+                       if (dbh->auto_commit ^ Z_BVAL_P(val)) {
+                               dbh->auto_commit = Z_BVAL_P(val);
+                               mysql_handle_autocommit(dbh TSRMLS_CC);
+                       }
+                       PDO_DBG_RETURN(1);
+
+               case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
+                       ((pdo_mysql_db_handle *)dbh->driver_data)->buffered = Z_BVAL_P(val);
+                       PDO_DBG_RETURN(1);
+               case PDO_MYSQL_ATTR_DIRECT_QUERY:
+               case PDO_ATTR_EMULATE_PREPARES:
+                       ((pdo_mysql_db_handle *)dbh->driver_data)->emulate_prepare = Z_BVAL_P(val);
+                       PDO_DBG_RETURN(1);
+               case PDO_ATTR_FETCH_TABLE_NAMES:
+                       ((pdo_mysql_db_handle *)dbh->driver_data)->fetch_table_names = Z_BVAL_P(val);
+                       PDO_DBG_RETURN(1);
+#ifndef PDO_USE_MYSQLND
+               case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
+                       if (Z_LVAL_P(val) < 0) {
+                               // TODO - Johannes, can we throw a warning here?
+                               ((pdo_mysql_db_handle *)dbh->driver_data)->max_buffer_size = 1024*1024;
+                               PDO_DBG_INF_FMT("Adjusting invalid buffer size to =%l", ((pdo_mysql_db_handle *)dbh->driver_data)->max_buffer_size);
+                       } else {
+                               ((pdo_mysql_db_handle *)dbh->driver_data)->max_buffer_size = Z_LVAL_P(val);
+                       }
+                       PDO_DBG_RETURN(1);
+                       break;
+#endif
+
+               default:
+                       PDO_DBG_RETURN(0);
        }
 }
+/* }}} */
 
+/* {{{ pdo_mysql_get_attribute */
 static int pdo_mysql_get_attribute(pdo_dbh_t *dbh, long attr, zval *return_value TSRMLS_DC)
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
 
+       PDO_DBG_ENTER("pdo_mysql_get_attribute");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+       PDO_DBG_INF_FMT("attr=%l", attr);
        switch (attr) {
                case PDO_ATTR_CLIENT_VERSION:
                        ZVAL_STRING(return_value, (char *)mysql_get_client_info(), 1);
@@ -343,42 +459,50 @@ static int pdo_mysql_get_attribute(pdo_dbh_t *dbh, long attr, zval *return_value
                case PDO_ATTR_CONNECTION_STATUS:
                        ZVAL_STRING(return_value, (char *)mysql_get_host_info(H->server), 1);
                        break;
-
                case PDO_ATTR_SERVER_INFO: {
                        char *tmp;
+#if PDO_USE_MYSQLND
+                       int tmp_len;
 
+                       if (mysqlnd_stat(H->server, &tmp, &tmp_len) == PASS) {
+                               ZVAL_STRINGL(return_value, tmp, tmp_len, 0);
+#else
                        if ((tmp = (char *)mysql_stat(H->server))) {
                                ZVAL_STRING(return_value, tmp, 1);
+#endif
                        } else {
                                pdo_mysql_error(dbh);
-                               return -1;
+                               PDO_DBG_RETURN(-1);
                        }
                }
                        break;
-
                case PDO_ATTR_AUTOCOMMIT:
                        ZVAL_LONG(return_value, dbh->auto_commit);
-                       return 1;
+                       break;
                        
                case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
                        ZVAL_LONG(return_value, H->buffered);
-                       return 1;
+                       break;
 
                case PDO_MYSQL_ATTR_DIRECT_QUERY:
                        ZVAL_LONG(return_value, H->emulate_prepare);
-                       return 1;
+                       break;
 
+#ifndef PDO_USE_MYSQLND
                case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
                        ZVAL_LONG(return_value, H->max_buffer_size);
-                       return 1;
+                       break;
+#endif
 
                default:
-                       return 0;       
+                       PDO_DBG_RETURN(0);      
        }
 
-       return 1;
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
+/* {{{ pdo_mysql_check_liveness */
 static int pdo_mysql_check_liveness(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
@@ -387,27 +511,31 @@ static int pdo_mysql_check_liveness(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */
        unsigned int my_errno;
 #endif
 
+       PDO_DBG_ENTER("pdo_mysql_check_liveness");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+
 #if MYSQL_VERSION_ID > 32230
        if (mysql_ping(H->server)) {
-               return FAILURE;
+               PDO_DBG_RETURN(FAILURE);
        }
 #else /* no mysql_ping() */
-       handler=signal(SIGPIPE, SIG_IGN);
+       handler = signal(SIGPIPE, SIG_IGN);
        mysql_stat(H->server);
        switch (mysql_errno(H->server)) {
                case CR_SERVER_GONE_ERROR:
-               /* case CR_SERVER_LOST: I'm not sure this means the same as "gone" for us */
+               case CR_SERVER_LOST:
                        signal(SIGPIPE, handler);
-                       return FAILURE;
+                       PDO_DBG_RETURN(FAILURE);
                default:
                        break;
        }
        signal(SIGPIPE, handler);
 #endif /* end mysql_ping() */
-       return SUCCESS;
+       PDO_DBG_RETURN(SUCCESS);
 } 
 /* }}} */
 
+/* {{{ mysql_methods */
 static struct pdo_dbh_methods mysql_methods = {
        mysql_handle_closer,
        mysql_handle_preparer,
@@ -422,8 +550,17 @@ static struct pdo_dbh_methods mysql_methods = {
        pdo_mysql_get_attribute,
        pdo_mysql_check_liveness
 };
+/* }}} */
 
+#ifndef PDO_MYSQL_UNIX_ADDR
+# ifdef PHP_WIN32
+#  define MYSQL_UNIX_ADDR      "MySQL"
+# else
+#  define MYSQL_UNIX_ADDR      PDO_MYSQL_G(default_socket)
+# endif
+#endif
 
+/* {{{ pdo_mysql_handle_factory */
 static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_db_handle *H;
@@ -436,7 +573,7 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
                { "dbname",   "",       0 },
                { "host",   "localhost",        0 },
                { "port",   "3306",     0 },
-               { "unix_socket",  PDO_MYSQL_UNIX_ADDR,  0 },
+               { "unix_socket",  MYSQL_UNIX_ADDR,      0 },
        };
        int connect_opts = 0
 #ifdef CLIENT_MULTI_RESULTS
@@ -447,31 +584,46 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
 #endif
                ;
 
+#if PDO_USE_MYSQLND
+       int dbname_len = 0;
+       int password_len = 0;
+#endif
+       PDO_DBG_ENTER("pdo_mysql_handle_factory");
+       PDO_DBG_INF_FMT("dbh=%p", dbh);
+#ifdef CLIENT_MULTI_RESULTS
+       PDO_DBG_INF("multi results");
+#endif
+
        php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 5);
 
        H = pecalloc(1, sizeof(pdo_mysql_db_handle), dbh->is_persistent);
-       
+
        H->einfo.errcode = 0;
        H->einfo.errmsg = NULL;
 
        /* allocate an environment */
 
        /* handle for the server */
-       if (!(H->server = mysql_init(NULL))) {
+       if (!(H->server = pdo_mysql_init(dbh->is_persistent))) {
                pdo_mysql_error(dbh);
                goto cleanup;
        }
        
        dbh->driver_data = H;
+
+#ifndef PDO_USE_MYSQLND
        H->max_buffer_size = 1024*1024;
+#endif
+
        H->buffered = H->emulate_prepare = 1;
 
        /* handle MySQL options */
        if (driver_options) {
                long connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30 TSRMLS_CC);
                long local_infile = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_LOCAL_INFILE, 0 TSRMLS_CC);
+#ifndef PDO_USE_MYSQLND
                char *init_cmd = NULL, *default_file = NULL, *default_group = NULL;
-
+#endif
                H->buffered = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_USE_BUFFERED_QUERY, 1 TSRMLS_CC);
 
                H->emulate_prepare = pdo_attr_lval(driver_options,
@@ -479,14 +631,21 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
                H->emulate_prepare = pdo_attr_lval(driver_options, 
                        PDO_ATTR_EMULATE_PREPARES, H->emulate_prepare TSRMLS_CC);
 
+#ifndef PDO_USE_MYSQLND
                H->max_buffer_size = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE, H->max_buffer_size TSRMLS_CC);
+#endif
 
                if (mysql_options(H->server, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&connect_timeout)) {
                        pdo_mysql_error(dbh);
                        goto cleanup;
                }
 
-               if ((PG(open_basedir) && PG(open_basedir)[0] != '\0') || PG(safe_mode)) {
+#if PHP_MAJOR_VERSION < 6
+               if ((PG(open_basedir) && PG(open_basedir)[0] != '\0') || PG(safe_mode))
+#else
+               if (PG(open_basedir) && PG(open_basedir)[0] != '\0') 
+#endif
+               {
                        local_infile = 0;
                }
 
@@ -503,7 +662,7 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
                        mysql_options(H->server, MYSQL_OPT_RECONNECT, (const char*)&reconnect);
                }
 #endif
-
+#ifndef PDO_USE_MYSQLND
                init_cmd = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_INIT_COMMAND, NULL TSRMLS_CC);
                if (init_cmd) {
                        if (mysql_options(H->server, MYSQL_INIT_COMMAND, (const char *)init_cmd)) {
@@ -533,6 +692,7 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
                        }
                        efree(default_group);
                }
+#endif
        }
 
        dbname = vars[1].optval;
@@ -543,7 +703,22 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
        if (vars[2].optval && !strcmp("localhost", vars[2].optval)) {
                unix_socket = vars[4].optval;  
        }
+
+       /* TODO: - Check zval cache + ZTS */
+#ifdef PDO_USE_MYSQLND
+       if (dbname) {
+               dbname_len = strlen(dbname);
+       }
+
+       if (dbh->password) {
+               password_len = strlen(dbh->password);
+       }
+
+       if (mysqlnd_connect(H->server, host, dbh->username, dbh->password, password_len, dbname, dbname_len,
+                                               port, unix_socket, connect_opts, PDO_MYSQL_G(mysqlnd_thd_zval_cache) TSRMLS_CC) == NULL) {
+#else
        if (mysql_real_connect(H->server, host, dbh->username, dbh->password, dbname, port, unix_socket, connect_opts) == NULL) {
+#endif
                pdo_mysql_error(dbh);
                goto cleanup;
        }
@@ -569,7 +744,7 @@ cleanup:
        
        dbh->methods = &mysql_methods;
 
-       return ret;
+       PDO_DBG_RETURN(ret);
 }
 /* }}} */
 
index 9f1f0f40336414f21f34396bd4c5d62426356d10..18544a8b3fbdb7b0e5d6c6580b35e51705d442cf 100755 (executable)
@@ -14,6 +14,7 @@
   +----------------------------------------------------------------------+
   | Author: George Schlossnagle <george@omniti.com>                      |
   |         Wez Furlong <wez@php.net>                                    |
+  |         Johannes Schlueter <johannes@mysql.com>                      |
   +----------------------------------------------------------------------+
 */
 
 #include "php_pdo_mysql.h"
 #include "php_pdo_mysql_int.h"
 
+#ifdef PDO_USE_MYSQLND
+#      define pdo_mysql_stmt_execute_prepared(stmt) pdo_mysql_stmt_execute_prepared_mysqlnd(stmt TSRMLS_CC)
+#      define pdo_free_bound_result(res) zval_dtor(res.zv)
+#      define pdo_mysql_stmt_close(stmt) mysqlnd_stmt_close(stmt, 0)
+#else
+#      define pdo_mysql_stmt_execute_prepared(stmt) pdo_mysql_stmt_execute_prepared_libmysql(stmt TSRMLS_CC)
+#      define pdo_free_bound_result(res) efree(res.buffer)
+#      define pdo_mysql_stmt_close(stmt) mysql_stmt_close(stmt)
+#endif
+
 
-static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
+
+static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
 
+       PDO_DBG_ENTER("pdo_mysql_stmt_dtor");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
        if (S->result) {
                /* free the resource */
                mysql_free_result(S->result);
@@ -45,29 +59,42 @@ static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
                pefree(S->einfo.errmsg, stmt->dbh->is_persistent);
                S->einfo.errmsg = NULL;
        }
-#if HAVE_MYSQL_STMT_PREPARE
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
        if (S->stmt) {
-               mysql_stmt_close(S->stmt);
+               pdo_mysql_stmt_close(S->stmt);
                S->stmt = NULL;
        }
+#endif /* HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND */
+
+#ifndef PDO_USE_MYSQLND
        if (S->params) {
                efree(S->params);
+       }
+       if (S->in_null) {
                efree(S->in_null);
+       }
+       if (S->in_length) {
                efree(S->in_length);
        }
+
+#endif /* PDO_USE_MYSQLND */
+
+#ifdef HAVE_MYSQL_STMT_PREPARE
        if (S->bound_result) 
        {
                int i;
                for (i = 0; i < stmt->column_count; i++) {
-                       efree(S->bound_result[i].buffer);
+                       pdo_free_bound_result(S->bound_result[i]);
                }
        
                efree(S->bound_result);
                efree(S->out_null);
                efree(S->out_length);
        }
-#endif
-#if HAVE_MYSQL_NEXT_RESULT
+#endif /* HAVE_MYSQL_STMT_PREPARE */
+
+
+#if HAVE_MYSQL_NEXT_RESULT || PDO_USE_MYSQLND
        if (S->H->server) {
                while (mysql_more_results(S->H->server)) {
                        MYSQL_RES *res;
@@ -80,132 +107,199 @@ static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
                                mysql_free_result(res);
                        }
                }
+       }       
+#endif /* HAVE_MYSQL_NEXT_RESULT || PDO_USE_MYSQLND */
+#if PDO_USE_MYSQLND
+       if (!S->stmt && S->current_data) {
+               free(S->current_data);
        }
-#endif
+#endif /* PDO_USE_MYSQLND */
+
        efree(S);
-       return 1;
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
-static int pdo_mysql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC)
+static void pdo_mysql_stmt_set_row_count(pdo_stmt_t *stmt) /* {{{ */
 {
-       pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
-       pdo_mysql_db_handle *H = S->H;
        my_ulonglong row_count;
-#if HAVE_MYSQL_STMT_PREPARE
-       int i;
+       pdo_mysql_stmt *S = stmt->driver_data;
+       row_count = mysql_stmt_affected_rows(S->stmt);
+       if (row_count != (my_ulonglong)-1) {
+               stmt->row_count = row_count;
+       }
+}
+/* }}} */
 
-       if (S->stmt) {
-               /* (re)bind the parameters */
-               if (mysql_stmt_bind_param(S->stmt, S->params)) {
-                       pdo_mysql_error_stmt(stmt);
-                       return 0;
-               }
+#ifdef HAVE_MYSQL_STMT_PREPARE
+static int pdo_mysql_stmt_execute_prepared_libmysql(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
+{
+       pdo_mysql_stmt *S = stmt->driver_data;
+       pdo_mysql_db_handle *H = S->H;
 
-               if (mysql_stmt_execute(S->stmt)) {
-                       pdo_mysql_error_stmt(stmt);
-                       return 0;
+       PDO_DBG_ENTER("pdo_mysql_stmt_execute_prepared_libmysql");
+
+       /* (re)bind the parameters */
+       if (mysql_stmt_bind_param(S->stmt, S->params) || mysql_stmt_execute(S->stmt)) {
+               if (S->params) {
+                       efree(S->params);
+                       S->params = 0;
                }
+               pdo_mysql_error_stmt(stmt);
+               if (mysql_stmt_errno(S->stmt) == 2057) {
+                       /* CR_NEW_STMT_METADATA makes the statement unusable */
+                       S->stmt = NULL;
+               }
+               PDO_DBG_RETURN(0);
+       }
 
-               if (!S->result) {
-                       /* figure out the result set format, if any */
-                       S->result = mysql_stmt_result_metadata(S->stmt);
-                       if (S->result) {
-                               int calc_max_length = H->buffered && S->max_length == 1;
+       if (!S->result) {
+               int i; 
+
+               /* figure out the result set format, if any */ 
+               S->result = mysql_stmt_result_metadata(S->stmt); 
+               if (S->result) { 
+                       int calc_max_length = H->buffered && S->max_length == 1; 
+                       S->fields = mysql_fetch_fields(S->result); 
+                       if (S->bound_result) { 
+                               int i; 
+                               for (i = 0; i < stmt->column_count; i++) { 
+                                       efree(S->bound_result[i].buffer);  
+                               } 
+                               efree(S->bound_result); 
+                               efree(S->out_null); 
+                               efree(S->out_length); 
+                       }
                        
-                               S->fields = mysql_fetch_fields(S->result);
-
-                               if (S->bound_result) {
-                                       int i;
-                                       for (i = 0; i < stmt->column_count; i++) {
-                                               efree(S->bound_result[i].buffer); 
-                                       }
-                                       efree(S->bound_result);
-                                       efree(S->out_null);
-                                       efree(S->out_length);
+                       stmt->column_count = (int)mysql_num_fields(S->result);
+                       S->bound_result = ecalloc(stmt->column_count, sizeof(MYSQL_BIND));
+                       S->out_null = ecalloc(stmt->column_count, sizeof(my_bool));
+                       S->out_length = ecalloc(stmt->column_count, sizeof(unsigned long));
+
+                       /* summon memory to hold the row */
+                       for (i = 0; i < stmt->column_count; i++) {
+                               if (calc_max_length && S->fields[i].type == FIELD_TYPE_BLOB) {
+                                       my_bool on = 1;
+                                       mysql_stmt_attr_set(S->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &on);
+                                       calc_max_length = 0;
+                               }
+                               switch (S->fields[i].type) {
+                                       case FIELD_TYPE_INT24:
+                                               S->bound_result[i].buffer_length = MAX_MEDIUMINT_WIDTH + 1;
+                                               break;
+                                       case FIELD_TYPE_LONG:
+                                               S->bound_result[i].buffer_length = MAX_INT_WIDTH + 1;
+                                               break;
+                                       case FIELD_TYPE_LONGLONG:
+                                               S->bound_result[i].buffer_length = MAX_BIGINT_WIDTH + 1;
+                                               break;
+                                       case FIELD_TYPE_TINY:
+                                               S->bound_result[i].buffer_length = MAX_TINYINT_WIDTH + 1;
+                                               break;
+                                       case FIELD_TYPE_SHORT:
+                                               S->bound_result[i].buffer_length = MAX_SMALLINT_WIDTH + 1;
+                                               break;
+                                       default:
+                                               S->bound_result[i].buffer_length =
+                                                       S->fields[i].max_length? S->fields[i].max_length:
+                                                       S->fields[i].length;
+                                               /* work-around for longtext and alike */
+                                               if (S->bound_result[i].buffer_length > H->max_buffer_size) {
+                                                       S->bound_result[i].buffer_length = H->max_buffer_size;
+                                               }
                                }
 
-                               stmt->column_count = (int)mysql_num_fields(S->result);
-                               S->bound_result = ecalloc(stmt->column_count, sizeof(MYSQL_BIND));
-                               S->out_null = ecalloc(stmt->column_count, sizeof(my_bool));
-                               S->out_length = ecalloc(stmt->column_count, sizeof(unsigned long));
-
-                               /* summon memory to hold the row */
-                               for (i = 0; i < stmt->column_count; i++) {
-                                       if (calc_max_length && S->fields[i].type == FIELD_TYPE_BLOB) {
-                                               my_bool on = 1;
-                                               mysql_stmt_attr_set(S->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &on);
-                                               calc_max_length = 0;
-                                       }
-                                       switch (S->fields[i].type) {
-                                               case FIELD_TYPE_INT24:
-                                                       S->bound_result[i].buffer_length = MAX_MEDIUMINT_WIDTH;
-                                                       break;
-                                               case FIELD_TYPE_LONG:
-                                                       S->bound_result[i].buffer_length = MAX_INT_WIDTH;
-                                                       break;
-                                               case FIELD_TYPE_LONGLONG:
-                                                       S->bound_result[i].buffer_length = MAX_BIGINT_WIDTH;
-                                                       break;
-                                               case FIELD_TYPE_TINY:
-                                                       S->bound_result[i].buffer_length = MAX_TINYINT_WIDTH;
-                                                       break;
-                                               case FIELD_TYPE_SHORT:
-                                                       S->bound_result[i].buffer_length = MAX_SMALLINT_WIDTH;
-                                                       break;
-                                               default:
-                                                       S->bound_result[i].buffer_length =
-                                                               S->fields[i].max_length? S->fields[i].max_length:
-                                                               S->fields[i].length;
-                                                       /* work-around for longtext and alike */
-                                                       if (S->bound_result[i].buffer_length > H->max_buffer_size) {
-                                                               S->bound_result[i].buffer_length = H->max_buffer_size;
-                                                       }
-                                       }
-#if 0
-                                       printf("%d: max_length=%d length=%d buffer_length=%d type=%d\n",
-                                                       i,
-                                                       S->fields[i].max_length,
-                                                       S->fields[i].length,
-                                                       S->bound_result[i].buffer_length,
-                                                       S->fields[i].type
-                                                       );
-#endif
-
-                                       /* there are cases where the length reported by mysql is too short.
-                                        * eg: when describing a table that contains an enum column. Since
-                                        * we have no way of knowing the true length either, we'll bump up
-                                        * our buffer size to a reasonable size, just in case */
-                                       if (S->fields[i].max_length == 0 && S->bound_result[i].buffer_length < 128 && MYSQL_TYPE_VAR_STRING) {
-                                               S->bound_result[i].buffer_length = 128;
-                                       }
+                               /* there are cases where the length reported by mysql is too short.
+                                * eg: when describing a table that contains an enum column. Since
+                                * we have no way of knowing the true length either, we'll bump up
+                                * our buffer size to a reasonable size, just in case */
+                               if (S->fields[i].max_length == 0 && S->bound_result[i].buffer_length < 128 && MYSQL_TYPE_VAR_STRING) {
+                                       S->bound_result[i].buffer_length = 128;
+                               }
 
-                                       S->out_length[i] = 0;
+                               S->out_length[i] = 0;
 
-                                       S->bound_result[i].buffer = emalloc(S->bound_result[i].buffer_length);
-                                       S->bound_result[i].is_null = &S->out_null[i];
-                                       S->bound_result[i].length = &S->out_length[i];
-                                       S->bound_result[i].buffer_type = MYSQL_TYPE_STRING;
-                               }
+                               S->bound_result[i].buffer = emalloc(S->bound_result[i].buffer_length);
+                               S->bound_result[i].is_null = &S->out_null[i];
+                               S->bound_result[i].length = &S->out_length[i];
+                               S->bound_result[i].buffer_type = MYSQL_TYPE_STRING;
+                       }
 
-                               if (mysql_stmt_bind_result(S->stmt, S->bound_result)) {
-                                       pdo_mysql_error_stmt(stmt);
-                                       return 0;
-                               }
+                       if (mysql_stmt_bind_result(S->stmt, S->bound_result)) {
+                               pdo_mysql_error_stmt(stmt);
+                               PDO_DBG_RETURN(0);
+                       }
 
-                               /* if buffered, pre-fetch all the data */
-                               if (H->buffered) {
-                                       mysql_stmt_store_result(S->stmt);
-                               }
+                       /* if buffered, pre-fetch all the data */
+                       if (H->buffered) {
+                               mysql_stmt_store_result(S->stmt);
                        }
                }
+       }
+       
+       pdo_mysql_stmt_set_row_count(stmt);
+       PDO_DBG_RETURN(1);
+}
+/* }}} */
+#endif
 
-               row_count = mysql_stmt_affected_rows(S->stmt);
-               if (row_count != (my_ulonglong)-1) {
-                       stmt->row_count = row_count;
+#ifdef PDO_USE_MYSQLND
+static int pdo_mysql_stmt_execute_prepared_mysqlnd(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
+{
+       pdo_mysql_stmt *S = stmt->driver_data;
+       pdo_mysql_db_handle *H = S->H;
+       unsigned int i;
+       
+       PDO_DBG_ENTER("pdo_mysql_stmt_execute_prepared_mysqlnd");
+       
+       if (mysql_stmt_execute(S->stmt)) {
+               pdo_mysql_error_stmt(stmt);
+               PDO_DBG_RETURN(0);
+       }
+
+       if (S->result) {
+               /* TODO: add a test to check if we really have zvals here... */
+               mysql_free_result(S->result);
+               S->result = NULL;
+       }
+
+       /* for SHOW/DESCRIBE and others the column/field count is not available before execute */
+       stmt->column_count = S->stmt->field_count;
+       for (i = 0; i < stmt->column_count; i++) {
+               mysqlnd_stmt_bind_one_result(S->stmt, i);
+       }
+
+       S->result = mysqlnd_stmt_result_metadata(S->stmt);
+       if (S->result) {
+               S->fields = mysql_fetch_fields(S->result);
+               /* if buffered, pre-fetch all the data */
+               if (H->buffered) {
+                       if (mysql_stmt_store_result(S->stmt)) {
+                               PDO_DBG_RETURN(0);
+                       }
                }
-               return 1;
        }
+       
+       pdo_mysql_stmt_set_row_count(stmt);
+       PDO_DBG_RETURN(1);
+}
+/* }}} */
 #endif
+
+static int pdo_mysql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
+{
+       pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
+       pdo_mysql_db_handle *H = S->H;
+       my_ulonglong row_count;
+       PDO_DBG_ENTER("pdo_mysql_stmt_execute");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
+
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
+       if (S->stmt) {
+               PDO_DBG_RETURN(pdo_mysql_stmt_execute_prepared(stmt));
+       }
+#endif
+       
        /* ensure that we free any previous unfetched results */
        if (S->result) {
                mysql_free_result(S->result);
@@ -214,7 +308,7 @@ static int pdo_mysql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC)
 
        if (mysql_real_query(H->server, stmt->active_query_string, stmt->active_query_stringlen) != 0) {
                pdo_mysql_error_stmt(stmt);
-               return 0;
+               PDO_DBG_RETURN(0);
        }
 
        row_count = mysql_affected_rows(H->server);
@@ -228,34 +322,88 @@ static int pdo_mysql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC)
                }
                if (NULL == S->result) {
                        pdo_mysql_error_stmt(stmt);
-                       return 0;
+                       PDO_DBG_RETURN(0);
                }
 
                stmt->row_count = mysql_num_rows(S->result);
+               stmt->column_count = (int) mysql_num_fields(S->result);
+               S->fields = mysql_fetch_fields(S->result);
 
-               if (!stmt->executed) {
-                       stmt->column_count = (int) mysql_num_fields(S->result);
-                       S->fields = mysql_fetch_fields(S->result);
-               }
        } else {
                /* this was a DML or DDL query (INSERT, UPDATE, DELETE, ... */
                stmt->row_count = row_count;
        }
 
-       return 1;
+       PDO_DBG_RETURN(1);
 }
+/* {{{ */
 
-static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC)
+static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
 {
-#if HAVE_MYSQL_NEXT_RESULT
+#if HAVE_MYSQL_NEXT_RESULT || PDO_USE_MYSQLND
        pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
        pdo_mysql_db_handle *H = S->H;
        my_ulonglong row_count;
        int ret;
+       PDO_DBG_ENTER("pdo_mysql_stmt_next_rowset");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
 
-       /* ensure that we free any previous unfetched results */
+#if PDO_USE_MYSQLND
+       if (!H->emulate_prepare) {
+               if (!mysqlnd_stmt_more_results(S->stmt)) {
+                       PDO_DBG_RETURN(0);
+               }
+               if (mysqlnd_stmt_next_result(S->stmt)) {
+                       PDO_DBG_RETURN(0);
+               }
+
+               if (!mysqlnd_stmt_more_results(S->stmt)) {
+                       /* 
+                       MySQL gives us n + 1 result sets for 
+                       CALL proc() and n result sets returned by the proc itself.
+                       Result set n + 1 is about the procedure call itself.
+                       As the PDO emulation does not return it, we skip it as well
+                       */
+                       PDO_DBG_RETURN(0);
+               }
+
+               /* TODO - this code is stolen from execute() - see above */
+               if (S->result) {
+                       mysql_free_result(S->result);
+                       S->result = NULL;
+               }
+               {
+                       /* for SHOW/DESCRIBE and others the column/field count is not available before execute */
+                       unsigned int i;
+
+                       stmt->column_count = S->stmt->field_count;
+                       for (i = 0; i < stmt->column_count; i++) {
+                               mysqlnd_stmt_bind_one_result(S->stmt, i);
+                       }
+               }
+
+               S->result = mysqlnd_stmt_result_metadata(S->stmt);
+               if (S->result) {
+                       S->fields = mysql_fetch_fields(S->result);
+
+                       /* if buffered, pre-fetch all the data */
+                       if (H->buffered) {
+                               if (mysql_stmt_store_result(S->stmt))
+                                       PDO_DBG_RETURN(1);
+                       }
+               }
+               row_count = mysql_stmt_affected_rows(S->stmt);
+               if (row_count != (my_ulonglong)-1) {
+                       stmt->row_count = row_count;
+               }
+               PDO_DBG_RETURN(1);
+       }
+#endif
+
+/* ensure that we free any previous unfetched results */
 #if HAVE_MYSQL_STMT_PREPARE
        if (S->stmt) {
+               stmt->column_count = (int)mysql_num_fields(S->result);
                mysql_stmt_free_result(S->stmt);
        }
 #endif
@@ -268,10 +416,10 @@ static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC)
 
        if (ret > 0) {
                pdo_mysql_error_stmt(stmt);
-               return 0;
+               PDO_DBG_RETURN(0);
        } else if (ret < 0) {
                /* No more results */
-               return 0;
+               PDO_DBG_RETURN(0);
        } else {
                if (!H->buffered) {
                        S->result = mysql_use_result(H->server);
@@ -280,65 +428,103 @@ static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt TSRMLS_DC)
                        S->result = mysql_store_result(H->server);
                        if ((my_ulonglong)-1 == (row_count = mysql_affected_rows(H->server))) {
                                pdo_mysql_error_stmt(stmt);
-                               return 0;
+                               PDO_DBG_RETURN(0);
                        }
                }
 
                if (NULL == S->result) {
-                       return 0;
+                       PDO_DBG_RETURN(0);
                }
 
                stmt->row_count = row_count;
                stmt->column_count = (int) mysql_num_fields(S->result);
                S->fields = mysql_fetch_fields(S->result);
-               return 1;
+               PDO_DBG_RETURN(1);
        }
 #else
        strcpy(stmt->error_code, "HYC00");
-       return 0;
-#endif
+       PDO_DBG_RETURN(0);
+#endif /* HAVE_MYSQL_STMT_PREPARE */
 }
+/* }}} */
+
+
+static const char * const pdo_param_event_names[] =
+{
+       "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",
+};
 
 
 static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param,
-               enum pdo_param_event event_type TSRMLS_DC)
+               enum pdo_param_event event_type TSRMLS_DC) /* {{{ */
 {
-#if HAVE_MYSQL_STMT_PREPARE
-       pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
-       MYSQL_BIND *b;
+#ifndef PDO_USE_MYSQLND
+       PDO_MYSQL_PARAM_BIND *b;
+#endif
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
+       pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data; 
 
+       PDO_DBG_ENTER("pdo_mysql_stmt_param_hook");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
+       PDO_DBG_INF_FMT("event = %s", pdo_param_event_names[event_type]);
        if (S->stmt && param->is_param) {
                switch (event_type) {
                        case PDO_PARAM_EVT_ALLOC:
                                /* sanity check parameter number range */
                                if (param->paramno < 0 || param->paramno >= S->num_params) {
                                        strcpy(stmt->error_code, "HY093");
-                                       return 0;
+                                       PDO_DBG_RETURN(0);
                                }
+                               S->params_given++;
+
+#ifndef PDO_USE_MYSQLND
                                b = &S->params[param->paramno];
                                param->driver_data = b;
                                b->is_null = &S->in_null[param->paramno];
                                b->length = &S->in_length[param->paramno];
-                               return 1;
+                               /* recall how many parameters have been provided */
+#endif
+                               PDO_DBG_RETURN(1);
 
                        case PDO_PARAM_EVT_EXEC_PRE:
-                               b = (MYSQL_BIND*)param->driver_data;
+                               if (S->params_given < S->num_params) {
+                                       /* too few parameter bound */
+                                       PDO_DBG_ERR("too few parameters bound");
+                                       strcpy(stmt->error_code, "HY093");
+                                       PDO_DBG_RETURN(0);
+                               }
 
-                               *b->is_null = 0;
-                               if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL || 
+#if PDO_USE_MYSQLND
+                               if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||
                                                Z_TYPE_P(param->parameter) == IS_NULL) {
-                                       *b->is_null = 1;
-                                       b->buffer_type = MYSQL_TYPE_STRING;
-                                       b->buffer = NULL;
-                                       b->buffer_length = 0;
-                                       *b->length = 0;
-                                       return 1;
+                                       mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, param->parameter, MYSQL_TYPE_NULL);
+                                       PDO_DBG_RETURN(1);
                                }
+#else
+                               b = (PDO_MYSQL_PARAM_BIND*)param->driver_data;
+                               *b->is_null = 0; 
+                               if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||  
+                                               Z_TYPE_P(param->parameter) == IS_NULL) { 
+                                       *b->is_null = 1; 
+                                       b->buffer_type = MYSQL_TYPE_STRING; 
+                                       b->buffer = NULL; 
+                                       b->buffer_length = 0; 
+                                       *b->length = 0; 
+                                       PDO_DBG_RETURN(1);
+                               } 
+#endif /* PDO_USE_MYSQLND */
        
                                switch (PDO_PARAM_TYPE(param->param_type)) {
                                        case PDO_PARAM_STMT:
-                                               return 0;
+                                               PDO_DBG_RETURN(0);
                                        case PDO_PARAM_LOB:
+                                               PDO_DBG_INF("PDO_PARAM_LOB");
                                                if (Z_TYPE_P(param->parameter) == IS_RESOURCE) {
                                                        php_stream *stm;
                                                        php_stream_from_zval_no_verify(stm, &param->parameter);
@@ -357,131 +543,220 @@ static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_da
                                        default:
                                                ;
                                }
-                       
+               
+#if PDO_USE_MYSQLND
+                               /* Is it really correct to check the zval's type? - But well, that's what the old code below does, too */
+                               PDO_DBG_INF_FMT("param->parameter->type=%d", Z_TYPE_P(param->parameter));
+                               switch (Z_TYPE_P(param->parameter)) {
+                                       case IS_STRING:
+                                               mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, param->parameter, MYSQL_TYPE_VAR_STRING);
+                                               break;
+                                       case IS_LONG:
+#if SIZEOF_LONG==8
+                                               mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, param->parameter, MYSQL_TYPE_LONGLONG);
+#elif SIZEOF_LONG==4
+                                               mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, param->parameter, MYSQL_TYPE_LONG);
+#endif /* SIZEOF_LONG */
+                                               break;
+                                       case IS_DOUBLE:
+                                               mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, param->parameter, MYSQL_TYPE_DOUBLE);
+                                               break;
+                                       default:
+                                               PDO_DBG_RETURN(0);
+                               }
+                               
+                               PDO_DBG_RETURN(1);
+#else  
+                               PDO_DBG_INF_FMT("param->parameter->type=%d", Z_TYPE_P(param->parameter));
                                switch (Z_TYPE_P(param->parameter)) {
                                        case IS_STRING:
                                                b->buffer_type = MYSQL_TYPE_STRING;
                                                b->buffer = Z_STRVAL_P(param->parameter);
                                                b->buffer_length = Z_STRLEN_P(param->parameter);
                                                *b->length = Z_STRLEN_P(param->parameter);
-                                               return 1;
+                                               PDO_DBG_RETURN(1);
 
                                        case IS_LONG:
                                                b->buffer_type = MYSQL_TYPE_LONG;
                                                b->buffer = &Z_LVAL_P(param->parameter);
-                                               return 1;
+                                               PDO_DBG_RETURN(1);
 
                                        case IS_DOUBLE:
                                                b->buffer_type = MYSQL_TYPE_DOUBLE;
                                                b->buffer = &Z_DVAL_P(param->parameter);
-                                               return 1;
+                                               PDO_DBG_RETURN(1);
 
                                        default:
-                                               return 0;
+                                               PDO_DBG_RETURN(0);
                                }
+#endif /* PDO_USE_MYSQLND */
+               case PDO_PARAM_EVT_FREE:
+               case PDO_PARAM_EVT_EXEC_POST:
+               case PDO_PARAM_EVT_FETCH_PRE:
+               case PDO_PARAM_EVT_FETCH_POST:
+               case PDO_PARAM_EVT_NORMALIZE:
+                       /* do nothing */
+                       break;
                }
        }
-#endif
-       return 1;
+#endif /* HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND */
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
 static int pdo_mysql_stmt_fetch(pdo_stmt_t *stmt,
-       enum pdo_fetch_orientation ori, long offset TSRMLS_DC)
+       enum pdo_fetch_orientation ori, long offset TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
-#if HAVE_MYSQL_STMT_PREPARE
+#if PDO_USE_MYSQLND
+       zend_bool fetched_anything;
+
+       PDO_DBG_ENTER("pdo_mysql_stmt_fetch");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
+       if (S->stmt) {
+               if (FAIL == mysqlnd_stmt_fetch(S->stmt, &fetched_anything) || fetched_anything == FALSE) {
+                       PDO_DBG_RETURN(0);
+               }
+
+               PDO_DBG_RETURN(1);
+       }
+#else
+#      if HAVE_MYSQL_STMT_PREPARE
        int ret;
        
        if (S->stmt) {
                ret = mysql_stmt_fetch(S->stmt);
 
-#ifdef MYSQL_DATA_TRUNCATED
+#              ifdef MYSQL_DATA_TRUNCATED
                if (ret == MYSQL_DATA_TRUNCATED) {
                        ret = 0;
                }
-#endif
+#              endif
 
                if (ret) {
                        if (ret != MYSQL_NO_DATA) {
                                pdo_mysql_error_stmt(stmt);
                        }
-                       return 0;
+                       PDO_DBG_RETURN(0);
                }
 
-               return 1;
+               PDO_DBG_RETURN(1);
        }
-#endif
-
+#      endif /* HAVE_MYSQL_STMT_PREPARE */
+#endif /* PDO_USE_MYSQLND */
+       
        if (!S->result) {
                strcpy(stmt->error_code, "HY000");
-               return 0;       
+               PDO_DBG_RETURN(0);
+       }
+#if PDO_USE_MYSQLND
+       if (!S->stmt && S->current_data) {
+               free(S->current_data);
        }
+#endif /* PDO_USE_MYSQLND */
+
        if ((S->current_data = mysql_fetch_row(S->result)) == NULL) {
                if (mysql_errno(S->H->server)) {
                        pdo_mysql_error_stmt(stmt);
                }
-               return 0;
+               PDO_DBG_RETURN(0);
        } 
+
        S->current_lengths = mysql_fetch_lengths(S->result);
-       return 1;       
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
-static int pdo_mysql_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC)
+static int pdo_mysql_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
        struct pdo_column_data *cols = stmt->columns;
        unsigned int i;
 
+       PDO_DBG_ENTER("pdo_mysql_stmt_describe");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
        if (!S->result) {
-               return 0;       
+               PDO_DBG_RETURN(0);
        }
 
        if (colno >= stmt->column_count) {
                /* error invalid column */
-               return 0;
+               PDO_DBG_RETURN(0);
        }
 
        /* fetch all on demand, this seems easiest 
        ** if we've been here before bail out 
        */
        if (cols[0].name) {
-               return 1;
+               PDO_DBG_RETURN(1);
        }
        for (i=0; i < stmt->column_count; i++) {
                int namelen;
-               namelen = strlen(S->fields[i].name);
+
+               if (S->H->fetch_table_names) {
+                       namelen = spprintf(&cols[i].name, 0, "%s.%s", S->fields[i].table, S->fields[i].name);
+                       cols[i].namelen = namelen;
+               } else {
+                       namelen = strlen(S->fields[i].name);
+                       cols[i].namelen = namelen;
+                       cols[i].name = estrndup(S->fields[i].name, namelen);
+               }
+               
                cols[i].precision = S->fields[i].decimals;
                cols[i].maxlen = S->fields[i].length;
-               cols[i].namelen = namelen;
-               cols[i].name = estrndup(S->fields[i].name, namelen);
-               cols[i].param_type = PDO_PARAM_STR;
+               
+#ifdef PDO_USE_MYSQLND
+               if (S->stmt) {
+                       cols[i].param_type = PDO_PARAM_ZVAL;
+               } else
+#endif
+               {
+                       cols[i].param_type = PDO_PARAM_STR;
+               }
        }
-       return 1;
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
-static int pdo_mysql_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC)
+static int pdo_mysql_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
+#if PDO_USE_MYSQLND
+       struct pdo_column_data *cols = stmt->columns;
+#endif
+       PDO_DBG_ENTER("pdo_mysql_stmt_get_col");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
+       if (!S->result) {
+               PDO_DBG_RETURN(0);
+       }
 
-#if HAVE_MYSQL_STMT_PREPARE
+       /* With mysqlnd data is stored inside mysqlnd, not S->current_data */
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
        if (!S->stmt) {
 #endif
                if (S->current_data == NULL || !S->result) {
-                       return 0;
+                       PDO_DBG_RETURN(0);
                }
-#if HAVE_MYSQL_STMT_PREPARE
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
        }
 #endif
        if (colno >= stmt->column_count) {
                /* error invalid column */
-               return 0;
+               PDO_DBG_RETURN(0);
        }
-#if HAVE_MYSQL_STMT_PREPARE
+#if PDO_USE_MYSQLND
+       if (S->stmt) {
+               Z_ADDREF_P(S->stmt->result_bind[colno].zv);
+               *ptr = (char*)&S->stmt->result_bind[colno].zv;
+               *len = sizeof(zval);
+               PDO_DBG_RETURN(1);
+       }
+#elif HAVE_MYSQL_STMT_PREPARE
        if (S->stmt) {
                if (S->out_null[colno]) {
                        *ptr = NULL;
                        *len = 0;
-                       return 1;
+                       PDO_DBG_RETURN(1);
                }
                *ptr = S->bound_result[colno].buffer;
                if (S->out_length[colno] > S->bound_result[colno].buffer_length) {
@@ -489,18 +764,18 @@ static int pdo_mysql_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsig
                        strcpy(stmt->error_code, "01004"); /* truncated */
                        S->out_length[colno] = S->bound_result[colno].buffer_length;
                        *len = S->out_length[colno];
-                       return 0;
+                       PDO_DBG_RETURN(0);
                }
                *len = S->out_length[colno];
-               return 1;
+               PDO_DBG_RETURN(1);
        }
-#endif
+#endif /* PDO_USE_MYSQLND else HAVE_MYSQL_STMT_PREPARE */
        *ptr = S->current_data[colno];
        *len = S->current_lengths[colno];
-       return 1;
-}
+       PDO_DBG_RETURN(1);
+} /* }}} */
 
-static char *type_to_name_native(int type)
+static char *type_to_name_native(int type) /* }}} */
 {
 #define PDO_MYSQL_NATIVE_TYPE_NAME(x)  case FIELD_TYPE_##x: return #x;
 
@@ -543,21 +818,24 @@ static char *type_to_name_native(int type)
         default:
             return NULL;
     }
-}
+#undef PDO_MYSQL_NATIVE_TYPE_NAME
+} /* }}} */
 
-static int pdo_mysql_stmt_col_meta(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC)
+static int pdo_mysql_stmt_col_meta(pdo_stmt_t *stmt, long colno, zval *return_value TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
-       MYSQL_FIELD *F;
+       const MYSQL_FIELD *F;
        zval *flags;
        char *str;
        
+       PDO_DBG_ENTER("pdo_mysql_stmt_col_meta");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
        if (!S->result) {
-               return FAILURE;
+               PDO_DBG_RETURN(FAILURE);
        }
        if (colno >= stmt->column_count) {
                /* error invalid column */
-               return FAILURE;
+               PDO_DBG_RETURN(FAILURE);
        }
 
        array_init(return_value);
@@ -589,33 +867,49 @@ static int pdo_mysql_stmt_col_meta(pdo_stmt_t *stmt, long colno, zval *return_va
                add_assoc_string(return_value, "native_type", str, 1);
        }
 
+#ifdef PDO_USE_MYSQLND
+       switch (F->type) {
+               case MYSQL_TYPE_BIT:
+               case MYSQL_TYPE_YEAR:
+               case MYSQL_TYPE_TINY:
+               case MYSQL_TYPE_SHORT:
+               case MYSQL_TYPE_INT24:
+               case MYSQL_TYPE_LONG:
+#if SIZEOF_LONG==8
+               case MYSQL_TYPE_LONGLONG:
+#endif
+                       add_assoc_long(return_value, "pdo_type", PDO_PARAM_INT);
+                       break;
+               default:
+                       add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR);
+                       break;
+       }
+#endif
+       
        add_assoc_zval(return_value, "flags", flags);
        add_assoc_string(return_value, "table",(F->table?F->table:""), 1);
-       return SUCCESS;
-}
+       PDO_DBG_RETURN(SUCCESS);
+} /* }}} */
 
-static int pdo_mysql_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC)
+static int pdo_mysql_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
 {
        pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
 
+       PDO_DBG_ENTER("pdo_mysql_stmt_cursor_closer");
+       PDO_DBG_INF_FMT("stmt=%p", S->stmt);
        if (S->result) {
                mysql_free_result(S->result);
                S->result = NULL;
        }
-#if HAVE_MYSQL_STMT_PREPARE
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
        if (S->stmt) {
                int retval;
-               if (!S->H->buffered) {
-                       retval = mysql_stmt_close(S->stmt);
-                       S->stmt = NULL;
-               } else {
-                       retval = mysql_stmt_free_result(S->stmt);
-               }
-               return retval ? 0 : 1;
+               retval = mysql_stmt_free_result(S->stmt);
+               PDO_DBG_RETURN(retval ? 0 : 1);
        }
 #endif
 
-#if HAVE_MYSQL_NEXT_RESULT
+#if HAVE_MYSQL_NEXT_RESULT || PDO_USE_MYSQLND
        while (mysql_more_results(S->H->server)) {
                MYSQL_RES *res;
                if (mysql_next_result(S->H->server) != 0) {
@@ -627,8 +921,9 @@ static int pdo_mysql_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC)
                }
        }
 #endif
-       return 1;
+       PDO_DBG_RETURN(1);
 }
+/* }}} */
 
 struct pdo_stmt_methods mysql_stmt_methods = {
        pdo_mysql_stmt_dtor,
index 0000f8b40c08518eede0f181ef5f454305fc964a..d13f45d71cebcd95e873bce9ed9dfa7ee853ad5c 100755 (executable)
@@ -13,6 +13,7 @@
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Author: George Schlossnagle <george@omniti.com>                      |
+  |         Johannes Schlueter <johannes@mysql.com>                      |
   +----------------------------------------------------------------------+
 */
 
 #include "php_pdo_mysql.h"
 #include "php_pdo_mysql_int.h"
 
-/* {{{ pdo_mysql_functions[] */
-const zend_function_entry pdo_mysql_functions[] = {
-       {NULL, NULL, NULL}
-};
-/* }}} */
+#ifdef COMPILE_DL_PDO_MYSQL
+ZEND_GET_MODULE(pdo_mysql)
+#endif
 
-/* {{{ pdo_mysql_functions[] */
-#if ZEND_MODULE_API_NO >= 20050922
-static const zend_module_dep pdo_mysql_deps[] = {
-       ZEND_MOD_REQUIRED("pdo")
-       {NULL, NULL, NULL}
-};
+#if PDO_USE_MYSQLND
+ZEND_DECLARE_MODULE_GLOBALS(pdo_mysql);
+
+#ifndef PHP_WIN32
+# ifndef PDO_MYSQL_UNIX_ADDR
+#  define PDO_MYSQL_UNIX_ADDR  "/tmp/mysql.sock"
+# endif
 #endif
-/* }}} */
 
-/* {{{ pdo_mysql_module_entry */
-zend_module_entry pdo_mysql_module_entry = {
-#if ZEND_MODULE_API_NO >= 20050922
-       STANDARD_MODULE_HEADER_EX, NULL,
-       pdo_mysql_deps,
-#else
-       STANDARD_MODULE_HEADER,
+
+/* {{{ PHP_INI_BEGIN
+*/
+PHP_INI_BEGIN()
+#ifndef PHP_WIN32
+       STD_PHP_INI_ENTRY("pdo_mysql.default_socket", PDO_MYSQL_UNIX_ADDR, PHP_INI_SYSTEM, OnUpdateString, default_socket, zend_pdo_mysql_globals, pdo_mysql_globals)
 #endif
-       "pdo_mysql",
-       pdo_mysql_functions,
-       PHP_MINIT(pdo_mysql),
-       PHP_MSHUTDOWN(pdo_mysql),
-       NULL,
-       NULL,
-       PHP_MINFO(pdo_mysql),
-       "1.0.2",
-       STANDARD_MODULE_PROPERTIES
-};
+#if PDO_DBG_ENABLED
+       STD_PHP_INI_ENTRY("pdo_mysql.debug",    NULL, PHP_INI_SYSTEM, OnUpdateString, debug, zend_pdo_mysql_globals, pdo_mysql_globals)
+#endif
+       STD_PHP_INI_ENTRY("pdo_mysql.cache_size",                       "2000", PHP_INI_SYSTEM,         OnUpdateLong,           cache_size,                     zend_pdo_mysql_globals,         pdo_mysql_globals)
+PHP_INI_END()
 /* }}} */
-
-#ifdef COMPILE_DL_PDO_MYSQL
-ZEND_GET_MODULE(pdo_mysql)
 #endif
 
 /* true global environment */
-
+#ifdef PDO_USE_MYSQLND
+static MYSQLND_ZVAL_PCACHE *mysql_mysqlnd_zval_cache;
+#endif
+               
+               
 /* {{{ PHP_MINIT_FUNCTION
  */
-PHP_MINIT_FUNCTION(pdo_mysql)
+static PHP_MINIT_FUNCTION(pdo_mysql)
 {
+#if PDO_USE_MYSQLND
+       REGISTER_INI_ENTRIES();
+#endif
+
        REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_USE_BUFFERED_QUERY", (long)PDO_MYSQL_ATTR_USE_BUFFERED_QUERY);
-       REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_LOCAL_INFILE", (long)PDO_MYSQL_ATTR_LOCAL_INFILE);
+       REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_LOCAL_INFILE", (long)PDO_MYSQL_ATTR_LOCAL_INFILE);    
+#ifndef PDO_USE_MYSQLND
+       REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_MAX_BUFFER_SIZE", (long)PDO_MYSQL_ATTR_MAX_BUFFER_SIZE);
        REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_INIT_COMMAND", (long)PDO_MYSQL_ATTR_INIT_COMMAND);
        REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_READ_DEFAULT_FILE", (long)PDO_MYSQL_ATTR_READ_DEFAULT_FILE);
        REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_READ_DEFAULT_GROUP", (long)PDO_MYSQL_ATTR_READ_DEFAULT_GROUP);
-       REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_MAX_BUFFER_SIZE", (long)PDO_MYSQL_ATTR_MAX_BUFFER_SIZE);
+#endif
        REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_DIRECT_QUERY", (long)PDO_MYSQL_ATTR_DIRECT_QUERY);
 
+#ifdef PDO_USE_MYSQLND
+       mysql_mysqlnd_zval_cache = mysqlnd_palloc_init_cache(PDO_MYSQL_G(cache_size));
+#endif
+       
        return php_pdo_register_driver(&pdo_mysql_driver);
 }
 /* }}} */
 
 /* {{{ PHP_MSHUTDOWN_FUNCTION
  */
-PHP_MSHUTDOWN_FUNCTION(pdo_mysql)
+static PHP_MSHUTDOWN_FUNCTION(pdo_mysql)
 {
        php_pdo_unregister_driver(&pdo_mysql_driver);
+#if PDO_USE_MYSQLND
+       UNREGISTER_INI_ENTRIES();
+#endif
+
        return SUCCESS;
 }
 /* }}} */
 
 /* {{{ PHP_MINFO_FUNCTION
  */
-PHP_MINFO_FUNCTION(pdo_mysql)
+static PHP_MINFO_FUNCTION(pdo_mysql)
 {
        php_info_print_table_start();
+#ifdef PDO_USE_MYSQLND
+       php_info_print_table_header(2, "PDO Driver for MySQL, mysql native driver version", mysql_get_client_info());
+
+       {
+               zval values;
+
+               php_info_print_table_header(2, "Persistent cache", mysql_mysqlnd_zval_cache? "enabled":"disabled");
+               
+               if (mysql_mysqlnd_zval_cache) {
+                       /* Now report cache status */
+                       mysqlnd_palloc_stats(mysql_mysqlnd_zval_cache, &values);
+                       mysqlnd_minfo_print_hash(&values);
+                       zval_dtor(&values);
+               }
+       }
+#else
        php_info_print_table_header(2, "PDO Driver for MySQL, client library version", mysql_get_client_info());
-       php_info_print_table_row(2, "MYSQL_SOCKET", PDO_MYSQL_UNIX_ADDR);
+#endif
        php_info_print_table_end();
+
+#ifdef PDO_USE_MYSQLND
+       DISPLAY_INI_ENTRIES();
+#endif
+}
+/* }}} */
+
+
+#if PDO_USE_MYSQLND
+/* {{{ PHP_RINIT_FUNCTION
+ */
+static PHP_RINIT_FUNCTION(pdo_mysql)
+{
+       PDO_MYSQL_G(mysqlnd_thd_zval_cache) = mysqlnd_palloc_rinit(mysql_mysqlnd_zval_cache);
+       
+#if PDO_DBG_ENABLED
+       if (PDO_MYSQL_G(debug)) {
+               MYSQLND_DEBUG *dbg = mysqlnd_debug_init(TSRMLS_C);
+               if (!dbg) {
+                       return FAILURE;
+               }
+               dbg->m->set_mode(dbg, PDO_MYSQL_G(debug));
+               PDO_MYSQL_G(dbg) = dbg;
+       }
+#endif
+       
+       return SUCCESS;
+}
+/* }}} */
+
+
+/* {{{ PHP_RSHUTDOWN_FUNCTION
+ */
+static PHP_RSHUTDOWN_FUNCTION(pdo_mysql)
+{
+       mysqlnd_palloc_rshutdown(PDO_MYSQL_G(mysqlnd_thd_zval_cache));
+       
+#if PDO_DBG_ENABLED
+       MYSQLND_DEBUG *dbg = PDO_MYSQL_G(dbg);
+       PDO_DBG_ENTER("RSHUTDOWN");
+       if (dbg) {
+               dbg->m->close(dbg);
+               dbg->m->free_handle(dbg);
+               PDO_MYSQL_G(dbg) = NULL;
+       }
+#endif
+       return SUCCESS;
+}
+/* }}} */
+
+
+/* {{{ PHP_GINIT_FUNCTION
+ */
+static PHP_GINIT_FUNCTION(pdo_mysql)
+{
+       pdo_mysql_globals->mysqlnd_thd_zval_cache = NULL; /* zval cache */
+       pdo_mysql_globals->cache_size = 0;
+#ifndef PHP_WIN32
+       pdo_mysql_globals->default_socket = NULL;
+#endif
+#if PDO_DBG_ENABLED
+       pdo_mysql_globals->debug = NULL;        /* The actual string */
+       pdo_mysql_globals->dbg = NULL;  /* The DBG object*/
+#endif
 }
 /* }}} */
+#endif
+
+
+/* {{{ pdo_mysql_functions[] */
+const zend_function_entry pdo_mysql_functions[] = {
+       {NULL, NULL, NULL}
+};
+/* }}} */
+
+/* {{{ pdo_mysql_deps[] */
+#if ZEND_MODULE_API_NO >= 20050922
+static const zend_module_dep pdo_mysql_deps[] = {
+       ZEND_MOD_REQUIRED("pdo")
+#ifdef PDO_USE_MYSQLND
+       ZEND_MOD_REQUIRED("mysqlnd")
+#endif
+       {NULL, NULL, NULL}
+};
+#endif
+/* }}} */
+
+/* {{{ pdo_mysql_module_entry */
+zend_module_entry pdo_mysql_module_entry = {
+       STANDARD_MODULE_HEADER_EX, NULL,
+       pdo_mysql_deps,
+       "pdo_mysql",
+       pdo_mysql_functions,
+       PHP_MINIT(pdo_mysql),
+       PHP_MSHUTDOWN(pdo_mysql),
+#if PDO_USE_MYSQLND
+       PHP_RINIT(pdo_mysql),
+       PHP_RSHUTDOWN(pdo_mysql),
+#else
+       NULL,
+       NULL,
+#endif
+       PHP_MINFO(pdo_mysql),
+       "1.0.2",
+#if PDO_USE_MYSQLND
+       PHP_MODULE_GLOBALS(pdo_mysql),
+       PHP_GINIT(pdo_mysql),
+       NULL,
+       NULL,
+       STANDARD_MODULE_PROPERTIES_EX
+#else
+       STANDARD_MODULE_PROPERTIES
+#endif
+};
+/* }}} */
+
 
 /*
  * Local variables:
index 8a9922f10fbc54143cc97cf865342b0a593c05e8..3da32cff34915dd3d5b995ce12c68d0bdc291381 100755 (executable)
 extern zend_module_entry pdo_mysql_module_entry;
 #define phpext_pdo_mysql_ptr &pdo_mysql_module_entry
 
+#ifdef PHP_WIN32
+#define PHP_PDO_MYSQL_API __declspec(dllexport)
+#else
+#define PHP_PDO_MYSQL_API
+#endif
+
 #ifdef ZTS
 #include "TSRM.h"
 #endif
 
-PHP_MINIT_FUNCTION(pdo_mysql);
-PHP_MSHUTDOWN_FUNCTION(pdo_mysql);
-PHP_RINIT_FUNCTION(pdo_mysql);
-PHP_RSHUTDOWN_FUNCTION(pdo_mysql);
-PHP_MINFO_FUNCTION(pdo_mysql);
 
 #endif /* PHP_PDO_MYSQL_H */
 
index 65bcbd2a2b0be3a85f25e34aafadabb83419dbf0..3ed96c90299d20f1a56371c829c00e2393f68b79 100755 (executable)
@@ -14,6 +14,7 @@
   +----------------------------------------------------------------------+
   | Author: George Schlossnagle <george@omniti.com>                      |
   |         Wez Furlong <wez@php.net>                                    |
+  |         Johannes Schlueter <johannes@mysql.com>                      |
   +----------------------------------------------------------------------+
 */
 
 #ifndef PHP_PDO_MYSQL_INT_H
 #define PHP_PDO_MYSQL_INT_H
 
-#include <mysql.h>
+#if defined(PDO_USE_MYSQLND)
+#      include "ext/mysqlnd/mysqlnd.h"
+#      include "ext/mysql/mysql_mysqlnd.h"
+#      include "ext/mysqlnd/mysqlnd_libmysql_compat.h"
+#      define PDO_MYSQL_PARAM_BIND MYSQLND_PARAM_BIND
+#else
+#      include <mysql.h>
+#      define PDO_MYSQL_PARAM_BIND MYSQL_BIND
+#endif
+
+#if defined(PDO_USE_MYSQLND) && PHP_DEBUG && !defined(PHP_WIN32)
+#define PDO_DBG_ENABLED 1
+
+#define PDO_DBG_INF(msg) do { if (dbg_skip_trace == FALSE) PDO_MYSQL_G(dbg)->m->log(PDO_MYSQL_G(dbg), __LINE__, __FILE__, -1, "info : ", (msg)); } while (0)
+#define PDO_DBG_ERR(msg) do { if (dbg_skip_trace == FALSE) PDO_MYSQL_G(dbg)->m->log(PDO_MYSQL_G(dbg), __LINE__, __FILE__, -1, "error: ", (msg)); } while (0)
+#define PDO_DBG_INF_FMT(...) do { if (dbg_skip_trace == FALSE) PDO_MYSQL_G(dbg)->m->log_va(PDO_MYSQL_G(dbg), __LINE__, __FILE__, -1, "info : ", __VA_ARGS__); } while (0)
+#define PDO_DBG_ERR_FMT(...) do { if (dbg_skip_trace == FALSE) PDO_MYSQL_G(dbg)->m->log_va(PDO_MYSQL_G(dbg), __LINE__, __FILE__, -1, "error: ", __VA_ARGS__); } while (0)
+#define PDO_DBG_ENTER(func_name) zend_bool dbg_skip_trace = TRUE; if (PDO_MYSQL_G(dbg)) dbg_skip_trace = !PDO_MYSQL_G(dbg)->m->func_enter(PDO_MYSQL_G(dbg), __LINE__, __FILE__, func_name, strlen(func_name));
+#define PDO_DBG_RETURN(value)  do { if (PDO_MYSQL_G(dbg)) PDO_MYSQL_G(dbg)->m->func_leave(PDO_MYSQL_G(dbg), __LINE__, __FILE__); return (value); } while (0)
+#define PDO_DBG_VOID_RETURN            do { if (PDO_MYSQL_G(dbg)) PDO_MYSQL_G(dbg)->m->func_leave(PDO_MYSQL_G(dbg), __LINE__, __FILE__); return; } while (0)
+
+#else
+#define PDO_DBG_ENABLED 0
+
+static inline void PDO_DBG_INF(char *msg) {}
+static inline void PDO_DBG_ERR(char *msg) {}
+static inline void PDO_DBG_INF_FMT(char *format, ...) {}
+static inline void PDO_DBG_ERR_FMT(char *format, ...) {}
+static inline void PDO_DBG_ENTER(char *func_name) {}
+#define PDO_DBG_RETURN(value)  return (value)
+#define PDO_DBG_VOID_RETURN            return;
+
+#endif
+
+#if defined(PDO_USE_MYSQLND)
+#include "ext/mysqlnd/mysqlnd_debug.h"
+#endif
+
+#ifdef PDO_USE_MYSQLND
+ZEND_BEGIN_MODULE_GLOBALS(pdo_mysql)
+       MYSQLND_THD_ZVAL_PCACHE *mysqlnd_thd_zval_cache;
+       long          cache_size;
+#ifndef PHP_WIN32
+       char          *default_socket;
+#endif
+#if PDO_DBG_ENABLED
+       char          *debug; /* The actual string */
+       MYSQLND_DEBUG *dbg;     /* The DBG object */
+#endif
+ZEND_END_MODULE_GLOBALS(pdo_mysql)
+
+ZEND_EXTERN_MODULE_GLOBALS(pdo_mysql);
+#endif
+
+#ifdef ZTS
+#define PDO_MYSQL_G(v) TSRMG(pdo_mysql_globals_id, zend_pdo_mysql_globals *, v)
+#else
+#define PDO_MYSQL_G(v) (pdo_mysql_globals.v)
+#endif
+
 
 typedef struct {
        const char *file;
@@ -38,8 +98,11 @@ typedef struct {
        unsigned attached:1;
        unsigned buffered:1;
        unsigned emulate_prepare:1;
-       unsigned _reserved:31;
+       unsigned fetch_table_names:1;
+       unsigned _reserved:31;  
+#if !PDO_USE_MYSQLND
        unsigned long max_buffer_size;
+#endif
 
        pdo_mysql_error_info einfo;
 } pdo_mysql_db_handle;
@@ -51,22 +114,31 @@ typedef struct {
 typedef struct {
        pdo_mysql_db_handle     *H;
        MYSQL_RES               *result;
-       MYSQL_FIELD         *fields;
+       const MYSQL_FIELD       *fields;
        MYSQL_ROW               current_data;
+#if PDO_USE_MYSQLND
+       unsigned long           *current_lengths;
+#else
        long                    *current_lengths;
+#endif
        pdo_mysql_error_info einfo;
-#if HAVE_MYSQL_STMT_PREPARE
-       MYSQL_STMT              *stmt;
-       
+#if HAVE_MYSQL_STMT_PREPARE || PDO_USE_MYSQLND
+#if PDO_USE_MYSQLND
+       MYSQLND_STMT            *stmt;
+#else
+       MYSQL_STMT              *stmt;
+#endif 
        int num_params;
-       MYSQL_BIND      *params;
+       PDO_MYSQL_PARAM_BIND    *params;
+#ifndef PDO_USE_MYSQLND
        my_bool                 *in_null;
-       unsigned long   *in_length;
-       
-       MYSQL_BIND              *bound_result;
+       unsigned long           *in_length;
+#endif
+       PDO_MYSQL_PARAM_BIND    *bound_result;
        my_bool                 *out_null;
-       unsigned long   *out_length;
-       unsigned max_length:1;
+       unsigned long           *out_length;
+       unsigned int            params_given;
+       unsigned                max_length:1;
 #endif
 } pdo_mysql_stmt;
 
@@ -81,19 +153,13 @@ extern struct pdo_stmt_methods mysql_stmt_methods;
 enum {
        PDO_MYSQL_ATTR_USE_BUFFERED_QUERY = PDO_ATTR_DRIVER_SPECIFIC,
        PDO_MYSQL_ATTR_LOCAL_INFILE,
+#ifndef PDO_USE_MYSQLND
        PDO_MYSQL_ATTR_INIT_COMMAND,
        PDO_MYSQL_ATTR_READ_DEFAULT_FILE,
        PDO_MYSQL_ATTR_READ_DEFAULT_GROUP,
        PDO_MYSQL_ATTR_MAX_BUFFER_SIZE,
+#endif
        PDO_MYSQL_ATTR_DIRECT_QUERY,
 };
 
-#ifndef PDO_MYSQL_UNIX_ADDR
-# ifdef PHP_WIN32
-#  define PDO_MYSQL_UNIX_ADDR  "MySQL"
-# else
-#  define PDO_MYSQL_UNIX_ADDR  "/tmp/mysql.sock"
-# endif
-#endif
-
 #endif
diff --git a/ext/pdo_mysql/tests/README b/ext/pdo_mysql/tests/README
new file mode 100644 (file)
index 0000000..920f5ef
--- /dev/null
@@ -0,0 +1,16 @@
+You must set the following environment variables to run the tests:
+
+  PDO_MYSQL_TEST_DSN  - DSN
+    For example: mysql:dbname=test;host=localhost;port=3306
+
+  PDO_MYSQL_TEST_HOST     - database host
+  PDO_MYSQL_TEST_DB      - database (schema) name
+  PDO_MYSQL_TEST_SOCKET  - database server socket
+  PDO_MYSQL_TEST_ENGINE  - storage engine to use
+  PDO_MYSQL_TEST_USER    - database user
+  PDO_MYSQL_TEST_PASS    - database user password
+  PDO_MYSQL_TEST_CHARSET - database charset
+
+  NOTE: if any of PDO_MYSQL_TEST_[HOST|DB|SOCKET|ENGINE|CHARSET] is
+  part of PDO_MYSQL_TEST_DSN, the values must match. That is, for example,
+  for PDO_MYSQL_TEST_DSN = mysql:dbname=test you MUST set PDO_MYSQL_TEST_DB=test.
index 74559a145b606b8bb96a3f47d5fc58dd49fb2b6b..88392ef32f2633adabb0c4521c381c120c7de1ff 100644 (file)
@@ -24,7 +24,15 @@ foreach ($db->query('SELECT * from test') as $row) {
 $stmt = $db->prepare('SELECT * from test');
 print_r($stmt->getColumnMeta(0));
 $stmt->execute();
-print_r($stmt->getColumnMeta(0));
+$tmp = $stmt->getColumnMeta(0);
+
+// libmysql and mysqlnd will show the pdo_type entry at a different position in the hash
+if (!isset($tmp['pdo_type']) || (isset($tmp['pdo_type']) && $tmp['pdo_type'] != 2))
+       printf("Expecting pdo_type = 2 got %s\n", $tmp['pdo_type']);
+else
+       unset($tmp['pdo_type']);
+
+print_r($tmp);
 ?>
 --EXPECTF--
 object(PDOStatement)#%d (1) {
@@ -48,5 +56,4 @@ Array
     [name] => bar
     [len] => 11
     [precision] => 0
-    [pdo_type] => 2
 )
diff --git a/ext/pdo_mysql/tests/bug_39858.phpt b/ext/pdo_mysql/tests/bug_39858.phpt
new file mode 100644 (file)
index 0000000..e557094
--- /dev/null
@@ -0,0 +1,96 @@
+--TEST--
+Bug #39858 (http://bugs.php.net/bug.php?id=39858)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+
+$row = $db->query('SELECT VERSION() as _version')->fetch(PDO::FETCH_ASSOC);
+$matches = array();
+if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches))
+       die(sprintf("skip Cannot determine MySQL Server version\n"));
+
+$version = $matches[0] * 10000 + $matches[1] * 100 + $matches[2];
+if ($version < 50000)
+       die(sprintf("skip Need MySQL Server 5.0.0+, found %d.%02d.%02d (%d)\n",
+               $matches[0], $matches[1], $matches[2], $version));
+?>
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+
+function bug_39858($db) {
+
+       $db->exec("DROP PROCEDURE IF EXISTS p");
+       $db->exec("
+               CREATE PROCEDURE p()
+                       NOT DETERMINISTIC
+                       CONTAINS SQL
+                       SQL SECURITY DEFINER
+                       COMMENT ''
+               BEGIN
+                       SELECT 2 * 2;
+               END;");
+
+       $stmt = $db->prepare("CALL p()");
+       $stmt->execute();
+       do {
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+       } while ($stmt->nextRowset());
+
+       $stmt = $db->prepare("CALL p()");
+       $stmt->execute();
+       do {
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+       } while ($stmt->nextRowset());
+       $stmt->closeCursor();
+
+}
+
+printf("Emulated Prepared Statements...\n");
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+bug_39858($db);
+
+printf("Native Prepared Statements...\n");
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+bug_39858($db);
+
+print "done!";
+?>
+--EXPECTF--
+Emulated Prepared Statements...
+array(1) {
+  [0]=>
+  array(1) {
+    ["2 * 2"]=>
+    string(1) "4"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["2 * 2"]=>
+    string(1) "4"
+  }
+}
+Native Prepared Statements...
+array(1) {
+  [0]=>
+  array(1) {
+    ["2 * 2"]=>
+    string(1) "4"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["2 * 2"]=>
+    string(1) "4"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_41125.phpt b/ext/pdo_mysql/tests/bug_41125.phpt
new file mode 100644 (file)
index 0000000..1ed3a0c
--- /dev/null
@@ -0,0 +1,44 @@
+--TEST--
+Bug #41125 (http://bugs.php.net/bug.php?id=41125)
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+
+$db = MySQLPDOTest::factory();
+$row = $db->query('SELECT VERSION() as _version')->fetch(PDO::FETCH_ASSOC);
+$matches = array();
+if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches))
+       die(sprintf("skip Cannot determine MySQL Server version\n"));
+
+$version = $matches[0] * 10000 + $matches[1] * 100 + $matches[2];
+die("skip $version");
+if ($version < 40100)
+       die(sprintf("skip Need MySQL Server 5.0.0+, found %d.%02d.%02d (%d)\n",
+               $matches[0], $matches[1], $matches[2], $version));
+?>
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::factory();
+
+// And now allow the evil to do his work
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+$sql = "CREATE TABLE IF NOT EXISTS test(id INT); INSERT INTO test(id) VALUES (1); SELECT * FROM test; INSERT INTO test(id) VALUES (2); SELECT * FROM test;";
+// NOTE: This will fail, it is OK to fail - you must not mix DML/DDL and SELECT
+// The PDO API does not support multiple queries properly!
+// Read http://blog.ulf-wendel.de/?p=192
+// Compare MySQL C-API documentation
+$stmt = $db->query($sql);
+do {
+       var_dump($stmt->fetchAll());
+} while ($stmt->nextRowset());
+
+print "done!";
+?>
+--EXPECTF--
+Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
+array(0) {
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_41997.phpt b/ext/pdo_mysql/tests/bug_41997.phpt
new file mode 100644 (file)
index 0000000..60bdfee
--- /dev/null
@@ -0,0 +1,64 @@
+--TEST--
+PDO MySQL Bug #41997 (stored procedure call returning single rowset blocks future queries)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+$row = $db->query('SELECT VERSION() as _version')->fetch(PDO::FETCH_ASSOC);
+$matches = array();
+if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches))
+       die(sprintf("skip Cannot determine MySQL Server version\n"));
+
+$version = $matches[0] * 10000 + $matches[1] * 100 + $matches[2];
+if ($version < 50000)
+       die(sprintf("skip Need MySQL Server 5.0.0+, found %d.%02d.%02d (%d)\n",
+               $matches[0], $matches[1], $matches[2], $version));
+?>
+--FILE--
+<?php
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+
+$db->exec('DROP PROCEDURE IF EXISTS p');
+$db->exec('CREATE PROCEDURE p() BEGIN SELECT 1 AS "one"; END');
+
+$stmt = $db->query("CALL p()");
+do {
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+} while ($stmt->nextRowset());
+var_dump($stmt->errorInfo());
+
+$stmt = $db->query('SELECT 2 AS "two"');
+var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+var_dump($stmt->errorInfo());
+print "done!";
+?>
+--EXPECT--
+array(1) {
+  [0]=>
+  array(1) {
+    ["one"]=>
+    string(1) "1"
+  }
+}
+array(1) {
+  [0]=>
+  string(5) "00000"
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["two"]=>
+    string(1) "2"
+  }
+}
+array(1) {
+  [0]=>
+  string(5) "00000"
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_42499.phpt b/ext/pdo_mysql/tests/bug_42499.phpt
new file mode 100644 (file)
index 0000000..6b05c17
--- /dev/null
@@ -0,0 +1,80 @@
+--TEST--
+PDO MySQL Bug #42499 (http://bugs.php.net/bug.php?id=42499)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+MySQLPDOTest::skip();
+
+$db = MySQLPDOTest::factory();
+$stmt = $db->query('SELECT VERSION() as _version');
+$row = $stmt->fetch(PDO::FETCH_ASSOC);
+$matches = array();
+if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches))
+       die(sprintf("skip Cannot determine MySQL Server version\n"));
+
+$version = $matches[0] * 10000 + $matches[1] * 100 + $matches[2];
+if ($version < 41000)
+       die(sprintf("skip Need MySQL Server 4.1.0+, found %d.%02d.%02d (%d)\n",
+               $matches[0], $matches[1], $matches[2], $version));
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::factory();
+
+function bug_42499($db) {
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       $db->exec('CREATE TABLE test(id CHAR(1)); INSERT INTO test(id) VALUES ("a")');
+
+       $stmt = $db->query('SELECT id AS _id FROM test');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       // You must not use exec() to run statements that create a result set!
+       $db->exec('SELECT id FROM test');
+       // This will bail at you because you have not fetched the SELECT results: this is not a bug!
+       $db->exec('INSERT INTO test(id) VALUES ("b")');
+
+}
+
+print "Emulated Prepared Statements...\n";
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
+bug_42499($db);
+
+print "Native Prepared Statements...\n";
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
+bug_42499($db);
+
+$db = MySQLPDOTest::factory();
+$db->exec('DROP TABLE IF EXISTS test');
+
+print "done!";
+?>
+--EXPECTF--
+Emulated Prepared Statements...
+array(1) {
+  [0]=>
+  array(1) {
+    ["_id"]=>
+    string(1) "a"
+  }
+}
+
+Warning: PDO::exec(): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in %s on line %d
+Native Prepared Statements...
+array(1) {
+  [0]=>
+  array(1) {
+    ["_id"]=>
+    string(1) "a"
+  }
+}
+
+Warning: PDO::exec(): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in %s on line %d
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_43371.phpt b/ext/pdo_mysql/tests/bug_43371.phpt
new file mode 100644 (file)
index 0000000..c0c65a2
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+Bug #43371 (http://bugs.php.net/bug.php?id=43371)
+--SKIPIF--
+<?php
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+$dsn = MySQLPDOTest::getDSN();
+$db = new PDO($dsn, PDO_MYSQL_TEST_USER, PDO_MYSQL_TEST_PASS, array(PDO::ATTR_PERSISTENT => true));
+
+print "done!";
+?>
+--EXPECT--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_44454.phpt b/ext/pdo_mysql/tests/bug_44454.phpt
new file mode 100644 (file)
index 0000000..a7c636a
--- /dev/null
@@ -0,0 +1,104 @@
+--TEST--
+Bug #44454 (http://bugs.php.net/bug.php?id=44454)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+
+function bug_44454($db) {
+
+       try {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec('CREATE TABLE test(a INT, b INT, UNIQUE KEY idx_ab (a, b))');
+               $db->exec('INSERT INTO test(a, b) VALUES (1, 1)');
+
+               $stmt = $db->query('SELECT a, b FROM test');
+               printf("... SELECT has returned %d row...\n", $stmt->rowCount());
+               while ($row = $stmt->fetch()) {
+                       try {
+                               printf("... INSERT should fail...\n");
+                               $db->exec('INSERT INTO test(a, b) VALUES (1, 1)');
+                       } catch (Exception $e) {
+                               printf("... STMT - %s\n", var_export($stmt->errorCode(), true));
+                               printf("... PDO  - %s\n", var_export($db->errorInfo(), true));
+                       }
+               }
+
+       } catch (Exception $e) {
+               printf("... While error %s\n", $e->getMessage()); ;
+       }
+
+       $stmt = $db->query('SELECT a, b FROM test');
+       printf("... SELECT has returned %d row...\n", $stmt->rowCount());
+       foreach ($stmt as $row) {
+               try {
+                       printf("... INSERT should fail...\n");
+                       $db->exec('INSERT INTO test(a, b) VALUES (1, 1)');
+               } catch (Exception $e) {
+                       printf("... STMT - %s\n", var_export($stmt->errorCode(), true));
+                       printf("... PDO  - %s\n", var_export($db->errorInfo(), true));
+               }
+       }
+
+}
+
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+print "Native Prepared Statements\n";
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+bug_44454($db);
+
+print "\nEmulated Prepared Statements\n";
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+bug_44454($db);
+
+print "done!";
+?>
+--XFAIL--
+For some reason the exception gets thrown at the wrong place
+--EXPECT--
+Native Prepared Statements
+... SELECT has returned 1 row...
+... INSERT should fail...
+... STMT - '00000'
+... PDO  - array (
+  0 => '23000',
+  1 => 1062,
+  2 => 'Duplicate entry \'1-1\' for key 1',
+)
+... SELECT has returned 1 row...
+... INSERT should fail...
+... STMT - '00000'
+... PDO  - array (
+  0 => '23000',
+  1 => 1062,
+  2 => 'Duplicate entry \'1-1\' for key 1',
+)
+
+Emulated Prepared Statements
+... SELECT has returned 1 row...
+... INSERT should fail...
+... STMT - '00000'
+... PDO  - array (
+  0 => '23000',
+  1 => 1062,
+  2 => 'Duplicate entry \'1-1\' for key 1',
+)
+... SELECT has returned 1 row...
+... INSERT should fail...
+... STMT - '00000'
+... PDO  - array (
+  0 => '23000',
+  1 => 1062,
+  2 => 'Duplicate entry \'1-1\' for key 1',
+)
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_44707.phpt b/ext/pdo_mysql/tests/bug_44707.phpt
new file mode 100644 (file)
index 0000000..c7e944f
--- /dev/null
@@ -0,0 +1,92 @@
+--TEST--
+Bug #44707 (http://bugs.php.net/bug.php?id=44707) = ! driver bug
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+MySQLPDOTest::skip();
+
+$db = MySQLPDOTest::factory();
+$stmt = $db->query('SELECT VERSION() as _version');
+$row = $stmt->fetch(PDO::FETCH_ASSOC);
+$matches = array();
+if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches))
+       die(sprintf("skip Cannot determine MySQL Server version\n"));
+
+$version = $matches[0] * 10000 + $matches[1] * 100 + $matches[2];
+if ($version < 41000)
+       die(sprintf("skip Will work different with MySQL Server < 4.1.0, found %d.%02d.%02d (%d)\n",
+               $matches[0], $matches[1], $matches[2], $version));
+?>
+--FILE--
+<?php
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+
+function bug_44707($db) {
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       $db->exec('CREATE TABLE test(id INT, mybool TINYINT)');
+
+       $id = 1;
+       $mybool = false;
+       var_dump($mybool);
+
+       $stmt = $db->prepare('INSERT INTO test(id, mybool) VALUES (?, ?)');
+       $stmt->bindParam(1, $id);
+       // From MySQL 4.1 on boolean and TINYINT don't match! INSERT will fail.
+       // Versions prior to 4.1 have a weak test and will accept this.
+       $stmt->bindParam(2, $mybool, PDO::PARAM_BOOL);
+       var_dump($mybool);
+
+       $stmt->execute();
+       var_dump($mybool);
+
+       $stmt = $db->query('SELECT * FROM test');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       $stmt = $db->prepare('INSERT INTO test(id, mybool) VALUES (?, ?)');
+       $stmt->bindParam(1, $id);
+       // INT and integer work well together
+       $stmt->bindParam(2, $mybool, PDO::PARAM_INT);
+       $stmt->execute();
+
+       $stmt = $db->query('SELECT * FROM test');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+}
+
+
+/*
+// This is beyond the control of the driver... - the driver never gets in touch with bound values
+print "Emulated Prepared Statements\n";
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+bug_44707($db);
+*/
+
+print "Native Prepared Statements\n";
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+bug_44707($db);
+
+print "done!";
+?>
+--EXPECT--
+Native Prepared Statements
+bool(false)
+bool(false)
+bool(false)
+array(0) {
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["mybool"]=>
+    string(1) "0"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_45120.phpt b/ext/pdo_mysql/tests/bug_45120.phpt
new file mode 100644 (file)
index 0000000..5f18342
--- /dev/null
@@ -0,0 +1,50 @@
+--TEST--
+Bug #45120 (http://bugs.php.net/bug.php?id=45120)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+
+function bug_45120($db) {
+
+       $stmt = $db->prepare("SELECT 1 AS 'one'");
+       if (true !== $stmt->execute())
+               printf("[001] Execute has failed: %s\n", var_export($stmt->errorInfo(), true));
+
+       $res = $stmt->fetch(PDO::FETCH_ASSOC);
+       if ($res['one'] != 1)
+               printf("[002] Wrong results: %s\n", var_export($res, true));
+
+       if (true !== $stmt->execute())
+               printf("[003] Execute has failed: %s\n", var_export($stmt->errorInfo(), true));
+
+       $res = $stmt->fetch(PDO::FETCH_ASSOC);
+       if ($res['one'] != 1)
+               printf("[004] Wrong results: %s\n", var_export($res, true));
+
+}
+
+print "Emulated Prepared Statements\n";
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+bug_45120($db);
+
+print "Native Prepared Statements\n";
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+bug_45120($db);
+
+print "done!";
+?>
+--XFAIL--
+This is an open PDO bug. It is not a PDO_MYSQL bug
+--EXPECT--
+Emulated Prepared Statements
+Native Prepared Statements
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_pecl_12925.phpt b/ext/pdo_mysql/tests/bug_pecl_12925.phpt
new file mode 100644 (file)
index 0000000..1e7cea5
--- /dev/null
@@ -0,0 +1,62 @@
+--TEST--
+PDO MySQL PECL bug #1295 (http://pecl.php.net/bugs/bug.php?id=12925)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::factory();
+
+function bug_pecl_1295($db) {
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       $db->exec('CREATE TABLE test(id CHAR(1))');
+       $db->exec('INSERT INTO test(id) VALUES ("a")');
+       $stmt = $db->prepare('UPDATE test SET id = "b"');
+       $stmt->execute();
+       $stmt = $db->prepare('UPDATE test SET id = "c"');
+       $stmt->execute();
+       $stmt = $db->prepare('SELECT id FROM test');
+       $stmt->execute();
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+       $stmt->closeCursor();
+
+}
+
+printf("Emulated...\n");
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+bug_pecl_1295($db);
+
+printf("Native...\n");
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+bug_pecl_1295($db);
+
+$db->exec('DROP TABLE IF EXISTS test');
+print "done!";
+?>
+--EXPECT--
+Emulated...
+array(1) {
+  [0]=>
+  array(1) {
+    ["id"]=>
+    string(1) "c"
+  }
+}
+Native...
+array(1) {
+  [0]=>
+  array(1) {
+    ["id"]=>
+    string(1) "c"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/bug_pecl_7976.phpt b/ext/pdo_mysql/tests/bug_pecl_7976.phpt
new file mode 100644 (file)
index 0000000..29ca98d
--- /dev/null
@@ -0,0 +1,87 @@
+--TEST--
+PDO MySQL Bug #42499 (http://bugs.php.net/bug.php?id=42499)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+
+$row = $db->query('SELECT VERSION() as _version')->fetch(PDO::FETCH_ASSOC);
+$matches = array();
+if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches))
+       die(sprintf("skip Cannot determine MySQL Server version\n"));
+
+$version = $matches[0] * 10000 + $matches[1] * 100 + $matches[2];
+if ($version < 50000)
+       die(sprintf("skip Need MySQL Server 5.0.0+, found %d.%02d.%02d (%d)\n",
+               $matches[0], $matches[1], $matches[2], $version));
+?>
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::factory();
+
+function bug_pecl_7976($db) {
+
+       $db->exec('DROP PROCEDURE IF EXISTS p');
+       $db->exec('CREATE PROCEDURE p() BEGIN SELECT "1" AS _one; END;');
+
+       $stmt = $db->query('CALL p()');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+       $stmt->closeCursor();
+
+       $stmt = $db->query('CALL p()');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+       $stmt->closeCursor();
+
+}
+
+printf("Emulated...\n");
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+bug_pecl_7976($db);
+
+printf("Native...\n");
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+bug_pecl_7976($db);
+
+$db->exec('DROP PROCEDURE IF EXISTS p');
+print "done!";
+?>
+--XFAIL--
+Works with mysqlnd. It is not supported by libmysql. For libmysql is good enough to see no crash.
+--EXPECT--
+Emulated...
+array(1) {
+  [0]=>
+  array(1) {
+    ["_one"]=>
+    string(1) "1"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["_one"]=>
+    string(1) "1"
+  }
+}
+Native...
+array(1) {
+  [0]=>
+  array(1) {
+    ["_one"]=>
+    string(1) "1"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["_one"]=>
+    string(1) "1"
+  }
+}
+done!
\ No newline at end of file
index 855f89bc1e3f3f718f5f2681afe4eb24bf467734..2530442c0eaabd753d69f71ff9d14984b3cdba26 100644 (file)
@@ -1,19 +1,52 @@
 <?php
+/* Overrule global settings, if need be */
 
 if (false !== getenv('PDO_MYSQL_TEST_DSN')) {
-    # user set them from their shell
-    $config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN');  
-    $config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER');
-    $config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS');
-    if (false !== getenv('PDO_MYSQL_TEST_ATTR')) {
-        $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR');
-    }   
+       # user set them from their shell
+       $config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN');
+       $config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER');
+       $config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS');
+       if (false !== getenv('PDO_MYSQL_TEST_ATTR')) {
+               $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR');
+       }
 } else {
-    $config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test';
-    $config['ENV']['PDOTEST_USER'] = 'root';
-    $config['ENV']['PDOTEST_PASS'] = '';
+       $config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test';
+       $config['ENV']['PDOTEST_USER'] = 'root';
+       $config['ENV']['PDOTEST_PASS'] = '';
 }
 
 foreach ($config['ENV'] as $k => $v) {
        putenv("$k=$v");
 }
+
+/* MySQL specific settings */
+define('PDO_MYSQL_TEST_ENGINE', (false !== getenv('PDO_MYSQL_TEST_ENGINE')) ? getenv('PDO_MYSQL_TEST_ENGINE') : 'MyISAM');
+define('PDO_MYSQL_TEST_HOST', (false !== getenv('PDO_MYSQL_TEST_HOST')) ? getenv('PDO_MYSQL_TEST_HOST') : 'localhost');
+define('PDO_MYSQL_TEST_PORT', (false !== getenv('PDO_MYSQL_TEST_PORT')) ? getenv('PDO_MYSQL_TEST_PORT') : NULL);
+define('PDO_MYSQL_TEST_DB', (false !== getenv('PDO_MYSQL_TEST_DB')) ? getenv('PDO_MYSQL_TEST_DB') : 'test');
+define('PDO_MYSQL_TEST_SOCKET', (false !== getenv('PDO_MYSQL_TEST_SOCKET')) ? getenv('PDO_MYSQL_TEST_SOCKET') : NULL);
+define('PDO_MYSQL_TEST_DSN', (false !== getenv('PDO_MYSQL_TEST_DSN')) ? getenv('PDO_MYSQL_TEST_DSN') : $config['ENV']['PDOTEST_DSN']);
+define('PDO_MYSQL_TEST_USER', (false !== getenv('PDO_MYSQL_TEST_USER')) ? getenv('PDO_MYSQL_TEST_USER') : $config['ENV']['PDOTEST_USER']);
+define('PDO_MYSQL_TEST_PASS', (false !== getenv('PDO_MYSQL_TEST_PASS')) ? getenv('PDO_MYSQL_TEST_PASS') : $config['ENV']['PDOTEST_PASS']);
+define('PDO_MYSQL_TEST_CHARSET', (false !== getenv('PDO_MYSQL_TEST_CHARSET')) ? getenv('PDO_MYSQL_TEST_CHARSET') : NULL);
+
+if (!function_exists('sys_get_temp_dir')) {
+       function sys_get_temp_dir() {
+
+               if (!empty($_ENV['TMP']))
+                       return realpath( $_ENV['TMP'] );
+               if (!empty($_ENV['TMPDIR']))
+                       return realpath( $_ENV['TMPDIR'] );
+               if (!empty($_ENV['TEMP']))
+                       return realpath( $_ENV['TEMP'] );
+
+               $temp_file = tempnam(md5(uniqid(rand(), TRUE)), '');
+               if ($temp_file) {
+                       $temp_dir = realpath(dirname($temp_file));
+                       unlink($temp_file);
+                       return $temp_dir;
+               }
+               return FALSE;
+       }
+}
+?>
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/mysql_pdo_test.inc b/ext/pdo_mysql/tests/mysql_pdo_test.inc
new file mode 100644 (file)
index 0000000..bf5f49d
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'config.inc');
+require_once(dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc');
+
+class MySQLPDOTest extends PDOTest {
+
+       static function factory($classname = 'PDO', $drop_test_tables = false, $myattr = null) {
+
+               $dsn    = self::getDSN();
+               $user   = PDO_MYSQL_TEST_USER;
+               $pass   = PDO_MYSQL_TEST_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");
+               }
+
+               $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+               $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
+
+               return $db;
+       }
+
+       static function createTestTable($db, $engine = null) {
+               if (!$engine)
+                       $engine = PDO_MYSQL_TEST_ENGINE;
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec('CREATE TABLE test(id INT, label CHAR(1), PRIMARY KEY(id)) ENGINE=' . $engine);
+               $db->exec('INSERT INTO test(id, label) VALUES (1, "a"), (2, "b"), (3, "c"), (4, "d"), (5, "e"), (6, "f")');
+       }
+
+       static function getTableEngine() {
+               return PDO_MYSQL_TEST_ENGINE;
+       }
+
+
+       static function getDSN($new_options = null, $addition = '') {
+               if (!$new_options)
+                       return PDO_MYSQL_TEST_DSN . $addition;
+
+               $old_options = array();
+               $dsn = substr(PDO_MYSQL_TEST_DSN,
+                       strpos(PDO_MYSQL_TEST_DSN, ':') + 1,
+                       strlen(PDO_MYSQL_TEST_DSN));
+
+               // no real parser - any excotic setting can fool us
+               $parts = explode(';', $dsn);
+               foreach ($parts as $k => $v) {
+                       $tmp = explode('=', $v);
+                       if (count($tmp) == 2)
+                               $old_options[$tmp[0]] = $tmp[1];
+               }
+
+               $options = $old_options;
+               foreach ($new_options as $k => $v)
+                       $options[$k] = $v;
+
+               $dsn = 'mysql:';
+               foreach ($options as $k => $v)
+                       $dsn .= sprintf('%s=%s;', $k, $v);
+
+               if ($addition)
+                       $dsn .= $addition;
+               else
+                       $dsn = substr($dsn, 0, strlen($dsn) -1);
+
+               return $dsn;
+       }
+
+       static function getClientVersion($db) {
+               return self::extractVersion($db->getAttribute(PDO::ATTR_CLIENT_VERSION));
+       }
+
+       static function getServerVersion($db) {
+               return self::extractVersion($db->getAttribute(PDO::ATTR_SERVER_VERSION));
+       }
+
+       static function extractVersion($version_string) {
+               /*
+               TODO:
+               We're a bit in trouble: PDO_MYSQL returns version strings.
+               That's wrong according to the manual. According to the manual
+               integers should be returned. However, this code needs to work
+               with stinky PDO_MYSQL and hopefully better PDO_MYSQLND.
+               */
+
+               // already an int value?
+               if (is_int($version_string))
+                       return $version_string;
+
+               // string but int value?
+               $tmp = (int)$version_string;
+               if (((string)$tmp) === $version_string)
+                       return $tmp;
+
+               // stinky string which we need to parse
+               $parts = explode('.', $version_string);
+               if (count($parts) != 3)
+                       return -1;
+
+               $version = (int)$parts[0] * 10000;
+               $version+= (int)$parts[1] * 100;
+               $version+= (int)$parts[2];
+
+               return $version;
+       }
+
+       static function getTempDir() {
+
+               if (!function_exists('sys_get_temp_dir')) {
+
+                       if (!empty($_ENV['TMP']))
+                               return realpath( $_ENV['TMP'] );
+                       if (!empty($_ENV['TMPDIR']))
+                               return realpath( $_ENV['TMPDIR'] );
+                       if (!empty($_ENV['TEMP']))
+                               return realpath( $_ENV['TEMP'] );
+
+                       $temp_file = tempnam(md5(uniqid(rand(), TRUE)), '');
+                       if ($temp_file) {
+                               $temp_dir = realpath(dirname($temp_file));
+                               unlink($temp_file);
+                               return $temp_dir;
+                       }
+                       return FALSE;
+               } else {
+                       return sys_get_temp_dir();
+               }
+
+       }
+
+       static function detect_transactional_mysql_engine($db) {
+               foreach ($db->query("show variables like 'have%'") as $row) {
+                       if ($row[1] == 'YES' && ($row[0] == 'have_innodb' || $row[0] == 'have_bdb')) {
+                               return str_replace("have_", "", $row[0]);
+                       }
+               }
+               return false;
+       }
+
+       static function isPDOMySQLnd() {
+                       ob_start();
+                       phpinfo();
+                       $tmp = ob_get_contents();
+                       ob_end_clean();
+                       return (true == stristr($tmp, 'PDO Driver for MySQL, mysql native driver version'));
+       }
+
+}
+?>
diff --git a/ext/pdo_mysql/tests/pdo_mysql___construct.phpt b/ext/pdo_mysql/tests/pdo_mysql___construct.phpt
new file mode 100644 (file)
index 0000000..b6191d1
--- /dev/null
@@ -0,0 +1,303 @@
+--TEST--
+MySQL PDO->__construct() - Generic + DSN
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function tryandcatch($offset, $code) {
+
+               try {
+                       eval($code);
+                       assert(sprintf("[%03d] Should have failed\n", $offset) != '');
+               } catch (PDOException $e) {
+                       return sprintf("[%03d] %s, [%s] %s\n",
+                               $offset,
+                               $e->getMessage(),
+                               (isset($db) && is_object($db)) ? $db->errorCode() : 'n/a',
+                               (isset($db) && is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
+               }
+
+               return '';
+       }
+
+       try {
+
+               if (NULL !== ($db = @new PDO()))
+                       printf("[001] Too few parameters\n");
+
+               print tryandcatch(2, '$db = new PDO(chr(0));');
+               print tryandcatch(3, '$db = new PDO("a" . chr(0) . "b");');
+               print tryandcatch(4, '$db = new PDO("MYSQL");');
+               print tryandcatch(5, '$db = new PDO("mysql");');
+               print tryandcatch(6, '$db = new PDO("mysql ");');
+               print tryandcatch(7, '$db = new PDO("fantasyandfriends :");');
+
+               $dsn = PDO_MYSQL_TEST_DSN;
+               // MySQL Server might accept anonymous connections, don't
+               // print anything
+               tryandcatch(8, '$db = new PDO("' . $dsn . '");');
+
+               $user = 'dontcreatesuchauser';
+               $pass = 'withthispassword';
+               print tryandcatch(9, '$db = new PDO("' . $dsn . '", "' . $user . '", "' . $pass . '");');
+
+               // should fail
+               $dsn = 'mysql:';
+               print tryandcatch(10, '$db = new PDO("' . $dsn . '", "' . $user . '", "' . $pass . '");');
+
+               $dsn = PDO_MYSQL_TEST_DSN;
+               $user = PDO_MYSQL_TEST_USER;
+               $pass   = PDO_MYSQL_TEST_PASS;
+               // should work...
+               $db = new PDO($dsn, $user, $pass);
+
+               $dsn = 'mysql:invalid=foo';
+               print tryandcatch(11, '$db = new PDO("' . $dsn . '", "' . $user . '", "' . $pass . '");');
+
+               $dsn = 'mysql:' . str_repeat('howmuch=canpdoeat;', 1000);
+               print tryandcatch(12, '$db = new PDO("' . $dsn . '", "' . $user . '", "' . $pass . '");');
+
+               $dsn = 'mysql:' . str_repeat('abcdefghij', 1024 * 10) . '=somevalue';
+               print tryandcatch(13, '$db = new PDO("' . $dsn . '", "' . $user . '", "' . $pass . '");');
+
+               if (PDO_MYSQL_TEST_HOST) {
+                       $host = PDO_MYSQL_TEST_HOST;
+                       $invalid_host = $host . 'invalid';
+
+                       // last host specification should be the one used
+                       $dsn = MySQLPDOTest::getDSN(array('host' => $host), 'host=' . $invalid_host);
+                       try { $db = @new PDO($dsn, $user, $pass); assert(false); printf("%s\n", $dsn); } catch (PDOException $e) {
+                               $tmp = $e->getMessage();
+                               if (!stristr($tmp, 'HY000') && !stristr($tmp, '2005') && !stristr($tmp, '2002'))
+                                       printf("[014] Cannot find proper error codes: %s\n", $tmp);
+                       }
+
+                       $dsn = MySQLPDOTest::getDSN(array('host' => $invalid_host), 'host=' . $host);
+                       try { $db = @new PDO($dsn, $user, $pass); } catch (PDOException $e) {
+                               printf("[015] DSN=%s, %s\n", $dsn, $e->getMessage());
+                       }
+
+                       $invalid_host = '-' . chr(0);
+
+                       $dsn = MySQLPDOTest::getDSN(array('host' => $invalid_host));
+                       try { $db = @new PDO($dsn, $user, $pass); assert(false); printf("%s\n", $dsn); } catch (PDOException $e) {
+                               $tmp = $e->getMessage();
+                               if (!stristr($tmp, 'HY000') && !stristr($tmp, '2005') && !stristr($tmp, '2002'))
+                                       printf("[016] Cannot find proper error codes: %s\n", $tmp);
+                       }
+
+                       // parsing should not get confused by chr(0)
+                       $dsn = MySQLPDOTest::getDSN(array('host' => $invalid_host), 'host=' . $host);
+                       try { $db = @new PDO($dsn, $user, $pass); } catch (PDOException $e) {
+                               printf("[017] DSN=%s, %s\n", $dsn, $e->getMessage());
+                       }
+
+               }
+
+               // what about long values for a valid option ...
+               $dsn = MySQLPDOTest::getDSN(array('host' => str_repeat('0123456789', 1024 * 100)));
+               try { $db = @new PDO($dsn, $user, $pass); assert(false); printf("%s\n", $dsn); } catch (PDOException $e) {
+                       $tmp = $e->getMessage();
+                       if (!stristr($tmp, 'HY000') && !stristr($tmp, '2005') && !stristr($tmp, '2002'))
+                               printf("[018] Cannot find proper error codes: %s\n", $tmp);
+               }
+
+               if (PDO_MYSQL_TEST_PORT && (PDO_MYSQL_TEST_SOCKET == '')) {
+                       // Playing with the port makes only sense if no socket gets used
+
+                       $port = PDO_MYSQL_TEST_PORT;
+                       // let's hope we don't hit a MySQL running on that port...
+                       $invalid_port = $port * 2;
+
+                       $dsn = MySQLPDOTest::getDSN(array('port' => $port), 'port=' . $invalid_port);
+                       try { $db = @new PDO($dsn, $user, $pass); assert(false); printf("%s\n", $dsn); } catch (PDOException $e) {
+                               $tmp = $e->getMessage();
+                               if (!stristr($tmp, 'HY000') && !stristr($tmp, '2005'))
+                                       printf("[019] Cannot find proper error codes: %s\n", $tmp);
+                       }
+
+                       $dsn = MySQLPDOTest::getDSN(array('port' => $invalid_port), 'port=' . $port);
+                       try { $db = @new PDO($dsn, $user, $pass); } catch (PDOException $e) {
+                               printf("[020] DSN=%s, %s\n", $dsn, $e->getMessage());
+                       }
+
+                       $invalid_port = 'abc';
+                       $dsn = MySQLPDOTest::getDSN(array('port' => $port), 'port=' . $invalid_port);
+                       try {
+                               $db = @new PDO($dsn, $user, $pass);
+                               // atoi('abc') = 0, 0 -> fallback to default 3306 -> may or may not fail!
+                       } catch (PDOException $e) {
+                       }
+
+               }
+
+               if (PDO_MYSQL_TEST_DB) {
+                       $db = PDO_MYSQL_TEST_DB;
+                       $invalid_db = 'letshopeitdoesnotexist';
+
+                       $dsn = MySQLPDOTest::getDSN(array('dbname' => $db), 'dbname=' . $invalid_db);
+                       try { $db = @new PDO($dsn, $user, $pass); assert(false); printf("%s\n", $dsn); } catch (PDOException $e) {
+                               $tmp = $e->getMessage();
+                               if (!stristr($tmp, '42000') && !stristr($tmp, '1049'))
+                                       printf("[022] Cannot find proper error codes: %s\n", $tmp);
+                       }
+
+                       $dsn = MySQLPDOTest::getDSN(array('dbname' => $invalid_db), 'dbname=' . $db);
+                       try { $db = @new PDO($dsn, $user, $pass); } catch (PDOException $e) {
+                               printf("[023] DSN=%s, %s\n", $dsn, $e->getMessage());
+                       }
+
+               }
+
+               if (PDO_MYSQL_TEST_SOCKET && (stristr(PDO_MYSQL_TEST_DSN, PDO_MYSQL_TEST_SOCKET) !== false)) {
+                       $socket = PDO_MYSQL_TEST_SOCKET;
+                       $invalid_socket = '/lets/hope/it/does/not/exist';
+
+                       $dsn = MySQLPDOTest::getDSN(array('unix_socket' => $socket), 'unix_socket=' . $invalid_socket);
+                       try { $db = @new PDO($dsn, $user, $pass); assert(false); printf("%s\n", $dsn); } catch (PDOException $e) {
+                               $tmp = $e->getMessage();
+                               if (!stristr($tmp, 'HY000') && !stristr($tmp, '2002'))
+                                       printf("[024] Cannot find proper error codes: %s\n", $tmp);
+                       }
+
+                       $dsn = MySQLPDOTest::getDSN(array('unix_socket' => $invalid_socket), 'unix_socket=' . $socket);
+                       try { $db = @new PDO($dsn, $user, $pass); } catch (PDOException $e) {
+                               printf("[025] DSN=%s, %s\n", $dsn, $e->getMessage());
+                       }
+
+               }
+
+               $have_charset_support = false;
+               $dsn = MySQLPDOTest::getDSN();
+               try {
+                       $db = new PDO($dsn, $user, $pass);
+                       $stmt = $db->query('SELECT VERSION() as _version');
+                       $version = $stmt->fetch(PDO::FETCH_ASSOC);
+
+                       $tmp = explode('.', $version['_version']);
+                       if ((count($tmp) == 3) &&
+                                       (($tmp[0] >= 4 && $tmp[1] >= 1) || ($tmp[0] >= 5))) {
+                               // MySQL Server 4.1 - charset support available
+                               $have_charset_support = true;
+                       }
+
+               } catch (PDOException $e) {
+                       printf("[026] DSN=%s, %s\n", $dsn, $e->getMessage());
+               }
+
+               if (PDO_MYSQL_TEST_CHARSET) {
+                       $charset = PDO_MYSQL_TEST_CHARSET;
+                       $invalid_charset = 'invalid';
+
+                       if ($have_charset_support) {
+                               $dsn = MySQLPDOTest::getDSN();
+                               $db = new PDO($dsn, $user, $pass);
+                               $stmt = $db->query(sprintf('SHOW CHARACTER SET LIKE "%s"', $charset));
+                               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+                               $have_charset = (empty($tmp)) ? false : true;
+
+                               if ($have_charset) {
+                                       $dsn = MySQLPDOTest::getDSN(array('charset' => $charset), 'charset=' . $invalid_charset);
+                                       try {
+                                               $db = @new PDO($dsn, $user, $pass);
+                                               /* NOTE: MySQL does a fallback to the charset suggested during the handshake - no error - no bug! */
+                                               assert(false);
+                                               printf("%s\n", $dsn);
+                                       } catch (PDOException $e) {
+                                               $tmp = $e->getMessage();
+                                               /* TODO: add proper codes */
+                                               if (!stristr($tmp, 'sqlstatecode') || !stristr($tmp, 'mysqlinternalerrcode'))
+                                                       printf("[027] TODO - Cannot find proper error codes: %s\n", $tmp);
+                                       }
+
+                                       $dsn = MySQLPDOTest::getDSN(array('charset' => $invalid_charset), 'charset=' . $charset);
+                                       try {
+                                               $db = @new PDO($dsn, $user, $pass);
+                                               /* Strictly speaking we should test more: character_set_client, character_set_results, and character_set_connection */
+                                               $stmt = $db->query('SELECT @@character_set_connection AS _charset');
+                                               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+                                               if ($tmp['_charset'] != $charset)
+                                                       printf("[028] Character sets has not been set, @@character_set_connection reports '%s', expecting '%s'",
+                                                               $tmp['_charset'], $charset);
+                                       } catch (PDOException $e) {
+                                               printf("[029] DSN=%s, %s\n", $dsn, $e->getMessage());
+                                       }
+                               } else {
+                                       printf("[030] You're trying to run the tests with charset '%s' which seems not supported by the server!", $charset);
+                               }
+
+                       }
+
+               }
+
+               if ($have_charset_support) {
+                       // In case the PDO_MYSQL_TEST_CHARSET interferes with any defaults
+                       // we do another test to verify that the charset has been set.
+                       $dsn = MySQLPDOTest::getDSN();
+                       $db = new PDO($dsn, $user, $pass);
+                       $stmt = $db->query('SHOW CHARACTER SET LIKE "latin1"');
+                       $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+                       $have_latin1 =(empty($tmp)) ? false : true;
+                       $stmt = $db->query('SHOW CHARACTER SET LIKE "latin2"');
+                       $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+                       $have_latin2 =(empty($tmp)) ? false : true;
+
+                       if ($have_latin1 && $have_latin2) {
+                               // very likely we do have both of them...
+                               try {
+                                       $dsn = MySQLPDOTest::getDSN(array('charset' => 'latin1'));
+                                       $db = new PDO($dsn, $user, $pass);
+                                       $stmt = $db->query('SELECT @@character_set_connection AS _charset');
+                                       $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+                                       if ($tmp['_charset'] != 'latin1')
+                                               printf("[031] DSN = %s, Character sets has not been set, @@character_set_connection reports '%s', expecting '%s'",
+                                                       $dsn, $tmp['_charset'], 'latin1');
+
+                               } catch (PDOException $e) {
+                                       printf("[032] %s\n", $e->getMessage());
+                               }
+
+                               try {
+                                       $dsn = MySQLPDOTest::getDSN(array('charset' => 'latin2'));
+                                       $db = new PDO($dsn, $user, $pass);
+                                       $stmt = $db->query('SELECT @@character_set_connection AS _charset');
+                                       $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+                                       if ($tmp['_charset'] != 'latin2')
+                                               printf("[033] DSN = %s, character sets has not been set, @@character_set_connection reports '%s', expecting '%s'",
+                                                       $dsn, $tmp['_charset'], 'latin2');
+
+                               } catch (PDOException $e) {
+                                       printf("[034] %s\n", $e->getMessage());
+                               }
+
+                       }
+               }
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       (is_object($db)) ? $db->errorCode() : 'n/a',
+                       (is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
+       }
+
+       print "done!";
+--EXPECTF--
+[002] invalid data source name, [n/a] n/a
+[003] invalid data source name, [n/a] n/a
+[004] invalid data source name, [n/a] n/a
+[005] invalid data source name, [n/a] n/a
+[006] invalid data source name, [n/a] n/a
+[007] could not find driver, [n/a] n/a
+[009] SQLSTATE[28000] [1045] Access denied for user 'dont%s'@'%s' (using password: YES), [n/a] n/a
+[010] SQLSTATE[28000] [1045] Access denied for user 'dont%s'@'%s' (using password: YES), [n/a] n/a
+[017] DSN=%s, SQLSTATE[%s] [%d] %s
+
+Warning: assert(): Assertion failed in %s on line %d
+mysql:%s
+[033] DSN = mysql:%s, character sets has not been set, @@character_set_connection reports 'latin1', expecting 'latin2'done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql___construct_ini.phpt b/ext/pdo_mysql/tests/pdo_mysql___construct_ini.phpt
new file mode 100644 (file)
index 0000000..5bd824b
--- /dev/null
@@ -0,0 +1,56 @@
+--TEST--
+MySQL PDO->__construct() - URI
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+/* TODO - fix this limitation */
+if (getenv('PDO_MYSQL_TEST_DSN') !== "mysql:dbname=phptest;unix_socket=/tmp/mysql.sock")
+       die("skip Fix test to run in other environments as well!");
+
+?>
+--INI--
+pdo.dsn.mysql="mysql:dbname=phptest;socket=/tmp/mysql.sock"
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       $found = false;
+       $values = ini_get_all();
+       foreach ($values as $name => $dsn)
+               if ('pdo.dsn.mysql' == $name) {
+                       printf("pdo.dsn.mysql=%s\n", $dsn);
+                       $found = true;
+                       break;
+               }
+
+       if (!$found) {
+               $dsn = ini_get('pdo.dsn.mysql');
+               $found = ($dsn !== false);
+       }
+
+       if (!$found)
+               printf("pdo.dsn.mysql cannot be accessed through ini_get_all()/ini_get()\n");
+
+       if (MySQLPDOTest::getDSN() == $dsn) {
+               // we are lucky, we can run the test
+               try {
+
+                       $user = PDO_MYSQL_TEST_USER;
+                       $pass   = PDO_MYSQL_TEST_PASS;
+                       $db = new PDO('mysql', $user, $pass);
+
+               } catch (PDOException $e) {
+                       printf("[001] %s, [%s] %s\n",
+                               $e->getMessage(),
+                               (is_object($db)) ? $db->errorCode() : 'n/a',
+                               (is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
+               }
+
+       }
+
+       print "done!";
+--EXPECTF--
+pdo.dsn.mysql cannot be accessed through ini_get_all()/ini_get()
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql___construct_options.phpt b/ext/pdo_mysql/tests/pdo_mysql___construct_options.phpt
new file mode 100644 (file)
index 0000000..8bd714d
--- /dev/null
@@ -0,0 +1,170 @@
+--TEST--
+MySQL PDO->__construct(), options
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function set_option_and_check($offset, $option, $value, $option_desc) {
+
+               $dsn = MySQLPDOTest::getDSN();
+               $user = PDO_MYSQL_TEST_USER;
+               $pass = PDO_MYSQL_TEST_PASS;
+
+               try {
+                       $db = new PDO($dsn, $user, $pass, array($option => $value));
+                       if (!is_object($db) || ($value !== ($tmp = @$db->getAttribute($option))))
+                               printf("[%03d] Execting '%s'/%s got '%s'/%s' for options '%s'\n",
+                                       $offset,
+                                       $value, gettype($value),
+                                       $tmp, gettype($tmp),
+                                       $option_desc);
+               } catch (PDOException $e) {
+                       printf("[%03d] %s\n", $offset, $e->getMessage());
+               }
+
+       }
+
+       try {
+
+               $dsn = MySQLPDOTest::getDSN();
+               $user = PDO_MYSQL_TEST_USER;
+               $pass = PDO_MYSQL_TEST_PASS;
+
+               $valid_options = array(
+                       /* pdo_dbh.c */
+                       PDO::ATTR_PERSISTENT                                                                    => 'PDO::ATTR_PERSISTENT',
+                       PDO::ATTR_AUTOCOMMIT                                                                    => 'PDO::ATTR_AUTOCOMMIT',
+                       /* mysql_driver.c */
+                       /* TODO Possible bug PDO::ATTR_TIMEOUT != MYSQLI_OPT_CONNECT_TIMEOUT*/
+                       PDO::ATTR_TIMEOUT                                                                               => 'PDO::ATTR_TIMEOUT',
+                       PDO::ATTR_EMULATE_PREPARES                                              => 'PDO::ATTR_EMULATE_PREPARES',
+
+                       PDO::MYSQL_ATTR_USE_BUFFERED_QUERY              => 'PDO::MYSQL_ATTR_USE_BUFFERED_QUERY',
+                       PDO::MYSQL_ATTR_LOCAL_INFILE                                    => 'PDO::MYSQL_ATTR_LOCAL_INFILE',
+                       PDO::MYSQL_ATTR_DIRECT_QUERY                                    => 'PDO::MYSQL_ATTR_DIRECT_QUERY',
+               );
+
+               $defaults = array(
+                       PDO::ATTR_PERSISTENT                                                                    => false,
+                       PDO::ATTR_AUTOCOMMIT                                                                    => 1,
+                       /* TODO - why is this a valid option if getAttribute() does not support it?! */
+                       PDO::ATTR_TIMEOUT                                                                               => false,
+                       PDO::ATTR_EMULATE_PREPARES                                              => 1,
+                       PDO::MYSQL_ATTR_USE_BUFFERED_QUERY              => 1,
+                       /* TODO getAttribute() does not handle it */
+                       PDO::MYSQL_ATTR_LOCAL_INFILE                                    => false,
+                       /* TODO getAttribute() does not handle it */
+                       PDO::MYSQL_ATTR_DIRECT_QUERY                                    => 1,
+               );
+
+               if (NULL !== ($db = @new PDO($dsn, $user, $pass, 'wrong type')))
+                       printf("[001] Expecting NULL got %s/%s\n", gettype($db), $db);
+
+               if (!is_object($db = new PDO($dsn, $user, $pass, array())))
+                       printf("[002] Expecting object got %s/%s¸\n", gettype($db), $db);
+
+               do {
+                       $invalid = mt_rand(-1000, 1000);
+               } while (isset($valid_options[$invalid]));
+               if (is_object($db = new PDO($dsn, $user, $pass, array($invalid => true))))
+                       printf("[003] [TODO][CHANGEREQUEST] Please, lets not ignore invalid options and bail out!\n");
+
+               $db = new PDO($dsn, $user, $pass);
+               foreach ($valid_options as $option => $name) {
+                       /* TODO getAttribute() is pretty poor in supporting the options, suppress errors */
+                       $tmp = @$db->getAttribute($option);
+                       if ($tmp !== $defaults[$option])
+                               printf("[003a] Expecting default value for '%s' of '%s'/%s, getAttribute() reports setting '%s'/%s\n",
+                                       $name, $defaults[$option], gettype($defaults[$option]),
+                                       $tmp, gettype($tmp));
+               }
+
+               $db = new PDO($dsn, $user, $pass, array(PDO::ATTR_AUTOCOMMIT => true));
+               if (!is_object($db) || !$db->getAttribute(PDO::ATTR_AUTOCOMMIT))
+                       printf("[004] Autocommit should be on\n");
+
+               $db = new PDO($dsn, $user, $pass, array(PDO::ATTR_AUTOCOMMIT => false));
+               if (!is_object($db) || $db->getAttribute(PDO::ATTR_AUTOCOMMIT))
+                       printf("[005] Autocommit should be off\n");
+
+               /* TODO: no way to check ATTR_TIMEOUT settings */
+               if (!is_object($db = new PDO($dsn, $user, $pass, array(PDO::ATTR_TIMEOUT => 10))))
+                       printf("[006] ATTR_TIMEOUT should be accepted\n");
+
+               if (!is_object($db = new PDO($dsn, $user, $pass, array(PDO::ATTR_TIMEOUT => PHP_INT_MAX))))
+                       printf("[007] ATTR_TIMEOUT should be accepted\n");
+
+               if (!is_object($db = new PDO($dsn, $user, $pass, array(PDO::ATTR_TIMEOUT => -PHP_INT_MAX))))
+                       printf("[008] ATTR_TIMEOUT should be accepted\n");
+
+               /* TODO: Its ugly that PDO::ATTR_EMULATE_PREPARES == PDO::MYSQL_ATTR_DIRECT_QUERY */
+               $db = new PDO($dsn, $user, $pass, array(PDO::ATTR_EMULATE_PREPARES => true));
+               if (!is_object($db))
+                       printf("[009] ATTR_EMULATE_PREPARES should be accepted and on\n");
+               if (!$db->getAttribute(PDO::ATTR_EMULATE_PREPARES))
+                       printf("[010] [TODO][CHANGEREQUEST] ATTR_EMULATE_PREPARES should be on\n");
+               if (!$db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[011] As PDO::MYSQL_ATTR_DIRECT_QUERY == PDO::ATTR_EMULATE_PREPARES
+                               and PDO::ATTR_EMULATE_PREPARES overrules the other, PDO::MYSQL_ATTR_DIRECT_QUERY should be on\n");
+
+               $db = new PDO($dsn, $user, $pass, array(PDO::ATTR_EMULATE_PREPARES => false));
+               if (!is_object($db))
+                       printf("[012] ATTR_EMULATE_PREPARES should be accepted and on\n");
+               if ($db->getAttribute(PDO::ATTR_EMULATE_PREPARES))
+                       printf("[013] [TODO][CHANGEREQUEST] ATTR_EMULATE_PREPARES should be off\n");
+               if ($db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[014] As PDO::MYSQL_ATTR_DIRECT_QUERY == PDO::ATTR_EMULATE_PREPARES
+                               and PDO::ATTR_EMULATE_PREPARES overrules the other, PDO::MYSQL_ATTR_DIRECT_QUERY should be off\n");
+
+               // PDO::ATTR_EMULATE_PREPARES overrules PDO::MYSQL_ATTR_DIRECT_QUERY
+               // TODO: is it clever that a generic setting overrules a specific setting?
+               $db = new PDO($dsn, $user, $pass, array(PDO::ATTR_EMULATE_PREPARES => true, PDO::MYSQL_ATTR_DIRECT_QUERY => false));
+               if (!$db->getAttribute(PDO::ATTR_EMULATE_PREPARES))
+                       printf("[015] PDO::ATTR_EMULATE_PREPARES should be on\n");
+               if (!$db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[016] PDO::MYSQL_ATTR_DIRECT_QUERY should be on\n");
+
+               $db = new PDO($dsn, $user, $pass, array(PDO::ATTR_EMULATE_PREPARES => false, PDO::MYSQL_ATTR_DIRECT_QUERY => true));
+               if ($db->getAttribute(PDO::ATTR_EMULATE_PREPARES))
+                       printf("[017] PDO::ATTR_EMULATE_PREPARES should be off\n");
+               if ($db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[018] PDO::MYSQL_ATTR_DIRECT_QUERY should be off\n");
+
+               set_option_and_check(19, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1, 'PDO::MYSQL_ATTR_USE_BUFFERED_QUERY');
+               set_option_and_check(20, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 0, 'PDO::MYSQL_ATTR_USE_BUFFERED_QUERY');
+
+               set_option_and_check(21, PDO::MYSQL_ATTR_LOCAL_INFILE, true, 'PDO::MYSQL_ATTR_LOCAL_INFILE');
+               set_option_and_check(22, PDO::MYSQL_ATTR_LOCAL_INFILE, false, 'PDO::MYSQL_ATTR_LOCAL_INFILE');
+
+               set_option_and_check(33, PDO::MYSQL_ATTR_DIRECT_QUERY, 1, 'PDO::MYSQL_ATTR_DIRECT_QUERY');
+               set_option_and_check(34, PDO::MYSQL_ATTR_DIRECT_QUERY, 0, 'PDO::MYSQL_ATTR_DIRECT_QUERY');
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       (is_object($db)) ? $db->errorCode() : 'n/a',
+                       (is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
+       }
+
+       print "done!";
+--EXPECTF--
+[003] [TODO][CHANGEREQUEST] Please, lets not ignore invalid options and bail out!
+[003a] Expecting default value for 'PDO::ATTR_EMULATE_PREPARES' of '1'/integer, getAttribute() reports setting ''/boolean
+
+Warning: PDO::getAttribute(): SQLSTATE[IM001]: Driver does not support this function: driver does not support that attribute in %s on line %d
+[010] [TODO][CHANGEREQUEST] ATTR_EMULATE_PREPARES should be on
+
+Warning: PDO::getAttribute(): SQLSTATE[IM001]: Driver does not support this function: driver does not support that attribute in %s on line %d
+
+Warning: PDO::getAttribute(): SQLSTATE[IM001]: Driver does not support this function: driver does not support that attribute in %s on line %d
+[015] PDO::ATTR_EMULATE_PREPARES should be on
+[016] PDO::MYSQL_ATTR_DIRECT_QUERY should be on
+
+Warning: PDO::getAttribute(): SQLSTATE[IM001]: Driver does not support this function: driver does not support that attribute in %s on line %d
+[018] PDO::MYSQL_ATTR_DIRECT_QUERY should be off
+[021] Execting '1'/boolean got ''/boolean' for options 'PDO::MYSQL_ATTR_LOCAL_INFILE'
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql___construct_options_libmysql.phpt b/ext/pdo_mysql/tests/pdo_mysql___construct_options_libmysql.phpt
new file mode 100644 (file)
index 0000000..8a8e347
--- /dev/null
@@ -0,0 +1,88 @@
+--TEST--
+MySQL PDO->__construct(), libmysql only options
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+if (MySQLPDOTest::isPDOMySQLnd())
+       die("skip libmysql only options")
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function set_option_and_check($offset, $option, $value, $option_desc, $ignore_diff = false) {
+
+               $dsn = MySQLPDOTest::getDSN();
+               $user = PDO_MYSQL_TEST_USER;
+               $pass = PDO_MYSQL_TEST_PASS;
+
+               try {
+                       $db = new PDO($dsn, $user, $pass, array($option => $value));
+                       if (!is_object($db) || (!$ignore_diff && ($value !== ($tmp = @$db->getAttribute($option)))))
+                               printf("[%03d] Execting '%s'/%s got '%s'/%s' for options '%s'\n",
+                                       $offset,
+                                       $value, gettype($value),
+                                       $tmp, gettype($tmp),
+                                       $option_desc);
+               } catch (PDOException $e) {
+                       printf("[%03d] %s\n", $offset, $e->getMessage());
+               }
+
+       }
+
+       try {
+
+               $dsn = MySQLPDOTest::getDSN();
+               $user = PDO_MYSQL_TEST_USER;
+               $pass = PDO_MYSQL_TEST_PASS;
+
+               $valid_options = array();
+               $valid_options[PDO::MYSQL_ATTR_MAX_BUFFER_SIZE] = 'PDO::MYSQL_ATTR_MAX_BUFFER_SIZE';
+               $valid_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'PDO::MYSQL_ATTR_INIT_COMMAND';
+               $valid_options[PDO::MYSQL_ATTR_READ_DEFAULT_FILE] = 'PDO::MYSQL_ATTR_READ_DEFAULT_FILE';
+               $valid_options[PDO::MYSQL_ATTR_READ_DEFAULT_GROUP] = 'PDO::MYSQL_ATTR_READ_DEFAULT_GROUP';
+
+               $defaults[PDO::MYSQL_ATTR_MAX_BUFFER_SIZE] = 1048576;
+               /* TODO getAttribute() does not handle it */
+               $defaults[PDO::MYSQL_ATTR_INIT_COMMAND] = '';
+               $defaults[PDO::MYSQL_ATTR_READ_DEFAULT_FILE] = false;
+               $defaults[PDO::MYSQL_ATTR_READ_DEFAULT_GROUP] = false;
+
+               $db = new PDO($dsn, $user, $pass);
+               foreach ($valid_options as $option => $name) {
+                       /* TODO getAttribute() is pretty poor in supporting the options, suppress errors */
+                       $tmp = @$db->getAttribute($option);
+                       if ($tmp !== $defaults[$option])
+                               printf("[001] Expecting default value for '%s' of '%s'/%s, getAttribute() reports setting '%s'/%s\n",
+                                       $name, $defaults[$option], gettype($defaults[$option]),
+                                       $tmp, gettype($tmp));
+               }
+
+               set_option_and_check(23, PDO::MYSQL_ATTR_INIT_COMMAND, 'SET @a=1', 'PDO::MYSQL_ATTR_INIT_COMMAND');
+               set_option_and_check(24, PDO::MYSQL_ATTR_INIT_COMMAND, '', 'PDO::MYSQL_ATTR_INIT_COMMAND');
+               set_option_and_check(25, PDO::MYSQL_ATTR_INIT_COMMAND, 'INSERT INTO nonexistent(invalid) VALUES (1)', 'PDO::MYSQL_ATTR_INIT_COMMAND');
+
+               set_option_and_check(26, PDO::MYSQL_ATTR_READ_DEFAULT_FILE, true, 'PDO::MYSQL_ATTR_READ_DEFAULT_FILE');
+               set_option_and_check(27, PDO::MYSQL_ATTR_READ_DEFAULT_FILE, false, 'PDO::MYSQL_ATTR_READ_DEFAULT_FILE');
+
+               set_option_and_check(30, PDO::MYSQL_ATTR_MAX_BUFFER_SIZE, -1, 'PDO::MYSQL_ATTR_MAX_BUFFER_SIZE', true);
+               set_option_and_check(31, PDO::MYSQL_ATTR_MAX_BUFFER_SIZE, PHP_INT_MAX, 'PDO::MYSQL_ATTR_MAX_BUFFER_SIZE');
+               set_option_and_check(32, PDO::MYSQL_ATTR_MAX_BUFFER_SIZE, 1, 'PDO::MYSQL_ATTR_MAX_BUFFER_SIZE');
+
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       (is_object($db)) ? $db->errorCode() : 'n/a',
+                       (is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
+       }
+
+       print "done!";
+--EXPECTF--
+[001] Expecting default value for 'PDO::MYSQL_ATTR_INIT_COMMAND' of ''/string, getAttribute() reports setting ''/boolean
+[023] Execting 'SET @a=1'/string got ''/boolean' for options 'PDO::MYSQL_ATTR_INIT_COMMAND'
+[024] SQLSTATE[42000] [1065] Query was empty
+[025] SQLSTATE[42S02] [1146] Table '%snonexistent' doesn't exist
+[026] Execting '1'/boolean got ''/boolean' for options 'PDO::MYSQL_ATTR_READ_DEFAULT_FILE'
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql___construct_uri.phpt b/ext/pdo_mysql/tests/pdo_mysql___construct_uri.phpt
new file mode 100644 (file)
index 0000000..87e1a1e
--- /dev/null
@@ -0,0 +1,75 @@
+--TEST--
+MySQL PDO->__construct() - URI
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       try {
+
+               if ($tmp = MySQLPDOTest::getTempDir()) {
+
+                       $file = $tmp . DIRECTORY_SEPARATOR . 'pdomuri.tst';
+                       $dsn = MySQLPDOTest::getDSN();
+                       $user = PDO_MYSQL_TEST_USER;
+                       $pass = PDO_MYSQL_TEST_PASS;
+                       $uri = sprintf('uri:file:%s', $file);
+
+                       if ($fp = @fopen($file, 'w')) {
+                               // ok, great we can create a file with a DSN in it
+                               fwrite($fp, $dsn);
+                               fclose($fp);
+                               clearstatcache();
+                               assert(file_exists($file));
+                               try {
+                                       $db = new PDO($uri, $user, $pass);
+                               } catch (PDOException $e) {
+                                       printf("[002] URI=%s, DSN=%s, File=%s (%d bytes, '%s'), %s\n",
+                                               $uri, $dsn,
+                                               $file, filesize($file), file_get_contents($file),
+                                               $e->getMessage());
+                               }
+                               unlink($file);
+                       }
+
+                       if ($fp = @fopen($file, 'w')) {
+                               fwrite($fp, sprintf('mysql:dbname=letshopeinvalid;%s%s',
+                                       chr(0), $dsn));
+                               fclose($fp);
+                               clearstatcache();
+                               assert(file_exists($file));
+                               try {
+                                       $db = new PDO($uri, $user, $pass);
+                               } catch (PDOException $e) {
+                                       printf("[003] URI=%s, DSN=%s, File=%s (%d bytes, '%s'), chr(0) test, %s\n",
+                                       $uri, $dsn,
+                                       $file, filesize($file), file_get_contents($file),
+                                       $e->getMessage());
+                               }
+                               unlink($file);
+                       }
+
+               }
+
+               /* TODO: safe mode */
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       (is_object($db)) ? $db->errorCode() : 'n/a',
+                       (is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
+       }
+
+       print "done!";
+--EXPECTF--
+Warning: PDO::__construct(%s
+[002] URI=uri:file:%spdomuri.tst, DSN=mysql%sdbname=%s, File=%spdomuri.tst (%d bytes, 'mysql%sdbname=%s'), invalid data source URI
+
+Warning: PDO::__construct(%s
+[003] URI=uri:file:%spdomuri.tst, DSN=mysql%sdbname=%s, File=%spdomuri.tst (%d bytes, 'mysql%sdbname=letshopeinvalid%s'), chr(0) test, invalid data source URI
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_autocommit.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_autocommit.phpt
new file mode 100644 (file)
index 0000000..b489528
--- /dev/null
@@ -0,0 +1,93 @@
+--TEST--
+PDO::ATTR_AUTOCOMMIT
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       // autocommit should be on by default
+       if (1 !== ($tmp = $db->getAttribute(PDO::ATTR_AUTOCOMMIT)))
+               printf("[001] Expecting int/1 got %s\n", var_export($tmp, true));
+
+       // lets see if the server agrees to that
+       $row = $db->query('SELECT @@autocommit AS _autocommit')->fetch(PDO::FETCH_ASSOC);
+       if (!$row['_autocommit'])
+               printf("[002] Server autocommit mode should be on, got '%s'\n", var_export($row['_autocommit']));
+
+       // on -> off
+       if (!$db->setAttribute(PDO::ATTR_AUTOCOMMIT, 0))
+               printf("[003] Cannot turn off autocommit\n");
+
+       $row = $db->query('SELECT @@autocommit AS _autocommit')->fetch(PDO::FETCH_ASSOC);
+       if ($row['_autocommit'])
+               printf("[004] Server autocommit mode should be off, got '%s'\n", var_export($row['_autocommit']));
+
+       // PDO thinks autocommit is off, but its manually turned on...
+       if (!$db->query('SET autocommit = 1'))
+               printf("[005] Cannot turn on server autocommit mode, %s\n", var_export($db->errorInfo(), true));
+
+       if (0 !== ($tmp = $db->getAttribute(PDO::ATTR_AUTOCOMMIT)))
+               printf("[006] Expecting int/0 got %s\n", var_export($tmp, true));
+
+       // off -> on
+       if (!$db->query('SET autocommit = 0'))
+               printf("[007] Cannot turn off server autocommit mode, %s\n", var_export($db->errorInfo(), true));
+
+       if (!$db->setAttribute(PDO::ATTR_AUTOCOMMIT, 1))
+               printf("[008] Cannot turn on autocommit\n");
+
+       $row = $db->query('SELECT @@autocommit AS _autocommit')->fetch(PDO::FETCH_ASSOC);
+       if (!$row['_autocommit'])
+               printf("[009] Server autocommit mode should be on, got '%s'\n", var_export($row['_autocommit']));
+
+       if (1 !== ($tmp = $db->getAttribute(PDO::ATTR_AUTOCOMMIT)))
+               printf("[010] Expecting int/1 got %s\n", var_export($tmp, true));
+
+       if (MySQLPDOTest::detect_transactional_mysql_engine($db)) {
+               // nice, we have a transactional engine to play with
+
+               MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+               $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+               $num = $row['_num'];
+
+               $db->query('INSERT INTO test(id, label) VALUES (100, "z")');
+               $num++;
+               $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+               if ($row['_num'] != $num)
+                       printf("[011] Insert has failed, test will fail\n");
+
+               // autocommit is on, no rollback possible
+               $db->query('ROLLBACK');
+               $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+               if ($row['_num'] != $num)
+                       printf("[012] ROLLBACK should not have undone anything\n");
+
+               if (!$db->setAttribute(PDO::ATTR_AUTOCOMMIT, 0))
+                       printf("[013] Cannot turn off autocommit\n");
+
+               $db->query('DELETE FROM test WHERE id = 100');
+               $db->query('ROLLBACK');
+               $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+               if ($row['_num'] != $num)
+                       printf("[014] ROLLBACK should have undone the DELETE\n");
+
+               $db->query('DELETE FROM test WHERE id = 100');
+               $db->query('COMMIT');
+               $num--;
+               $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+               if ($row['_num'] != $num)
+                       printf("[015] DELETE should have been committed\n");
+
+       }
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS test'));
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_case.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_case.phpt
new file mode 100644 (file)
index 0000000..c7a2037
--- /dev/null
@@ -0,0 +1,219 @@
+--TEST--
+PDO::ATTR_CASE
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       $default =  $db->getAttribute(PDO::ATTR_CASE);
+       $known = array(
+               PDO::CASE_LOWER => 'PDO::CASE_LOWER',
+               PDO::CASE_UPPER => 'PDO::CASE_UPPER',
+               PDO::CASE_NATURAL => 'PDO::CASE_NATURAL'
+       );
+       if (!isset($known[$default]))
+               printf("[001] getAttribute(PDO::ATTR_CASE) returns unknown value '%s'\n",
+                       var_export($default, true));
+       else
+               var_dump($known[$default]);
+
+       // lets see what the default is...
+       if (!is_object($stmt = $db->query('SELECT id, id AS "ID_UPPER", label FROM test ORDER BY id ASC LIMIT 2')))
+               printf("[002] %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       var_dump($stmt->fetchAll(PDO::FETCH_BOTH));
+
+       if (true !== $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER))
+               printf("[003] Cannot set PDO::ATTR_CASE = PDO::CASE_LOWER, %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       if (($tmp = $db->getAttribute(PDO::ATTR_CASE)) !== PDO::CASE_LOWER)
+               printf("[004] getAttribute(PDO::ATTR_CASE) returns wrong value '%s'\n",
+                       var_export($tmp, true));
+
+       if (true === $db->exec('ALTER TABLE test ADD MiXeD CHAR(1)'))
+               printf("[005] Cannot add column %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       if (false === $db->exec('ALTER TABLE test ADD MYUPPER CHAR(1)'))
+               printf("[006] Cannot add column %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       if (!is_object($stmt = $db->query('SELECT id, id AS "ID_UPPER", label, MiXeD, MYUPPER FROM test ORDER BY id ASC LIMIT 2')))
+               printf("[007] %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       var_dump($stmt->fetchAll(PDO::FETCH_BOTH));
+
+       if (true !== $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER))
+               printf("[008] Cannot set PDO::ATTR_CASE = PDO::CASE_UPPER %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       if (($tmp = $db->getAttribute(PDO::ATTR_CASE)) !== PDO::CASE_UPPER)
+               printf("[009] getAttribute(PDO::ATTR_CASE) returns wrong value '%s'\n",
+                       var_export($tmp, true));
+
+       if (!is_object($stmt = $db->query('SELECT id, label, MiXeD, MYUPPER, MYUPPER AS "lower" FROM test ORDER BY id ASC LIMIT 1')))
+               printf("[010] %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       var_dump($stmt->fetchAll(PDO::FETCH_BOTH));
+
+       if (true !== $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL))
+               printf("[011] Cannot set PDO::ATTR_CASE = PDO::CASE_NATURAL %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       if (($tmp = $db->getAttribute(PDO::ATTR_CASE)) !== PDO::CASE_NATURAL)
+               printf("[012] getAttribute(PDO::ATTR_CASE) returns wrong value '%s'\n",
+                       var_export($tmp, true));
+
+       if (!is_object($stmt = $db->query('SELECT id, label, MiXeD, MYUPPER, id AS "ID" FROM test ORDER BY id ASC LIMIT 1')))
+               printf("[013] %s - %s\n",
+                       var_export($db->errorInfo(), true), var_export($db->errorCode(), true));
+
+       var_dump($stmt->fetchAll(PDO::FETCH_BOTH));
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS test'));
+       print "done!";
+--EXPECTF--
+string(15) "PDO::CASE_LOWER"
+array(2) {
+  [0]=>
+  array(6) {
+    ["id"]=>
+    string(1) "1"
+    [0]=>
+    string(1) "1"
+    ["id_upper"]=>
+    string(1) "1"
+    [1]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+    [2]=>
+    string(1) "a"
+  }
+  [1]=>
+  array(6) {
+    ["id"]=>
+    string(1) "2"
+    [0]=>
+    string(1) "2"
+    ["id_upper"]=>
+    string(1) "2"
+    [1]=>
+    string(1) "2"
+    ["label"]=>
+    string(1) "b"
+    [2]=>
+    string(1) "b"
+  }
+}
+array(2) {
+  [0]=>
+  array(10) {
+    ["id"]=>
+    string(1) "1"
+    [0]=>
+    string(1) "1"
+    ["id_upper"]=>
+    string(1) "1"
+    [1]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+    [2]=>
+    string(1) "a"
+    ["mixed"]=>
+    NULL
+    [3]=>
+    NULL
+    ["myupper"]=>
+    NULL
+    [4]=>
+    NULL
+  }
+  [1]=>
+  array(10) {
+    ["id"]=>
+    string(1) "2"
+    [0]=>
+    string(1) "2"
+    ["id_upper"]=>
+    string(1) "2"
+    [1]=>
+    string(1) "2"
+    ["label"]=>
+    string(1) "b"
+    [2]=>
+    string(1) "b"
+    ["mixed"]=>
+    NULL
+    [3]=>
+    NULL
+    ["myupper"]=>
+    NULL
+    [4]=>
+    NULL
+  }
+}
+array(1) {
+  [0]=>
+  array(10) {
+    ["ID"]=>
+    string(1) "1"
+    [0]=>
+    string(1) "1"
+    ["LABEL"]=>
+    string(1) "a"
+    [1]=>
+    string(1) "a"
+    ["MIXED"]=>
+    NULL
+    [2]=>
+    NULL
+    ["MYUPPER"]=>
+    NULL
+    [3]=>
+    NULL
+    ["LOWER"]=>
+    NULL
+    [4]=>
+    NULL
+  }
+}
+array(1) {
+  [0]=>
+  array(10) {
+    ["id"]=>
+    string(1) "1"
+    [0]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+    [1]=>
+    string(1) "a"
+    ["MiXeD"]=>
+    NULL
+    [2]=>
+    NULL
+    ["MYUPPER"]=>
+    NULL
+    [3]=>
+    NULL
+    ["ID"]=>
+    string(1) "1"
+    [4]=>
+    string(1) "1"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_client_version.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_client_version.phpt
new file mode 100644 (file)
index 0000000..e3f21fb
--- /dev/null
@@ -0,0 +1,36 @@
+--TEST--
+PDO::ATTR_CLIENT_VERSION
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       assert(('' == $db->errorCode()) || ('00000' == $db->errorCode()));
+
+       $version = $db->getAttribute(PDO::ATTR_CLIENT_VERSION);
+
+       // No more constraints - mysqlnd and libmysql return different strings at least
+       // with mysqli. Return type check is already performed in the generic test.
+       // According to the manual we should get an int but as of today we do get a string...
+       if ('' == $version)
+               printf("[001] Client version must not be empty\n");
+
+
+       // Read-only
+       if (false !== $db->setAttribute(PDO::ATTR_CLIENT_VERSION, '1.0'))
+               printf("[002] Wonderful, I can change the client version!\n");
+
+       $new_version = $db->getAttribute(PDO::ATTR_CLIENT_VERSION);
+       if ($new_version !== $version)
+               printf("[003] Did we change it from '%s' to '%s'?\n", $version, $new_version);
+
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_connection_status.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_connection_status.phpt
new file mode 100644 (file)
index 0000000..038df3c
--- /dev/null
@@ -0,0 +1,36 @@
+--TEST--
+PDO::ATTR_CONNECTION_STATUS
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       $status = $db->getAttribute(PDO::ATTR_CONNECTION_STATUS);
+       if (ini_get('unicode.semantics')) {
+               if (!is_unicode($status))
+                       printf("[001] Expecting unicode, got '%s'\n", var_export($status, true));
+       } else {
+               if (!is_string($status))
+                       printf("[002] Expecting string, got '%s'\n", var_export($status, true));
+       }
+
+       if ('' == $status)
+               printf("[003] Connection status string must not be empty\n");
+
+       if (false !== $db->setAttribute(PDO::ATTR_CONNECTION_STATUS, 'my own connection status'))
+               printf("[004] Changing read only attribute\n");
+
+       $status2 = $db->getAttribute(PDO::ATTR_CONNECTION_STATUS);
+       if ($status !== $status2)
+               printf("[005] Connection status should not have changed\n");
+
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_driver_name.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_driver_name.phpt
new file mode 100644 (file)
index 0000000..0c156e4
--- /dev/null
@@ -0,0 +1,30 @@
+--TEST--
+PDO::ATTR_DRIVER_NAME
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       assert(('' == $db->errorCode()) || ('00000' == $db->errorCode()));
+
+       $name = $db->getAttribute(PDO::ATTR_DRIVER_NAME);
+       var_dump($name);
+
+       if (false !== $db->setAttribute(PDO::ATTR_DRIVER_NAME, 'mydriver'))
+               printf("[001] Wonderful, I can create new PDO drivers!\n");
+
+       $new_name = $db->getAttribute(PDO::ATTR_DRIVER_NAME);
+       if ($name != $new_name)
+               printf("[002] Did we change it from '%s' to '%s'?\n", $name, $new_name);
+
+       print "done!";
+--EXPECTF--
+string(5) "mysql"
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_errmode.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_errmode.phpt
new file mode 100644 (file)
index 0000000..b037089
--- /dev/null
@@ -0,0 +1,166 @@
+--TEST--
+PDO::ATTR_ERRMODE
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--INI--
+error_reporting=E_ALL
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       $valid = array(PDO::ERRMODE_SILENT, PDO::ERRMODE_WARNING, PDO::ERRMODE_EXCEPTION);
+       do {
+               $invalid = mt_rand(-1000, 1000);
+       } while (in_array($invalid, $valid));
+
+
+       $tmp = array();
+       if (false != @$db->setAttribute(PDO::ATTR_ERRMODE, $tmp))
+               printf("[001] Maybe PDO could indicate that this is not a proper way of setting the ERRMODE...\n");
+
+       $tmp = new stdClass();
+       $ret = @$db->setAttribute(PDO::ATTR_ERRMODE, $tmp);
+       if (false != $ret)
+               printf("[002] Maybe PDO could indicate that this is not a proper way of setting the ERRMODE...%s\n",
+                       var_export($ret, true));
+
+       $ret = @$db->setAttribute(PDO::ATTR_ERRMODE, 'pdo');
+       if (false != $ret)
+               printf("[003] Maybe PDO could indicate that this is not a proper way of setting the ERRMODE...%s\n",
+                       var_export($ret, true));
+
+       if (false != @$db->setAttribute(PDO::ATTR_ERRMODE, $invalid))
+               printf("[004] Invalid ERRMODE should be rejected\n");
+
+       $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
+       // no message for any PDO call but...
+       $db->query('THIS IS NOT VALID SQL');
+       // ... still messages for everything else
+       $code = $db->errorCode();
+       $info = $db->errorInfo();
+
+       if ($code != '42000')
+               printf("[005] Expecting SQL code 42000 got '%s'\n", $code);
+       if ($code !== $info[0])
+               printf("[006] Code and info should be identical, got errorCode() = %s, errorInfo()[0] = %s\n",
+                       $code, $info[0]);
+       if ('' == $info[1])
+               printf("[007] Driver specific error code not set\n");
+       if ('' == $info[2])
+               printf("[008] Driver specific error message not set\n");
+
+       $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+       $db->query('THIS IS NOT VALID SQL');
+
+       $code = $db->errorCode();
+       $info = $db->errorInfo();
+
+       if ($code != '42000')
+               printf("[009] Expecting SQL code 42000 got '%s'\n", $code);
+       if ($code !== $info[0])
+               printf("[010] Code and info should be identical, got errorCode() = %s, errorInfo()[0] = %s\n",
+                       $code, $info[0]);
+       if ('' == $info[1])
+               printf("[011] Driver specific error code not set\n");
+       if ('' == $info[2])
+               printf("[012] Driver specific error message not set\n");
+
+       $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+       try {
+               $line = __LINE__ + 1;
+               $db->query('THIS IS NOT VALID SQL');
+       } catch (PDOException $e) {
+
+               $code = $db->errorCode();
+               $info = $db->errorInfo();
+
+               if ($code != '42000')
+                       printf("[013] Expecting SQL code 42000 got '%s'\n", $code);
+               if ($code !== $info[0])
+                       printf("[014] Code and info should be identical, got errorCode() = %s, errorInfo()[0] = %s\n",
+                               $code, $info[0]);
+               if ('' == $info[1])
+                       printf("[015] Driver specific error code not set\n");
+               if ('' == $info[2])
+                       printf("[016] Driver specific error message not set\n");
+
+               if ($e->getCode() !== $code)
+                       printf("[017] Exception code '%s' differs from errorCode '%s'\n",
+                               $e->getCode(), $code);
+
+               $msg = $e->getMessage();
+               foreach ($info as $k => $v) {
+                       if (false === stristr($msg, (string)$v)) {
+                               printf("[018] Cannot find all parts of the error info ('%s') in the exception message '%s'\n",
+                                       $v, $msg);
+                       }
+               }
+
+               if ($e->getLine() !== $line)
+                       printf("[019] Exception has been thrown in line %d, exception object reports line %d\n",
+                               $line, $e->getLine());
+
+               if ($e->getFile() !== __FILE__)
+                       printf("[020] Exception has been thrown in file '%s', exception object reports file '%s'\n",
+                               __FILE__, $e->getFile());
+
+       }
+
+       function my_handler($e) {
+               global $db, $line;
+
+               $code = $db->errorCode();
+               $info = $db->errorInfo();
+
+               if ($code != '42000')
+                       printf("[021] Expecting SQL code 42000 got '%s'\n", $code);
+               if ($code !== $info[0])
+                       printf("[022] Code and info should be identical, got errorCode() = %s, errorInfo()[0] = %s\n",
+                               $code, $info[0]);
+               if ('' == $info[1])
+                       printf("[023] Driver specific error code not set\n");
+               if ('' == $info[2])
+                       printf("[024] Driver specific error message not set\n");
+
+               if ($e->getCode() !== $code)
+                       printf("[025] Exception code '%s' differs from errorCode '%s'\n",
+                               $e->getCode(), $code);
+
+               $msg = $e->getMessage();
+               foreach ($info as $k => $v) {
+                       if (false === stristr($msg, (string)$v)) {
+                               printf("[026] Cannot find all parts of the error info ('%s') in the exception message '%s'\n",
+                                       $v, $msg);
+                       }
+               }
+
+               if ($e->getLine() !== $line)
+                       printf("[027] Exception has been thrown in line %d, exception object reports line %d\n",
+                               $line, $e->getLine());
+
+               if ($e->getFile() !== __FILE__)
+                       printf("[028] Exception has been thrown in file '%s', exception object reports file '%s'\n",
+                               __FILE__, $e->getFile());
+
+               if (get_class($e) != 'PDOException')
+                       printf("[029] Expecting PDO exception got exception of type '%s'\n", get_class($e));
+
+               print "\nend of execution";
+       }
+       set_exception_handler('my_handler');
+       $line = __LINE__ + 1;
+       $db->query('THIS IS NOT VALID SQL');
+
+       print "done!\n";
+--EXPECTF--
+[003] Maybe PDO could indicate that this is not a proper way of setting the ERRMODE...true
+
+Warning: PDO::query(): SQLSTATE[42000]: Syntax error or access violation: %d You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%s' at line %d in %s on line %d
+
+end of execution
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_fetch_table_names.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_fetch_table_names.phpt
new file mode 100644 (file)
index 0000000..3b2b092
--- /dev/null
@@ -0,0 +1,41 @@
+--TEST--
+PDO::ATTR_FETCH_TABLE_NAMES
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       $db->setAttribute(PDO::ATTR_FETCH_TABLE_NAMES, 1);
+       $stmt = $db->query('SELECT label FROM test LIMIT 1');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+       $stmt->closeCursor();
+
+       $db->setAttribute(PDO::ATTR_FETCH_TABLE_NAMES, 0);
+       $stmt = $db->query('SELECT label FROM test LIMIT 1');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+       $stmt->closeCursor();
+
+       print "done!";
+--EXPECTF--
+array(1) {
+  [0]=>
+  array(1) {
+    ["test.label"]=>
+    string(1) "a"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(1) "a"
+  }
+}
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_init_command.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_init_command.phpt
new file mode 100644 (file)
index 0000000..6b13465
--- /dev/null
@@ -0,0 +1,51 @@
+--TEST--
+PDO::MYSQL_ATTR_INIT_COMMAND
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+if (MySQLPDOTest::isPDOMySQLnd())
+       die("skip PDO::MYSQL_ATTR_MAX_INIT_COMMAND not supported with mysqlnd");
+?>
+--INI--
+error_reporting=E_ALL
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       $dsn = MySQLPDOTest::getDSN();
+       $user = PDO_MYSQL_TEST_USER;
+       $pass = PDO_MYSQL_TEST_PASS;
+
+       $table = sprintf("test_%s", md5(mt_rand(0, PHP_INT_MAX)));
+       $db = new PDO($dsn, $user, $pass);
+       $db->exec(sprintf('DROP TABLE IF EXISTS %s', $table));
+
+       $create = sprintf('CREATE TABLE %s(id INT)', $table);
+       var_dump($create);
+       $db = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => $create));
+
+       var_dump($db->errorInfo());
+
+       $db->exec(sprintf('INSERT INTO %s(id) VALUES (1)', $table));
+       $stmt = $db->query(sprintf('SELECT id FROM %s', $table));
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS %s', $table));
+       print "done!\n";
+--EXPECTF--
+string(58) "CREATE TABLE test_%s(id INT)"
+array(1) {
+  [0]=>
+  string(5) "00000"
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["id"]=>
+    string(1) "1"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_max_buffer_size.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_max_buffer_size.phpt
new file mode 100644 (file)
index 0000000..9ba8dee
--- /dev/null
@@ -0,0 +1,70 @@
+--TEST--
+MySQL PDO->__construct(), PDO::MYSQL_ATTR_MAX_BUFFER_SIZE
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+if (MySQLPDOTest::isPDOMySQLnd())
+       die("skip PDO::MYSQL_ATTR_MAX_BUFFER_SIZE not supported with mysqlnd");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function try_buffer_size($offset, $buffer_size) {
+
+               try {
+
+                       $dsn = MySQLPDOTest::getDSN();
+                       $user = PDO_MYSQL_TEST_USER;
+                       $pass = PDO_MYSQL_TEST_PASS;
+
+                       /* unsigned overflow possible ? */
+                       $db = new PDO($dsn, $user, $pass,
+                       array(
+                               PDO::MYSQL_ATTR_MAX_BUFFER_SIZE => $buffer_size,
+                               /* buffer is only relevant with native PS */
+                               PDO::MYSQL_ATTR_DIRECT_QUERY => 0,
+                               PDO::ATTR_EMULATE_PREPARES => 0,
+                       ));
+
+                       $db->exec('DROP TABLE IF EXISTS test');
+                       $db->exec(sprintf('CREATE TABLE test(id INT, val LONGBLOB) ENGINE = %s', PDO_MYSQL_TEST_ENGINE));
+
+                       // 10 * (10 * 1024) = 10 * (10 * 1k) = 100k
+                       $db->exec('INSERT INTO test(id, val) VALUES (1, REPEAT("01234567890", 10240))');
+
+                       $stmt = $db->prepare('SELECT id, val FROM test');
+                       $stmt->execute();
+
+                       $id = $val = NULL;
+                       $stmt->bindColumn(1, $id);
+                       $stmt->bindColumn(2, $val);
+                       while ($row = $stmt->fetch(PDO::FETCH_BOUND)) {
+                               printf("[%03d] id = %d, val = %s... (length: %d)\n",
+                                       $offset, $id, substr($val, 0, 10), strlen($val));
+                       }
+                       $db->exec('DROP TABLE IF EXISTS test');
+
+               } catch (PDOException $e) {
+                       printf("[%03d] %s, [%s] %s\n",
+                               $offset,
+                               $e->getMessage(),
+                               (is_object($db)) ? $db->errorCode() : 'n/a',
+                               (is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
+               }
+       }
+
+       try_buffer_size(1, -1);
+       try_buffer_size(2, 1000);
+       try_buffer_size(3, NULL);
+       try_buffer_size(4, 2000);
+
+       print "done!";
+--EXPECTF--
+[001] id = 1, val = 0123456789... (length: %d)
+[002] id = 1, val = 0123456789... (length: 1000)
+[003] id = 1, val = 0123456789... (length: %d)
+[004] id = 1, val = 0123456789... (length: 2000)
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_oracle_nulls.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_oracle_nulls.phpt
new file mode 100644 (file)
index 0000000..9e938ef
--- /dev/null
@@ -0,0 +1,120 @@
+--TEST--
+PDO::ATTR_ORACLE_NULLS
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       $tmp = array();
+       if (false !== @$db->setAttribute(PDO::ATTR_ORACLE_NULLS, $tmp))
+               printf("[001] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS...\n");
+
+       $tmp = new stdClass();
+       if (false !== @$db->setAttribute(PDO::ATTR_ORACLE_NULLS, $tmp));
+               printf("[002] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS...\n");
+
+       if (false !== @$db->setAttribute(PDO::ATTR_ORACLE_NULLS, 'pdo'))
+               printf("[003] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS...\n");
+
+       $db->setAttribute(PDO::ATTR_ORACLE_NULLS, 1);
+       $stmt = $db->query('SELECT NULL AS z, "" AS a, " " AS b, TRIM(" ") as c, " d" AS d, "' . chr(0) . ' e" AS e');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       $db->setAttribute(PDO::ATTR_ORACLE_NULLS, 0);
+       $stmt = $db->query('SELECT NULL AS z, "" AS a, " " AS b, TRIM(" ") as c, " d" AS d, "' . chr(0) . ' e" AS e');
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       $db->setAttribute(PDO::ATTR_ORACLE_NULLS, 1);
+       $stmt = $db->query('SELECT VERSION() as _version');
+       $row = $stmt->fetch(PDO::FETCH_ASSOC);
+       if ((int)substr($row['_version'], 0, 1) >= 5)
+               $have_procedures = true;
+       else
+               $have_procedures = false;
+
+       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+       $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+
+       if ($have_procedures && (false !== $db->exec('DROP PROCEDURE IF EXISTS p')) &&
+               (false !== $db->exec('CREATE PROCEDURE p() BEGIN SELECT NULL as z, "" AS a, " " AS b, TRIM(" ") as c, " d" AS d, " e" AS e; END;'))) {
+               // requires MySQL 5+
+               $stmt = $db->prepare('CALL p()');
+               $stmt->execute();
+               $expected = array(
+                       array(
+                               "z" => NULL,
+                               "a" => NULL,
+                               "b" => " ",
+                               "c" => NULL,
+                               "d" => " d",
+                               "e" => " e",
+                       ),
+               );
+               do {
+                       $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC);
+                       if ($tmp != $expected) {
+                               printf("[004] Expecting %s got %s\n",
+                                       var_export($expected, true), var_export($tmp, true));
+                       }
+               } while ($stmt->nextRowset());
+
+               $stmt->execute();
+               do {
+                       $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC);
+                       if ($tmp != $expected) {
+                               printf("[005] Expecting %s got %s\n",
+                                       var_export($expected, true), var_export($tmp, true));
+                       }
+               } while ($stmt->nextRowset());
+
+       }
+
+       if ($have_procedures)
+               @$db->exec('DROP PROCEDURE IF EXISTS p');
+
+       print "done!";
+--EXPECTF--
+[002] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS...
+[003] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS...
+array(1) {
+  [0]=>
+  array(6) {
+    ["z"]=>
+    NULL
+    ["a"]=>
+    NULL
+    ["b"]=>
+    string(1) " "
+    ["c"]=>
+    NULL
+    ["d"]=>
+    string(2) " d"
+    ["e"]=>
+    string(3) "%se"
+  }
+}
+array(1) {
+  [0]=>
+  array(6) {
+    ["z"]=>
+    NULL
+    ["a"]=>
+    string(0) ""
+    ["b"]=>
+    string(1) " "
+    ["c"]=>
+    string(0) ""
+    ["d"]=>
+    string(2) " d"
+    ["e"]=>
+    string(3) "%se"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_prefetch.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_prefetch.phpt
new file mode 100644 (file)
index 0000000..456a796
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+PDO::ATTR_PREFETCH
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       var_dump($db->getAttribute(PDO::ATTR_PREFETCH));
+       var_dump($db->setAttribute(PDO::ATTR_PREFETCH, true));
+       print "done!";
+--EXPECTF--
+Warning: PDO::getAttribute(): SQLSTATE[IM001]: Driver does not support this function: driver does not support that attribute in %s on line %d
+bool(false)
+bool(false)
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_server_info.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_server_info.phpt
new file mode 100644 (file)
index 0000000..558ab23
--- /dev/null
@@ -0,0 +1,48 @@
+--TEST--
+PDO::ATTR_SERVER_INFO
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       assert(('' == $db->errorCode()) || ('00000' == $db->errorCode()));
+
+       $info = $db->getAttribute(PDO::ATTR_SERVER_INFO);
+       if ('' == $info)
+               printf("[001] Server info must not be empty\n");
+
+       // Read-only?
+       if (false !== $db->setAttribute(PDO::ATTR_SERVER_INFO, 'new uptime: 0s'))
+               printf("[002] Wonderful, I can change the client version!\n");
+
+       $new_info = $db->getAttribute(PDO::ATTR_SERVER_INFO);
+       if ($new_info !== $info)
+               printf("[003] Did we change it from '%s' to '%s'?\n", $info, $info);
+
+       // lets hope we always run this in the same second as we did run the server info request...
+       if (!$stmt = $db->query('SHOW STATUS LIKE "%uptime%"'))
+               printf("[004] Cannot run SHOW STATUS, [%s]\n", $db->errorCode());
+       else {
+               if (!$row = $stmt->fetch(PDO::FETCH_NUM))
+                       printf("[005] Unable to fetch uptime, [%s]\n", $db->errorCode());
+               else
+                       $uptime = $row[1];
+               $stmt->closeCursor();
+       }
+
+       if (!preg_match('/Uptime/i', $info))
+               printf("[006] Can't find uptime in server info '%s'\n", $info);
+
+       if (isset($uptime) && !preg_match(sprintf('/Uptime: %d/i', $uptime), $info))
+               printf("[007] SHOW STATUS and server info have reported a different uptime, please check. Server info: '%s', SHOW STATUS: '%s'\n", $info, $uptime);
+
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_server_version.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_server_version.phpt
new file mode 100644 (file)
index 0000000..07bb8c3
--- /dev/null
@@ -0,0 +1,64 @@
+--TEST--
+PDO::ATTR_SERVER_VERSION
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       assert(('' == $db->errorCode()) || ('00000' == $db->errorCode()));
+
+       $version = $db->getAttribute(PDO::ATTR_SERVER_VERSION);
+       if ('' == $version)
+               printf("[001] Server version must not be empty\n");
+
+       // Ideally the server version would be an integer - as documented but BC break!
+       // If its a version string it should be of the format \d+\.\d+\.\d+.*
+
+       if (is_string($version)) {
+               // Its not an int like documented but a string - maybe for BC reasons...
+               if (!preg_match('/(\d+)\.(\d+)\.(\d+)(.*)/', $version, $matches))
+                       printf("[002] Client version string seems wrong, got '%s'\n", $version);
+               else {
+                       // Difficult to define any meaningful constraints
+                       // A possible better check would be calling mysqli_get_server_version() and
+                       // comparing what we get. However, mysqli_get_server_version() needs a mysqli handle
+                       // for which in turn one needs to parse the PDO test environment variables
+                       // for connection parameter...
+                       if ($matches[1] < 3)
+                               printf("[003] Strange major version: '%s'. Should be more than 3\n", $matches[1]);
+                       if ($matches[2] < 0)
+                               printf("[004] Minor version should be at least 0, got '%s'\n", $matches[2]);
+                       if ($matches[3] < 0)
+                               printf("[005] Sub version should be at least 0, got '%s'\n", $matches[2]);
+               }
+       } else if (is_int($version)) {
+               // Lets accept also int if it follows the rules from the original MYSQL C API
+               $major = floor($version / 10000);
+               $minor = floor(($version - ($main * 10000)) / 100);
+               $sub = $version - ($main * 10000) - ($minor * 100);
+               if ($major < 3)
+                       printf("[006] Strange major version: '%s'. Should be more than 3\n", $major);
+               if ($minor < 0)
+                       printf("[007] Minor version should be at least 0, got '%s'\n", $minor);
+               if ($sub < 0)
+                       printf("[008] Sub version should be at least 0, got '%s'\n", $sub);
+       }
+
+       // Read-only?
+       if (false !== $db->setAttribute(PDO::ATTR_CLIENT_VERSION, '1.0'))
+               printf("[009] Wonderful, I can change the client version!\n");
+
+       $new_version = $db->getAttribute(PDO::ATTR_SERVER_VERSION);
+       if ($new_version !== $version)
+               printf("[010] Did we change it from '%s' to '%s'?\n", $version, $new_version);
+
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_statement_class.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_statement_class.phpt
new file mode 100644 (file)
index 0000000..d077f6e
--- /dev/null
@@ -0,0 +1,154 @@
+--TEST--
+PDO::ATTR_STATEMENT_CLASS
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       $default =  $db->getAttribute(PDO::ATTR_STATEMENT_CLASS);
+       var_dump($default);
+
+       if (false !== ($tmp = @$db->setAttribute(PDO::ATTR_STATEMENT_CLASS)))
+               printf("[001] Expecting boolean/false got %s\n", var_export($tmp, true));
+
+       if (false !== ($tmp = @$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, 'foo')))
+               printf("[002] Expecting boolean/false got %s\n", var_export($tmp, true));
+
+       if (false !== ($tmp = @$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('classname'))))
+               printf("[003] Expecting boolean/false got %s\n", var_export($tmp, true));
+
+       // unknown class
+       if (false !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('classname', array()))))
+               printf("[004] Expecting boolean/false got %s\n", var_export($tmp, true));
+
+       // class not derived from PDOStatement
+       class myclass {
+               function __construct() {
+                       printf("myclass\n");
+               }
+       }
+       if (false !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('myclass', array()))))
+               printf("[005] Expecting boolean/false got %s\n", var_export($tmp, true));
+
+       // public constructor not allowed
+       class mystatement extends PDOStatement {
+               public function __construct() {
+                       printf("mystatement\n");
+               }
+       }
+
+       if (false !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('mystatement', array()))))
+               printf("[006] Expecting boolean/false got %s\n", var_export($tmp, true));
+
+       // ... but a public destructor is allowed
+       class mystatement2 extends PDOStatement {
+               public function __destruct() {
+                       printf("mystatement\n");
+               }
+       }
+
+       if (true !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('mystatement2', array()))))
+               printf("[007] Expecting boolean/true got %s\n", var_export($tmp, true));
+
+       // private constructor
+       class mystatement3 extends PDOStatement {
+               private function __construct($msg) {
+                       printf("mystatement3\n");
+                       var_dump($msg);
+               }
+       }
+       if (true !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('mystatement3', array('param1')))))
+               printf("[008] Expecting boolean/true got %s\n", var_export($tmp, true));
+
+       // private constructor
+       class mystatement4 extends PDOStatement {
+               private function __construct($msg) {
+                       printf("%s\n", get_class($this));
+                       var_dump($msg);
+               }
+       }
+       if (true !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('mystatement4', array('param1')))))
+               printf("[008] Expecting boolean/true got %s\n", var_export($tmp, true));
+
+       var_dump($db->getAttribute(PDO::ATTR_STATEMENT_CLASS));
+       $stmt = $db->query('SELECT id, label FROM test ORDER BY id ASC LIMIT 2');
+
+       class mystatement5 extends mystatement4 {
+               public function fetchAll($fetch_style = 1, $column_index = 1, $ctor_args = array()) {
+                       return "no data :)";
+               }
+       }
+
+       if (true !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('mystatement5', array('mystatement5')))))
+               printf("[009] Expecting boolean/true got %s\n", var_export($tmp, true));
+       $stmt = $db->query('SELECT id, label FROM test ORDER BY id ASC LIMIT 2');
+       var_dump($stmt->fetchAll());
+
+       if (true !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement'))))
+               printf("[010] Expecting boolean/true got %s\n", var_export($tmp, true));
+
+       $stmt = $db->query('SELECT id, label FROM test ORDER BY id ASC LIMIT 1');
+       var_dump($stmt->fetchAll());
+
+       // Yes, this is a fatal error and I want it to fail.
+       abstract class mystatement6 extends mystatement5 {
+       }
+       if (true !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('mystatement6', array('mystatement6')))))
+               printf("[011] Expecting boolean/true got %s\n", var_export($tmp, true));
+       $stmt = $db->query('SELECT id, label FROM test ORDER BY id ASC LIMIT 1');
+
+       print "done!";
+--EXPECTF--
+array(1) {
+  [0]=>
+  string(12) "PDOStatement"
+}
+
+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
+
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d
+
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: user-supplied statement class must be derived from PDOStatement in %s on line %d
+
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d
+
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: user-supplied statement class cannot have a public constructor in %s on line %d
+
+Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d
+array(2) {
+  [0]=>
+  string(12) "mystatement4"
+  [1]=>
+  array(1) {
+    [0]=>
+    string(6) "param1"
+  }
+}
+mystatement4
+string(6) "param1"
+mystatement5
+string(12) "mystatement5"
+string(10) "no data :)"
+array(1) {
+  [0]=>
+  array(4) {
+    ["id"]=>
+    string(1) "1"
+    [0]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+    [1]=>
+    string(1) "a"
+  }
+}
+
+Fatal error: Cannot instantiate abstract class mystatement6 in %s on line %d
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_begintransaction.phpt b/ext/pdo_mysql/tests/pdo_mysql_begintransaction.phpt
new file mode 100644 (file)
index 0000000..209bf3d
--- /dev/null
@@ -0,0 +1,201 @@
+--TEST--
+PDO->beginTransaction()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+if (false == MySQLPDOTest::detect_transactional_mysql_engine($db))
+       die("skip Transactional engine not found");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+
+       if (1 !== $db->getAttribute(PDO::ATTR_AUTOCOMMIT))
+               printf("[001] Autocommit should be on by default\n");
+
+       if (false == $db->beginTransaction())
+               printf("[002] Cannot start a transaction, [%s] [%s]\n",
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (1 !== $db->getAttribute(PDO::ATTR_AUTOCOMMIT))
+               printf("[003] Autocommit should be on by default, beginTransaction() shall not impact it\n");
+
+       if (0 == $db->exec('DELETE FROM test'))
+               printf("[004] No rows deleted, can't be true.\n");
+
+       /* This is the PDO way to close a connection */
+       $db = null;
+       $db = MySQLPDOTest::factory();
+
+       /* Autocommit was off - by definition. Commit was not issued. DELETE should have been rolled back. */
+       if (!($stmt = $db->query('SELECT id, label FROM test ORDER BY id ASC')))
+               printf("[005] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       $row = $stmt->fetch(PDO::FETCH_ASSOC);
+       var_dump($row);
+
+       if (!$db->beginTransaction())
+               printf("[006] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (1 !== $db->exec(sprintf('DELETE FROM test WHERE id = %d', $row['id'])))
+               printf("[007] DELETE should have indicated 1 deleted row, [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (!$db->commit())
+               printf("[008] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (1 !== $db->getAttribute(PDO::ATTR_AUTOCOMMIT))
+               printf("[009] Autocommit should be on after commit()\n");
+
+       if (!($stmt = $db->query(sprintf('SELECT id, label FROM test WHERE id = %d', $row['id']))))
+               printf("[010] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       var_dump($stmt->fetch(PDO::FETCH_ASSOC));
+
+       if (!$db->beginTransaction())
+               printf("[011] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       $db->exec(sprintf('INSERT INTO test(id, label) VALUES (%d, "z")', $row['id']));
+
+       if (!($stmt = $db->query(sprintf('SELECT id, label FROM test WHERE id = %d', $row['id']))))
+               printf("[012] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       $new_row1 = $stmt->fetch(PDO::FETCH_ASSOC);
+       var_dump($new_row1);
+
+       if (!$db->commit())
+               printf("[013] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (!($stmt = $db->query(sprintf('SELECT id, label FROM test WHERE id = %d', $row['id']))))
+               printf("[014] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       $new_row2 = $stmt->fetch(PDO::FETCH_ASSOC);
+       if ($new_row1 != $new_row2) {
+               printf("[015] Results must not differ!\n");
+               var_dump($new_row1);
+               var_dump($new_row2);
+       }
+
+       if (!$db->beginTransaction())
+               printf("[016] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (1 !== $db->exec(sprintf('DELETE FROM test WHERE id = %d', $row['id'])))
+               printf("[017] DELETE should have indicated 1 deleted row, [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (!$db->rollback())
+               printf("[018] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (1 !== $db->getAttribute(PDO::ATTR_AUTOCOMMIT))
+               printf("[019] Autocommit should be on after rollback\n");
+
+       if (!($stmt = $db->query(sprintf('SELECT id, label FROM test WHERE id = %d', $row['id']))))
+               printf("[020] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       $new_row2 = $stmt->fetch(PDO::FETCH_ASSOC);
+       if ($new_row1 != $new_row2) {
+               printf("[021] Results must not differ!\n");
+               var_dump($new_row1);
+               var_dump($new_row2);
+       }
+
+       // now, lets check the server variables
+       if (!($stmt = $db->query('SELECT @@autocommit as auto_commit')))
+               printf("[022] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+       if ($tmp['auto_commit'] != 1)
+               printf("[023] MySQL Server should indicate autocommit mode, expecting 1, got '%s', [%d] %s\n",
+                       $tmp['auto_commit'], $stmt->errorCode(), $stmt->errorInfo());
+
+       if (!$db->beginTransaction())
+               printf("[024] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (!($stmt = $db->query('SELECT @@autocommit as auto_commit')))
+               printf("[025] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+       if ($tmp['auto_commit'] != 0)
+               printf("[026] Autocommit mode of the MySQL Server should be off, got '%s', [%d] %s\n",
+                       $tmp['auto_commit'], $stmt->errorCode(), implode(' ', $stmt->errorInfo()));
+
+       $db->commit();
+       // Now we should be back to autocommit - we've issues a commit
+       if ($tmp['auto_commit'] != 1)
+               printf("[027] MySQL Server should indicate autocommit mode, expecting 1, got '%s', [%d] %s\n",
+                       $tmp['auto_commit'], $stmt->errorCode(), $stmt->errorInfo());
+
+       // Turn off autocommit using a server variable
+       $db->exec('SET @@autocommit = 0');
+       if (1 === $db->getAttribute(PDO::ATTR_AUTOCOMMIT))
+               printf("[028] I'm confused, how can autocommit be on? Didn't I say I want to manually control transactions?\n");
+
+       if (!$db->beginTransaction())
+               printf("[029] Cannot start a transaction, [%d] %s\n",
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       try {
+               if (false !== $db->beginTransaction()) {
+                       printf("[030] No false and no exception - that's wrong.\n");
+               }
+       } catch (PDOException $e) {
+               assert($e->getMessage() != '');
+       }
+
+       // TODO: What about an engine that does not support transactions?
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, 'MyISAM');
+
+       if (false == $db->beginTransaction())
+               printf("[031] Cannot start a transaction, [%s] [%s]\n",
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (1 !== $db->getAttribute(PDO::ATTR_AUTOCOMMIT))
+               printf("[032] Autocommit should be on my default, beginTransaction() should not change that\n");
+
+       if (0 == $db->exec('DELETE FROM test'))
+               printf("[033] No rows deleted, can't be true.\n");
+
+       if (!$db->commit())
+               printf("[034] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (false == $db->beginTransaction())
+               printf("[035] Cannot start a transaction, [%s] [%s]\n",
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       if (0 == $db->exec('INSERT INTO test(id, label) VALUES (1, "a")'))
+               printf("[036] Cannot insert data, [%s] [%s]\n",
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       // Should cause a Server warning but no error
+       if (!$db->rollback())
+               printf("[037] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       var_dump($db->errorCode());
+
+       if (1 != $db->exec('DELETE FROM test'))
+               printf("[038] No rows deleted, can't be true.\n");
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS test'));
+       print "done!";
+--EXPECTF--
+array(2) {
+  ["id"]=>
+  string(1) "1"
+  ["label"]=>
+  string(1) "a"
+}
+bool(false)
+array(2) {
+  ["id"]=>
+  string(1) "1"
+  ["label"]=>
+  string(1) "z"
+}
+[026] Autocommit mode of the MySQL Server should be off, got '1', [0] 00000
+[028] I'm confused, how can autocommit be on? Didn't I say I want to manually control transactions?
+string(5) "00000"
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_bit.phpt b/ext/pdo_mysql/tests/pdo_mysql_bit.phpt
new file mode 100644 (file)
index 0000000..18506ac
--- /dev/null
@@ -0,0 +1,59 @@
+--TEST--
+MySQL PDO->exec(), BIT columns - remove after fix!
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+if (MySQLPDOTest::isPDOMySQLnd())
+       die("skip Known bug - mysqlnd handles BIT incorrectly!");
+?>
+--FILE--
+<?php
+       /* TODO: remove this test after fix and enable the BIT test in pdo_mysql_types.phpt again */
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function test_type(&$db, $offset, $sql_type, $value, $ret_value = NULL, $pattern = NULL) {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $sql = sprintf('CREATE TABLE test(id INT, label %s) ENGINE=%s', $sql_type, MySQLPDOTest::getTableEngine());
+               @$db->exec($sql);
+               if ($db->errorCode() != 0) {
+                       // not all MySQL Server versions and/or engines might support the type
+                       return true;
+               }
+
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (?, ?)');
+               $stmt->bindValue(1, $offset);
+               $stmt->bindValue(2, $value);
+               if (!$stmt->execute()) {
+                       printf("[%03d + 1] INSERT failed, %s\n", $offset, var_export($stmt->errorInfo(), true));
+                       return false;
+               }
+               $stmt = $db->query('SELECT  id, label FROM test');
+               $row = $stmt->fetch(PDO::FETCH_ASSOC);
+               var_dump($row);
+               var_dump($value);
+
+
+               return true;
+       }
+
+       $db = MySQLPDOTest::factory();
+       $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+       $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+
+       test_type($db, 20, 'BIT(8)', 1);
+
+       echo "done!\n";
+?>
+--EXPECTF--
+array(2) {
+  ["id"]=>
+  string(2) "20"
+  ["label"]=>
+  string(1) "1"
+}
+int(1)
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_class_constants.phpt b/ext/pdo_mysql/tests/pdo_mysql_class_constants.phpt
new file mode 100644 (file)
index 0000000..cfecaf7
--- /dev/null
@@ -0,0 +1,78 @@
+--TEST--
+PDO MySQL specific class constants
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       $expected = array(
+               'MYSQL_ATTR_USE_BUFFERED_QUERY'         => true,
+               'MYSQL_ATTR_LOCAL_INFILE'                                       => true,
+               'MYSQL_ATTR_DIRECT_QUERY'                                       => true,
+       );
+
+       if (!MySQLPDOTest::isPDOMySQLnd()) {
+               $expected['MYSQL_ATTR_MAX_BUFFER_SIZE']                 = true;
+               $expected['MYSQL_ATTR_INIT_COMMAND']                            = true;
+               $expected['MYSQL_ATTR_READ_DEFAULT_FILE']       = true;
+               $expected['MYSQL_ATTR_READ_DEFAULT_GROUP']      = true;
+
+       }
+
+       /*
+       TODO
+
+               MYSQLI_OPT_CONNECT_TIMEOUT != PDO::ATTR_TIMEOUT  (integer)
+    Sets the timeout value in seconds for communications with the database.
+               ^  Potential BUG, PDO::ATTR_TIMEOUT is used in pdo_mysql_handle_factory
+
+               MYSQLI_SET_CHARSET_NAME -> DSN/charset=<charset_name>
+               ^ Undocumented and pitfall for ext/mysqli users
+
+               Assorted mysqlnd settings missing
+       */
+       $ref = new ReflectionClass('PDO');
+       $constants = $ref->getConstants();
+       $values = array();
+
+       foreach ($constants as $name => $value)
+               if (substr($name, 0, 11) == 'MYSQL_ATTR_') {
+                       if (!isset($values[$value]))
+                               $values[$value] = array($name);
+                       else
+                               $values[$value][] = $name;
+
+                       if (isset($expected[$name])) {
+                               unset($expected[$name]);
+                               unset($constants[$name]);
+                       }
+
+               } else {
+                       unset($constants[$name]);
+               }
+
+       if (!empty($constants)) {
+               printf("[001] Dumping list of unexpected constants\n");
+               var_dump($constants);
+       }
+
+       if (!empty($expected)) {
+               printf("[002] Dumping list of missing constants\n");
+               var_dump($expected);
+       }
+
+       if (!empty($values)) {
+               foreach ($values as $value => $constants) {
+                       if (count($constants) > 1) {
+                               printf("[003] Several constants share the same value '%s'\n", $value);
+                               var_dump($constants);
+                       }
+               }
+       }
+
+       print "done!";
+--EXPECT--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_commit.phpt b/ext/pdo_mysql/tests/pdo_mysql_commit.phpt
new file mode 100644 (file)
index 0000000..056c0e0
--- /dev/null
@@ -0,0 +1,84 @@
+--TEST--
+MySQL PDO->commit()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+if (false == MySQLPDOTest::detect_transactional_mysql_engine($db))
+       die("skip Transactional engine not found");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+
+       try {
+               if (true !== ($tmp = $db->beginTransaction())) {
+                       printf("[001] Expecting true, got %s/%s\n", gettype($tmp), $tmp);
+               }
+
+               // DDL will issue an implicit commit
+               $db->exec(sprintf('DROP TABLE IF EXISTS test_commit'));
+               $db->exec(sprintf('CREATE TABLE test_commit(id INT) ENGINE=%s', MySQLPDOTest::detect_transactional_mysql_engine($db)));
+               if (true !== ($tmp = $db->commit())) {
+                       printf("[002] No commit allowed? [%s] %s\n",
+                               $db->errorCode(), implode(' ', $db->errorInfo()));
+               }
+
+               // pdo_transaction_transitions should check this as well...
+               // ... just to be sure the most basic stuff really works we check it again...
+               if (1 !== ($tmp = $db->getAttribute(PDO::ATTR_AUTOCOMMIT)))
+                       printf("[003] According to the manual we should be back to autocommit mode, got %s/%s\n",
+                               gettype($tmp), var_export($tmp, true));
+
+               if (true !== ($tmp = $db->beginTransaction()))
+                       printf("[004] Expecting true, got %s/%s\n", gettype($tmp), $tmp);
+
+               $db->exec('INSERT INTO test(id, label) VALUES (100, "z")');
+
+               if (true !== ($tmp = $db->commit()))
+                       printf("[005] No commit allowed? [%s] %s\n",
+                               $db->errorCode(), implode(' ', $db->errorInfo()));
+
+               // a weak test without unicode etc. - lets leave that to dedicated tests
+               $stmt = $db->query('SELECT id, label FROM test WHERE id = 100');
+               $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+               if (!isset($rows[0]['label']) || ($rows[0]['label'] != 'z')) {
+                       printf("[006] Record data is strange, dumping rows\n");
+                       var_dump($rows);
+               }
+
+               // Ok, lets check MyISAM resp. any other non-transactional engine
+               // pdo_mysql_begin_transaction has more on this, quick check only
+               $db = MySQLPDOTest::factory();
+               MySQLPDOTest::createTestTable($db, 'MyISAM');
+
+               if (true !== ($tmp = $db->beginTransaction()))
+                       printf("[007] Expecting true, got %s/%s\n", gettype($tmp), $tmp);
+
+               $db->exec('INSERT INTO test(id, label) VALUES (100, "z")');
+               if (true !== ($tmp = $db->commit()))
+                       printf("[008] No commit allowed? [%s] %s\n",
+                               $db->errorCode(), implode(' ', $db->errorInfo()));
+
+               // a weak test without unicode etc. - lets leave that to dedicated tests
+               $stmt = $db->query('SELECT id, label FROM test WHERE id = 100');
+               $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+               if (!isset($rows[0]['label']) || ($rows[0]['label'] != 'z')) {
+                       printf("[009] Record data is strange, dumping rows\n");
+                       var_dump($rows);
+               }
+
+       } catch (PDOException $e) {
+               printf("[002] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS test_commit'));
+       print "done!";
+--EXPECT--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_errorcode.phpt b/ext/pdo_mysql/tests/pdo_mysql_errorcode.phpt
new file mode 100644 (file)
index 0000000..35056a7
--- /dev/null
@@ -0,0 +1,81 @@
+--TEST--
+MySQL PDO->errorCode()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       function check_error($offset, &$obj, $expected = '00000') {
+
+               $code = $obj->errorCode();
+               if (($code != $expected) && (($expected != '00000') && ($code != ''))) {
+                       printf("[%03d] Expecting error code '%s' got code '%s'\n",
+                               $offset, $expected, $code);
+               }
+
+       }
+
+       try {
+
+               /*
+               If you create a PDOStatement object through PDO->prepare()
+               or PDO->query() and invoke an error on the statement handle,
+               PDO->errorCode() will not reflect that error. You must call
+               PDOStatement->errorCode() to return the error code for an
+               operation performed on a particular statement handle.
+               */
+               $code = $db->errorCode();
+               check_error(2, $db);
+
+               $stmt = $db->query('SELECT id, label FROM test');
+               $stmt2 = &$stmt;
+               check_error(3, $db);
+               check_error(4, $stmt);
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               @$stmt->execute();
+               check_error(4, $db);
+               check_error(5, $stmt, '42S02');
+               check_error(6, $stmt2, '42S02');
+
+               $db->exec('DROP TABLE IF EXISTS unknown');
+               @$stmt = $db->query('SELECT id, label FROM unknown');
+               check_error(7, $db, '42S02');
+
+               MySQLPDOTest::createTestTable($db);
+               $stmt = $db->query('SELECT id, label FROM test');
+               check_error(8, $db);
+               check_error(9, $stmt);
+
+               $db2 = &$db;
+               @$db->query('SELECT id, label FROM unknown');
+               check_error(10, $db, '42S02');
+               check_error(11, $db2, '42S02');
+               check_error(12, $stmt);
+               check_error(13, $stmt2);
+
+               // lets hope this is an invalid attribute code
+               $invalid_attr = -1 * PHP_INT_MAX + 3;
+               $tmp = @$db->getAttribute($invalid_attr);
+               check_error(14, $db, 'IM001');
+               check_error(15, $db2, 'IM001');
+               check_error(16, $stmt);
+               check_error(17, $stmt2);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_errorinfo.phpt b/ext/pdo_mysql/tests/pdo_mysql_errorinfo.phpt
new file mode 100644 (file)
index 0000000..8829dc4
--- /dev/null
@@ -0,0 +1,173 @@
+--TEST--
+MySQL PDO->errorInfo()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       function check_error($offset, &$obj, $expected = '00000') {
+
+               $info = $obj->errorInfo();
+               if (count($info) != 3)
+                       printf("[%03d] Info should have three fields, got %s\n",
+                               $offset, var_export($info, true));
+
+               $code = $info[0];
+               if (($code != $expected) && (($expected != '00000') && ($code != ''))) {
+                       printf("[%03d] Expecting error code '%s' got code '%s'\n",
+                               $offset, $expected, $code);
+               }
+
+               if ($expected != '00000') {
+                       if (!isset($info[1]) || $info[1] == '')
+                               printf("[%03d] Driver-specific error code not set\n", $offset);
+                       if (!isset($info[2]) || $info[2] == '')
+                               printf("[%03d] Driver-specific error message.not set\n", $offset);
+               }
+
+       }
+
+       function pdo_mysql_errorinfo($db, $offset) {
+
+               try {
+
+                       /*
+                       If you create a PDOStatement object through PDO->prepare()
+                       or PDO->query() and invoke an error on the statement handle,
+                       PDO->errorCode() will not reflect that error. You must call
+                       PDOStatement->errorCode() to return the error code for an
+                       operation performed on a particular statement handle.
+                       */
+                       $code = $db->errorCode();
+                       check_error($offset + 2, $db);
+
+                       $stmt = $db->query('SELECT id, label FROM test');
+                       $stmt2 = &$stmt;
+                       check_error($offset + 3, $db);
+                       check_error($offset + 4, $stmt);
+
+                       $db->exec('DROP TABLE IF EXISTS test');
+                       @$stmt->execute();
+                       check_error($offset + 5, $db);
+                       check_error($offset + 6, $stmt, '42S02');
+                       check_error($offset + 7, $stmt2, '42S02');
+
+                       @$stmt = $db->query('SELECT id, label FROM unknown');
+                       check_error($offset + 8, $db, '42S02');
+
+                       MySQLPDOTest::createTestTable($db);
+                       $stmt = $db->query('SELECT id, label FROM test');
+                       check_error($offset + 9, $db);
+                       check_error($offset + 10, $stmt);
+
+                       $db2 = &$db;
+                       $db->exec('DROP TABLE IF EXISTS unknown');
+                       @$db->query('SELECT id, label FROM unknown');
+                       check_error($offset + 11, $db, '42S02');
+                       check_error($offset + 12, $db2, '42S02');
+                       check_error($offset + 13, $stmt);
+                       check_error($offset + 14, $stmt2);
+
+                       // lets hope this is an invalid attribute code
+                       $invalid_attr = -1 * PHP_INT_MAX + 3;
+                       $tmp = @$db->getAttribute($invalid_attr);
+                       check_error($offset + 15, $db, 'IM001');
+                       check_error($offset + 16, $db2, 'IM001');
+                       check_error($offset + 17, $stmt);
+                       check_error($offset + 18, $stmt2);
+
+               } catch (PDOException $e) {
+                       printf("[%03d] %s [%s] %s\n",
+                               $offset + 19, $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+               }
+       }
+
+       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+       printf("Emulated Prepared Statements...\n");
+       pdo_mysql_errorinfo($db, 0);
+
+       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+       printf("Native Prepared Statements...\n");
+       pdo_mysql_errorinfo($db, 20);
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+--EXPECTF--
+Emulated Prepared Statements...
+[002] Info should have three fields, got array (
+  0 => '00000',
+)
+[003] Info should have three fields, got array (
+  0 => '00000',
+)
+[004] Info should have three fields, got array (
+  0 => '00000',
+)
+[005] Info should have three fields, got array (
+  0 => '00000',
+)
+[009] Info should have three fields, got array (
+  0 => '00000',
+)
+[010] Info should have three fields, got array (
+  0 => '00000',
+)
+[013] Info should have three fields, got array (
+  0 => '00000',
+)
+[014] Info should have three fields, got array (
+  0 => '00000',
+)
+[015] Info should have three fields, got array (
+  0 => 'IM001',
+)
+[015] Driver-specific error code not set
+[015] Driver-specific error message.not set
+[016] Info should have three fields, got array (
+  0 => 'IM001',
+)
+[016] Driver-specific error code not set
+[016] Driver-specific error message.not set
+[017] Info should have three fields, got array (
+  0 => '00000',
+)
+[018] Info should have three fields, got array (
+  0 => '00000',
+)
+Native Prepared Statements...
+[022] Info should have three fields, got array (
+  0 => '00000',
+)
+[023] Info should have three fields, got array (
+  0 => '00000',
+)
+[024] Info should have three fields, got array (
+  0 => '00000',
+)
+[025] Info should have three fields, got array (
+  0 => '00000',
+)
+[030] Info should have three fields, got array (
+  0 => '00000',
+)
+[033] Info should have three fields, got array (
+  0 => '00000',
+)
+[034] Info should have three fields, got array (
+  0 => '00000',
+)
+[037] Info should have three fields, got array (
+  0 => '00000',
+)
+[038] Info should have three fields, got array (
+  0 => '00000',
+)
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_exec.phpt b/ext/pdo_mysql/tests/pdo_mysql_exec.phpt
new file mode 100644 (file)
index 0000000..5ca5f44
--- /dev/null
@@ -0,0 +1,179 @@
+--TEST--
+MySQL PDO->exec(), affected rows
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       function exec_and_count($offset, &$db, $sql, $exp = NULL) {
+
+               try {
+
+                       $ret = $db->exec($sql);
+                       if (!is_null($exp) && ($ret !== $exp)) {
+                               printf("[%03d] Expecting '%s'/%s got '%s'/%s when running '%s', [%s] %s\n",
+                                       $offset, $exp, gettype($exp), $ret, gettype($ret), $sql,
+                                       $db->errorCode(), implode(' ', $db->errorInfo()));
+                               return false;
+                       }
+
+               } catch (PDOException $e) {
+                       printf("[%03d] '%s' has failed, [%s] %s\n",
+                               $offset, $sql, $db->errorCode(), implode(' ', $db->errorInfo()));
+                       return false;
+               }
+
+               return true;
+       }
+
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+
+       /* affected rows related */
+       try {
+
+               exec_and_count(2, $db, 'DROP TABLE IF EXISTS test', 0);
+               exec_and_count(3, $db, sprintf('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, col1 CHAR(10)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE), 0);
+               exec_and_count(4, $db, 'INSERT INTO test(id, col1) VALUES (1, "a")', 1);
+               exec_and_count(5, $db, 'INSERT INTO test(id, col1) VALUES (2, "b"), (3, "c")', 2);
+               exec_and_count(6, $db, 'UPDATE test SET id = 4 WHERE id = 3', 1);
+               exec_and_count(7, $db, 'INSERT INTO test(id, col1) VALUES (1, "d") ON DUPLICATE KEY UPDATE id = 3', 2);
+               exec_and_count(8, $db, 'UPDATE test SET id = 5 WHERE id = 5', 0);
+               exec_and_count(9, $db, 'INSERT INTO test(id, col1) VALUES (5, "e") ON DUPLICATE KEY UPDATE id = 6', 1);
+               exec_and_count(10, $db, 'REPLACE INTO test(id, col1) VALUES (5, "f")', 2);
+               exec_and_count(11, $db, 'REPLACE INTO test(id, col1) VALUES (6, "g")', 1);
+               exec_and_count(12, $db, 'DELETE FROM test WHERE id > 2', 4);
+               exec_and_count(13, $db, 'DROP TABLE test', 0);
+               exec_and_count(14, $db, 'SET @myvar = 1', 0);
+
+               exec_and_count(15, $db, 'THIS IS NOT VALID SQL, I HOPE', false);
+               printf("[016] [%s] %s\n", $db->errorCode(), implode(' ', $db->errorInfo()));
+
+               exec_and_count(36, $db, sprintf('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, col1 CHAR(10)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE), 0);
+               exec_and_count(37, $db, 'INSERT INTO test(id, col1) VALUES (1, "a")', 1);
+               // Results may vary. Typically you will get 1. But the MySQL 5.1 manual states: Truncation operations do not return the number of deleted rows.
+               // Don't rely on any return value!
+               exec_and_count(38, $db, 'TRUNCATE TABLE test', NULL);
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+
+       /* CREATE, DROP, CALL SP and SF */
+       if (MySQLPDOTest::getServerVersion($db) > 50000) {
+               // let's try to play with stored procedures
+               try {
+                       $ignore_exception = true;
+                       exec_and_count(18, $db, 'DROP PROCEDURE IF EXISTS p', 0);
+                       exec_and_count(19, $db, 'CREATE PROCEDURE p(OUT ver_param VARCHAR(255)) BEGIN SELECT VERSION() INTO ver_param; END;', 0);
+                       // we got this far without problems. If there's an issue from now on, its a failure
+                       $ignore_exception = false;
+                       exec_and_count(20, $db, 'CALL p(@version)', 0);
+                       $stmt = $db->query('SELECT @version AS p_version');
+                       $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC);
+                       if (count($tmp) > 1 || !isset($tmp[0]['p_version'])) {
+                               printf("[022] Data seems wrong, dumping\n");
+                               var_dump($tmp);
+                       } else {
+                               $p_version = $tmp[0]['p_version'];
+                       }
+
+                       $stmt = $db->query('SELECT VERSION() AS _version');
+                       $tmp  = $stmt->fetchAll(PDO::FETCH_ASSOC);
+                       if (count($tmp) > 1 || !isset($tmp[0]['_version'])) {
+                               printf("[023] Data seems wrong, dumping\n");
+                               var_dump($tmp);
+                       } else {
+                               if ($p_version !== $tmp[0]['_version']) {
+                                       printf("[024] Found different version strings, SP returned '%s'/%s, SELECT returned '%s'/%s\n",
+                                               $p_version, gettype($p_version),
+                                               $tmp[0]['_version'], gettype($tmp[0]['_version']));
+                               }
+                       }
+                       exec_and_count(25, $db, 'DROP PROCEDURE IF EXISTS p', 0);
+
+               } catch (PDOException $e) {
+                       // ignore it, we might not have sufficient permissions
+                       if (!$ignore_exception)
+                               printf("[021] %s, [%s] %s\n",
+                                       $e->getMessage(),
+                                       $db->errorCode(), implode(' ', $db->errorInfo()));
+               }
+
+               // stored function
+               try {
+                       $ignore_exception = true;
+                       exec_and_count(27, $db, 'DROP FUNCTION IF EXISTS f', 0);
+                       exec_and_count(28, $db, 'CREATE FUNCTION f( ver_param VARCHAR(255)) RETURNS VARCHAR(255) DETERMINISTIC RETURN ver_param;', 0);
+                       // we got this far without problems. If there's an issue from now on, its a failure
+                       $ignore_exception = false;
+                       $stmt = $db->query('SELECT f(VERSION()) AS f_version');
+                       $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC);
+                       if (count($tmp) > 1 || !isset($tmp[0]['f_version'])) {
+                               printf("[029] Data seems wrong, dumping\n");
+                               var_dump($tmp);
+                       } else {
+                               $f_version = $tmp[0]['f_version'];
+                       }
+                       $stmt = $db->query('SELECT VERSION() AS _version');
+                       $tmp  = $stmt->fetchAll(PDO::FETCH_ASSOC);
+                       if (count($tmp) > 1 || !isset($tmp[0]['_version'])) {
+                               printf("[030] Data seems wrong, dumping\n");
+                               var_dump($tmp);
+                       } else {
+                               if ($f_version !== $tmp[0]['_version']) {
+                                       printf("[031] Found different version strings, SF returned '%s'/%s, SELECT returned '%s'/%s\n",
+                                               $f_version, gettype($f_version),
+                                               $tmp[0]['_version'], gettype($tmp[0]['_version']));
+                               }
+                       }
+                       exec_and_count(32, $db, 'DROP FUNCTION IF EXISTS f', 0);
+
+               } catch (PDOException $e) {
+                       // ignore it, we might not have sufficient permissions
+                       if (!$ignore_exception)
+                               printf("[026] %s, [%s] %s\n",
+                                       $e->getMessage(),
+                                       $db->errorCode(), implode(' ', $db->errorInfo()));
+               }
+       }
+
+       // multi query
+       try {
+
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+               $exp = 0;
+
+               $tmp = @$db->exec(sprintf('DROP TABLE IF EXISTS test; CREATE TABLE test(id INT) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+               if ($exp !== $tmp)
+                       printf("[034] Expecting %s/%s got %s/%s, [%s] %s\n",
+                               $exp, gettype($exp),
+                               $tmp, gettype($tmp),
+                               $db->errorCode(), var_export($db->errorInfo(), true));
+
+               // this is interesting: if we get sort of affected rows, what will happen now?
+               $tmp = @$db->exec('INSERT INTO test(id) VALUES (1); INSERT INTO test(id) VALUES (2)');
+               printf("[035] With emulated PS it works but makes no sense given that exec() returns sort of affected rows...\n");
+
+
+       } catch (PDOException $e) {
+               printf("[033] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+
+       @$db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+--EXPECTF--
+Warning: PDO::exec(): SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'THIS IS NOT VALID SQL, I HOPE' at line 1 in %s on line %d
+[016] [42000] 42000 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'THIS IS NOT VALID SQL, I HOPE' at line %d
+[035] With emulated PS it works but makes no sense given that exec() returns sort of affected rows...
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_exec_ddl.phpt b/ext/pdo_mysql/tests/pdo_mysql_exec_ddl.phpt
new file mode 100644 (file)
index 0000000..00a2de1
--- /dev/null
@@ -0,0 +1,88 @@
+--TEST--
+MySQL PDO->exec(), affected rows
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       function exec_and_count($offset, &$db, $sql, $exp, $suppress_warning = false) {
+
+               try {
+
+                       if ($suppress_warning)
+                               $ret = @$db->exec($sql);
+                       else
+                               $ret = $db->exec($sql);
+
+                       if ($ret !== $exp) {
+                               printf("[%03d] Expecting '%s'/%s got '%s'/%s when running '%s', [%s] %s\n",
+                                       $offset, $exp, gettype($exp), $ret, gettype($ret), $sql,
+                                       $db->errorCode(), implode(' ', $db->errorInfo()));
+                               return false;
+                       }
+
+               } catch (PDOException $e) {
+                       printf("[%03d] '%s' has failed, [%s] %s\n",
+                               $offset, $sql, $db->errorCode(), implode(' ', $db->errorInfo()));
+                       return false;
+               }
+
+               return true;
+       }
+
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+
+       /* affected rows related */
+       try {
+
+               @$db->exec('DROP DATABASE IF EXISTS pdo_exec_ddl');
+               @$db->exec('DROP DATABASE IF EXISTS pdo_exec_ddl2');
+               if (1 === @$db->exec('CREATE DATABASE pdo_exec_ddl')) {
+                       // yippie - we can create databases etc.
+                       exec_and_count(3, $db, 'ALTER DATABASE pdo_exec_ddl CHARACTER SET latin1', 1);
+               }
+
+               exec_and_count(4, $db, 'DROP TABLE IF EXISTS pdo_exec_ddl', 0);
+               exec_and_count(5, $db, 'DROP TABLE IF EXISTS pdo_exec_ddl2', 0);
+               if (0 === $db->exec('CREATE TABLE pdo_exec_ddl(id INT, col1 CHAR(2))')) {
+                       exec_and_count(5, $db, 'CREATE INDEX idx1 ON pdo_exec_ddl(id)', 0);
+                       exec_and_count(6, $db, 'DROP INDEX idx1 ON pdo_exec_ddl', 0);
+                       exec_and_count(7, $db, 'ALTER TABLE pdo_exec_ddl DROP id', 0);
+                       exec_and_count(8, $db, 'ALTER TABLE pdo_exec_ddl ADD id INT', 0);
+                       exec_and_count(9, $db, 'ALTER TABLE pdo_exec_ddl ALTER id SET DEFAULT 1', 0);
+                       exec_and_count(10, $db, 'RENAME TABLE pdo_exec_ddl TO pdo_exec_ddl2', 0);
+               }
+
+               /*
+               11.1.2. ALTER LOGFILE GROUP Syntax
+               11.1.3. ALTER SERVER Syntax
+               11.1.5. ALTER TABLESPACE Syntax
+               11.1.8. CREATE LOGFILE GROUP Syntax
+               11.1.9. CREATE SERVER Syntax
+               11.1.11. CREATE TABLESPACE Syntax
+               11.1.14. DROP LOGFILE GROUP Syntax
+               11.1.15. DROP SERVER Syntax
+               11.1.17. DROP TABLESPACE Syntax
+               */
+
+               // clean up
+               @$db->exec('DROP TABLE IF EXISTS pdo_exec_ddl');
+               @$db->exec('DROP TABLE IF EXISTS pdo_exec_ddl2');
+               @$db->exec('DROP DATABASE IF EXISTS pdo_exec_ddl');
+               @$db->exec('DROP DATABASE IF EXISTS pdo_exec_ddl2');
+
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_exec_load_data.phpt b/ext/pdo_mysql/tests/pdo_mysql_exec_load_data.phpt
new file mode 100644 (file)
index 0000000..ecfa502
--- /dev/null
@@ -0,0 +1,102 @@
+--TEST--
+MySQL PDO->exec(), affected rows
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+
+// Run test only locally - not against remote hosts
+$db = MySQLPDOTest::factory();
+$stmt = $db->query('SELECT USER() as _user');
+$row = $stmt->fetch(PDO::FETCH_ASSOC);
+$tmp = explode('@', $row['_user']);
+if (count($tmp) < 2)
+       die("skip Cannot detect if test is run against local or remote database server");
+if (($tmp[1] !== 'localhost') && ($tmp[1] !== '127.0.0.1'))
+       die("skip Test cannot be run against remote database server");
+
+?>
+--FILE--
+<?php
+       function exec_and_count($offset, &$db, $sql, $exp) {
+
+               try {
+
+                       $ret = $db->exec($sql);
+                       if ($ret !== $exp) {
+                               printf("[%03d] Expecting '%s'/%s got '%s'/%s when running '%s', [%s] %s\n",
+                                       $offset, $exp, gettype($exp), $ret, gettype($ret), $sql,
+                                       $db->errorCode(), implode(' ', $db->errorInfo()));
+                               return false;
+                       }
+
+               } catch (PDOException $e) {
+
+                       if (42000 == $db->errorCode()) {
+                               // Error: 1148 SQLSTATE: 42000  (ER_NOT_ALLOWED_COMMAND)
+                               // Load data infile not allowed
+                               return false;
+                       }
+
+                       printf("[%03d] '%s' has failed, [%s] %s\n",
+                               $offset, $sql, $db->errorCode(), implode(' ', $db->errorInfo()));
+                       return false;
+               }
+
+               return true;
+       }
+
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+
+       /* affected rows related */
+       try {
+
+               exec_and_count(2, $db, 'DROP TABLE IF EXISTS test', 0);
+               exec_and_count(3, $db, sprintf('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, col1 CHAR(10)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE), 0);
+
+               $stmt = $db->query('SHOW VARIABLES LIKE "secure_file_priv"');
+               if (($row = $stmt->fetch(PDO::FETCH_ASSOC)) && ($row['value'] != '')) {
+                       $filename = $row['value'] . DIRECTORY_SEPARATOR  . "pdo_mysql_exec_load_data.csv";
+               } else {
+                       $filename =  MySQLPDOTest::getTempDir() . DIRECTORY_SEPARATOR  . "pdo_mysql_exec_load_data.csv";
+               }
+
+               $fp = fopen($filename, "w");
+               fwrite($fp, b"1;foo\n");
+               fwrite($fp, b"2;bar");
+               fclose($fp);
+
+               $sql = sprintf("LOAD DATA INFILE %s INTO TABLE test FIELDS TERMINATED BY ';' LINES TERMINATED  BY '\n'", $db->quote($filename));
+
+               if (exec_and_count(4, $db, $sql, 2)) {
+
+                       $stmt = $db->query('SELECT id, col1 FROM test ORDER BY id ASC');
+                       $expected = array(array("id" => 1, "col1" => "foo"), array("id" => 2, "col1" => "bar"));
+                       $ret = $stmt->fetchAll(PDO::FETCH_ASSOC);
+                       foreach ($expected as $offset => $exp) {
+                               foreach ($exp as $key => $value) {
+                                       if ($ret[$offset][$key] != $value) {
+                                               printf("Results seem wrong, check manually\n");
+                                               var_dump($ret);
+                                               var_dump($expected);
+                                               break 2;
+                                       }
+                               }
+                       }
+               }
+
+               unlink($filename);
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_exec_select.phpt b/ext/pdo_mysql/tests/pdo_mysql_exec_select.phpt
new file mode 100644 (file)
index 0000000..a759f72
--- /dev/null
@@ -0,0 +1,59 @@
+--TEST--
+MySQL PDO->exec(), SELECT
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       function exec_and_count($offset, &$db, $sql, $exp) {
+
+               try {
+
+                       $ret = $db->exec($sql);
+                       if ($ret !== $exp) {
+                               printf("[%03d] Expecting '%s'/%s got '%s'/%s when running '%s', [%s] %s\n",
+                                       $offset, $exp, gettype($exp), $ret, gettype($ret), $sql,
+                                       $db->errorCode(), implode(' ', $db->errorInfo()));
+                               return false;
+                       }
+
+               } catch (PDOException $e) {
+                       printf("[%03d] '%s' has failed, [%s] %s\n",
+                               $offset, $sql, $db->errorCode(), implode(' ', $db->errorInfo()));
+                       return false;
+               }
+
+               return true;
+       }
+
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+
+       /* affected rows related */
+       try {
+
+               exec_and_count(2, $db, 'DROP TABLE IF EXISTS test', 0);
+               exec_and_count(3, $db, sprintf('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, col1 CHAR(10)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE), 0);
+               exec_and_count(4, $db, 'INSERT INTO test(id, col1) VALUES (1, "a")', 1);
+               // question is: will the result set be cleaned up, will it be possible to run more queries on the line?
+               // buffered or unbuffered does not matter!
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+               exec_and_count(5, $db, 'SELECT id FROM test', 0);
+               exec_and_count(6, $db, 'INSERT INTO test(id, col1) VALUES (2, "b")', 1);
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       @$db->exec(sprintf('DROP TABLE IF EXISTS test'));
+       print "done!";
+--EXPECTF--
+Warning: PDO::exec(): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in %s on line %d
+[006] Expecting '1'/integer got ''/boolean when running 'INSERT INTO test(id, col1) VALUES (2, "b")', [HY000] HY000 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_fetch_both.phpt b/ext/pdo_mysql/tests/pdo_mysql_fetch_both.phpt
new file mode 100644 (file)
index 0000000..eac6bf3
--- /dev/null
@@ -0,0 +1,88 @@
+--TEST--
+MySQL PDOStatement->fetch()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       function fetch($offset, &$db, $query, $expect = null) {
+
+               try {
+                       $stmt = $db->query('SELECT 1');
+                       $num = $stmt->fetch(PDO::FETCH_NUM);
+
+                       $stmt = $db->query('SELECT 1');
+                       $assoc = $stmt->fetch(PDO::FETCH_ASSOC);
+
+                       $stmt = $db->query('SELECT 1');
+                       $both = $stmt->fetch(PDO::FETCH_BOTH);
+
+                       $computed_both = array_merge($num, $assoc);
+                       if ($computed_both != $both) {
+                               printf("[%03d] Suspicious FETCH_BOTH result, dumping\n", $offset);
+                               var_dump($computed_both);
+                               var_dump($both);
+                       }
+
+                       if (!is_null($expect) && ($expect != $both)) {
+                               printf("[%03d] Expected differes from returned data, dumping\n", $offset);
+                               var_dump($expect);
+                               var_dump($both);
+                       }
+
+               } catch (PDOException $e) {
+
+                       printf("[%03d] %s, [%s] %s\n",
+                               $offset,
+                               $e->getMessage(), $db->errroCode(), implode(' ', $db->errorInfo()));
+
+               }
+
+       }
+
+       try {
+
+               fetch(2, &$db, 'SELECT 1', array(0 => '1', '1' => '1'));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS test'));
+       print "done!";
+--EXPECTF--
+[002] Suspicious FETCH_BOTH result, dumping
+array(2) {
+  [0]=>
+  string(1) "1"
+  [1]=>
+  string(1) "1"
+}
+array(2) {
+  [1]=>
+  string(1) "1"
+  [2]=>
+  string(1) "1"
+}
+[002] Expected differes from returned data, dumping
+array(2) {
+  [0]=>
+  string(1) "1"
+  [1]=>
+  string(1) "1"
+}
+array(2) {
+  [1]=>
+  string(1) "1"
+  [2]=>
+  string(1) "1"
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_get_attribute.phpt b/ext/pdo_mysql/tests/pdo_mysql_get_attribute.phpt
new file mode 100644 (file)
index 0000000..74f17d5
--- /dev/null
@@ -0,0 +1,101 @@
+--TEST--
+MySQL PDO->getAttribute()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+if (false == MySQLPDOTest::detect_transactional_mysql_engine($db))
+       die("skip Transactional engine not found");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+
+       function find_invalid_int($valid_options) {
+               do {
+                       $invalid = mt_rand(-10000, 10000);
+               } while (in_array($invalid, $valid_options));
+               return $invalid;
+       }
+
+       function set_and_get($offset, $db, $attribute, $value) {
+
+               $value_type = gettype($value);
+               try {
+
+                       if (!$db->setAttribute($attribute, $value)) {
+                               printf("[%03d] Cannot set attribute '%s' to value '%s'\n",
+                                       $offset, $attribute, var_export($tmp, true));
+                               return false;
+                       }
+
+                       if (gettype($value) != $value_type) {
+                               printf("[%03d] Call to PDO::setAttribute(int attribute, mixed value) has changed the type of value from %s to %s, test will not work properly\n",
+                                       $offset, $value_type, gettype($value));
+                               return false;
+                       }
+
+                       $tmp = $db->getAttribute($attribute);
+                       if ($tmp !== $value) {
+                               printf("[%03d] Attribute '%s' was set to '%s'/%s but getAttribute() reports '%s'/%s\n",
+                                       $offset, $attribute, var_export($value, true), gettype($value), var_export($tmp, true), gettype($tmp));
+                               return false;
+                       }
+
+               } catch (PDOException $e) {
+                       printf("[%03d] %s, [%s] %s\n",
+                               $offset, $e->getMessage(),
+                               $db->errorCode(), implode(' ', $db->errorInfo()));
+                       return false;
+               }
+
+               return true;
+       }
+
+       set_and_get(1, $db, PDO::ATTR_AUTOCOMMIT, 1);
+/*
+       set_and_get(2, $db, PDO::ATTR_AUTOCOMMIT, 0);
+       set_and_get(3, $db, PDO::ATTR_AUTOCOMMIT, -1);
+       $obj = new stdClass();
+       set_and_get(4, $db, PDO::ATTR_AUTOCOMMIT, $obj);
+
+       set_and_get(5, $db, PDO::MYSQL_ATTR_LOCAL_INFILE, 1);
+       set_and_get(6, $db, PDO::MYSQL_ATTR_LOCAL_INFILE, 0);
+       set_and_get(7, $db, PDO::MYSQL_ATTR_LOCAL_INFILE, -1);
+       $tmp = array();
+       set_and_get(8, $db, PDO::MYSQL_ATTR_LOCAL_INFILE, $tmp);
+
+       set_and_get(9, $db, PPDO::MYSQL_ATTR_INIT_COMMAND, '');
+       set_and_get(10, $db, PPDO::MYSQL_ATTR_INIT_COMMAND, 'SOME SQL');
+       set_and_get(11, $db, PPDO::MYSQL_ATTR_INIT_COMMAND, -1);
+
+*/
+/*
+PDO::MYSQL_ATTR_READ_DEFAULT_FILE (integer)
+
+    Read options from the named option file instead of from my.cnf.
+PDO::MYSQL_ATTR_READ_DEFAULT_GROUP (integer)
+
+    Read options from the named group from my.cnf or the file specified with MYSQL_READ_DEFAULT_FILE.
+PDO::MYSQL_ATTR_MAX_BUFFER_SIZE (integer)
+
+    Maximum buffer size. Defaults to 1 MiB.
+PDO::MYSQL_ATTR_DIRECT_QUERY (integer)
+
+    Perform direct queries, don't use prepared statements.
+*/
+/*
+TODO - read only
+PDO::ATTR_CONNECTION_STATUS
+PDO::ATTR_SERVER_INFO
+*/
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS test'));
+       print "done!";
+--EXPECTF--
+[001] Call to PDO::setAttribute(int attribute, mixed value) has changed the type of value from integer to boolean, test will not work properly
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_interface.phpt b/ext/pdo_mysql/tests/pdo_mysql_interface.phpt
new file mode 100644 (file)
index 0000000..00c282b
--- /dev/null
@@ -0,0 +1,58 @@
+--TEST--
+MySQL PDO class interface
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::getDriver();
+if (false == MySQLPDOTest::detect_transactional_mysql_engine($db))
+       die("skip Transactional engine not found");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       $expected = array(
+               '__construct'                                   => true,
+               'prepare'                                               => true,
+               'beginTransaction'              => true,
+               'commit'                                                        => true,
+               'rollBack'                                              => true,
+               'setAttribute'                          => true,
+               'exec'                                                          => true,
+               'query'                                                         => true,
+               'lastInsertId'                          => true,
+               'errorCode'                                             => true,
+               'errorInfo'                                             => true,
+               'getAttribute'                          => true,
+               'quote'                                                         => true,
+               '__wakeup'                                              => true,
+               '__sleep'                                                       => true,
+               'getAvailableDrivers'   => true,
+       );
+       $classname = get_class($db);
+
+       $methods = get_class_methods($classname);
+       foreach ($methods as $k => $method) {
+               if (isset($expected[$method])) {
+                       unset($expected[$method]);
+                       unset($methods[$k]);
+               }
+               if ($method == $classname) {
+                       unset($expected['__construct']);
+                       unset($methods[$k]);
+               }
+       }
+       if (!empty($expected)) {
+               printf("Dumping missing class methods\n");
+               var_dump($expected);
+       }
+       if (!empty($methods)) {
+               printf("Found more methods than expected, dumping list\n");
+               var_dump($methods);
+       }
+
+       print "done!";
+--EXPECT--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_last_insert_id.phpt b/ext/pdo_mysql/tests/pdo_mysql_last_insert_id.phpt
new file mode 100644 (file)
index 0000000..552128c
--- /dev/null
@@ -0,0 +1,114 @@
+--TEST--
+MySQL PDO->lastInsertId()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+               if ('0' !== ($tmp = $db->lastInsertId()))
+                       printf("[001] No query has been run, lastInsertId() should return '0'/string got '%s'/%s\n",
+                               var_export($tmp, true), gettype($tmp));
+
+               if ('0' !== ($tmp = $db->lastInsertId('sequence_name')))
+                       printf("[002] MySQL does not support sequences, expecting '0'/string got '%s'/%s\n",
+                               var_export($tmp, true), gettype($tmp));
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               if ('0' !== ($tmp = $db->lastInsertId()))
+                       printf("[003] Expecting '0'/string got '%s'/%s", var_export($tmp, true), gettype($tmp));
+
+               $db->exec(sprintf('CREATE TABLE test(id INT, col1 CHAR(10)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+               if ('0' !== ($tmp = $db->lastInsertId()))
+                       printf("[004] Expecting '0'/string got '%s'/%s", var_export($tmp, true), gettype($tmp));
+
+               $stmt = $db->query('SELECT id FROM test LIMIT 1');
+               if ('0' !== ($tmp = $db->lastInsertId()))
+                       printf("[005] Expecting '0'/string got '%s'/%s", var_export($tmp, true), gettype($tmp));
+
+               // no auto increment column
+               $db->exec('INSERT INTO test(id, col1) VALUES (100, "a")');
+               if ('0' !== ($tmp = $db->lastInsertId()))
+                       printf("[006] Expecting '0'/string got '%s'/%s", var_export($tmp, true), gettype($tmp));
+
+               $db->exec('ALTER TABLE test MODIFY id INT AUTO_INCREMENT PRIMARY KEY');
+               if ('0' !== ($tmp = $db->lastInsertId()))
+                       printf("[006] Expecting '0'/string got '%s'/%s", var_export($tmp, true), gettype($tmp));
+
+               // duplicate key
+               @$db->exec('INSERT INTO test(id, col1) VALUES (100, "a")');
+               if ('0' !== ($tmp = $db->lastInsertId()))
+                       printf("[007] Expecting '0'/string got '%s'/%s", var_export($tmp, true), gettype($tmp));
+
+               $db->exec('INSERT INTO test(id, col1) VALUES (101, "b")');
+               if ('101' !== ($tmp = $db->lastInsertId()))
+                       printf("[008] Expecting '0'/string got '%s'/%s", var_export($tmp, true), gettype($tmp));
+
+               $db->exec('ALTER TABLE test MODIFY col1 CHAR(10) UNIQUE');
+               // replace = delete + insert -> new auto increment value
+               $db->exec('REPLACE INTO test(col1) VALUES ("b")');
+               $next_id = (int)$db->lastInsertId();
+
+               if ($next_id <= 101)
+                       printf("[009] Expecting at least 102, got %d\n",$next_id);
+
+               $stmt = $db->query('SELECT LAST_INSERT_ID() as _last_id');
+               $row = $stmt->fetch(PDO::FETCH_ASSOC);
+               $last_id = $row['_last_id'];
+               if ($next_id != $last_id) {
+                       printf("[010] LAST_INSERT_ID() = %d and lastInserId() = %d differ\n",
+                               $last_id, $next_id);
+               }
+
+               $db->exec('INSERT INTO test(col1) VALUES ("c"), ("d"), ("e")');
+               $next_id = (int)$db->lastInsertId();
+               if ($next_id <= $last_id)
+                       printf("[011] Expecting at least %d, got %d\n", $last_id + 1, $next_id);
+
+               // warnings are unhandy, lets go for exceptions for a second
+               $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+               try {
+                       $ignore_exception = true;
+                       $db->exec('LOCK TABLE test WRITE');
+                       $ignore_exception = false;
+
+                       if (MySQLPDOTest::getServerVersion($db) >= 50000) {
+                               $stmt = $db->query('SELECT @@auto_increment_increment AS inc');
+                               $row = $stmt->fetch(PDO::FETCH_ASSOC);
+                               $inc = $row['inc'];
+                       } else {
+                               $inc = 1;
+                       }
+
+                       $stmt = $db->query('SELECT LAST_INSERT_ID() as _last_id');
+                       $row = $stmt->fetch(PDO::FETCH_ASSOC);
+                       $last_id = $row['_last_id'];
+
+                       $db->exec('INSERT INTO test(col1) VALUES ("z")');
+                       $next_id = (int)$db->lastInsertId();
+                       if ($next_id < ($last_id + $inc))
+                               printf("[012] Expecting at least %d, got %d\n", $last_id + $inc, $next_id);
+
+               } catch (PDOException $e) {
+                       if (!$ignore_exception)
+                               printf("[014] %s, [%s} %s\n", $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+               }
+               $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+               @$db->exec('UNLOCK TABLE test');
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS test'));
+       print "done!";
+--EXPECTF--
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_pconnect.phpt b/ext/pdo_mysql/tests/pdo_mysql_pconnect.phpt
new file mode 100644 (file)
index 0000000..e5ab3d5
--- /dev/null
@@ -0,0 +1,101 @@
+--TEST--
+MySQL PDO->__construct(), PDO::ATTR_PERSISTENT
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       try {
+
+               $dsn = MySQLPDOTest::getDSN();
+               $user = PDO_MYSQL_TEST_USER;
+               $pass = PDO_MYSQL_TEST_PASS;
+
+               $db1 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
+               $db2 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
+               $db1->exec('SET @pdo_persistent_connection=1');
+               $stmt = $db2->query('SELECT @pdo_persistent_connection as _pers');
+               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+               if ($tmp['_pers'] !== '1')
+                       printf("[001] Both handles should use the same connection.");
+
+               $stmt = $db1->query('SELECT CONNECTION_ID() as _con1');
+               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+               $con1 = $tmp['_con1'];
+
+               $stmt = $db2->query('SELECT CONNECTION_ID() as _con2');
+               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+               $con2 = $tmp['_con2'];
+
+               if ($con1 !== $con2)
+                       printf("[002] Both handles should report the same MySQL thread ID");
+
+               $db1 = NULL; /* should be equal to closing to my understanding */
+               $db1 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
+               $stmt = $db1->query('SELECT CONNECTION_ID() as _con1');
+               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+               $con1 = $tmp['_con1'];
+
+               if ($con1 !== $con2)
+                       printf("[003] Both handles should report the same MySQL thread ID");
+
+               $affected = $db1->exec(sprintf('KILL %d', $con1));
+               // Server needs some think-time sometimes
+               sleep(1);
+               if ('00000' == $db1->errorCode()) {
+                       // looks like KILL has worked ? Or not... TODO: why no warning with libmysql?!
+                       @$db1->exec("SET @pdo_persistent_connection=2");
+                       // but now I want to see some error...
+                       if ('HY000' != $db1->errorCode())
+                               printf("[004] Wrong error code %s\n", $db1->errorCode());
+
+                       $tmp = implode(' ', $db1->errorInfo());
+                       if (!strstr($tmp, '2006'))
+                               printf("[005] Wrong error info %s\n", $tmp);
+               }
+
+               $db1 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => false));
+               $stmt = $db1->query('SELECT CONNECTION_ID() as _con1');
+               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+               $con1 = $tmp['_con1'];
+
+               $db2 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
+               $stmt = $db2->query('SELECT CONNECTION_ID() as _con2');
+               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+               $con2 = $tmp['_con2'];
+
+               if ($con1 == $con2)
+                       printf("[006] Looks like the persistent and the non persistent connection are using the same link?!\n");
+
+               // lets go crazy and create a few pconnections...
+               $connections = array();
+               for ($i = 0; $i <= 20; $i++) {
+                       $connections[$i] = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
+               }
+               do {
+                       $i = mt_rand(0, 20);
+                       if (isset($connections[$i]))
+                               unset($connections[$i]);
+               } while (!empty($connections));
+
+
+       } catch (PDOException $e) {
+               printf("[001] %s, [%s] %s\n",
+                       $e->getMessage(),
+                       (is_object($db)) ? $db->errorCode() : 'n/a',
+                       (is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
+       }
+
+       print "done!";
+--XFAIL--
+Expected to fail in debug mode as PDO doesn't properly clean persistent connections
+--EXPECTF--
+Warning: PDO::exec(): MySQL server has gone away in %s on line %d
+
+Warning: PDO::exec(): Error while reading SET_OPTION's EOF packet. PID=%d in %s on line %d
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_phpinfo.phpt b/ext/pdo_mysql/tests/pdo_mysql_phpinfo.phpt
new file mode 100644 (file)
index 0000000..8e978a2
--- /dev/null
@@ -0,0 +1,34 @@
+--TEST--
+MySQL PDO phpinfo() output
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       ob_start();
+       phpinfo();
+       $tmp = ob_get_contents();
+       ob_end_clean();
+
+       /*      PDO Driver for MySQL, client library version => 6.0.3-alpha     */
+       $expected = sprintf('PDO Driver for MySQL, client library version => %s',
+               $db->getAttribute(PDO::ATTR_CLIENT_VERSION));
+
+       if (false === stristr($tmp, $expected)) {
+               // maybe its PDO_MYSQLND
+               $expected = sprintf('PDO Driver for MySQL, mysql native driver version => %s',
+                       $db->getAttribute(PDO::ATTR_CLIENT_VERSION));
+               if (false === stristr($tmp, $expected))
+                       printf("[001] Cannot find MySQL PDO driver line in phpinfo() output\n");
+       }
+
+       print "done!";
+--EXPECT--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated.phpt
new file mode 100644 (file)
index 0000000..fa942f3
--- /dev/null
@@ -0,0 +1,414 @@
+--TEST--
+MySQL PDO->prepare(), emulated PS
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       function prepex($offset, &$db, $query, $input_params = null, $error_info = null) {
+
+               try {
+
+                       if (is_array($error_info) && isset($error_info['prepare']))
+                               $stmt = @$db->prepare($query);
+                       else
+                               $stmt = $db->prepare($query);
+
+                       if (is_array($error_info) && isset($error_info['prepare'])) {
+                               $tmp = $db->errorInfo();
+
+                               if (isset($error_info['prepare']['sqlstate']) &&
+                                       ($error_info['prepare']['sqlstate'] !== $tmp[0])) {
+                                       printf("[%03d] prepare() - expecting SQLSTATE '%s' got '%s'\n",
+                                               $offset, $error_info['prepare']['sqlstate'], $tmp[0]);
+                                       return false;
+                               }
+
+                               if (isset($error_info['prepare']['mysql']) &&
+                                       ($error_info['prepare']['mysql'] !== $tmp[1])) {
+                                       printf("[%03d] prepare() - expecting MySQL Code '%s' got '%s'\n",
+                                               $offset, $error_info['prepare']['mysql'], $tmp[0]);
+                                       return false;
+                               }
+
+                               return false;
+                       }
+
+                       if (is_null($input_params))
+                               $input_params = array();
+
+                       if (is_array($error_info) && isset($error_info['execute']))
+                               $ret = @$stmt->execute($input_params);
+                       else
+                               $ret = $stmt->execute($input_params);
+
+                       if (!is_bool($ret))
+                               printf("[%03d] PDO::execute() should return a boolean value, got %s/%s\n",
+                                       var_export($ret, true), $ret);
+
+                       if (is_array($error_info) && isset($error_info['execute'])) {
+                               $tmp = $stmt->errorInfo();
+
+                               if (isset($error_info['execute']['sqlstate']) &&
+                                       ($error_info['execute']['sqlstate'] !== $tmp[0])) {
+                                       printf("[%03d] execute() - expecting SQLSTATE '%s' got '%s'\n",
+                                               $offset, $error_info['execute']['sqlstate'], $tmp[0]);
+                                       return false;
+                               }
+
+                               if (isset($error_info['execute']['mysql']) &&
+                                       ($error_info['execute']['mysql'] !== $tmp[1])) {
+                                       printf("[%03d] execute() - expecting MySQL Code '%s' got '%s'\n",
+                                               $offset, $error_info['execute']['mysql'], $tmp[0]);
+                                       return false;
+                               }
+
+                               return false;
+                       }
+
+               } catch (PDOException $e) {
+                       printf("[%03d] %s, [%s} %s\n",
+                               $offset, $e->getMessage(),
+                               $db->errorCode(), implode(' ', $db->errorInfo()));
+                       return false;
+               }
+
+               return $stmt;
+       }
+
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to switch to emulated prepared statements, test will fail\n");
+
+               // TODO - that's PDO - you can prepare empty statements!
+               prepex(3, $db, '',
+                       array(), array('execute' => array('sqlstate' => '42000')));
+
+               // lets be fair and do the most simple SELECT first
+               $stmt = prepex(4, $db, 'SELECT 1 as "one"');
+               var_dump($stmt->fetch(PDO::FETCH_ASSOC));
+
+               prepex(5, $db, 'DROP TABLE IF EXISTS test');
+               prepex(6, $db, sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+               prepex(7, $db, 'INSERT INTO test(id, label) VALUES(1, ":placeholder")');
+               $stmt = prepex(8, $db, 'SELECT label FROM test');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               prepex(9, $db, 'DELETE FROM test');
+               prepex(10, $db, 'INSERT INTO test(id, label) VALUES(1, ":placeholder")',
+                       array(':placeholder' => 'first row'));
+               $stmt = prepex(11, $db, 'SELECT label FROM test');
+
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               prepex(12, $db, 'DELETE FROM test');
+               prepex(13, $db, 'INSERT INTO test(id, label) VALUES(1, :placeholder)',
+                       array(':placeholder' => 'first row'));
+               prepex(14, $db, 'INSERT INTO test(id, label) VALUES(2, :placeholder)',
+                       array(':placeholder' => 'second row'));
+               $stmt = prepex(15, $db, 'SELECT label FROM test');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // Is PDO fun?
+               prepex(16, $db, 'SELECT label FROM test WHERE :placeholder > 1',
+                       array(':placeholder' => 'id'));
+               prepex(17, $db, 'SELECT :placeholder FROM test WHERE id > 1',
+                       array(':placeholder' => 'id'));
+               prepex(18, $db, 'SELECT :placeholder FROM test WHERE :placeholder > :placeholder',
+                       array(':placeholder' => 'test'));
+
+               for ($num_params = 2; $num_params < 100; $num_params++) {
+                       $params = array(':placeholder' => 'a');
+                       for ($i = 1; $i < $num_params; $i++) {
+                               $params[str_repeat('a', $i)] = 'some data';
+                       }
+                       prepex(19, $db, 'SELECT id, label FROM test WHERE label > :placeholder',
+                               $params, array('execute' => array('sqlstate' => 'HY093')));
+               }
+
+               prepex(20, $db, 'DELETE FROM test');
+               prepex(21, $db, 'INSERT INTO test(id, label) VALUES (1, :placeholder), (2, :placeholder)',
+                       array(':placeholder' => 'row'));
+               $stmt = prepex(22, $db, 'SELECT id, label FROM test');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               $stmt = prepex(23, $db, 'SELECT id, label FROM test WHERE :placeholder IS NOT NULL',
+                       array(':placeholder' => 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                       printf("[024] '1' IS NOT NULL evaluates to true, expecting two rows, got %d rows\n", $tmp);
+
+               $stmt = prepex(25, $db, 'SELECT id, label FROM test WHERE :placeholder IS NULL',
+                       array(':placeholder' => 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                       printf("[026] '1' IS NOT NULL evaluates to true, expecting zero rows, got %d rows\n", $tmp);
+
+               prepex(27, $db, 'DROP TABLE IF EXISTS test');
+               prepex(28, $db, 'CREATE TABLE test(id INT, label CHAR(255)) ENGINE=MyISAM');
+               if (is_object(prepex(29, $db, 'CREATE FULLTEXT INDEX idx1 ON test(label)'))) {
+                       prepex(30, $db, 'INSERT INTO test(id, label) VALUES (1, :placeholder)',
+                               array(':placeholder' => 'MySQL is the best database in the world!'));
+                       prepex(31, $db, 'INSERT INTO test(id, label) VALUES (1, :placeholder)',
+                               array(':placeholder' => 'If I have the freedom to choose, I would always go again for the MySQL Server'));
+                       $stmt = prepex(32, $db, 'SELECT id, label FROM test WHERE MATCH label AGAINST (:placeholder)',
+                               array(':placeholder' => 'mysql'));
+                       /*
+                       Lets ignore this
+                       if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                               printf("[033] Expecting two rows, got %d rows\n", $tmp);
+                       */
+               }
+               prepex(34, $db, 'DELETE FROM test');
+               prepex(35, $db, 'INSERT INTO test(id, label) VALUES (1, :placeholder), (2, :placeholder)',
+                       array(':placeholder' => 'row'));
+/*
+               $stmt = prepex(36, $db, 'SELECT id, label FROM "test WHERE MATCH label AGAINST (:placeholder)',
+                       array(':placeholder' => 'row'),
+                       array('execute' => array('sqlstate' => '42000', 'mysql' => 1064)));
+*/
+               $stmt = prepex(37, $db, 'SELECT id, label FROM \'test WHERE MATCH label AGAINST (:placeholder)',
+                       array(':placeholder' => 'row'),
+                       array('execute' => array('sqlstate' => '42000', 'mysql' => 1064)));
+
+               $stmt = prepex(38, $db, 'SELECT id, label AS "label" FROM test WHERE label = :placeholder',
+                       array(':placeholder' => 'row'));
+
+               $sql = sprintf("SELECT id, label FROM test WHERE (label LIKE %s) AND (id = :placeholder)",
+                       $db->quote('%ro%'));
+               $stmt = prepex(39, $db, $sql,   array('placeholder' => -1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                               printf("[040] Expecting zero rows, got %d rows\n", $tmp);
+
+
+               $sql = sprintf("SELECT id, label FROM test WHERE  (id = :placeholder) OR (label LIKE %s)",
+                       $db->quote('%ro%'));
+               $stmt = prepex(41, $db, $sql,   array('placeholder' => 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                               printf("[042] Expecting two rows, got %d rows\n", $tmp);
+
+               $sql = "SELECT id, label FROM test WHERE id = :placeholder AND label = (SELECT label AS 'SELECT' FROM test WHERE id = :placeholder)";
+               $stmt = prepex(43, $db, $sql,   array('placeholder' => 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 1)
+                               printf("[044] Expecting onw row, got %d rows\n", $tmp);
+
+               // and now, the same with anonymous placeholders...
+               prepex(45, $db, 'DROP TABLE IF EXISTS test');
+               prepex(46, $db, sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+               prepex(47, $db, 'INSERT INTO test(id, label) VALUES(1, "?")');
+               $stmt = prepex(48, $db, 'SELECT label FROM test');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               prepex(49, $db, 'DELETE FROM test');
+               prepex(50, $db, 'INSERT INTO test(id, label) VALUES(1, "?")',
+                       array('first row'));
+               $stmt = prepex(51, $db, 'SELECT label FROM test');
+
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               prepex(52, $db, 'DELETE FROM test');
+               prepex(53, $db, 'INSERT INTO test(id, label) VALUES(1, ?)',
+                       array('first row'));
+               prepex(54, $db, 'INSERT INTO test(id, label) VALUES(2, ?)',
+                       array('second row'));
+               $stmt = prepex(55, $db, 'SELECT label FROM test');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // Is PDO fun?
+               prepex(56, $db, 'SELECT label FROM test WHERE ? > 1',
+                       array('id'));
+               prepex(57, $db, 'SELECT ? FROM test WHERE id > 1',
+                       array('id'));
+               prepex(58, $db, 'SELECT ? FROM test WHERE ? > ?',
+                       array('test'), array('execute' => array('sqlstate' => 'HY093')));
+
+               prepex(59, $db, 'SELECT ? FROM test WHERE ? > ?',
+                       array('id', 'label', 'value'));
+
+               for ($num_params = 2; $num_params < 100; $num_params++) {
+                       $params = array('a');
+                       for ($i = 1; $i < $num_params; $i++) {
+                               $params[] = 'some data';
+                       }
+                       prepex(60, $db, 'SELECT id, label FROM test WHERE label > ?',
+                               $params, array('execute' => array('sqlstate' => 'HY093')));
+               }
+
+               prepex(61, $db, 'DELETE FROM test');
+               prepex(62, $db, 'INSERT INTO test(id, label) VALUES (1, ?), (2, ?)',
+                       array('row', 'row'));
+               $stmt = prepex(63, $db, 'SELECT id, label FROM test');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               $stmt = prepex(64, $db, 'SELECT id, label FROM test WHERE ? IS NOT NULL',
+                       array(1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                       printf("[065] '1' IS NOT NULL evaluates to true, expecting two rows, got %d rows\n", $tmp);
+
+               $stmt = prepex(66, $db, 'SELECT id, label FROM test WHERE ? IS NULL',
+                       array(1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                       printf("[067] '1' IS NOT NULL evaluates to true, expecting zero rows, got %d rows\n", $tmp);
+
+               prepex(68, $db, 'DROP TABLE IF EXISTS test');
+               prepex(69, $db, 'CREATE TABLE test(id INT, label CHAR(255)) ENGINE=MyISAM');
+               if (is_object(prepex(70, $db, 'CREATE FULLTEXT INDEX idx1 ON test(label)'))) {
+                       prepex(71, $db, 'INSERT INTO test(id, label) VALUES (1, ?)',
+                               array('MySQL is the best database in the world!'));
+                       prepex(72, $db, 'INSERT INTO test(id, label) VALUES (1, ?)',
+                               array('If I have the freedom to choose, I would always go again for the MySQL Server'));
+                       $stmt = prepex(73, $db, 'SELECT id, label FROM test WHERE MATCH label AGAINST (?)',
+                               array('mysql'));
+                       /*
+                       Lets ignore that
+                       if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                               printf("[074] Expecting two rows, got %d rows\n", $tmp);
+                       */
+               }
+
+               prepex(74, $db, 'DELETE FROM test');
+               prepex(75, $db, 'INSERT INTO test(id, label) VALUES (1, ?), (2, ?)',
+                       array('row', 'row'));
+
+               $stmt = prepex(76, $db, 'SELECT id, label FROM "test WHERE MATCH label AGAINST (?)',
+                       array('row'),
+                       array('execute' => array('sqlstate' => '42000', 'mysql' => 1064)));
+
+               /*
+               TODO enable after fix
+               $stmt = prepex(37, $db, 'SELECT id, label FROM \'test WHERE MATCH label AGAINST (:placeholder)',
+                       array(':placeholder' => 'row'),
+                       array('execute' => array('sqlstate' => '42000', 'mysql' => 1064)));
+               */
+
+               $stmt = prepex(78, $db, 'SELECT id, label AS "label" FROM test WHERE label = ?',
+                       array('row'));
+
+               $sql = sprintf("SELECT id, label FROM test WHERE (label LIKE %s) AND (id = ?)",
+                       $db->quote('%ro%'));
+               $stmt = prepex(79, $db, $sql,   array(-1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                               printf("[080] Expecting zero rows, got %d rows\n", $tmp);
+
+
+               $sql = sprintf("SELECT id, label FROM test WHERE  (id = ?) OR (label LIKE %s)",
+                       $db->quote('%ro%'));
+               $stmt = prepex(81, $db, $sql,   array(1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                               printf("[082] Expecting two rows, got %d rows\n", $tmp);
+
+               $sql = "SELECT id, label FROM test WHERE id = ? AND label = (SELECT label AS 'SELECT' FROM test WHERE id = ?)";
+               $stmt = prepex(83, $db, $sql,   array(1, 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 1)
+                               printf("[084] Expecting one row, got %d rows\n", $tmp);
+
+               $sql = "SELECT id, label FROM test WHERE id = :placeholder AND label = (SELECT label AS 'SELECT' FROM test WHERE id = ?)";
+               $stmt = prepex(85, $db, $sql,   array(1, 1), array('execute' => array('sqlstate' => 'HY093')));
+               if (is_object($stmt) && count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                               printf("[086] Expecting no rows, got %d rows\n", $tmp);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--XFAIL--
+PDO's PS parser has some problems with invalid SQL and crashes from time to time
+(check with valgrind...)
+--EXPECTF--
+array(1) {
+  ["one"]=>
+  string(1) "1"
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(12) ":placeholder"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(12) ":placeholder"
+  }
+}
+array(2) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(9) "first row"
+  }
+  [1]=>
+  array(1) {
+    ["label"]=>
+    string(10) "second row"
+  }
+}
+array(2) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(3) "row"
+  }
+  [1]=>
+  array(2) {
+    ["id"]=>
+    string(1) "2"
+    ["label"]=>
+    string(3) "row"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(1) "?"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(1) "?"
+  }
+}
+array(2) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(9) "first row"
+  }
+  [1]=>
+  array(1) {
+    ["label"]=>
+    string(10) "second row"
+  }
+}
+array(2) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(3) "row"
+  }
+  [1]=>
+  array(2) {
+    ["id"]=>
+    string(1) "2"
+    ["label"]=>
+    string(3) "row"
+  }
+}
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_anonymous.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_anonymous.phpt
new file mode 100644 (file)
index 0000000..1f333b9
--- /dev/null
@@ -0,0 +1,78 @@
+--TEST--
+MySQL PDO->prepare(), emulated PS, anonymous placeholder
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to switch to emulated prepared statements, test will fail\n");
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES(1, "?")');
+               // you can bind as many values as you want no matter if they can be replaced or not
+               $stmt->execute(array('first row'));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[003] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               $stmt = $db->prepare('SELECT id, label FROM test');
+               $stmt->execute();
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // now the same with native PS
+               printf("now the same with native PS\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to switch off emulated prepared statements, test will fail\n");
+
+               $db->exec('DELETE FROM test');
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES(1, "?")');
+               // you can bind as many values as you want no matter if they can be replaced or not
+               $stmt->execute(array('first row'));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[005] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               $stmt = $db->prepare('SELECT id, label FROM test');
+               $stmt->execute();
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "?"
+  }
+}
+now the same with native PS
+[005] Execute has failed, 'HY093' array (
+  0 => 'HY093',
+)
+array(0) {
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_placeholder_everywhere.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_placeholder_everywhere.phpt
new file mode 100644 (file)
index 0000000..be83446
--- /dev/null
@@ -0,0 +1,75 @@
+--TEST--
+MySQL PDO->prepare(), emulated PS, anonymous placeholder
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+// TODO: This test is MySQL version specific - for whatever reason
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+               // native PS
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to switch off emulated prepared statements, test will fail\n");
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+               $db->exec('INSERT INTO test(id, label) VALUES (1, "row1")');
+
+               // So, what will happen? More placeholder but values and
+               // placeholders in interesting places...
+               $stmt = $db->prepare('SELECT ? FROM test WHERE ? > ?');
+               $stmt->execute(array('test'));
+               if ('00000' !== $stmt->errorCode()) {
+                       printf("[003] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+               }
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // now the same with emulated PS
+               printf("now the same with emulated PS\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to switch on emulated prepared statements, test will fail\n");
+
+               $stmt = $db->prepare('SELECT ? FROM test WHERE ? > ?');
+               $stmt->execute(array('test'));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[005] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec(sprintf('DROP TABLE IF EXISTS test'));
+       print "done!";
+?>
+--EXPECTF--
+Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number in %s on line %d
+[003] Execute has failed, 'HY093' array (
+  0 => 'HY093',
+)
+array(0) {
+}
+now the same with emulated PS
+
+Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens in %s on line %d
+
+Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number in %s on line 33
+[005] Execute has failed, 'HY093' array (
+  0 => 'HY093',
+)
+array(0) {
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_match_against.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_match_against.phpt
new file mode 100644 (file)
index 0000000..965d9cb
--- /dev/null
@@ -0,0 +1,48 @@
+--TEST--
+MySQL PDO->prepare(MATCH ... AGAINST (:placeholder)) - similar: http://bugs.php.net/bug.php?id=41876
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=MyISAM');
+               $db->exec('CREATE FULLTEXT INDEX idx1 ON test(label)');
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE MATCH label AGAINST (:placeholder)');
+               $stmt->execute(array(':placeholder' => 'row'));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE MATCH label AGAINST (:placeholder)');
+               $stmt->execute(array('placeholder' => 'row'));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE MATCH label AGAINST (?)');
+               $stmt->execute(array('row'));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       } catch (PDOException $e) {
+
+               printf("[001] %s, [%s} %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+--EXPECTF--
+array(0) {
+}
+array(0) {
+}
+array(0) {
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native.phpt
new file mode 100644 (file)
index 0000000..8743801
--- /dev/null
@@ -0,0 +1,380 @@
+--TEST--
+MySQL PDO->prepare(), native PS
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       function prepex($offset, &$db, $query, $input_params = null, $error_info = null, $suppress_warning = false) {
+
+               try {
+
+                       if ($suppress_warning || (is_array($error_info) && isset($error_info['prepare'])))
+                               $stmt = @$db->prepare($query);
+                       else
+                               $stmt = $db->prepare($query);
+
+                       if (is_array($error_info) && isset($error_info['prepare'])) {
+                               $tmp = $db->errorInfo();
+
+                               if (isset($error_info['prepare']['sqlstate']) &&
+                                       ($error_info['prepare']['sqlstate'] !== $tmp[0])) {
+                                       printf("[%03d] prepare() - expecting SQLSTATE '%s' got '%s'\n",
+                                               $offset, $error_info['prepare']['sqlstate'], $tmp[0]);
+                                       return false;
+                               }
+
+                               if (isset($error_info['prepare']['mysql']) &&
+                                       ($error_info['prepare']['mysql'] !== $tmp[1])) {
+                                       printf("[%03d] prepare() - expecting MySQL Code '%s' got '%s'\n",
+                                               $offset, $error_info['prepare']['mysql'], $tmp[0]);
+                                       return false;
+                               }
+
+                               return false;
+                       }
+
+                       if (!is_object($stmt))
+                               return false;
+
+                       if (is_null($input_params))
+                               $input_params = array();
+// 5.0.18, 5.1.14 @ 15
+// printf("[%03d]\n", $offset);
+                       if ($suppress_warning || (is_array($error_info) && isset($error_info['execute'])))
+                               $ret = @$stmt->execute($input_params);
+                       else
+                               $ret = $stmt->execute($input_params);
+
+                       if (!is_bool($ret))
+                               printf("[%03d] PDO::execute() should return a boolean value, got %s/%s\n",
+                                       var_export($ret, true), $ret);
+
+                       $tmp = $stmt->errorInfo();
+                       if (isset($tmp[1]) && ($tmp[1] == 2030)) {
+                               // Trying to hack around MySQL Server version dependent features
+                               // 2030 This command is not supported in the prepared statement protocol yet
+                               return false;
+                       }
+
+                       if (is_array($error_info) && isset($error_info['execute'])) {
+
+                               if (isset($error_info['execute']['sqlstate']) &&
+                                       ($error_info['execute']['sqlstate'] !== $tmp[0])) {
+                                       printf("[%03d] execute() - expecting SQLSTATE '%s' got '%s'\n",
+                                               $offset, $error_info['execute']['sqlstate'], $tmp[0]);
+                                       return false;
+                               }
+
+                               if (isset($error_info['execute']['mysql']) &&
+                                       ($error_info['execute']['mysql'] !== $tmp[1])) {
+                                       printf("[%03d] execute() - expecting MySQL Code '%s' got '%s'\n",
+                                               $offset, $error_info['execute']['mysql'], $tmp[0]);
+                                       return false;
+                               }
+
+                               return false;
+
+                       }
+
+               } catch (PDOException $e) {
+                       printf("[%03d] %s, [%s} %s\n",
+                               $offset, $e->getMessage(),
+                               $db->errorCode(), implode(' ', $db->errorInfo()));
+                       return false;
+               }
+
+               return $stmt;
+       }
+
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn off emulated prepared statements\n");
+
+               // TODO - that's PDO - you can prepare empty statements!
+               prepex(3, $db, '',
+                       array(), array('prepare' => array('sqlstate' => '42000')));
+
+               // lets be fair and do the most simple SELECT first
+               $stmt = prepex(4, $db, 'SELECT 1 as "one"');
+               if (MySQLPDOTest::isPDOMySQLnd())
+                       // native types - int
+                       $expected = array('one' => 1);
+               else
+                       // always strings, like STRINGIFY flag
+                       $expected = array('one' => '1');
+
+               $row = $stmt->fetch(PDO::FETCH_ASSOC);
+               if ($row !== $expected) {
+                       printf("[004a] Expecting %s got %s\n", var_export($expected, true), vat_export($row, true));
+               }
+
+               prepex(5, $db, 'DROP TABLE IF EXISTS test');
+               prepex(6, $db, sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+               prepex(7, $db, 'INSERT INTO test(id, label) VALUES(1, ":placeholder")');
+               $stmt = prepex(8, $db, 'SELECT label FROM test ORDER BY id ASC');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               prepex(9, $db, 'DELETE FROM test');
+               prepex(10, $db, 'INSERT INTO test(id, label) VALUES(1, :placeholder)',
+                       array(':placeholder' => 'first row'));
+               prepex(11, $db, 'INSERT INTO test(id, label) VALUES(2, :placeholder)',
+                       array(':placeholder' => 'second row'));
+               $stmt = prepex(12, $db, 'SELECT label FROM test ORDER BY id ASC');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // Is PDO fun?
+               $stmt = prepex(13, $db, 'SELECT label FROM test WHERE :placeholder > 1',
+                       array(':placeholder' => 'id'));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               for ($num_params = 2; $num_params < 100; $num_params++) {
+                       $params = array(':placeholder' => 'a');
+                       for ($i = 1; $i < $num_params; $i++) {
+                               $params[str_repeat('a', $i)] = 'some data';
+                       }
+                       prepex(16, $db, 'SELECT id, label FROM test WHERE label > :placeholder',
+                               $params, array('execute' => array('sqlstate' => 'HY093')));
+               }
+
+               $stmt = prepex(16, $db, 'SELECT id, label FROM test WHERE :placeholder IS NOT NULL',
+                       array(':placeholder' => 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                       printf("[017] '1' IS NOT NULL evaluates to true, expecting two rows, got %d rows\n", $tmp);
+
+               $stmt = prepex(18, $db, 'SELECT id, label FROM test WHERE :placeholder IS NULL',
+                       array(':placeholder' => 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                       printf("[019] '1' IS NOT NULL evaluates to true, expecting zero rows, got %d rows\n", $tmp);
+
+               prepex(20, $db, 'DROP TABLE IF EXISTS test');
+               prepex(21, $db, 'CREATE TABLE test(id INT, label CHAR(255)) ENGINE=MyISAM');
+               // Not every MySQL Server version supports this
+               if (is_object(prepex(22, $db, 'CREATE FULLTEXT INDEX idx1 ON test(label)', null, null, true))) {
+                       prepex(23, $db, 'INSERT INTO test(id, label) VALUES (1, :placeholder)',
+                               array(':placeholder' => 'MySQL is the best database in the world!'));
+                       prepex(24, $db, 'INSERT INTO test(id, label) VALUES (2, :placeholder)',
+                               array(':placeholder' => 'If I have the freedom to choose, I would always go again for the MySQL Server'));
+                       $stmt = prepex(25, $db, 'SELECT id, label FROM test WHERE MATCH label AGAINST (:placeholder)',
+                               array(':placeholder' => 'mysql'), null, true);
+                       if (is_object($stmt)) {
+                               /*
+                               Lets ignore this
+                               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                                       printf("[033] Expecting two rows, got %d rows\n", $tmp);
+                               */
+                               $stmt = prepex(26, $db, 'SELECT id, label FROM test ORDER BY id ASC');
+                               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                                       printf("[027] Expecting two rows, got %d rows\n", $tmp);
+
+                               if ($tmp[0]['label'] !== 'MySQL is the best database in the world!') {
+                                       printf("[028] INSERT seems to have failed, dumping data, check manually\n");
+                                       var_dump($tmp);
+                               }
+                       }
+               }
+
+               $db->exec('DELETE FROM test');
+               $db->exec('INSERT INTO test(id, label) VALUES (1, "row1")');
+               $db->exec('INSERT INTO test(id, label) VALUES (2, "row2")');
+
+               $sql = sprintf("SELECT id, label FROM test WHERE (label LIKE %s) AND (id = :placeholder)",
+                       $db->quote('%ro%'));
+               $stmt = prepex(29, $db, $sql,   array('placeholder' => -1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                               printf("[030] Expecting zero rows, got %d rows\n", $tmp);
+
+               $sql = sprintf("SELECT id, label FROM test WHERE  (id = :placeholder) OR (label LIKE %s)",
+                       $db->quote('%go%'));
+               $stmt = prepex(31, $db, $sql,   array('placeholder' => 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 1)
+                               printf("[032] Expecting one row, got %d rows\n", $tmp);
+
+               // and now, the same with anonymous placeholders...
+               prepex(33, $db, 'DROP TABLE IF EXISTS test');
+               prepex(34, $db, sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+               prepex(35, $db, 'INSERT INTO test(id, label) VALUES(1, "?")');
+               $stmt = prepex(36, $db, 'SELECT label FROM test');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               prepex(37, $db, 'DELETE FROM test');
+               prepex(38, $db, 'INSERT INTO test(id, label) VALUES(1, ?)',
+                       array('first row'));
+               prepex(39, $db, 'INSERT INTO test(id, label) VALUES(2, ?)',
+                       array('second row'));
+               $stmt = prepex(40, $db, 'SELECT label FROM test');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // Is PDO fun?
+               prepex(40, $db, 'SELECT label FROM test WHERE ? > 1',
+                       array('id'));
+               prepex(41, $db, 'SELECT ? FROM test WHERE id > 1',
+                       array('id'));
+               prepex(42, $db, 'SELECT ? FROM test WHERE ? > ?',
+                       array('id', 'label', 'value'));
+
+               for ($num_params = 2; $num_params < 100; $num_params++) {
+                       $params = array('a');
+                       for ($i = 1; $i < $num_params; $i++) {
+                               $params[] = 'some data';
+                       }
+                       prepex(43, $db, 'SELECT id, label FROM test WHERE label > ?',
+                               $params, array('execute' => array('sqlstate' => 'HY093')));
+               }
+
+               prepex(44, $db, 'DELETE FROM test');
+               prepex(45, $db, 'INSERT INTO test(id, label) VALUES (1, ?), (2, ?)',
+                       array('row', 'row'));
+               $stmt = prepex(46, $db, 'SELECT id, label FROM test');
+               $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC);
+               $exp = array(
+                       0 => array(
+                               "id"  => "1",
+                               "label" => "row"
+                       ),
+                       1 => array(
+                               "id" => "2",
+                               "label" => "row"
+                       ),
+               );
+
+               if (MySQLPDOTest::isPDOMySQLnd()) {
+                       // mysqlnd returns native types
+                       $exp[0]['id'] = 1;
+                       $exp[1]['id'] = 2;
+               }
+               if ($tmp !== $exp) {
+                       printf("[064] Results seem wrong. Please check dumps manually.\n");
+                       var_dump($exp);
+                       var_dump($tmp);
+               }
+
+               $stmt = prepex(47, $db, 'SELECT id, label FROM test WHERE ? IS NOT NULL',
+                       array(1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                       printf("[048] '1' IS NOT NULL evaluates to true, expecting two rows, got %d rows\n", $tmp);
+
+               $stmt = prepex(49, $db, 'SELECT id, label FROM test WHERE ? IS NULL',
+                       array(1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                       printf("[050] '1' IS NOT NULL evaluates to true, expecting zero rows, got %d rows\n", $tmp);
+
+               prepex(51, $db, 'DROP TABLE IF EXISTS test');
+               prepex(52, $db, 'CREATE TABLE test(id INT, label CHAR(255)) ENGINE=MyISAM');
+               if (is_object(prepex(53, $db, 'CREATE FULLTEXT INDEX idx1 ON test(label)', null, null, true))) {
+                       prepex(54, $db, 'INSERT INTO test(id, label) VALUES (1, ?)',
+                               array('MySQL is the best database in the world!'));
+                       prepex(55, $db, 'INSERT INTO test(id, label) VALUES (1, ?)',
+                               array('If I have the freedom to choose, I would always go again for the MySQL Server'));
+                       $stmt = prepex(56, $db, 'SELECT id, label FROM test WHERE MATCH label AGAINST (?)',
+                               array('mysql'), null, true);
+                       /*
+                       Lets ignore that
+                       if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                               printf("[074] Expecting two rows, got %d rows\n", $tmp);
+                       */
+               }
+
+               prepex(57, $db, 'DELETE FROM test');
+               prepex(58, $db, 'INSERT INTO test(id, label) VALUES (1, ?), (2, ?)',
+                       array('row1', 'row2'));
+
+               /*
+               TODO enable after fix
+               $stmt = prepex(37, $db, 'SELECT id, label FROM \'test WHERE MATCH label AGAINST (:placeholder)',
+                       array(':placeholder' => 'row'),
+                       array('execute' => array('sqlstate' => '42000', 'mysql' => 1064)));
+               */
+
+               $stmt = prepex(59, $db, 'SELECT id, label AS "label" FROM test WHERE label = ?',
+                       array('row1'));
+               $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC);
+               $exp = array(
+                       0 => array("id" => "1", "label" => "row1")
+               );
+
+               if (MySQLPDOTest::isPDOMySQLnd()) {
+                       // mysqlnd returns native types
+                       $exp[0]['id'] = 1;
+               }
+               if ($tmp !== $exp) {
+                       printf("[065] Results seem wrong. Please check dumps manually.\n");
+                       var_dump($exp);
+                       var_dump($tmp);
+               }
+
+               $sql = sprintf("SELECT id, label FROM test WHERE (label LIKE %s) AND (id = ?)",
+                       $db->quote('%ro%'));
+               $stmt = prepex(60, $db, $sql,   array(-1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 0)
+                               printf("[061] Expecting zero rows, got %d rows\n", $tmp);
+
+               $sql = sprintf("SELECT id, label FROM test WHERE  (id = ?) OR (label LIKE %s)",
+                       $db->quote('%ro%'));
+               $stmt = prepex(61, $db, $sql,   array(1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 2)
+                               printf("[062] Expecting two rows, got %d rows\n", $tmp);
+
+               $sql = "SELECT id, label FROM test WHERE id = ? AND label = (SELECT label AS 'SELECT' FROM test WHERE id = ?)";
+               $stmt = prepex(63, $db, $sql,   array(1, 1));
+               if (count(($tmp = $stmt->fetchAll(PDO::FETCH_ASSOC))) != 1)
+                               printf("[064] Expecting one row, got %d rows\n", $tmp);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(12) ":placeholder"
+  }
+}
+array(2) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(9) "first row"
+  }
+  [1]=>
+  array(1) {
+    ["label"]=>
+    string(10) "second row"
+  }
+}
+array(0) {
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(1) "?"
+  }
+}
+array(2) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(9) "first row"
+  }
+  [1]=>
+  array(1) {
+    ["label"]=>
+    string(10) "second row"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_clear_error.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_clear_error.phpt
new file mode 100644 (file)
index 0000000..33c699c
--- /dev/null
@@ -0,0 +1,91 @@
+--TEST--
+MySQL PDO->prepare(), native PS, clear line after error
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+
+               // We need to run the emulated version first. Native version will cause a fatal error
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               // INSERT a single row
+               $db->exec('INSERT INTO test(id, label) VALUES (1, "row1")');
+
+               $stmt = $db->prepare('SELECT unknown_column FROM test WHERE id > :placeholder ORDER BY id ASC');
+               $stmt->execute(array(':placeholder' => 0));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[003] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > :placeholder ORDER BY id ASC');
+               $stmt->execute(array(':placeholder' => 0));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[004] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // Native PS
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[005] Unable to turn off emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT unknown_column FROM test WHERE id > :placeholder ORDER BY id ASC');
+               $stmt->execute(array(':placeholder' => 0));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[006] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > :placeholder ORDER BY id ASC');
+               $stmt->execute(array(':placeholder' => 0));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[007] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Warning: PDOStatement::execute(): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'unknown_column' in 'field list' in %s on line %d
+[003] Execute has failed, '42S22' array (
+  0 => '42S22',
+  1 => 1054,
+  2 => 'Unknown column \'unknown_column\' in \'field list\'',
+)
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(4) "row1"
+  }
+}
+
+Warning: PDO::prepare(): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'unknown_column' in 'field list' in %s on line %d
+
+Fatal error: Call to a member function execute() on a non-object in %s on line %d
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_column.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_column.phpt
new file mode 100644 (file)
index 0000000..b497268
--- /dev/null
@@ -0,0 +1,44 @@
+--TEST--
+MySQL PDO->prepare(), native PS, named placeholder
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+       if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+               printf("[002] Unable to turn off emulated prepared statements\n");
+
+       $stmt = $db->prepare("SELECT :param FROM test ORDER BY id ASC LIMIT 1");
+       $stmt->execute(array(':param' => 'id'));
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       $db->prepare('SELECT :placeholder FROM test WHERE :placeholder > :placeholder');
+       $stmt->execute(array(':placeholder' => 'test'));
+
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+array(1) {
+  [0]=>
+  array(1) {
+    ["?"]=>
+    string(2) "id"
+  }
+}
+
+Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: parameter was not defined in %s on line %d
+array(0) {
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_dup_named_placeholder.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_dup_named_placeholder.phpt
new file mode 100644 (file)
index 0000000..a84c8d5
--- /dev/null
@@ -0,0 +1,136 @@
+--TEST--
+MySQL PDO->prepare(), native PS, named placeholder II
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(id INT, label1 CHAR(255), label2 CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn off emulated prepared statements\n");
+               printf("Native...\n");
+
+               // INSERT a single row
+               $stmt = $db->prepare('INSERT INTO test(id, label1, label2) VALUES (1, :placeholder, :placeholder)');
+
+               $stmt->execute(array(':placeholder' => 'row1'));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[003] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               // Ok, what has happened: anything inserted into the DB?
+               $stmt = $db->prepare('SELECT id, label1, label2 FROM test WHERE id = 1');
+               $stmt->execute();
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // Now the same with emulated PS.
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to turn on emulated prepared statements\n");
+               printf("Emulated...\n");
+
+               $stmt = $db->prepare('INSERT INTO test(id, label1, label2) VALUES(2, :placeholder, :placeholder)');
+               // No replacement shall be made
+               $stmt->execute(array(':placeholder' => 'row2'));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[005] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               // Now, what do we have in the DB?
+               $stmt = $db->prepare('SELECT id, label1, label2 FROM test WHERE id = 2');
+               $stmt->execute();
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               //
+               // Another variation of the theme
+               //
+
+               $db->exec('DELETE FROM test');
+               $db->exec('INSERT INTO test (id, label1, label2) VALUES (1, "row1", "row2")');
+               $sql = "SELECT id, label1 FROM test WHERE id = :placeholder AND label1 = (SELECT label1 AS 'SELECT' FROM test WHERE id = :placeholder)";
+
+               // emulated...
+               $stmt = $db->prepare($sql);
+               $stmt->execute(array(':placeholder' => 1));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[006] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // native...
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[007] Unable to turn off emulated prepared statements\n");
+               printf("Native...\n");
+
+               $stmt = $db->prepare($sql);
+               $stmt->execute(array(':placeholder' => 1));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[008] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Native...
+
+Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number in %s on line %d
+[003] Execute has failed, 'HY093' array (
+  0 => 'HY093',
+)
+array(0) {
+}
+Emulated...
+array(1) {
+  [0]=>
+  array(3) {
+    ["id"]=>
+    string(1) "2"
+    ["label1"]=>
+    string(4) "row2"
+    ["label2"]=>
+    string(4) "row2"
+  }
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label1"]=>
+    string(4) "row1"
+  }
+}
+Native...
+
+Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number in %s on line %d
+[008] Execute has failed, 'HY093' array (
+  0 => 'HY093',
+)
+array(0) {
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_mixed_style.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_mixed_style.phpt
new file mode 100644 (file)
index 0000000..c8d1a5b
--- /dev/null
@@ -0,0 +1,35 @@
+--TEST--
+MySQL PDO->prepare(), native PS, mixed, wired style
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+       if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+               printf("[002] Unable to turn off emulated prepared statements\n");
+
+       $stmt = $db->query('DELETE FROM test');
+       $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (1, ?), (2, ?)');
+       $stmt->execute(array('a', 'b'));
+       $stmt = $db->prepare("SELECT id, label FROM test WHERE id = :placeholder AND label = (SELECT label AS 'SELECT' FROM test WHERE id = ?)");
+       $stmt->execute(array(1, 1));
+       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Warning: PDO::prepare(): SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters in %s on line %d
+
+Warning: PDO::prepare(): SQLSTATE[HY093]: Invalid parameter number in %s on line %d
+
+Fatal error: Call to a member function execute() on a non-object in %s on line %d
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_named_placeholder.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_named_placeholder.phpt
new file mode 100644 (file)
index 0000000..6f673c7
--- /dev/null
@@ -0,0 +1,85 @@
+--TEST--
+MySQL PDO->prepare(), native PS, named placeholder
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn off emulated prepared statements\n");
+
+               // INSERT a single row
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (100, ":placeholder")');
+
+               // Yes, there is no placeholder to bind to and named placeholder
+               // do not work with MySQL native PS, but lets see what happens!
+               // The ':placeholder' is a string constant in the INSERT statement.
+               // I would expect to get an error message, but this is not what happens.
+               $stmt->execute(array(':placeholder' => 'row1'));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[003] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               // Ok, what has happened: anything inserted into the DB?
+               $stmt = $db->prepare('SELECT id, label FROM test');
+               $stmt->execute();
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // Now the same with emulated PS.
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to turn on emulated prepared statements\n");
+
+               // Note that the "named placeholder" is enclosed by double quotes.
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES(101, ":placeholder")');
+               // No replacement shall be made
+               $stmt->execute(array(':placeholder' => 'row1'));
+               // Again, I'd like to see an error message
+               if ('00000' !== $stmt->errorCode())
+                       printf("[005] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               // Now, what do we have in the DB?
+               $stmt = $db->prepare('SELECT id, label FROM test ORDER BY id');
+               $stmt->execute();
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+[003] Execute has failed, 'HY093' array (
+  0 => 'HY093',
+)
+array(0) {
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(3) "101"
+    ["label"]=>
+    string(12) ":placeholder"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_placeholder_everywhere.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_placeholder_everywhere.phpt
new file mode 100644 (file)
index 0000000..aa7815d
--- /dev/null
@@ -0,0 +1,85 @@
+--TEST--
+MySQL PDO->prepare(),native PS, anonymous placeholder
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to switch on emulated prepared statements, test will fail\n");
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(id INT, label CHAR(255)) ENGINE=%s', PDO_MYSQL_TEST_ENGINE));
+               $db->exec('INSERT INTO test(id, label) VALUES (1, "row1")');
+
+               $stmt = $db->prepare('SELECT ?, id, label FROM test WHERE ? = ? ORDER BY id ASC');
+               $stmt->execute(array('id', 'label', 'label'));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[003] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               // now the same with native PS
+               printf("now the same with native PS\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to switch off emulated prepared statements, test will fail\n");
+
+               $stmt = $db->prepare('SELECT ?, id, label FROM test WHERE ? = ? ORDER BY id ASC');
+               $stmt->execute(array('id', 'label', 'label'));
+               if ('00000' !== $stmt->errorCode())
+                       printf("[005] Execute has failed, %s %s\n",
+                               var_export($stmt->errorCode(), true),
+                               var_export($stmt->errorInfo(), true));
+
+               $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC);
+               if (!MySQLPDOTest::isPDOMySQLnd()) {
+                       if (isset($tmp[0]['id'])) {
+                               // libmysql should return a string here whereas mysqlnd returns a native int
+                               if (gettype($tmp[0]['id']) == 'string')
+                                       // convert to int for the test output...
+                                       settype($tmp[0]['id'], 'integer');
+                       }
+               }
+               var_dump($tmp);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(4) "row1"
+  }
+}
+now the same with native PS
+array(1) {
+  [0]=>
+  array(3) {
+    ["?"]=>
+    string(2) "id"
+    ["id"]=>
+    int(1)
+    ["label"]=>
+    string(4) "row1"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_rollback.phpt b/ext/pdo_mysql/tests/pdo_mysql_rollback.phpt
new file mode 100644 (file)
index 0000000..358d4a7
--- /dev/null
@@ -0,0 +1,90 @@
+--TEST--
+PDO::rollBack()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+if (false == MySQLPDOTest::detect_transactional_mysql_engine($db))
+       die("skip Transactional engine not found");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
+
+       $db->beginTransaction();
+
+       $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+       $num = $row['_num'];
+
+       $db->query('INSERT INTO test(id, label) VALUES (100, "z")');
+       $num++;
+       $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+       if ($row['_num'] != $num)
+               printf("[001] INSERT has failed, test will fail\n");
+
+       $db->rollBack();
+       $num--;
+       $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+       if ($row['_num'] != $num)
+               printf("[002] ROLLBACK has failed\n");
+
+       $db->beginTransaction();
+       $db->query('INSERT INTO test(id, label) VALUES (100, "z")');
+       $db->query('DROP TABLE IF EXISTS test2');
+       $db->query('CREATE TABLE test2(id INT)');
+       $num++;
+       $db->rollBack();
+       $row = $db->query('SELECT COUNT(*) AS _num FROM test')->fetch(PDO::FETCH_ASSOC);
+       if ($row['_num'] != $num)
+               printf("[002] ROLLBACK should have no effect because of the implicit COMMIT
+                       triggered by DROP/CREATE TABLE\n");
+
+
+       $db->query('DROP TABLE IF EXISTS test2');
+       $db->query('CREATE TABLE test2(id INT) ENGINE=MyISAM');
+       $db->beginTransaction();
+       $db->query('INSERT INTO test2(id) VALUES (1)');
+       $db->rollBack();
+       $row = $db->query('SELECT COUNT(*) AS _num FROM test2')->fetch(PDO::FETCH_ASSOC);
+       if ($row['_num'] != 1)
+               printf("[003] ROLLBACK should have no effect\n");
+
+       $db->query('DROP TABLE IF EXISTS test2');
+
+       $db->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);
+       $db->beginTransaction();
+       $db->query('DELETE FROM test');
+       $db->rollBack();
+       var_dump($db->getAttribute(PDO::ATTR_AUTOCOMMIT));
+
+       $db->setAttribute(PDO::ATTR_AUTOCOMMIT, 0);
+       $db->beginTransaction();
+       $db->query('DELETE FROM test');
+       $db->rollBack();
+       var_dump($db->getAttribute(PDO::ATTR_AUTOCOMMIT));
+
+       $db->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);
+       $db->beginTransaction();
+       $db->query('DELETE FROM test');
+       $db->commit();
+       var_dump($db->getAttribute(PDO::ATTR_AUTOCOMMIT));
+
+       $db->setAttribute(PDO::ATTR_AUTOCOMMIT, 0);
+       $db->beginTransaction();
+       $db->query('DELETE FROM test');
+       $db->commit();
+       var_dump($db->getAttribute(PDO::ATTR_AUTOCOMMIT));
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       $db->exec('DROP TABLE IF EXISTS test2');
+       print "done!";
+--EXPECTF--
+int(1)
+int(0)
+int(1)
+int(0)
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_bindcolumn.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_bindcolumn.phpt
new file mode 100644 (file)
index 0000000..394ed4f
--- /dev/null
@@ -0,0 +1,110 @@
+--TEST--
+MySQL PDOStatement->bindColumn()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT id, label FROM test ORDER BY id ASC LIMIT 2');
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[003] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[004] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $data = array();
+               while ($stmt->fetch(PDO::FETCH_BOUND)) {
+                       printf("id = %s (%s) / label = %s (%s)\n",
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+                       $data[] = array('id' => $id, 'label' => $label);
+               }
+
+               $stmt = $db->query('SELECT id, label FROM test ORDER BY id ASC LIMIT 2');
+               $index = 0;
+               while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+                       if ($row['id'] != $data[$index]['id']) {
+                               printf("[005] Fetch bound and fetch assoc differ - column 'id', bound: %s/%s, assoc: %s/%s\n",
+                                       var_export($data[$index]['id'], true), gettype($data[$index]['id']),
+                                       var_export($row['id'], true), gettype($row['id']));
+                       }
+                       if ($row['label'] != $data[$index]['label']) {
+                               printf("[006] Fetch bound and fetch assoc differ - column 'label', bound: %s/%s, assoc: %s/%s\n",
+                                       var_export($data[$index]['label'], true), gettype($data[$index]['label']),
+                                       var_export($row['label'], true), gettype($row['label']));
+                       }
+                       $index++;
+               }
+
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[007] Unable to turn off emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT id, label FROM test ORDER BY id ASC LIMIT 2, 2');
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[008] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[009] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $data = array();
+               while ($stmt->fetch(PDO::FETCH_BOUND)) {
+                       printf("id = %s (%s) / label = %s (%s)\n",
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+                       $data[] = array('id' => $id, 'label' => $label);
+               }
+
+               $stmt = $db->query('SELECT id, label FROM test ORDER BY id ASC LIMIT 2, 2');
+               $index = 0;
+               while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+                       if ($row['id'] != $data[$index]['id']) {
+                               printf("[010] Fetch bound and fetch assoc differ - column 'id', bound: %s/%s, assoc: %s/%s\n",
+                                       var_export($data[$index]['id'], true), gettype($data[$index]['id']),
+                                       var_export($row['id'], true), gettype($row['id']));
+                       }
+                       if ($row['label'] != $data[$index]['label']) {
+                               printf("[011] Fetch bound and fetch assoc differ - column 'label', bound: %s/%s, assoc: %s/%s\n",
+                                       var_export($data[$index]['label'], true), gettype($data[$index]['label']),
+                                       var_export($row['label'], true), gettype($row['label']));
+                       }
+                       $index++;
+               }
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+id = 1 (integer) / label = 'a' (string)
+id = 2 (integer) / label = 'b' (string)
+id = 3 (integer) / label = 'c' (string)
+id = 4 (integer) / label = 'd' (string)
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_bindparam.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_bindparam.phpt
new file mode 100644 (file)
index 0000000..993ea04
--- /dev/null
@@ -0,0 +1,155 @@
+--TEST--
+MySQL PDOStatement->bindParam()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
+
+       MySQLPDOTest::createTestTable($db);
+
+       function pdo_mysql_stmt_bindparam($db, $offset) {
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindParam(1, $in))
+                       printf("[%03d + 1] Cannot bind parameter, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[%03d + 2] Cannot bind integer column, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[%03d + 3] Cannot bind string column, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               printf("Same again...\n");
+               $stmt->execute();
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               // NULL values
+               printf("NULL...\n");
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (100, ?)');
+               $label = null;
+               if (!$stmt->bindParam(1, $label))
+                       printf("[%03d + 4] Cannot bind parameter, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->execute())
+                       printf("[%03d + 5] Cannot execute statement, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               /* NOTE: you cannot use PDO::query() with unbuffered, native PS - see extra test */
+               $stmt = $db->prepare('SELECT id, NULL AS _label FROM test WHERE label IS NULL');
+               $stmt->execute();
+
+               $id = $label = 'bogus';
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[%03d + 6] Cannot bind NULL column, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[%03d + 3] Cannot bind string column, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+       }
+
+       try {
+               printf("Emulated PS...\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               printf("Buffered...\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+               pdo_mysql_stmt_bindparam($db, 3);
+
+               printf("Unbuffered...\n");
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+               pdo_mysql_stmt_bindparam($db, 4);
+
+               printf("Native PS...\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to turn off emulated prepared statements\n");
+
+               printf("Buffered...\n");
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+               pdo_mysql_stmt_bindparam($db, 5);
+
+               printf("Unbuffered...\n");
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+               pdo_mysql_stmt_bindparam($db, 6);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Emulated PS...
+Buffered...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Same again...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+NULL...
+in = 0 -> id = 100 (integer) / label = NULL (NULL)
+Unbuffered...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Same again...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+NULL...
+in = 0 -> id = 100 (integer) / label = NULL (NULL)
+Native PS...
+Buffered...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Same again...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+NULL...
+in = 0 -> id = 100 (integer) / label = NULL (NULL)
+Unbuffered...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Same again...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+NULL...
+in = 0 -> id = 100 (integer) / label = NULL (NULL)
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_bindparam_types.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_bindparam_types.phpt
new file mode 100644 (file)
index 0000000..64d4df0
--- /dev/null
@@ -0,0 +1,169 @@
+--TEST--
+MySQL PDOStatement->bindParam() - SQL column types
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       function pdo_mysql_stmt_bindparam_types_do($db, $offset, $native, $sql_type, $value) {
+
+                       if ($native)
+                               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+                       else
+                               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+
+                       $db->exec('DROP TABLE IF EXISTS test');
+                       $sql = sprintf('CREATE TABLE test(id INT, label %s) ENGINE=%s', $sql_type, MySQLPDOTest::getTableEngine());
+                       if ((!$stmt = @$db->prepare($sql)) || (!@$stmt->execute()))
+                               // Server might not support column type - skip it
+                               return true;
+
+                       $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (1, ?)');
+                       if (!$stmt->bindParam(1, $value)) {
+                               printf("[%03d/%s + 1] %s\n", $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($stmt->errorInfo(), true));
+                               return false;
+                       }
+                       if (!$stmt->execute()) {
+                               printf("[%03d/%s + 2] %s\n", $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($stmt->errorInfo(), true));
+                               return false;
+                       }
+
+                       $stmt = $db->query('SELECT id, label FROM test');
+                       $id = $label = null;
+                       if (!$stmt->bindColumn(1, $id)) {
+                               printf("[%03d/%s + 3] %s\n", $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($stmt->errorInfo(), true));
+                               return false;
+                       }
+                       if (!$stmt->bindColumn(2, $label)) {
+                               printf("[%03d/%s + 4] %s\n", $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($stmt->errorInfo(), true));
+                               return false;
+                       }
+
+                       if (!$stmt->fetch(PDO::FETCH_BOUND)) {
+                               printf("[%03d/%s + 5] %s\n", $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($stmt->errorInfo(), true));
+                               return false;
+                       }
+                       $stmt->closeCursor();
+
+                       if ($label != $value) {
+                               printf("[%03d/%s + 6] Got %s expecting %s - plase check manually\n",
+                                       $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($label, true), var_export($value, true));
+                               // fall through
+                       }
+
+                       $stmt->execute();
+                       $row = $stmt->fetch(PDO::FETCH_ASSOC);
+                       if (empty($row)) {
+                               printf("[%03d/%s + 7] %s\n", $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($stmt->errorInfo(), true));
+                               return false;
+                       }
+
+                       if ($row['label'] != $value) {
+                               printf("[%03d/%s + 8] Got %s expecting %s - plase check manually\n",
+                                       $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($row['label'], true), var_export($value, true));
+                               return false;
+                       }
+
+                       if ($row['label'] != $label) {
+                               printf("[%03d/%s + 9] Got %s from FETCH_ASSOC and %s from FETCH_BOUND- plase check manually\n",
+                                       $offset, ($native) ? 'native' : 'emulated',
+                                       var_export($row['label'], true), var_export($value, true));
+                               return false;
+                       }
+
+                       $db->exec('DROP TABLE IF EXISTS test');
+                       return true;
+       }
+
+       function pdo_mysql_stmt_bindparam_types($db, $offset, $sql_type, $value) {
+
+               pdo_mysql_stmt_bindparam_types_do($db, $offset, true, $sql_type, $value);
+               pdo_mysql_stmt_bindparam_types_do($db, $offset, false, $sql_type, $value);
+
+       }
+
+       try {
+
+               // pdo_mysql_stmt_bindparam_types($db, 2, 'BIT(8)', 1);
+               pdo_mysql_stmt_bindparam_types($db, 3, 'TINYINT', -127);
+               pdo_mysql_stmt_bindparam_types($db, 4, 'TINYINT UNSIGNED', 255);
+               pdo_mysql_stmt_bindparam_types($db, 5, 'BOOLEAN', 1);
+               pdo_mysql_stmt_bindparam_types($db, 6, 'SMALLINT', -32768);
+               pdo_mysql_stmt_bindparam_types($db, 7, 'SMALLINT UNSIGNED', 65535);
+               pdo_mysql_stmt_bindparam_types($db, 8, 'MEDIUMINT', -8388608);
+               pdo_mysql_stmt_bindparam_types($db, 9, 'MEDIUMINT UNSIGNED', 16777215);
+               pdo_mysql_stmt_bindparam_types($db, 10, 'INT', -2147483648);
+               pdo_mysql_stmt_bindparam_types($db, 11, 'INT UNSIGNED', 4294967295);
+               pdo_mysql_stmt_bindparam_types($db, 12, 'BIGINT',  -1000);
+               pdo_mysql_stmt_bindparam_types($db, 13, 'BIGINT UNSIGNED', 1000);
+               pdo_mysql_stmt_bindparam_types($db, 14, 'REAL', -1000);
+               pdo_mysql_stmt_bindparam_types($db, 15, 'REAL UNSIGNED', 1000);
+               pdo_mysql_stmt_bindparam_types($db, 16, 'REAL ZEROFILL', '0000000000000000000000');
+               pdo_mysql_stmt_bindparam_types($db, 17, 'REAL UNSIGNED ZEROFILL', '0000000000000000000010');
+               pdo_mysql_stmt_bindparam_types($db, 18, 'DOUBLE', -1000);
+               pdo_mysql_stmt_bindparam_types($db, 19, 'DOUBLE UNSIGNED', 1000);
+               pdo_mysql_stmt_bindparam_types($db, 20, 'DOUBLE ZEROFILL', '000000000000');
+               pdo_mysql_stmt_bindparam_types($db, 21, 'DOUBLE ZEROFILL UNSIGNED', '000000001000');
+               pdo_mysql_stmt_bindparam_types($db, 22, 'FLOAT', -1000);
+               pdo_mysql_stmt_bindparam_types($db, 23, 'FLOAT UNSIGNED', 1000);
+               pdo_mysql_stmt_bindparam_types($db, 24, 'FLOAT ZEROFILL', '000000000000');
+               pdo_mysql_stmt_bindparam_types($db, 25, 'FLOAT ZEROFILL UNSIGNED', '000000001000');
+               pdo_mysql_stmt_bindparam_types($db, 26, 'DECIMAL', -1000);
+               pdo_mysql_stmt_bindparam_types($db, 27, 'DECIMAL UNSIGNED', 1000);
+               pdo_mysql_stmt_bindparam_types($db, 28, 'DECIMAL ZEROFILL', '000000000000');
+               pdo_mysql_stmt_bindparam_types($db, 29, 'DECIMAL ZEROFILL UNSIGNED', '000000001000');
+               pdo_mysql_stmt_bindparam_types($db, 30, 'NUMERIC', -1000);
+               pdo_mysql_stmt_bindparam_types($db, 31, 'NUMERIC UNSIGNED', 1000);
+               pdo_mysql_stmt_bindparam_types($db, 32, 'NUMERIC ZEROFILL', '000000000000');
+               pdo_mysql_stmt_bindparam_types($db, 33, 'NUMERIC ZEROFILL UNSIGNED', '000000001000');
+               pdo_mysql_stmt_bindparam_types($db, 34, 'DATE', '2008-04-23');
+               pdo_mysql_stmt_bindparam_types($db, 35, 'TIME', '16:43:12');
+               pdo_mysql_stmt_bindparam_types($db, 36, 'TIMESTAMP', '2008-04-23 16:44:53');
+               pdo_mysql_stmt_bindparam_types($db, 37, 'DATETIME', '2008-04-23 16:44:53');
+               pdo_mysql_stmt_bindparam_types($db, 38, 'YEAR', '2008');
+               pdo_mysql_stmt_bindparam_types($db, 39, 'CHAR(1)', 'a');
+               pdo_mysql_stmt_bindparam_types($db, 40, 'CHAR(255)', 'abc');
+               pdo_mysql_stmt_bindparam_types($db, 41, 'VARCHAR(255)', str_repeat('a', 255));
+               pdo_mysql_stmt_bindparam_types($db, 42, 'BINARY(255)', str_repeat('a', 255));
+               pdo_mysql_stmt_bindparam_types($db, 43, 'VARBINARY(255)', str_repeat('a', 255));
+               pdo_mysql_stmt_bindparam_types($db, 44, 'TINYBLOB', str_repeat('a', 255));
+               pdo_mysql_stmt_bindparam_types($db, 45, 'BLOB', str_repeat('b', 300));
+               pdo_mysql_stmt_bindparam_types($db, 46, 'MEDIUMBLOB', str_repeat('b', 300));
+               pdo_mysql_stmt_bindparam_types($db, 47, 'LONGBLOB', str_repeat('b', 300));
+               pdo_mysql_stmt_bindparam_types($db, 48, 'TINYTEXT', str_repeat('c', 255));
+               pdo_mysql_stmt_bindparam_types($db, 49, 'TINYTEXT BINARY', str_repeat('c', 255));
+               pdo_mysql_stmt_bindparam_types($db, 50, 'TEXT', str_repeat('d', 300));
+               pdo_mysql_stmt_bindparam_types($db, 51, 'TEXT BINARY', str_repeat('d', 300));
+               pdo_mysql_stmt_bindparam_types($db, 52, 'MEDIUMTEXT', str_repeat('d', 300));
+               pdo_mysql_stmt_bindparam_types($db, 53, 'MEDIUMTEXT BINARY', str_repeat('d', 300));
+               pdo_mysql_stmt_bindparam_types($db, 54, 'LONGTEXT', str_repeat('d', 300));
+               pdo_mysql_stmt_bindparam_types($db, 55, 'LONGTEXT BINARY', str_repeat('d', 300));
+               pdo_mysql_stmt_bindparam_types($db, 56, "ENUM('yes', 'no') DEFAULT 'yes'", "no");
+               pdo_mysql_stmt_bindparam_types($db, 57, "SET('yes', 'no') DEFAULT 'yes'", "no");
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_bindvalue.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_bindvalue.phpt
new file mode 100644 (file)
index 0000000..6dc557d
--- /dev/null
@@ -0,0 +1,332 @@
+--TEST--
+MySQL PDOStatement->bindValue()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       printf("Testing native PS...\n");
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn off emulated prepared statements\n");
+
+               printf("Binding variable...\n");
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindValue(1, $in))
+                       printf("[003] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[004] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[005] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               printf("Binding value and not variable...\n");
+               if (!$stmt->bindValue(1, 0))
+                       printf("[006] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[007] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[008] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               printf("Binding variable which references another variable...\n");
+               $in = 0;
+               $in_ref = &$in;
+               if (!$stmt->bindValue(1, $in_ref))
+                       printf("[009] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[010] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[011] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+
+               printf("Binding a variable and a value...\n");
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? AND id <= ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindValue(1, $in))
+                       printf("[012] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindValue(2, 2))
+                       printf("[013] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[014] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[015] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               printf("Binding a variable to two placeholders and changing the variable value in between the binds...\n");
+               // variable value change shall have no impact
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? AND id <= ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindValue(1, $in))
+                       printf("[016] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $in = 2;
+               if (!$stmt->bindValue(2, $in))
+                       printf("[017] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[018] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[019] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       printf("Testing emulated PS...\n");
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               printf("Binding variable...\n");
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindValue(1, $in))
+                       printf("[003] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[004] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[005] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               printf("Binding value and not variable...\n");
+               if (!$stmt->bindValue(1, 0))
+                       printf("[006] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[007] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[008] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               printf("Binding variable which references another variable...\n");
+               $in = 0;
+               $in_ref = &$in;
+               if (!$stmt->bindValue(1, $in_ref))
+                       printf("[009] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[010] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[011] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+
+               printf("Binding a variable and a value...\n");
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? AND id <= ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindValue(1, $in))
+                       printf("[012] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindValue(2, 2))
+                       printf("[013] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[014] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[015] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               printf("Binding a variable to two placeholders and changing the variable value in between the binds...\n");
+               // variable value change shall have no impact
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? AND id <= ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindValue(1, $in))
+                       printf("[016] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $in = 2;
+               if (!$stmt->bindValue(2, $in))
+                       printf("[017] Cannot bind value, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[018] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[019] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Testing native PS...
+Binding variable...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Binding value and not variable...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Binding variable which references another variable...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Binding a variable and a value...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Binding a variable to two placeholders and changing the variable value in between the binds...
+in = 2 -> id = 1 (integer) / label = 'a' (string)
+in = 2 -> id = 2 (integer) / label = 'b' (string)
+Testing emulated PS...
+Binding variable...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Binding value and not variable...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Binding variable which references another variable...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Binding a variable and a value...
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Binding a variable to two placeholders and changing the variable value in between the binds...
+in = 2 -> id = 1 (integer) / label = 'a' (string)
+in = 2 -> id = 2 (integer) / label = 'b' (string)
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_blobfromsteam.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_blobfromsteam.phpt
new file mode 100644 (file)
index 0000000..88b847c
--- /dev/null
@@ -0,0 +1,143 @@
+--TEST--
+MySQL PDOStatement - inserting BLOB from stream
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+
+$tmp = MySQLPDOTest::getTempDir();
+if (!$tmp)
+       die("skip Can't create temporary file");
+
+$file = $tmp . DIRECTORY_SEPARATOR . 'pdoblob.tst';
+$fp = fopen($file, 'w');
+if (!$fp)
+       die("skip Can't create temporary file");
+
+if (4 != fwrite($fp, 'test')) {
+       die("skip Can't create temporary file");
+}
+fclose($fp);
+clearstatcache();
+
+if (!file_exists($file))
+       die("skip Can't create temporary file");
+
+unlink($file);
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function blob_from_stream($offset, $db, $file, $blob) {
+
+                       @unlink($file);
+                       clearstatcache();
+                       if (file_exists($file)) {
+                               printf("[%03d + 1] Cannot remove old test file\n", $offset);
+                               return false;
+                       }
+
+                       $fp = fopen($file, 'w');
+                       if (!$fp || !fwrite($fp, $blob)) {
+                               printf("[%03d + 2] Cannot create test file '%s'\n", $offset, $file);
+                               return false;
+                       }
+
+                       fclose($fp);
+                       clearstatcache();
+                       if (!file_exists($file)) {
+                               printf("[%03d + 3] Failed to create test file '%s'\n", $offset, $file);
+                               return false;
+                       }
+
+                       $db->exec('DROP TABLE IF EXISTS test');
+                       $sql = sprintf('CREATE TABLE test(id INT, label BLOB) ENGINE=%s', PDO_MYSQL_TEST_ENGINE);
+                       $db->exec($sql);
+
+                       if (!$stmt = $db->prepare('INSERT INTO test(id, label) VALUES (?, ?)')) {
+                               printf("[%03d + 4] %s\n", $offset, var_export($db->errorInfo(), true));
+                               return false;
+                       }
+
+                       $fp = fopen($file, 'r');
+                       if (!$fp) {
+                               printf("[%03d + 5] Cannot create test file '%s'\n", $offset, $file);
+                               return false;
+                       }
+
+
+                       $id = 1;
+                       $stmt->bindParam(1, $id);
+                       if (true !== ($tmp = $stmt->bindParam(2, $fp, PDO::PARAM_LOB))) {
+                               printf("[%03d + 6] Expecting true, got %s. %s\n",
+                                       $offset,
+                                       var_export($tmp, true),
+                                       var_export($db->errorInfo(), true));
+                               return false;
+                       }
+
+                       if (true !== $stmt->execute()) {
+                               printf("[%03d + 7] Failed to INSERT data, %s\n", $offset, var_export($stmt->errorInfo(), true));
+                               return false;
+                       }
+
+                       $stmt2 = $db->query('SELECT id, label FROM test WHERE id = 1');
+                       $row = $stmt2->fetch(PDO::FETCH_ASSOC);
+                       if ($row['label'] != $blob) {
+                               printf("[%03d + 8] INSERT and/or SELECT has failed, dumping data.\n", $offset);
+                               var_dump($row);
+                               var_dump($blob);
+                               return false;
+                       }
+
+                       // Lets test the chr(0) handling in case the streaming has failed:
+                       // is the bug about chr(0) or the streaming...
+                       $db->exec('DELETE FROM test');
+                       $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (?, ?)');
+                       $stmt->bindParam(1, $id);
+                       $stmt->bindParam(2, $blob);
+                       if (true !== $stmt->execute())
+                               printf("[%03d + 9] %s\n", $offset, var_export($stmt->errorInfo(), true));
+
+                       $stmt2 = $db->query('SELECT id, label FROM test WHERE id = 1');
+                       $row = $stmt2->fetch(PDO::FETCH_ASSOC);
+                       if ($row['label'] != $blob) {
+                               printf("[%03d + 10] INSERT and/or SELECT has failed, dumping data.\n", $offset);
+                               var_dump($row);
+                               var_dump($blob);
+                               return false;
+                       }
+
+                       return true;
+       }
+
+       $db = MySQLPDOTest::factory();
+       $blob = 'I am a mighty BLOB!' . chr(0) . "I am a binary thingie!";
+       $tmp = MySQLPDOTest::getTempDir();
+       $file = $tmp . DIRECTORY_SEPARATOR . 'pdoblob.tst';
+
+       try {
+
+               printf("Emulated PS...\n");
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+               blob_from_stream(10, $db, $file, $blob);
+
+               printf("Native PS...\n");
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+               blob_from_stream(30, $db, $file, $blob);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       @unlink($file);
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Emulated PS...
+Native PS...
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_blobs.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_blobs.phpt
new file mode 100644 (file)
index 0000000..2914583
--- /dev/null
@@ -0,0 +1,91 @@
+--TEST--
+MySQL Prepared Statements and BLOBs
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       $blobs = array(
+               'TINYBLOB'              => 255,
+               'TINYTEXT'              => 255,
+               'BLOB'                          => 32767,
+               'TEXT'                          => 32767,
+               'MEDIUMBLOB'    => 100000,
+               'MEDIUMTEXT'    => 100000,
+               'LONGBLOB'              => 100000,
+               'LONGTEXT'              => 100000,
+       );
+
+       function test_blob($db, $offset, $sql_type, $test_len) {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(id INT, label %s) ENGINE=%s', $sql_type, PDO_MYSQL_TEST_ENGINE));
+
+               $value = str_repeat('a', $test_len);
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (?, ?)');
+               $stmt->bindValue(1, 1);
+               $stmt->bindValue(2, $value);
+               if (!$stmt->execute()) {
+                       printf("[%03d + 1] %d %s\n",
+                               $offset, $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+                       return false;
+               }
+
+               $stmt = $db->query('SELECT id, label FROM test');
+               $id = $label = NULL;
+               $stmt->bindColumn(1, $id, PDO::PARAM_INT);
+               $stmt->bindColumn(2, $label, PDO::PARAM_LOB);
+
+               if (!$stmt->fetch(PDO::FETCH_BOUND)) {
+                       printf("[%03d + 2] %d %s\n",
+                               $offset, $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+                       return false;
+               }
+
+               if ($label !== $value) {
+                       printf("[%03d + 3] Returned value seems to be wrong (%d vs. %d charachters). Check manually\n",
+                               $offset, strlen($label), strlen($value));
+                       return false;
+               }
+
+               if (1 != $id) {
+                       printf("[%03d + 3] Returned id column value seems wrong, expecting 1 got %s.\n",
+                               $offset, var_export($id, true));
+                       return false;
+               }
+
+               $stmt = $db->query('SELECT id, label FROM test');
+               $ret = $stmt->fetch(PDO::FETCH_ASSOC);
+
+               if ($ret['label'] !== $value) {
+                       printf("[%03d + 3] Returned value seems to be wrong (%d vs. %d charachters). Check manually\n",
+                               $offset, strlen($ret['label']), strlen($value));
+                       return false;
+               }
+
+               if (1 != $ret['id']) {
+                       printf("[%03d + 3] Returned id column value seems wrong, expecting 1 got %s.\n",
+                               $offset, var_export($ret['id'], true));
+                       return false;
+               }
+
+               return true;
+       }
+
+       $offset = 0;
+       foreach ($blobs as $sql_type => $test_len) {
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+               test_blob($db, ++$offset, $sql_type, $test_len);
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+               test_blob($db, ++$offset, $sql_type, $test_len);
+       }
+
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_closecursor.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_closecursor.phpt
new file mode 100644 (file)
index 0000000..c96da1f
--- /dev/null
@@ -0,0 +1,172 @@
+--TEST--
+MySQL PDOStatement->closeCursor()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       /* TODO the results look wrong, why do we get 2014 with buffered AND unbuffered queries */
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       function pdo_mysql_stmt_closecursor($db) {
+
+               // This one should fail. I let it fail to prove that closeCursor() makes a difference.
+               // If no error messages gets printed do not know if proper usage of closeCursor() makes any
+               // difference or not. That's why we need to cause an error here.
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+               $stmt1 = $db->query('SELECT id, label FROM test ORDER BY id ASC');
+               // query() shall fail!
+               $stmt2 = $db->query('SELECT id, label FROM test ORDER BY id ASC');
+               $stmt1->closeCursor();
+
+               // This is proper usage of closeCursor(). It shall prevent any further error messages.
+               if (MySQLPDOTest::isPDOMySQLnd()) {
+                       $stmt1 = $db->query('SELECT id, label FROM test ORDER BY id ASC');
+               } else {
+                       // see pdo_mysql_stmt_unbuffered_2050.phpt for an explanation
+                       unset($stmt1);
+                       $stmt1 = $db->query('SELECT id, label FROM test ORDER BY id ASC');
+               }
+               // fetch only the first rows and let closeCursor() clean up
+               $row1 = $stmt1->fetch(PDO::FETCH_ASSOC);
+               $stmt1->closeCursor();
+
+               $stmt2 = $db->prepare('UPDATE test SET label = ? WHERE id = ?');
+               $stmt2->bindValue(1, "z");
+
+               $stmt2->bindValue(2, $row1['id']);
+               $stmt2->execute();
+               $stmt2->closeCursor();
+
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+               // check if changing the fetch mode from unbuffered to buffered will
+               // cause any harm to a statement created prior to the change
+               $stmt1->execute();
+               $row2 = $stmt1->fetch(PDO::FETCH_ASSOC);
+               $stmt1->closeCursor();
+               if (!isset($row2['label']) || ('z' !== $row2['label']))
+                       printf("Expecting array(id => 1, label => z) got %s\n", var_export($row2, true));
+               unset($stmt1);
+
+               $stmt1 = $db->query('SELECT id, label FROM test ORDER BY id ASC');
+               // should work
+               $stmt2 = $db->query('SELECT id, label FROM test ORDER BY id ASC');
+               $stmt1->closeCursor();
+
+               $stmt1 = $db->query('SELECT id, label FROM test ORDER BY id ASC');
+               // fetch only the first rows and let closeCursor() clean up
+               $row3 = $stmt1->fetch(PDO::FETCH_ASSOC);
+               $stmt1->closeCursor();
+               assert($row3 == $row2);
+
+               $stmt2 = $db->prepare('UPDATE test SET label = ? WHERE id = ?');
+               $stmt2->bindValue(1, "a");
+               $stmt2->bindValue(2, $row1['id']);
+               $stmt2->execute();
+               $stmt2->closeCursor();
+
+               $stmt1->execute();
+               $row4 = $stmt1->fetch(PDO::FETCH_ASSOC);
+               $stmt1->closeCursor();
+               assert($row4 == $row1);
+
+               $offset = 0;
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindParam(1, $in))
+                       printf("[%03d + 1] Cannot bind parameter, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[%03d + 2] Cannot bind integer column, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[%03d + 3] Cannot bind string column, %s %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               $stmt->closeCursor();
+               $stmt->execute();
+
+       }
+
+
+       try {
+
+               printf("Testing emulated PS...\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               printf("Buffered...\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+               MySQLPDOTest::createTestTable($db);
+               pdo_mysql_stmt_closecursor($db);
+
+               printf("Unbuffered...\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+               MySQLPDOTest::createTestTable($db);
+               pdo_mysql_stmt_closecursor($db);
+
+               printf("Testing native PS...\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn off emulated prepared statements\n");
+
+               printf("Buffered...\n");
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+               pdo_mysql_stmt_closecursor($db);
+
+               printf("Unbuffered...\n");
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+               pdo_mysql_stmt_closecursor($db);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Testing emulated PS...
+Buffered...
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in %s on line %d
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Unbuffered...
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in %s on line %d
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Testing native PS...
+Buffered...
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in %s on line %d
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+Unbuffered...
+
+Warning: PDO::query(): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in %s on line %d
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_closecursor_empty.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_closecursor_empty.phpt
new file mode 100644 (file)
index 0000000..a575014
--- /dev/null
@@ -0,0 +1,69 @@
+--TEST--
+MySQL PDOStatement->closeCursor()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+
+       try {
+
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn off emulated prepared statements\n");
+
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+               MySQLPDOTest::createTestTable($db);
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id > ? ORDER BY id ASC LIMIT 2');
+               $in = 0;
+               if (!$stmt->bindParam(1, $in))
+                       printf("[003] Cannot bind parameter, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               $stmt->execute();
+               $id = $label = null;
+
+               if (!$stmt->bindColumn(1, $id, PDO::PARAM_INT))
+                       printf("[004] Cannot bind integer column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               if (!$stmt->bindColumn(2, $label, PDO::PARAM_STR))
+                       printf("[005] Cannot bind string column, %s %s\n",
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+               $stmt->closeCursor();
+               $stmt->execute();
+               while ($stmt->fetch(PDO::FETCH_BOUND))
+                       printf("in = %d -> id = %s (%s) / label = %s (%s)\n",
+                               $in,
+                               var_export($id, true), gettype($id),
+                               var_export($label, true), gettype($label));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+in = 0 -> id = 1 (integer) / label = 'a' (string)
+in = 0 -> id = 2 (integer) / label = 'b' (string)
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_columncount.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_columncount.phpt
new file mode 100644 (file)
index 0000000..508ba0a
--- /dev/null
@@ -0,0 +1,65 @@
+--TEST--
+MySQL PDOStatement->columnCount()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       // The only purpose of this is to check if emulated and native PS
+  // return the same. If it works for one, it should work for all.
+       // Internal data structures should be the same in both cases.
+       printf("Testing emulated PS...\n");
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT id, label, "?" as foo FROM test');
+               $stmt->execute();
+               var_dump($stmt->columnCount());
+
+               $stmt = $db->query('SELECT * FROM test');
+               var_dump($stmt->columnCount());
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       printf("Testing native PS...\n");
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to turn off emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT id, label, "?" as foo, "TODO - Stored Procedure" as bar FROM test');
+               $stmt->execute();
+               var_dump($stmt->columnCount());
+
+               $stmt = $db->query('SELECT * FROM test');
+               var_dump($stmt->columnCount());
+
+       } catch (PDOException $e) {
+               printf("[003] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Testing emulated PS...
+int(3)
+int(2)
+Testing native PS...
+int(4)
+int(2)
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_errorcode.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_errorcode.phpt
new file mode 100644 (file)
index 0000000..8772006
--- /dev/null
@@ -0,0 +1,61 @@
+--TEST--
+MySQL PDOStatement->errorCode();
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       $db->exec('DROP TABLE IF EXISTS ihopeitdoesnotexist');
+
+       printf("Testing emulated PS...\n");
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT id FROM ihopeitdoesnotexist ORDER BY id ASC');
+               $stmt->execute();
+               var_dump($stmt->errorCode());
+
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       printf("Testing native PS...\n");
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to turn off emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT id FROM ihopeitdoesnotexist ORDER BY id ASC');
+               $stmt->execute();
+               var_dump($stmt->errorCode());
+
+
+
+       } catch (PDOException $e) {
+               printf("[003] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       print "done!";
+?>
+--EXPECTF--
+Testing emulated PS...
+
+Warning: PDOStatement::execute(): SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s.ihopeitdoesnotexist' doesn't exist in %s on line %d
+string(5) "42S02"
+Testing native PS...
+
+Warning: PDO::prepare(): SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s.ihopeitdoesnotexist' doesn't exist in %s on line %d
+
+Fatal error: Call to a member function execute() on a non-object in %s on line %d
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_errorinfo.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_errorinfo.phpt
new file mode 100644 (file)
index 0000000..c29ffff
--- /dev/null
@@ -0,0 +1,119 @@
+--TEST--
+MySQL PDOStatement->errorInfo();
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       printf("Testing emulated PS...\n");
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT id FROM ihopeitdoesnotexist ORDER BY id ASC');
+               var_dump($stmt->errorInfo());
+               $stmt->execute();
+               var_dump($stmt->errorInfo());
+
+               MySQLPDOTest::createTestTable($db);
+               $stmt = $db->prepare('SELECT label FROM test ORDER BY id ASC LIMIT 1');
+               $db->exec('DROP TABLE test');
+               var_dump($stmt->execute());
+               var_dump($stmt->errorInfo());
+               var_dump($db->errorInfo());
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorInfo(), implode(' ', $db->errorInfo()));
+       }
+
+       printf("Testing native PS...\n");
+       try {
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to turn off emulated prepared statements\n");
+
+               $stmt = $db->prepare('SELECT id FROM ihopeitdoesnotexist ORDER BY id ASC');
+               var_dump($stmt);
+
+               MySQLPDOTest::createTestTable($db);
+               $stmt = $db->prepare('SELECT label FROM test ORDER BY id ASC LIMIT 1');
+               var_dump($stmt->errorInfo());
+               $db->exec('DROP TABLE test');
+               $stmt->execute();
+               var_dump($stmt->errorInfo());
+               var_dump($db->errorInfo());
+
+       } catch (PDOException $e) {
+               printf("[003] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorInfo(), implode(' ', $db->errorInfo()));
+       }
+       print "done!";
+?>
+--EXPECTF--
+Testing emulated PS...
+array(1) {
+  [0]=>
+  string(0) ""
+}
+
+Warning: PDOStatement::execute(): SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s.ihopeitdoesnotexist' doesn't exist in %s on line %d
+array(3) {
+  [0]=>
+  string(5) "42S02"
+  [1]=>
+  int(1146)
+  [2]=>
+  string(%d) "Table '%s.ihopeitdoesnotexist' doesn't exist"
+}
+
+Warning: PDOStatement::execute(): SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s.test' doesn't exist in %s on line %d
+bool(false)
+array(3) {
+  [0]=>
+  string(5) "42S02"
+  [1]=>
+  int(1146)
+  [2]=>
+  string(%d) "Table '%s.test' doesn't exist"
+}
+array(1) {
+  [0]=>
+  string(5) "00000"
+}
+Testing native PS...
+
+Warning: PDO::prepare(): SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s.ihopeitdoesnotexist' doesn't exist in %s on line %d
+bool(false)
+array(1) {
+  [0]=>
+  string(0) ""
+}
+
+Warning: PDOStatement::execute(): SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s.test' doesn't exist in %s on line %d
+array(3) {
+  [0]=>
+  string(5) "42S02"
+  [1]=>
+  int(1146)
+  [2]=>
+  string(%d) "Table '%s.test' doesn't exist"
+}
+array(3) {
+  [0]=>
+  string(5) "00000"
+  [1]=>
+  int(1146)
+  [2]=>
+  string(%d) "Table '%s.ihopeitdoesnotexist' doesn't exist"
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_non_select.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_non_select.phpt
new file mode 100644 (file)
index 0000000..fdf63ea
--- /dev/null
@@ -0,0 +1,187 @@
+--TEST--
+MySQL PDOStatement->execute()/fetch(), Non-SELECT
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       try {
+
+               // Emulated PS first
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
+               if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn on emulated prepared statements\n");
+
+               if (!is_object($stmt = $db->query('DESCRIBE test id')))
+                       printf("[003] Emulated PS, DESCRIBE failed, %s\n", var_export($db->errorInfo(), true));
+
+               $describe = array();
+               $valid = false;
+               while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+                       $describe[] = $row;
+                       foreach ($row as $column => $value)
+                       if (isset($row['field']) && ($row['field'] == 'id'))
+                               $valid = true;
+               }
+               if (empty($describe))
+                       printf("[004] Emulated PS, DESCRIBE returned no results\n");
+               else if (!$valid)
+                       printf("[005] Emulated PS, DESCRIBE, returned data seems wrong, dumping %s\n",
+                               var_export($describe, true));
+
+               if (!is_object($stmt = $db->query('SHOW ENGINES')))
+                       printf("[006] Emulated PS, SHOW failed, %s\n", var_export($db->errorInfo(), true));
+
+               $show = array();
+               $valid = false;
+               while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+                       $show[] = $row;
+                       foreach ($row as $column => $value)
+                               // MyISAM engine should be part of _every_ MySQL today
+                               if ($value == 'MyISAM')
+                                       $valid = true;
+               }
+               if (empty($show))
+                       printf("[007] Emulated PS, SHOW returned no results\n");
+               else if (!$valid)
+                       printf("[008] Emulated PS, SHOW data seems wrong, dumping %s\n",
+                               var_export($show, true));
+
+               if (!is_object($stmt = $db->query("EXPLAIN SELECT id FROM test")))
+                       printf("[009] Emulated PS, EXPLAIN returned no results\n");
+
+               $explain = array();
+               while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
+                       $explain[] = $row;
+
+               if (empty($explain))
+                       printf("[010] Emulated PS, EXPLAIN returned no results\n");
+
+               // And now native PS
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[011] Unable to turn off emulated prepared statements\n");
+
+               $native_support = 'no';
+               if ($db->exec('PREPARE mystmt FROM "DESCRIBE test id"')) {
+                       $native_support = 'yes';
+                       $db->exec('DEALLOCATE PREPARE mystmt');
+               }
+
+               if (!is_object($stmt = $db->query('DESCRIBE test id')))
+                       printf("[012] Native PS (native support: %s), DESCRIBE failed, %s\n",
+                               $native_support,
+                               var_export($db->errorInfo(), true));
+
+               $describe_native = array();
+               $valid = false;
+               while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+                       $describe_native[] = $row;
+                       foreach ($row as $column => $value)
+                       if (isset($row['field']) && ($row['field'] == 'id'))
+                               $valid = true;
+               }
+               if (empty($describe_native))
+                       printf("[013] Native PS (native support: %s), DESCRIBE returned no results\n",
+                               $native_support);
+               else if (!$valid)
+                       printf("[014] Native PS (native support: %s), DESCRIBE, returned data seems wrong, dumping %s\n",
+                               $native_support,
+                               var_export($describe_native, true));
+
+               if ($describe != $describe_native)
+                       printf("[015] Emulated and native PS (native support: %s) results of DESCRIBE differ: %s vs. %s\n",
+                               $native_support,
+                               var_export($describe, true),
+                               var_export($describe_native, true));
+
+
+               $native_support = 'no';
+               if ($db->exec('PREPARE mystmt FROM "SHOW ENGINES"')) {
+                       $native_support = 'yes';
+                       $db->exec('DEALLOCATE PREPARE mystmt');
+               }
+
+               if (!is_object($stmt = $db->query('SHOW ENGINES')))
+                       printf("[016] Native PS (native support: %s), SHOW failed, %s\n",
+                               $native_support,
+                               var_export($db->errorInfo(), true));
+
+               $show_native = array();
+               $valid = false;
+               while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+                       $show_native[] = $row;
+                       foreach ($row as $column => $value)
+                               // MyISAM engine should be part of _every_ MySQL today
+                               if ($value == 'MyISAM')
+                                       $valid = true;
+               }
+               if (empty($show_native))
+                       printf("[017] Native PS (native support: %s), SHOW returned no results\n",
+                               $native_support);
+               else if (!$valid)
+                       printf("[018] Native PS (native support: %s), SHOW data seems wrong, dumping %s\n",
+                               var_export($show_native, true));
+
+               if ($show != $show_native)
+                       printf("Native PS (native support: %s) and emulated PS returned different data for SHOW: %s vs. %s\n",
+                               $native_support,
+                               var_export($show, true),
+                               var_export($show_native, true));
+
+               $native_support = 'no';
+               if ($db->exec('PREPARE mystmt FROM "EXPLAIN SELECT id FROM test"')) {
+                       $native_support = 'yes';
+                       $db->exec('DEALLOCATE PREPARE mystmt');
+               }
+
+               if (!is_object($stmt = $db->query("EXPLAIN SELECT id FROM test")))
+                       printf("[012] Native PS (native support: %s), EXPLAIN failed, %s\n",
+                               $native_support,
+                               var_export($db->errorInfo(), true));
+
+               $explain_native = array();
+               while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
+                       $explain_native[] = $row;
+
+               if (empty($explain_native))
+                       printf("[013] Native PS (native support: %s), EXPLAIN returned no results\n",
+                               $native_support);
+
+               if ($explain != $explain_native)
+                       printf("Native PS (native support: %s) and emulated PS returned different data for EXPLAIN: %s vs. %s\n",
+                               $native_support,
+                               var_export($explain, true),
+                               var_export($explain_native, true));
+
+               $stmt->execute();
+               $explain_native = $stmt->fetchAll(PDO::FETCH_ASSOC);
+               if ($explain != $explain_native)
+                       printf("Native PS (native support: %s) and emulated PS returned different data for EXPLAIN: %s vs. %s\n",
+                               $native_support,
+                               var_export($explain, true),
+                               var_export($explain_native, true));
+
+               $stmt->execute();
+               $stmt->execute();
+               // libmysql needs this - otherwise we get a 2015 error
+               if (!MYSQLPDOTest::isPDOMySQLnd())
+                       $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!\n";
+?>
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_serialize.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_serialize.phpt
new file mode 100644 (file)
index 0000000..73ed5a2
--- /dev/null
@@ -0,0 +1,151 @@
+--TEST--
+MySQL PDOStatement->fetch(), PDO::FETCH_SERIALIZE
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+if (version_compare(PHP_VERSION, '5.1.0', '<'))
+       die("skip Needs 5.1.0 and Interface Serializable");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+
+               class myclass implements Serializable {
+
+                       private static $instance = null;
+                       protected $myprotected = 'a protected propery';
+
+                       // Good old magic stuff
+                       private function __construct($caller = NULL) {
+                               printf("%s(%s)\n", __METHOD__, $caller);
+                       }
+
+
+                       public function __destruct() {
+                               // printf("%s()\n", __METHOD__);
+                       }
+
+                       public function __sleep() {
+                               printf("%s()\n", __METHOD__);
+                       }
+
+                       public function __wakeup() {
+                               printf("%s()\n", __METHOD__);
+                       }
+
+                       public function __call($method, $params) {
+                               printf("%s(%s, %s)\n", __METHOD__, $method, var_export($params, true));
+                       }
+
+                       public function __set($prop, $value) {
+                               printf("%s(%s, %s)\n", __METHOD__, $prop, var_export($value, true));
+                               $this->{$prop} = $value;
+                       }
+
+                       public function __get($prop) {
+                               printf("%s(%s)\n", __METHOD__, $prop);
+                               return NULL;
+                       }
+
+                       // Singleton
+                       public static function singleton($caller) {
+                               printf("%s(%s)\n", __METHOD__, $caller);
+
+                               if (!self::$instance) {
+                                       $c = __CLASS__;
+                                       self::$instance = new $c($caller);
+                               }
+                               return self::$instance;
+                       }
+
+                       // Serializable
+                       public function serialize() {
+                               printf("%s()\n", __METHOD__);
+                               return 'Data from serialize';
+                       }
+
+                       public function unserialize($data) {
+                               printf("%s(%s)\n", __METHOD__, var_export($data, true));
+                       }
+
+               }
+
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[002] Unable to turn off emulated prepared statements\n");
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(id INT, myobj BLOB) ENGINE=%s',
+                       MySQLPDOTest::getTableEngine()));
+
+               printf("Creating an object, serializing it and writing it to DB...\n");
+               $id = 1;
+               $obj = myclass::singleton('Creating object');
+               $myobj = serialize($obj);
+               $stmt = $db->prepare('INSERT INTO test(id, myobj) VALUES (?, ?)');
+               $stmt->bindValue(1, $id);
+               $stmt->bindValue(2, $myobj);
+               $stmt->execute();
+
+               printf("\nUnserializing the previously serialized object...\n");
+               var_dump(unserialize($myobj));
+
+               printf("\nUsing PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE to fetch the object from DB and unserialize it...\n");
+               $stmt = $db->prepare('SELECT myobj FROM test');
+               $stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE, 'myclass', array('PDO shall not call __construct()'));
+               $stmt->execute();
+               var_dump($stmt->fetch());
+
+               printf("\nUsing PDO::FETCH_CLASS to fetch the object from DB and unserialize it...\n");
+               $stmt = $db->prepare('SELECT myobj FROM test');
+               $stmt->setFetchMode(PDO::FETCH_CLASS, 'myclass', array('PDO shall call __construct()'));
+               $stmt->execute();
+               var_dump($stmt->fetch());
+
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!\n";
+?>
+--EXPECTF--
+Creating an object, serializing it and writing it to DB...
+myclass::singleton(Creating object)
+myclass::__construct(Creating object)
+myclass::serialize()
+
+Unserializing the previously serialized object...
+myclass::unserialize('Data from serialize')
+object(myclass)#4 (1) {
+  ["myprotected":protected]=>
+  string(19) "a protected propery"
+}
+
+Using PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE to fetch the object from DB and unserialize it...
+myclass::__set(myobj, 'C:7:"myclass":19:{Data from serialize}')
+myclass::__construct(PDO shall not call __construct())
+object(myclass)#%d (2) {
+  ["myprotected":protected]=>
+  string(19) "a protected propery"
+  ["myobj"]=>
+  string(38) "C:7:"myclass":19:{Data from serialize}"
+}
+
+Using PDO::FETCH_CLASS to fetch the object from DB and unserialize it...
+myclass::__set(myobj, 'C:7:"myclass":19:{Data from serialize}')
+myclass::__construct(PDO shall call __construct())
+object(myclass)#%d (2) {
+  ["myprotected":protected]=>
+  string(19) "a protected propery"
+  ["myobj"]=>
+  string(38) "C:7:"myclass":19:{Data from serialize}"
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_serialize_simple.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_fetch_serialize_simple.phpt
new file mode 100644 (file)
index 0000000..4233d91
--- /dev/null
@@ -0,0 +1,98 @@
+--TEST--
+MySQL PDOStatement->fetch(), PDO::FETCH_SERIALIZE
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+if (version_compare(PHP_VERSION, '5.1.0', '<'))
+       die("skip Needs 5.1.0 and Interface Serializable");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+
+               class myclass implements Serializable {
+
+                       public function __construct($caller = null) {
+                               printf("%s(%s) - note that it must not be called when unserializing\n", __METHOD__, var_export($caller, true));
+                       }
+
+                       public function __set($prop, $value) {
+                               printf("%s(%s, %s)\n", __METHOD__, var_export($prop, true), var_export($value, true));
+                               $this->{$prop} = $value;
+                       }
+
+                       public function serialize() {
+                               printf("%s()\n", __METHOD__);
+                               return 'Value from serialize()';
+                       }
+
+                       public function unserialize($data) {
+                               printf("%s(%s)\n", __METHOD__, var_export($data, true));
+                       }
+
+               }
+
+               printf("Lets see what the Serializeable interface makes our object behave like...\n");
+               $obj = new myclass('Called by script');
+               $tmp = unserialize(serialize($obj));
+               var_dump($tmp);
+
+               printf("\nAnd now magic PDO using fetchAll(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE)...\n");
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec(sprintf('CREATE TABLE test(myobj BLOB) ENGINE=%s', MySQLPDOTest::getTableEngine()));
+               $db->exec('INSERT INTO test(myobj) VALUES ("Data fetched from DB to be given to unserialize()")');
+
+               $stmt = $db->prepare('SELECT myobj FROM test');
+               $stmt->execute();
+               $rows = $stmt->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE, 'myclass', array('Called by PDO'));
+               var_dump($rows[0]);
+
+               $stmt->execute();
+               $rows = $stmt->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE, 'myclass');
+               var_dump($rows[0]);
+
+               printf("\nAnd now PDO using setFetchMode(PDO::FETCH:CLASS|PDO::FETCH_SERIALIZE) + fetch()...\n");
+               $stmt = $db->prepare('SELECT myobj FROM test');
+               $stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE, 'myclass', array('Called by PDO'));
+               $stmt->execute();
+               var_dump($stmt->fetch());
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!\n";
+?>
+--EXPECTF--
+Lets see what the Serializeable interface makes our object behave like...
+myclass::__construct('Called by script') - note that it must not be called when unserializing
+myclass::serialize()
+myclass::unserialize('Value from serialize()')
+object(myclass)#%d (0) {
+}
+
+And now magic PDO using fetchAll(PDO::FETCH_CLASS|PDO::FETCH_SERIALIZE)...
+myclass::unserialize('Data fetched from DB to be given to unserialize()')
+myclass::__construct('Called by PDO') - note that it must not be called when unserializing
+object(myclass)#%d (0) {
+}
+myclass::unserialize('Data fetched from DB to be given to unserialize()')
+myclass::__construct(NULL) - note that it must not be called when unserializing
+object(myclass)#%d (0) {
+}
+
+And now PDO using setFetchMode(PDO::FETCH:CLASS|PDO::FETCH_SERIALIZE) + fetch()...
+myclass::__set('myobj', 'Data fetched from DB to be given to unserialize()')
+myclass::__construct('Called by PDO') - note that it must not be called when unserializing
+object(myclass)#%d (1) {
+  ["myobj"]=>
+  string(49) "Data fetched from DB to be given to unserialize()"
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_fetchobject.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_fetchobject.phpt
new file mode 100644 (file)
index 0000000..79ef0c3
--- /dev/null
@@ -0,0 +1,95 @@
+--TEST--
+MySQL PDO: PDOStatement->fetchObject()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+?>
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::factory();
+MySQLPDOTest::createTestTable($db);
+
+try {
+
+       // default settings
+       $query = "SELECT id, '', NULL, \"\" FROM test ORDER BY id ASC LIMIT 3";
+       $stmt = $db->prepare($query);
+
+       class myclass {
+
+               private $set_calls = 0;
+               protected static $static_set_calls = 0;
+
+               // NOTE: PDO does not care about protected
+               protected $grp;
+
+               // NOTE: PDO does not care about private and calls __construct() after __set()
+               private function __construct($param1, $param2) {
+                       printf("myclass::__construct(%s, %s): %d / %d\n",
+                               $param1, $param2,
+                               self::$static_set_calls, $this->set_calls);
+               }
+
+               // NOTE: PDO will call __set() prior to calling __construct()
+               public function __set($prop, $value) {
+                       $this->not_a_magic_one();
+                       printf("myclass::__set(%s, -%s-) %d\n",
+                               $prop, var_export($value, true), $this->set_calls, self::$static_set_calls);
+                       $this->{$prop} = $value;
+               }
+
+               // NOTE: PDO can call regular methods prior to calling __construct()
+               public function not_a_magic_one() {
+                       $this->set_calls++;
+                       self::$static_set_calls++;
+               }
+
+       }
+       $stmt->execute();
+       $rowno = 0;
+       $rows[] = array();
+       while (is_object($rows[] = $stmt->fetchObject('myclass', array($rowno++, $rowno))))
+               ;
+
+       var_dump($rows[$rowno - 1]);
+
+} catch (PDOException $e) {
+       // we should never get here, we use warnings, but never trust a system...
+       printf("[001] %s, [%s} %s\n",
+               $e->getMessage(), $db->errorInfo(), implode(' ', $db->errorInfo()));
+}
+
+$db->exec('DROP TABLE IF EXISTS test');
+print "done!";
+?>
+--EXPECTF--
+myclass::__set(id, -'1'-) 1
+myclass::__set(, -''-) 2
+myclass::__set(null, -NULL-) 3
+myclass::__set(, -''-) 4
+myclass::__construct(0, 1): 4 / 4
+myclass::__set(id, -'2'-) 1
+myclass::__set(, -''-) 2
+myclass::__set(null, -NULL-) 3
+myclass::__set(, -''-) 4
+myclass::__construct(1, 2): 8 / 4
+myclass::__set(id, -'3'-) 1
+myclass::__set(, -''-) 2
+myclass::__set(null, -NULL-) 3
+myclass::__set(, -''-) 4
+myclass::__construct(2, 3): 12 / 4
+object(myclass)#%d (4) {
+  ["set_calls":"myclass":private]=>
+  int(4)
+  ["grp":protected]=>
+  NULL
+  ["id"]=>
+  string(1) "3"
+  ["null"]=>
+  NULL
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_getcolumnmeta.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_getcolumnmeta.phpt
new file mode 100644 (file)
index 0000000..19ae195
--- /dev/null
@@ -0,0 +1,309 @@
+--TEST--
+MySQL: PDOStatement->getColumnMeta()
+--SKIPIF--
+<?php # vim:ft=php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+// Too many differences among MySQL version - run only with a recent one
+$db = MySQLPDOTest::factory();
+$stmt = $db->query('SELECT VERSION() as _version');
+$row = $stmt->fetch(PDO::FETCH_ASSOC);
+$version = ((int)substr($row['_version'], 0, 1) * 10) + (int)substr($row['_version'], 2, 1);
+if ($version < 51)
+       die("skip Test needs MySQL 5.1+");
+?>
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+MySQLPDOTest::createTestTable($db);
+
+try {
+
+       $stmt = $db->prepare('SELECT id FROM test ORDER BY id ASC');
+
+       // execute() has not been called yet
+       // NOTE: no warning
+       if (false !== ($tmp = $stmt->getColumnMeta(0)))
+               printf("[002] Expecting false got %s\n", var_export($tmp, true));
+
+       $stmt->execute();
+       // Warning: PDOStatement::getColumnMeta() expects exactly 1 parameter, 0 given in
+       if (false !== ($tmp = @$stmt->getColumnMeta()))
+               printf("[003] Expecting false got %s\n", var_export($tmp, true));
+
+       // invalid offset
+       if (false !== ($tmp = @$stmt->getColumnMeta(-1)))
+               printf("[004] Expecting false got %s\n", var_export($tmp, true));
+
+       // Warning: PDOStatement::getColumnMeta() expects parameter 1 to be long, array given in
+       if (false !== ($tmp = @$stmt->getColumnMeta(array())))
+               printf("[005] Expecting false got %s\n", var_export($tmp, true));
+
+       // Warning: PDOStatement::getColumnMeta() expects exactly 1 parameter, 2 given in
+       if (false !== ($tmp = @$stmt->getColumnMeta(1, 1)))
+               printf("[006] Expecting false got %s\n", var_export($tmp, true));
+
+       $emulated =  $stmt->getColumnMeta(0);
+
+       printf("Testing native PS...\n");
+       $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[007] Unable to turn off emulated prepared statements\n");
+
+       $stmt = $db->prepare('SELECT id FROM test ORDER BY id ASC');
+       $stmt->execute();
+       $native = $stmt->getColumnMeta(0);
+       if (count($native) == 0) {
+               printf("[008] Meta data seems wrong, %s / %s\n",
+                       var_export($native, true), var_export($emulated, true));
+       }
+
+       // invalid offset
+       if (false !== ($tmp = $stmt->getColumnMeta(1)))
+               printf("[009] Expecting false because of invalid offset got %s\n", var_export($tmp, true));
+
+
+       function test_meta(&$db, $offset, $sql_type, $value, $native_type, $pdo_type) {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+
+               $sql = sprintf('CREATE TABLE test(id INT, label %s) ENGINE=%s', $sql_type, MySQLPDOTest::getTableEngine());
+               if (!($stmt = @$db->prepare($sql)) || (!@$stmt->execute())) {
+                       // Some engines and/or MySQL server versions might not support the data type
+                       return true;
+               }
+
+               if (!$db->exec(sprintf('INSERT INTO test(id, label) VALUES (1, "%s")', $value))) {
+                       printf("[%03d] + 1] Insert failed, %d - %s\n", $offset,
+                               $db->errorCode(), var_export($db->errorInfo(), true));
+                       return false;
+               }
+
+               $stmt = $db->prepare('SELECT id, label FROM test');
+               $stmt->execute();
+               $meta = $stmt->getColumnMeta(1);
+               $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+               if (empty($meta)) {
+                       printf("[%03d + 2] getColumnMeta() failed, %d - %s\n", $offset,
+                               $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+                       return false;
+               }
+
+               $elements = array('flags', 'table', 'name', 'len', 'precision', 'pdo_type');
+               foreach ($elements as $k => $element)
+                       if (!isset($meta[$element])) {
+                               printf("[%03d + 3] Element %s missing, %s\n", $offset,
+                                       $element, var_export($meta, true));
+                               return false;
+                       }
+
+               if (($meta['table'] != 'test') || ($meta['name'] != 'label')) {
+                       printf("[%03d + 4] Table or field name is wrong, %s\n", $offset,
+                               var_export($meta, true));
+                       return false;
+               }
+
+               if (!is_null($native_type)) {
+                       if (!isset($meta['native_type'])) {
+                               printf("[%03d + 5] Element native_type missing, %s\n", $offset,
+                                       var_export($meta, true));
+                               return false;
+                       }
+
+                       if (!is_array($native_type))
+                               $native_type = array($native_type);
+
+                       $found = false;
+                       foreach ($native_type as $k => $type) {
+                               if ($meta['native_type'] == $type) {
+                                       $found = true;
+                                       break;
+                               }
+                       }
+
+                       if (!$found) {
+                               printf("[%03d + 6] Expecting native type %s, %s\n", $offset,
+                                       var_export($native_type, true), var_export($meta, true));
+                               return false;
+                       }
+               }
+
+               if (!is_null($pdo_type) && ($meta['pdo_type'] != $pdo_type)) {
+                       printf("[%03d + 6] Expecting PDO type %s got %s (%s)\n", $offset,
+                               $pdo_type, var_export($meta, true), var_export($meta['native_type']));
+                       return false;
+               }
+
+               return true;
+       }
+
+       $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
+       $is_mysqlnd = MySQLPDOTest::isPDOMySQLnd();
+       test_meta($db, 20, 'BIT(8)', 1, NULL, ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+       test_meta($db, 30, 'TINYINT', -127, NULL, ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+       test_meta($db, 40, 'TINYINT UNSIGNED', 255, NULL, ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+       test_meta($db, 50, 'BOOLEAN', 1, NULL, ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+
+       test_meta($db, 60, 'SMALLINT', -32768, 'SHORT', ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+       test_meta($db, 70, 'SMALLINT UNSIGNED', 65535, 'SHORT', ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+
+       test_meta($db, 80, 'MEDIUMINT', -8388608, 'INT24', ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+       test_meta($db, 90, 'MEDIUMINT UNSIGNED', 16777215, 'INT24', ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+
+       test_meta($db, 100, 'INT', -2147483648, 'LONG', ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+       test_meta($db, 110, 'INT UNSIGNED', 4294967295, 'LONG', ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+
+       test_meta($db, 120, 'BIGINT', -9223372036854775808, 'LONGLONG', ($is_mysqlnd) ? ((PHP_INT_SIZE == 4) ? PDO::PARAM_STR : PDO::PARAM_INT) : PDO::PARAM_STR);
+       test_meta($db, 130, 'BIGINT UNSIGNED', 18446744073709551615, 'LONGLONG', ($is_mysqlnd) ? ((PHP_INT_SIZE == 4) ? PDO::PARAM_STR : PDO::PARAM_INT) : PDO::PARAM_STR);
+
+       test_meta($db, 130, 'REAL', -1.01, 'DOUBLE', PDO::PARAM_STR);
+       test_meta($db, 140, 'REAL UNSIGNED', 1.01, 'DOUBLE', PDO::PARAM_STR);
+       test_meta($db, 150, 'REAL ZEROFILL', -1.01, 'DOUBLE', PDO::PARAM_STR);
+       test_meta($db, 160, 'REAL UNSIGNED ZEROFILL', 1.01, 'DOUBLE', PDO::PARAM_STR);
+
+       test_meta($db, 170, 'DOUBLE', -1.01, 'DOUBLE', PDO::PARAM_STR);
+       test_meta($db, 180, 'DOUBLE UNSIGNED', 1.01, 'DOUBLE', PDO::PARAM_STR);
+       test_meta($db, 190, 'DOUBLE ZEROFILL', -1.01, 'DOUBLE', PDO::PARAM_STR);
+       test_meta($db, 200, 'DOUBLE UNSIGNED ZEROFILL', 1.01, 'DOUBLE', PDO::PARAM_STR);
+
+       test_meta($db, 210, 'FLOAT', -1.01, 'FLOAT', PDO::PARAM_STR);
+       test_meta($db, 220, 'FLOAT UNSIGNED', 1.01, 'FLOAT', PDO::PARAM_STR);
+       test_meta($db, 230, 'FLOAT ZEROFILL', -1.01, 'FLOAT', PDO::PARAM_STR);
+       test_meta($db, 240, 'FLOAT UNSIGNED ZEROFILL', 1.01, 'FLOAT', PDO::PARAM_STR);
+
+       test_meta($db, 250, 'DECIMAL', -1.01, array('DECIMAL', 'NEWDECIMAL'), PDO::PARAM_STR);
+       test_meta($db, 260, 'DECIMAL UNSIGNED', 1.01, array('DECIMAL', 'NEWDECIMAL'), PDO::PARAM_STR);
+       test_meta($db, 270, 'DECIMAL ZEROFILL', -1.01, array('DECIMAL', 'NEWDECIMAL'), PDO::PARAM_STR);
+       test_meta($db, 280, 'DECIMAL UNSIGNED ZEROFILL', 1.01, array('DECIMAL', 'NEWDECIMAL'), PDO::PARAM_STR);
+
+       test_meta($db, 290, 'NUMERIC', -1.01, array('DECIMAL', 'NEWDECIMAL'), PDO::PARAM_STR);
+       test_meta($db, 300, 'NUMERIC UNSIGNED', 1.01, array('DECIMAL', 'NEWDECIMAL'), PDO::PARAM_STR);
+       test_meta($db, 310, 'NUMERIC ZEROFILL', -1.01, array('DECIMAL', 'NEWDECIMAL'), PDO::PARAM_STR);
+       test_meta($db, 320, 'NUMERIC UNSIGNED ZEROFILL', 1.01, array('DECIMAL', 'NEWDECIMAL'), PDO::PARAM_STR);
+
+       test_meta($db, 330, 'DATE', '2008-04-23', array('DATE', 'NEWDATE'), PDO::PARAM_STR);
+       test_meta($db, 340, 'TIME', '14:37:00', 'TIME', PDO::PARAM_STR);
+       test_meta($db, 350, 'TIMESTAMP', time(), 'TIMESTAMP', PDO::PARAM_STR);
+       test_meta($db, 360, 'DATETIME', '2008-03-23 14:38:00', 'DATETIME', PDO::PARAM_STR);
+       test_meta($db, 370, 'YEAR', '2008', NULL, ($is_mysqlnd) ? PDO::PARAM_INT : PDO::PARAM_STR);
+
+       test_meta($db, 380, 'CHAR(1)', 'a', 'STRING', PDO::PARAM_STR);
+       test_meta($db, 390, 'CHAR(10)', '0123456789', 'STRING', PDO::PARAM_STR);
+       test_meta($db, 400, 'CHAR(255)', str_repeat('z', 255), 'STRING', PDO::PARAM_STR);
+       test_meta($db, 410, 'VARCHAR(1)', 'a', 'VAR_STRING', PDO::PARAM_STR);
+       test_meta($db, 420, 'VARCHAR(10)', '0123456789', 'VAR_STRING', PDO::PARAM_STR);
+       test_meta($db, 430, 'VARCHAR(255)', str_repeat('z', 255), 'VAR_STRING', PDO::PARAM_STR);
+
+       test_meta($db, 440, 'BINARY(1)', str_repeat('a', 1), 'STRING', PDO::PARAM_STR);
+       test_meta($db, 450, 'BINARY(255)', str_repeat('b', 255), 'STRING', PDO::PARAM_STR);
+       test_meta($db, 460, 'VARBINARY(1)', str_repeat('a', 1), 'VAR_STRING', PDO::PARAM_STR);
+       test_meta($db, 470, 'VARBINARY(255)', str_repeat('b', 255), 'VAR_STRING', PDO::PARAM_STR);
+
+       test_meta($db, 480, 'TINYBLOB', str_repeat('b', 255), 'BLOB', PDO::PARAM_STR);
+       test_meta($db, 490, 'BLOB', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+       test_meta($db, 500, 'MEDIUMBLOB', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+       test_meta($db, 510, 'LONGBLOB', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+
+       test_meta($db, 520, 'TINYTEXT', str_repeat('b', 255), 'BLOB', PDO::PARAM_STR);
+       test_meta($db, 530, 'TINYTEXT BINARY', str_repeat('b', 255), 'BLOB', PDO::PARAM_STR);
+
+       test_meta($db, 560, 'TEXT', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+       test_meta($db, 570, 'TEXT BINARY', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+
+       test_meta($db, 580, 'MEDIUMTEXT', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+       test_meta($db, 590, 'MEDIUMTEXT BINARY', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+
+       test_meta($db, 600, 'LONGTEXT', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+       test_meta($db, 610, 'LONGTEXT BINARY', str_repeat('b', 256), 'BLOB', PDO::PARAM_STR);
+
+       test_meta($db, 620, "ENUM('yes', 'no') DEFAULT 'yes'", 'no', NULL, PDO::PARAM_STR);
+       test_meta($db, 630, "SET('yes', 'no') DEFAULT 'yes'", 'no', NULL, PDO::PARAM_STR);
+
+/*
+  | spatial_type
+*/
+
+       // unique key
+       $db->exec('DROP TABLE IF EXISTS test');
+       $sql = sprintf('CREATE TABLE test(id INT, label INT UNIQUE) ENGINE = %s', MySQLPDOTest::getTableEngine());
+       if (($stmt = @$db->prepare($sql)) && @$stmt->execute()) {
+               $db->exec('INSERT INTO test(id, label) VALUES (1, 2)');
+               $stmt = $db->query('SELECT id, label FROM test');
+               $meta = $stmt->getColumnMeta(1);
+               if (!isset($meta['flags'])) {
+                       printf("[1000] No flags contained in metadata %s\n", var_export($meta, true));
+               } else {
+                       $flags = $meta['flags'];
+                       $found = false;
+                       foreach ($flags as $k => $flag) {
+                               if ($flag == 'unique_key')
+                                       $found = true;
+                       }
+                       if (!$found)
+                               printf("[1001] Flags seem wrong %s\n", var_export($meta, true));
+               }
+       }
+
+       // primary key
+       $db->exec('DROP TABLE IF EXISTS test');
+       $sql = sprintf('CREATE TABLE test(id INT PRIMARY KEY NOT NULL AUTO_INCREMENT) ENGINE = %s', MySQLPDOTest::getTableEngine());
+       if (($stmt = @$db->prepare($sql)) && @$stmt->execute()) {
+               $db->exec('INSERT INTO test(id) VALUES (1)');
+               $stmt = $db->query('SELECT id FROM test');
+               $meta = $stmt->getColumnMeta(0);
+               if (!isset($meta['flags'])) {
+                       printf("[1002] No flags contained in metadata %s\n", var_export($meta, true));
+               } else {
+                       $flags = $meta['flags'];
+                       $found = false;
+                       foreach ($flags as $k => $flag) {
+                               if ($flag == 'primary_key')
+                                       $found = true;
+                       }
+                       if (!$found)
+                               printf("[1003] Flags seem wrong %s\n", var_export($meta, true));
+               }
+       }
+
+       // multiple key
+       $db->exec('DROP TABLE IF EXISTS test');
+       $sql = sprintf('CREATE TABLE test(id INT, label1 INT, label2 INT, INDEX idx1(label1, label2)) ENGINE = %s', MySQLPDOTest::getTableEngine());
+       if (($stmt = @$db->prepare($sql)) && @$stmt->execute()) {
+               $db->exec('INSERT INTO test(id, label1, label2) VALUES (1, 2, 3)');
+               $stmt = $db->query('SELECT id, label1, label2 FROM test');
+               $meta = $stmt->getColumnMeta(1);
+               if (!isset($meta['flags'])) {
+                       printf("[1004] No flags contained in metadata %s\n", var_export($meta, true));
+               } else {
+                       $flags = $meta['flags'];
+                       $found = false;
+                       foreach ($flags as $k => $flag) {
+                               if ($flag == 'multiple_key')
+                                       $found = true;
+                       }
+                       if (!$found)
+                               printf("[1005] Flags seem wrong %s\n", var_export($meta, true));
+               }
+       }
+
+       $stmt = $db->query('SELECT NULL AS col1');
+       $meta = $stmt->getColumnMeta(0);
+       if ('NULL' !== $meta['native_type'])
+               printf("[1006] Expecting NULL got %s\n", $meta['native_type']);
+
+} catch (PDOException $e) {
+       // we should never get here, we use warnings, but never trust a system...
+       printf("[001] %s, [%s} %s\n",
+               $e->getMessage(), $db->errorInfo(), implode(' ', $db->errorInfo()));
+}
+
+$db->exec('DROP TABLE IF EXISTS test');
+print "done!";
+?>
+--EXPECTF--
+Testing native PS...
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_multiquery.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_multiquery.phpt
new file mode 100644 (file)
index 0000000..dbf60d5
--- /dev/null
@@ -0,0 +1,90 @@
+--TEST--
+PDOStatements and multi query
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function mysql_stmt_multiquery_wrong_usage($db) {
+
+               $stmt = $db->query('SELECT label FROM test ORDER BY id ASC LIMIT 1; SELECT label FROM test ORDER BY id ASC LIMIT 1');
+               var_dump($stmt->errorInfo());
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               var_dump($stmt->errorInfo());
+
+       }
+
+       function mysql_stmt_multiquery_proper_usage($db) {
+
+               $stmt = $db->query('SELECT label FROM test ORDER BY id ASC LIMIT 1; SELECT label FROM test ORDER BY id ASC LIMIT 1');
+               do {
+                       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               } while ($stmt->nextRowset());
+
+       }
+
+       try {
+
+               printf("Emulated Prepared Statements...\n");
+               $db = MySQLPDOTest::factory();
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+               mysql_stmt_multiquery_wrong_usage($db);
+               mysql_stmt_multiquery_proper_usage($db);
+
+               printf("Native Prepared Statements...\n");
+               $db = MySQLPDOTest::factory();
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+               mysql_stmt_multiquery_wrong_usage($db);
+               mysql_stmt_multiquery_proper_usage($db);
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Emulated Prepared Statements...
+array(1) {
+  [0]=>
+  string(5) "00000"
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(1) "a"
+  }
+}
+array(1) {
+  [0]=>
+  string(5) "00000"
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(1) "a"
+  }
+}
+array(1) {
+  [0]=>
+  array(1) {
+    ["label"]=>
+    string(1) "a"
+  }
+}
+Native Prepared Statements...
+
+Warning: PDO::query(): SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '; SELECT label FROM test ORDER BY id ASC LIMIT 1' at line %d in %s on line %d
+
+Fatal error: Call to a member function errorInfo() on a non-object in %s on line %d
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_nextrowset.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_nextrowset.phpt
new file mode 100644 (file)
index 0000000..f491536
--- /dev/null
@@ -0,0 +1,309 @@
+--TEST--
+MySQL PDOStatement->nextRowSet()
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+$row = $db->query('SELECT VERSION() as _version')->fetch(PDO::FETCH_ASSOC);
+$matches = array();
+if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches))
+       die(sprintf("skip Cannot determine MySQL Server version\n"));
+
+$version = $matches[0] * 10000 + $matches[1] * 100 + $matches[2];
+if ($version < 50000)
+       die(sprintf("skip Need MySQL Server 5.0.0+, found %d.%02d.%02d (%d)\n",
+               $matches[0], $matches[1], $matches[2], $version));
+
+if (!MySQLPDOTest::isPDOMySQLnd())
+       die("skip This will not work with libmysql");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+
+       MySQLPDOTest::createTestTable($db);
+
+       $stmt = $db->query('SELECT id FROM test');
+       if (false !== ($tmp = $stmt->nextRowSet()))
+               printf("[002] Expecting false got %s\n", var_export($tmp, true));
+
+       // TODO: should give a warning, but its PDO, let's ignore the missing warning for now
+       if (false !== ($tmp = $stmt->nextRowSet(1)))
+               printf("[003] Expecting false got %s\n", var_export($tmp, true));
+
+       function test_proc1($db) {
+
+               $stmt = $db->query('SELECT @VERSION as _version');
+               $tmp = $stmt->fetch(PDO::FETCH_ASSOC);
+               assert($tmp['_version'] === NULL);
+               while ($stmt->fetch()) ;
+
+               $db->exec('DROP PROCEDURE IF EXISTS p');
+               $db->exec('CREATE PROCEDURE p(OUT ver_param VARCHAR(25)) BEGIN SELECT VERSION() INTO ver_param; END;');
+               $db->exec('CALL p(@VERSION)');
+               $stmt = $db->query('SELECT @VERSION as _version');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               var_dump($stmt->nextRowSet());
+
+       }
+
+       function test_proc2($db) {
+
+               $db->exec('DROP PROCEDURE IF EXISTS p');
+               $db->exec('CREATE PROCEDURE p() BEGIN SELECT id FROM test ORDER BY id ASC LIMIT 3; SELECT id, label FROM test WHERE id < 4 ORDER BY id DESC LIMIT 3; END;');
+               $stmt = $db->query('CALL p()');
+               do {
+                       var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               } while ($stmt->nextRowSet());
+               var_dump($stmt->nextRowSet());
+
+       }
+
+       try {
+
+               // Emulated PS
+               printf("Emulated PS...\n");
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
+               test_proc1($db);
+               test_proc2($db);
+
+               $db = MySQLPDOTest::factory();
+               $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 0);
+               test_proc1($db);
+               test_proc2($db);
+
+               // Native PS
+               printf("Native PS...\n");
+               $db = MySQLPDOTest::factory();
+               $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+               test_proc1($db);
+               test_proc2($db);
+
+               $db = MySQLPDOTest::factory();
+               $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 0);
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+
+               test_proc1($db);
+               test_proc2($db);
+
+               @$db->exec('DROP PROCEDURE IF EXISTS p');
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Emulated PS...
+array(1) {
+  [0]=>
+  array(1) {
+    ["_version"]=>
+    string(%d) "%s"
+  }
+}
+bool(false)
+array(3) {
+  [0]=>
+  array(1) {
+    ["id"]=>
+    string(1) "1"
+  }
+  [1]=>
+  array(1) {
+    ["id"]=>
+    string(1) "2"
+  }
+  [2]=>
+  array(1) {
+    ["id"]=>
+    string(1) "3"
+  }
+}
+array(3) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "3"
+    ["label"]=>
+    string(1) "c"
+  }
+  [1]=>
+  array(2) {
+    ["id"]=>
+    string(1) "2"
+    ["label"]=>
+    string(1) "b"
+  }
+  [2]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+bool(false)
+array(1) {
+  [0]=>
+  array(1) {
+    ["_version"]=>
+    string(%d) "%s"
+  }
+}
+bool(false)
+array(3) {
+  [0]=>
+  array(1) {
+    ["id"]=>
+    string(1) "1"
+  }
+  [1]=>
+  array(1) {
+    ["id"]=>
+    string(1) "2"
+  }
+  [2]=>
+  array(1) {
+    ["id"]=>
+    string(1) "3"
+  }
+}
+array(3) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "3"
+    ["label"]=>
+    string(1) "c"
+  }
+  [1]=>
+  array(2) {
+    ["id"]=>
+    string(1) "2"
+    ["label"]=>
+    string(1) "b"
+  }
+  [2]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+bool(false)
+Native PS...
+array(1) {
+  [0]=>
+  array(1) {
+    ["_version"]=>
+    string(%d) "%s"
+  }
+}
+bool(false)
+array(3) {
+  [0]=>
+  array(1) {
+    ["id"]=>
+    string(1) "1"
+  }
+  [1]=>
+  array(1) {
+    ["id"]=>
+    string(1) "2"
+  }
+  [2]=>
+  array(1) {
+    ["id"]=>
+    string(1) "3"
+  }
+}
+array(3) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "3"
+    ["label"]=>
+    string(1) "c"
+  }
+  [1]=>
+  array(2) {
+    ["id"]=>
+    string(1) "2"
+    ["label"]=>
+    string(1) "b"
+  }
+  [2]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+bool(false)
+array(1) {
+  [0]=>
+  array(1) {
+    ["_version"]=>
+    string(%d) "%s"
+  }
+}
+bool(false)
+array(3) {
+  [0]=>
+  array(1) {
+    ["id"]=>
+    string(1) "1"
+  }
+  [1]=>
+  array(1) {
+    ["id"]=>
+    string(1) "2"
+  }
+  [2]=>
+  array(1) {
+    ["id"]=>
+    string(1) "3"
+  }
+}
+array(3) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "3"
+    ["label"]=>
+    string(1) "c"
+  }
+  [1]=>
+  array(2) {
+    ["id"]=>
+    string(1) "2"
+    ["label"]=>
+    string(1) "b"
+  }
+  [2]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+bool(false)
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_rowcount.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_rowcount.phpt
new file mode 100644 (file)
index 0000000..8883b84
--- /dev/null
@@ -0,0 +1,32 @@
+--TEST--
+MySQL PDOStatement->rowCount() @ SELECT
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+       MySQLPDOTest::createTestTable($db);
+
+       try {
+
+               if (0 !== ($tmp = $db->query('SELECT id FROM test WHERE 1 = 0')->rowCount()))
+                       printf("[002] Expecting 0 got %s", var_export($tmp, true));
+
+               if (1 !== ($tmp = $db->query('SELECT id FROM test WHERE id = 1')->rowCount()))
+                       printf("[003] Expecting 1 got %s", var_export($tmp, true));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_unbuffered_2050.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_unbuffered_2050.phpt
new file mode 100644 (file)
index 0000000..0be9eac
--- /dev/null
@@ -0,0 +1,180 @@
+--TEST--
+MySQL PDO:query() vs. PDO::prepare() and MySQL error 2050
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+if (MYSQLPDOTest::isPDOMySQLnd())
+       die("skip libmysql only test");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       try {
+
+               printf("Native PS...\n");
+               $db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 0);
+               if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
+                       printf("[004] Unable to turn off emulated prepared statements\n");
+
+               printf("Buffered...\n");
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
+               $stmt = $db->query('SELECT id, label FROM test WHERE id = 1');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               $stmt = $db->query('SELECT id, label FROM test WHERE id = 1');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               printf("Unbuffered...\n");
+               MySQLPDOTest::createTestTable($db);
+               $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+               $stmt = $db->query('SELECT id, label FROM test WHERE id = 1');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               /*
+               NOTE - this will cause an error and it OK
+               When using unbuffered prepared statements MySQL expects you to
+               fetch all data from the row before sending new data to the server.
+               PDO::query() will prepare and execute a statement in one step.
+               After the execution of PDO::query(), MySQL expects you to fetch
+               the results from the line before sending new commands. However,
+               PHP/PDO will send a CLOSE message as part of the PDO::query() call.
+
+               The following happens:
+
+                       $stmt = PDO::query(<some query>)
+                               mysql_stmt_prepare()
+                               mysql_stmt_execute()
+
+                       $stmt->fetchAll()
+                               mysql_stmt_fetch()
+
+                       And now the right side of the expression will be executed first:
+                               $stmt = PDO::query(<some query>)
+                                       PDO::query(<some query>)
+                                               mysql_stmt_prepare
+                                               mysql_stmt_execute
+
+                       PHP continues at the left side of the expression:
+
+                               $stmt = PDO::query(<some query>)
+
+                                       What happens is that $stmt gets overwritten. The reference counter of the
+                                       zval representing the current value of $stmt. PDO gets a callback that
+                                       it has to free the resources associated with the zval representing the
+                                       current value of stmt:
+                                               mysql_stmt_close
+                                                       ---> ERROR
+                                                       ---> execute() has been send on the line, you are supposed to fetch
+                                                       ---> you must not try to send a CLOSE after execute()
+                                                       ---> Error: 2050 (CR_FETCH_CANCELED)
+                                                       ---> Message: Row retrieval was canceled by mysql_stmt_close() call
+                                                       ---> MySQL does its best to recover the line and cancels the retrieval
+
+                                       PHP proceeds and assigns the new statement object/zval obtained from
+                                       PDO to $stmt.
+
+               Solutions:
+                               - use mysqlnd
+                               - use prepare() + execute() instead of query()
+                               - as there is no explicit close() in PDO, try unset($stmt) before the new assignment
+                               - fix PDO::query() [not the driver, fix PDO itself]
+               */
+
+               $stmt = $db->query('SELECT id, label FROM test WHERE id = 1');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id = 1');
+               $stmt->execute();
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               $stmt = $db->prepare('SELECT id, label FROM test WHERE id = 1');
+               $stmt->execute();
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               unset($stmt);
+               $stmt = $db->query('SELECT id, label FROM test WHERE id = 1');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+               unset($stmt);
+               $stmt = $db->query('SELECT id, label FROM test WHERE id = 1');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!";
+?>
+--EXPECTF--
+Native PS...
+Buffered...
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+Unbuffered...
+
+Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: 2050  in %s on line %d
+array(0) {
+}
+
+Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: 2050  in %s on line %d
+array(0) {
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(1) "1"
+    ["label"]=>
+    string(1) "a"
+  }
+}
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_variable_columncount.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_variable_columncount.phpt
new file mode 100644 (file)
index 0000000..853f5a3
--- /dev/null
@@ -0,0 +1,122 @@
+--TEST--
+MySQL Prepared Statements and different column counts
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+$db = MySQLPDOTest::factory();
+
+$row = $db->query('SELECT VERSION() as _version')->fetch(PDO::FETCH_ASSOC);
+$matches = array();
+if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches))
+       die(sprintf("skip Cannot determine MySQL Server version\n"));
+
+$version = $matches[0] * 10000 + $matches[1] * 100 + $matches[2];
+if ($version < 50000)
+       die(sprintf("skip Need MySQL Server 5.0.0+, found %d.%02d.%02d (%d)\n",
+               $matches[0], $matches[1], $matches[2], $version));
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+       $db = MySQLPDOTest::factory();
+
+       function check_result($offset, $stmt, $columns) {
+
+               do {
+                               $row = $stmt->fetch(PDO::FETCH_ASSOC);
+               } while ($stmt->nextRowSet());
+
+               if (!isset($row['one']) || ($row['one'] != 1)) {
+                               printf("[%03d + 1] Expecting array('one' => 1), got %s\n", $offset, var_export($row, true));
+                               return false;
+               }
+
+               if (($columns == 2) &&
+                       (!isset($row['two']) || ($row['two'] != 2))) {
+                               printf("[%03d + 2] Expecting array('one' => 1, 'two' => 2), got %s\n", $offset, var_export($row, true));
+                               return false;
+               } else if (($columns == 1) && isset($row['two'])) {
+                               printf("[%03d + 3] Expecting one array element got two\n", $offset);
+                               return false;
+               }
+
+               return true;
+       }
+
+       try {
+
+               // What will happen if a PS returns a differen number of result set column upon each execution?
+               // Lets try with a SP accepting parameters...
+               $db->exec('DROP PROCEDURE IF EXISTS p');
+               $db->exec('CREATE PROCEDURE p(IN cols INT) BEGIN IF cols < 2 THEN SELECT cols AS "one"; ELSE SELECT 1 AS "one", cols AS "two"; END IF; END;');
+
+               // Emulates PS first
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+               $stmt = $db->prepare('CALL p(?)');
+
+               $columns = null;
+               $stmt->bindParam(1, $columns);
+               for ($i = 0; $i < 5; $i++) {
+                       $columns = ($i % 2) + 1;
+                       $stmt->execute();
+                       check_result($i, $stmt, $columns);
+               }
+
+               if (MySQLPDOTest::isPDOMySQLnd()) {
+                       // Native PS
+                       // Libmysql cannot handle such a stored procedure. You will see leaks with libmysql
+                       $db = MySQLPDOTest::factory();
+                       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+                       $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
+                       $stmt = $db->prepare('CALL p(?)');
+                       $stmt->bindParam(1, $columns);
+                       for ($i = 5; $i < 10; $i++) {
+                               $columns = ($i % 2) + 1;
+                               $stmt->execute();
+                               check_result($i, $stmt, $columns);
+                       }
+               }
+
+               // And now without parameters... - this gives a different control flow inside PDO
+               $db->exec('DROP PROCEDURE IF EXISTS p');
+               $db->exec('CREATE PROCEDURE p() BEGIN DECLARE cols INT; SELECT @numcols INTO cols; IF cols < 2 THEN SET @numcols = 2; SELECT cols AS "one"; ELSE SET @numcols = 1; SELECT 1 AS "one", cols AS "two"; END IF; END;');
+
+                               // Emulates PS first
+               $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
+               $db->exec('SET @numcols = 1');
+               $stmt = $db->prepare('CALL p()');
+               $stmt->execute();
+               check_result(11, $stmt, 1);
+               $stmt->execute();
+               check_result(12, $stmt, 2);
+               $db->exec('SET @numcols = 1');
+               $stmt->execute();
+               check_result(13, $stmt, 1);
+
+               if (MySQLPDOTest::isPDOMySQLnd()) {
+                       // Native PS
+                       // Libmysql cannot handle such a stored procedure. You will see leaks with libmysql
+                       $db = MySQLPDOTest::factory();
+                       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+                       $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
+                       $db->exec('SET @numcols = 1');
+                       $stmt = $db->prepare('CALL p()');
+                       $stmt->execute();
+                       check_result(14, $stmt, 1);
+                       $stmt->execute();
+                       check_result(15, $stmt, 2);
+                       $db->exec('SET @numcols = 1');
+                       $stmt->execute();
+                       check_result(16, $stmt, 1);
+               }
+
+       } catch (PDOException $e) {
+               printf("[99] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       print "done!";
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt b/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
new file mode 100644 (file)
index 0000000..90d4fb2
--- /dev/null
@@ -0,0 +1,101 @@
+--TEST--
+MySQL PDOStatement->execute()/fetch(), Non-SELECT
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+if (version_compare(PHP_VERSION, '5.0.0', '<'))
+       die("skip Requires PHP 5.0+");
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       // No silly strict mode warnings, please!
+       error_reporting(E_ALL^E_STRICT);
+       ini_set('display_errors', false);
+
+       try {
+
+               class MyPDO extends PDO {
+
+                       public function __construct() {
+                               $this->protocol();
+                               return call_user_func_array(array($this, 'parent::__construct'), func_get_args());
+                       }
+
+                       public function exec() {
+                               $this->protocol();
+                               return call_user_func_array(array($this, 'parent::exec'), func_get_args());
+                       }
+
+                       public function query() {
+                               $this->protocol();
+                               return call_user_func_array(array($this, 'parent::query'), func_get_args());
+                       }
+
+                       public function __call($method, $args) {
+                               print "__call()";
+                               // $this->protocol();
+                       }
+
+                       private function protocol() {
+                               $stack = debug_backtrace();
+                               if (!isset($stack[1]))
+                                       return;
+
+                               printf("%s(", $stack[1]['function']);
+                               $args = '';
+                               foreach ($stack[1]['args'] as $k => $v)
+                                       $args .= sprintf("%s, ", var_export($v, true));
+                               if ($args != '')
+                                       printf("%s", substr($args, 0, -2));
+                               printf(")\n");
+                       }
+
+               }
+
+               $db = new MyPDO(PDO_MYSQL_TEST_DSN, PDO_MYSQL_TEST_USER, PDO_MYSQL_TEST_PASS);
+               $db->exec('DROP TABLE IF EXISTS test');
+               $db->exec('CREATE TABLE test(id INT)');
+               $db->exec('INSERT INTO test(id) VALUES (1), (2)');
+               $stmt = $db->query('SELECT * FROM test ORDER BY id ASC');
+               var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
+               var_dump($stmt->fetch());
+               $db->intercept_call();
+
+
+       } catch (PDOException $e) {
+               printf("[001] %s [%s] %s\n",
+                       $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo()));
+       }
+
+       $db->exec('DROP TABLE IF EXISTS test');
+       print "done!\n";
+?>
+--XFAIL--
+PDO doesn't like __call()
+--EXPECTF--
+__construct('%s', '%s', '%s')
+exec('DROP TABLE IF EXISTS test')
+exec('CREATE TABLE test(id INT)')
+exec('INSERT INTO test(id) VALUES (1), (2)')
+query('SELECT * FROM test ORDER BY id ASC')
+array(2) {
+  [0]=>
+  array(1) {
+    ["id"]=>
+    string(1) "1"
+  }
+  [1]=>
+  array(1) {
+    ["id"]=>
+    string(1) "2"
+  }
+}
+bool(false)
+__call('intercept_call', array (
+))
+exec('DROP TABLE IF EXISTS test')
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_types.phpt b/ext/pdo_mysql/tests/pdo_mysql_types.phpt
new file mode 100644 (file)
index 0000000..ae4005b
--- /dev/null
@@ -0,0 +1,174 @@
+--TEST--
+MySQL PDO->exec(), native types wo ZEROFILL
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function test_type(&$db, $offset, $sql_type, $value, $ret_value = NULL, $pattern = NULL) {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $sql = sprintf('CREATE TABLE test(id INT, label %s) ENGINE=%s', $sql_type, MySQLPDOTest::getTableEngine());
+               @$db->exec($sql);
+               if ($db->errorCode() != 0) {
+                       // not all MySQL Server versions and/or engines might support the type
+                       return true;
+               }
+
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (?, ?)');
+               $stmt->bindValue(1, $offset);
+               $stmt->bindValue(2, $value);
+               if (!$stmt->execute()) {
+                       printf("[%03d + 1] INSERT failed, %s\n", $offset, var_export($stmt->errorInfo(), true));
+                       return false;
+               }
+               $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
+               $stmt = $db->query('SELECT  id, label FROM test');
+               $row = $stmt->fetch(PDO::FETCH_ASSOC);
+               $stmt->closeCursor();
+
+               if (!isset($row['id']) || !isset($row['label'])) {
+                       printf("[%03d + 2] Fetched result seems wrong, dumping result: %s\n", $offset, var_export($row, true));
+                       return false;
+               }
+
+               if ($row['id'] != $offset) {
+                       printf("[%03d + 3] Expecting %s got %s\n", $offset, $row['id']);
+                       return false;
+               }
+
+               if (!is_null($pattern)) {
+                       if (!preg_match($pattern, $row['label'])) {
+                               printf("[%03d + 5] Value seems wrong, accepting pattern %s got %s, check manually\n",
+                                       $offset, $pattern, var_export($row['label'], true));
+                               return false;
+                       }
+
+               } else {
+
+                       $exp = $value;
+                       if (!is_null($ret_value)) {
+                               // we expect a different return value than our input value
+                               // typically the difference is only the type
+                               $exp = $ret_value;
+                       }
+                       if ($row['label'] !== $exp) {
+                               printf("[%03d + 4] %s - input = %s/%s, output = %s/%s\n", $offset,
+                                       $sql_type, var_export($exp, true), gettype($exp),
+                                       var_export($row['label'], true), gettype($row['label']));
+                               return false;
+                       }
+
+               }
+
+               $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+               $stmt = $db->query('SELECT id, label FROM test');
+               $row_string = $stmt->fetch(PDO::FETCH_ASSOC);
+               $stmt->closeCursor();
+               if (is_null($pattern) && ($row['label'] != $row_string['label'])) {
+                       printf("%s - STRINGIGY = %s, NATIVE = %s\n", $sql_type, var_export($row_string['label'], true), var_export($row['label'], true));
+                       return false;
+               } else if (!is_null($pattern) && !preg_match($pattern, $row_string['label'])) {
+                       printf("%s - STRINGIGY = %s, NATIVE = %s, pattern '%s'\n", $sql_type, var_export($row_string['label'], true), var_export($row['label'], true), $pattern);
+                       return false;
+               }
+
+
+               return true;
+       }
+
+       $db = MySQLPDOTest::factory();
+       $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+       $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
+
+/*
+       test_type($db, 20, 'BIT(8)', 1);
+*/
+       $is_mysqlnd = MySQLPDOTest::isPDOMySQLnd();
+
+       test_type($db, 30, 'TINYINT', -127, ($is_mysqlnd) ? -127: '-127');
+       test_type($db, 40, 'TINYINT UNSIGNED', 255, ($is_mysqlnd) ? 255 : '255');
+       test_type($db, 50, 'BOOLEAN', 1, ($is_mysqlnd) ? 1 : '1');
+
+       test_type($db, 60, 'SMALLINT', -32768, ($is_mysqlnd) ? -32768 : '-32768');
+       test_type($db, 70, 'SMALLINT UNSIGNED', 65535, ($is_mysqlnd) ? 65535 : '65535');
+
+       test_type($db, 80, 'MEDIUMINT', -8388608, ($is_mysqlnd) ? -8388608 : '-8388608');
+       test_type($db, 90, 'MEDIUMINT UNSIGNED', 16777215, ($is_mysqlnd) ? 16777215 : '16777215');
+
+       test_type($db, 100, 'INT', -2147483648, ($is_mysqlnd) ? -2147483648 : '-2147483648');
+       test_type($db, 110, 'INT UNSIGNED', 4294967295, ($is_mysqlnd) ? 4294967295 : '4294967295');
+
+       // no chance to return int with the current PDO version - we are forced to return strings
+       test_type($db, 120, 'BIGINT', 1, ($is_mysqlnd) ? 1 : '1');
+       // to avoid trouble with  numeric ranges, lets pass the numbers as a string
+       test_type($db, 130, 'BIGINT', '-9223372036854775808', NULL, '/^\-9[\.]*22/');
+       test_type($db, 140, 'BIGINT UNSIGNED', '18446744073709551615', NULL, '/^1[\.]*844/');
+
+       test_type($db, 150, 'REAL', -1.01, ($is_mysqlnd) ? -1.01 : '-1.01');
+       test_type($db, 160, 'REAL UNSIGNED', 1.01, ($is_mysqlnd) ? 1.01 : '1.01');
+
+       test_type($db, 170, 'DOUBLE', -1.01, ($is_mysqlnd) ? -1.01 : '-1.01');
+       test_type($db, 180, 'DOUBLE UNSIGNED', 1.01, ($is_mysqlnd) ? 1.01 : '1.01');
+
+       test_type($db, 210, 'FLOAT', -1.01, NULL, '/^\-1.0\d+/');
+       test_type($db, 220, 'FLOAT UNSIGNED', 1.01, NULL, '/^1.0\d+/');
+
+       test_type($db, 250, 'DECIMAL', -1.01, '-1');
+       test_type($db, 260, 'DECIMAL UNSIGNED', 1.01, '1');
+
+
+       test_type($db, 290, 'NUMERIC', -1.01, '-1');
+       test_type($db, 300, 'NUMERIC UNSIGNED', 1.01, '1');
+
+       test_type($db, 330, 'DATE', '2008-04-23');
+       test_type($db, 340, 'TIME', '14:37:00');
+       test_type($db, 350, 'TIMESTAMP', '2008-05-06 21:09:00');
+       test_type($db, 360, 'DATETIME', '2008-03-23 14:38:00');
+       test_type($db, 370, 'YEAR', 2008, ($is_mysqlnd) ? 2008 : '2008');
+
+       test_type($db, 380, 'CHAR(1)', 'a');
+       test_type($db, 390, 'CHAR(10)', '0123456789');
+       test_type($db, 400, 'CHAR(255)', str_repeat('z', 255));
+       test_type($db, 410, 'VARCHAR(1)', 'a');
+       test_type($db, 420, 'VARCHAR(10)', '0123456789');
+       test_type($db, 430, 'VARCHAR(255)', str_repeat('z', 255));
+
+       test_type($db, 440, 'BINARY(1)', str_repeat('a', 1));
+       test_type($db, 450, 'BINARY(255)', str_repeat('b', 255));
+       test_type($db, 460, 'VARBINARY(1)', str_repeat('a', 1));
+       test_type($db, 470, 'VARBINARY(255)', str_repeat('b', 255));
+
+       test_type($db, 480, 'TINYBLOB', str_repeat('b', 255));
+       test_type($db, 490, 'BLOB', str_repeat('b', 256));
+       test_type($db, 500, 'MEDIUMBLOB', str_repeat('b', 256));
+       test_type($db, 510, 'LONGBLOB', str_repeat('b', 256));
+
+       test_type($db, 520, 'TINYTEXT', str_repeat('b', 255));
+       test_type($db, 530, 'TINYTEXT BINARY', str_repeat('b', 255));
+
+       test_type($db, 560, 'TEXT', str_repeat('b', 256));
+       test_type($db, 570, 'TEXT BINARY', str_repeat('b', 256));
+
+       test_type($db, 580, 'MEDIUMTEXT', str_repeat('b', 256));
+       test_type($db, 590, 'MEDIUMTEXT BINARY', str_repeat('b', 256));
+
+       test_type($db, 600, 'LONGTEXT', str_repeat('b', 256));
+       test_type($db, 610, 'LONGTEXT BINARY', str_repeat('b', 256));
+
+       test_type($db, 620, "ENUM('yes', 'no') DEFAULT 'yes'", 'no');
+       test_type($db, 630, "SET('yes', 'no') DEFAULT 'yes'", 'no');
+
+       test_type($db, 640, 'DECIMAL(3,2)', -1.01, '-1.01');
+
+
+       echo "done!\n";
+?>
+--EXPECTF--
+done!
diff --git a/ext/pdo_mysql/tests/pdo_mysql_types_zerofill.phpt b/ext/pdo_mysql/tests/pdo_mysql_types_zerofill.phpt
new file mode 100644 (file)
index 0000000..9d521a1
--- /dev/null
@@ -0,0 +1,117 @@
+--TEST--
+MySQL PDO->exec(), native types - ZEROFILL
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+       require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+       function test_type(&$db, $offset, $sql_type, $value, $ret_value = NULL, $pattern = NULL) {
+
+               $db->exec('DROP TABLE IF EXISTS test');
+               $sql = sprintf('CREATE TABLE test(id INT, label %s) ENGINE=%s', $sql_type, MySQLPDOTest::getTableEngine());
+               @$db->exec($sql);
+               if ($db->errorCode() != 0) {
+                       // not all MySQL Server versions and/or engines might support the type
+                       return true;
+               }
+
+               $stmt = $db->prepare('INSERT INTO test(id, label) VALUES (?, ?)');
+               $stmt->bindValue(1, $offset);
+               $stmt->bindValue(2, $value);
+               try {
+                       if (!$stmt->execute()) {
+                               printf("[%03d + 1] INSERT failed, %s\n", $offset, var_export($stmt->errorInfo(), true));
+                               return false;
+                       }
+               } catch (PDOException $e) {
+                       // This might be a SQL warning on signed values inserted in unsigned columns
+                       // Zerofill implies unsigned but the test plays with signed = negative values as well!
+                       return true;
+               }
+
+               $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
+               $stmt = $db->query('SELECT id, label FROM test');
+               $row = $stmt->fetch(PDO::FETCH_ASSOC);
+               $stmt->closeCursor();
+               if (!isset($row['id']) || !isset($row['label'])) {
+                       printf("[%03d + 2] Fetched result seems wrong, dumping result: %s\n", $offset, var_export($row, true));
+                       return false;
+               }
+
+               if ($row['id'] != $offset) {
+                       printf("[%03d + 3] Expecting %s got %s\n", $offset, $row['id']);
+                       return false;
+               }
+
+               if (!is_null($pattern)) {
+
+                       if (!preg_match($pattern, $row['label'])) {
+                               printf("[%03d + 5] Value seems wrong, accepting pattern %s got %s, check manually\n",
+                                       $offset, $pattern, var_export($row['label'], true));
+                               return false;
+                       }
+
+               } else {
+
+                       $exp = $value;
+                       if (!is_null($ret_value)) {
+                               // we expect a different return value than our input value
+                               // typically the difference is only the type
+                               $exp = $ret_value;
+                       }
+
+                       if ($row['label'] !== $exp) {
+                               printf("[%03d + 4] %s - input = %s/%s, output = %s/%s\n", $offset,
+                                       $sql_type, var_export($exp, true), gettype($exp),
+                                       var_export($row['label'], true), gettype($row['label']));
+                               return false;
+                       }
+
+               }
+
+               $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+               $stmt = $db->query('SELECT id, label FROM test');
+               $row_string = $stmt->fetch(PDO::FETCH_ASSOC);
+               $stmt->closeCursor();
+               if ($row['label'] != $row_string['label']) {
+                       printf("%s - STRINGIGY = %s, NATIVE = %s\n", $sql_type, var_export($row_string['label'], true), var_export($row['label'], true));
+                       return false;
+               }
+
+               return true;
+       }
+
+       $db = MySQLPDOTest::factory();
+       $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+       $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+       $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
+
+       test_type($db, 100, 'REAL ZEROFILL', -1.01, NULL, '/^[0]*0$/');
+       test_type($db, 110, 'REAL ZEROFILL', 1.01, NULL, '/^[0]*1\.01$/');
+       test_type($db, 120, 'REAL UNSIGNED ZEROFILL', 1.01, NULL, '/^[0]*1\.01$/');
+
+       test_type($db, 130, 'DOUBLE ZEROFILL', -1.01, NULL, '/^[0]*0$/');
+       test_type($db, 140, 'DOUBLE ZEROFILL', 1.01, NULL, '/^[0]*1\.01$/');
+       test_type($db, 150, 'DOUBLE UNSIGNED ZEROFILL', 1.01, NULL, '/^[0]*1\.01$/');
+
+       test_type($db, 160, 'FLOAT ZEROFILL', -1.01, NULL, '/^[0]*0$/');
+       test_type($db, 170, 'FLOAT ZEROFILL', 1, NULL, '/^[0]*1$/');
+       test_type($db, 180, 'FLOAT UNSIGNED ZEROFILL', -1, NULL, '/^[0]*0$/');
+
+       test_type($db, 190, 'DECIMAL ZEROFILL', -1.01, NULL, '/^[0]*0$/');
+       test_type($db, 200, 'DECIMAL ZEROFILL', 1.01, NULL, '/^[0]*1$/');
+       test_type($db, 210, 'DECIMAL UNSIGNED ZEROFILL', 1.01, NULL, '/^[0]*1$/');
+
+       test_type($db, 220, 'NUMERIC ZEROFILL', -1, NULL, '/^[0]*0$/');
+       test_type($db, 230, 'NUMERIC ZEROFILL', 1, NULL, '/^[0]*1$/');
+       test_type($db, 240, 'NUMERIC UNSIGNED ZEROFILL', 1.01, NULL, '/^[0]*1$/');
+
+       echo "done!\n";
+?>
+--EXPECTF--
+done!
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/skipif.inc b/ext/pdo_mysql/tests/skipif.inc
new file mode 100755 (executable)
index 0000000..d48670a
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql'))
+       die('skip PDO_MySQL driver not loaded');
+
+if (version_compare(PHP_VERSION, '5.1.0') < 0)
+       die('skip Most tests assume PHP 5.1+');
+?>
\ No newline at end of file
diff --git a/ext/pdo_mysql/tests/table.inc b/ext/pdo_mysql/tests/table.inc
new file mode 100644 (file)
index 0000000..c7bb9cc
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+if (!$db) {
+       require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+       $db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+}
+// $db->exec('DROP TABLE IF EXISTS test');
+$db->exec('CREATE TABLE test(id INT, label CHAR(1), PRIMARY KEY(id)) ENGINE=' . $engine);
+$db->exec('INSERT INTO test(id, label) VALUES (1, "a"), (2, "b"), (3, "c"), (4, "d"), (5, "e"), (6, "f")');
+?>
\ No newline at end of file