1--TEST-- 2MySQL Prepared Statements and different column counts 3--EXTENSIONS-- 4pdo_mysql 5--SKIPIF-- 6<?php 7require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc'); 8MySQLPDOTest::skip(); 9$db = MySQLPDOTest::factory(); 10 11$row = $db->query('SELECT VERSION() as _version')->fetch(PDO::FETCH_ASSOC); 12$matches = array(); 13if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches)) 14 die(sprintf("skip Cannot determine MySQL Server version\n")); 15 16$version = $matches[1] * 10000 + $matches[2] * 100 + $matches[3]; 17if ($version < 50000) 18 die(sprintf("skip Need MySQL Server 5.0.0+, found %d.%02d.%02d (%d)\n", 19 $matches[1], $matches[2], $matches[3], $version)); 20?> 21--FILE-- 22<?php 23 require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc'); 24 $db = MySQLPDOTest::factory(); 25 26 function check_result($offset, $stmt, $columns) { 27 $row = $stmt->fetch(PDO::FETCH_ASSOC); 28 $stmt->nextRowSet(); 29 30 if (!isset($row['one']) || ($row['one'] != 1)) { 31 printf("[%03d + 1] Expecting array('one' => 1), got %s\n", $offset, var_export($row, true)); 32 return false; 33 } 34 35 if (($columns == 2) && 36 (!isset($row['two']) || ($row['two'] != 2))) { 37 printf("[%03d + 2] Expecting array('one' => 1, 'two' => 2), got %s\n", $offset, var_export($row, true)); 38 return false; 39 } else if (($columns == 1) && isset($row['two'])) { 40 printf("[%03d + 3] Expecting one array element got two\n", $offset); 41 return false; 42 } 43 44 return true; 45 } 46 47 try { 48 49 // What will happen if a PS returns a different number of result set column upon each execution? 50 // Lets try with a SP accepting parameters... 51 $db->exec('DROP PROCEDURE IF EXISTS p'); 52 $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;'); 53 54 // Emulates PS first 55 $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1); 56 $stmt = $db->prepare('CALL p(?)'); 57 58 $columns = null; 59 $stmt->bindParam(1, $columns); 60 for ($i = 0; $i < 5; $i++) { 61 $columns = ($i % 2) + 1; 62 $stmt->execute(); 63 check_result($i, $stmt, $columns); 64 } 65 66 if (MySQLPDOTest::isPDOMySQLnd()) { 67 // Native PS 68 // Libmysql cannot handle such a stored procedure. You will see leaks with libmysql 69 $db = MySQLPDOTest::factory(); 70 $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0); 71 $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1); 72 $stmt = $db->prepare('CALL p(?)'); 73 $stmt->bindParam(1, $columns); 74 for ($i = 5; $i < 10; $i++) { 75 $columns = ($i % 2) + 1; 76 $stmt->execute(); 77 check_result($i, $stmt, $columns); 78 } 79 } 80 81 // And now without parameters... - this gives a different control flow inside PDO 82 $db->exec('DROP PROCEDURE IF EXISTS p'); 83 $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;'); 84 85 // Emulates PS first 86 $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1); 87 $db->exec('SET @numcols = 1'); 88 $stmt = $db->prepare('CALL p()'); 89 $stmt->execute(); 90 check_result(11, $stmt, 1); 91 $stmt->execute(); 92 check_result(12, $stmt, 2); 93 $db->exec('SET @numcols = 1'); 94 $stmt->execute(); 95 check_result(13, $stmt, 1); 96 97 if (MySQLPDOTest::isPDOMySQLnd()) { 98 // Native PS 99 // Libmysql cannot handle such a stored procedure. You will see leaks with libmysql 100 $db = MySQLPDOTest::factory(); 101 $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0); 102 $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1); 103 $db->exec('SET @numcols = 1'); 104 $stmt = $db->prepare('CALL p()'); 105 $stmt->execute(); 106 check_result(14, $stmt, 1); 107 $stmt->execute(); 108 check_result(15, $stmt, 2); 109 $db->exec('SET @numcols = 1'); 110 $stmt->execute(); 111 check_result(16, $stmt, 1); 112 } 113 114 } catch (PDOException $e) { 115 printf("[99] %s [%s] %s\n", 116 $e->getMessage(), $db->errorCode(), implode(' ', $db->errorInfo())); 117 } 118 119 print "done!"; 120?> 121--CLEAN-- 122<?php 123require_once __DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc'; 124$pdo = MySQLPDOTest::factory(); 125$pdo->query('DROP PROCEDURE IF EXISTS p'); 126?> 127--EXPECT-- 128done! 129