Mike is asking I`m hoping that MySQL stored procedures will work properly. Especially multiple calls in one request. in reply to PDO_MYSQLND: R[a|u]mbling and a breeze of progress. Mike, it depends what you mean by "properly". If you use the API properly it works fine with mysqlnd. Read on for code examples.
mysqlnd | libmysql | |||
---|---|---|---|---|
Emulated PS | Native PS | Emulated PS | Native PS | |
buffered fetch | PASS | PASS | PASS* | PASS* |
unbuffered fetch | PASS | PASS | PASS* | PASS* |
* See below! |
Executing a basic stored procedure twice
Code take from the internal test pdo_mysql_attr_oracle_nulls.phpt
. Status: PASS. NOTE the use of $stmt->nextRowset()
. If you do not call $stmt->nextRowset()
in a loop to fetch all result sets you will get the usual 2014 error. MySQLs Stored Procedures always return n (here: 1) result sets for the actual data followed by another result set with the status code. You must use PDOStatement::nextRowset()
to fetch them.
/*
Set as required to check all four permuations
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0|1);
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false|true);
*/
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());
}
Using a SELECT and a Stored Procedure not returning a result set
Code take from the internal test pdo_mysql_stmt_nextrowset.phpt
. Status: PASS with mysqlnd. The MySQL native driver for PHP can handle this. This will not work with the MySQL Client Library (AKA libmysql) and it will not work with PDO_MYSQL as PDO_MYSQL only supports the MySQL Client Library.
NOTE about function test_proc1
: in case of unbuffered fetches (PDO default: buffered) you must clean the line by fetching all results from the SELECT
before creating a new result set! If you do not use the API properly, you will get a 2014. This is not a bug. If you want unbuffered fetches the rule is that YOU take care of fetching all data before sending any new commands to the server – see also my 2050 race condition example.
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());
}
Stored Procedure returning a different number of columns upon each execution
Code take from the internal test pdo_mysql_stmt_variable_column_count.phpt
. Status: PASS
// What will happen if a PS returns a different number of result set column upon each execution?
$db->exec('DROP PROCEDURE IF EXISTS p');
$db->exec('CREATE PROCEDURE p() BEGIN IF RAND() > 0.5 THEN SELECT 1 AS "one"; ELSE SELECT 1 AS "one", 2 AS "two"; END IF; END;');
// Emulates PS first
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
$column_count = $last_column_count = null;
$stmt = $db->query('CALL p()');
do {
do {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
} while ($stmt->nextRowSet());
if (empty($row)) {
printf("[002] Results seem wrong, %s\n",
var_export($stmt->errorInfo(), true));
break;
}
if (!isset($row['one']) || ($row['one'] != 1)) {
printf("[003] Expecting array('one' => 1), got %s\n", var_export($row, true));
break;
}
if ((count($row) == 2) &&
(!isset($row['two']) || ($row['two'] != 2))) {
printf("[004] Expecting array('one' => 1, 'two' => 2), got %s\n", var_export($row, true));
break;
}
$stmt->execute();
if ($last_column_count == NULL) {
$last_column_count = $column_count = count($row);
continue;
}
$column_count = count($row);
} while ($last_column_count == $column_count);
// Native PS
$db = MySQLPDOTest::factory();
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
$column_count = $last_column_count = null;
$stmt = $db->prepare('CALL p()');
do {
$stmt->execute();
do {
while ($tmp = $stmt->fetch(PDO::FETCH_ASSOC))
$row = $tmp;
} while ($stmt->nextRowSet());
if (empty($row)) {
printf("[005] Results seem wrong, %s\n",
var_export($stmt->errorInfo(), true));
break;
}
if (!isset($row['one']) || ($row['one'] != 1)) {
printf("[006] Expecting array('one' => 1), got %s\n", var_export($row, true));
break;
}
if ((count($row) == 2) &&
(!isset($row['two']) || ($row['two'] != 2))) {
printf("[007] Expecting array('one' => 1, 'two' => 2), got %s\n", var_export($row, true));
break;
}
if ($last_column_count == NULL) {
$last_column_count = $column_count = count($row);
continue;
}
$column_count = count($row);
} while ($last_column_count == $column_count);
All of the above examples work fine with mysqlnd. Go mysqlnd – with ext/mysql, ext/mysqli and in the future with PDO_MYSQLND as well.
3 Comments
Leave a reply →