User defined MySQL function not accessible with PHP PDO connection - php

I've got a trivial MySQL function:
DELIMITER $$
DROP FUNCTION IF EXISTS `mydb`.`CALC` $$
CREATE FUNCTION `mydb`.`CALC_IT`(Flag VARCHAR(1), One FLOAT, Other FLOAT)
RETURNS FLOAT
BEGIN
IF One IS NULL THEN RETURN 0; END IF;
IF Other IS NULL THEN RETURN 0; END IF;
IF Flag = 'Y' THEN
RETURN Other - One;
ELSE
RETURN Other
END IF;
END $$
DELIMITER ;
And it's called in a query from PHP using a PDO connection:
$query = 'SELECT CALC_IT(`Flag`, `One`, `Two`) FROM `mydb`.`table` WHERE `Condition` = 1';
$db = new PDO('mysql:host=localhost', 'user', 'pass');
$stmt = $db->prepare($query);
if (!$stmt->execute()) {
var_dump($stmt->errorInfo());
}
But, it reports the following:
array
0 => string '42000' (length=5)
1 => int 1305
2 => string 'FUNCTION CALC_IT does not exist' (length=37)
And, if you try it with the legacy Mysql code, it works:
$db = mysql_connect('localhost', 'user', 'pass');
$result = mysql_query($query);
if (mysql_error()) {
var_dump(mysql_error());
}
The query also works if you try it with any other mysql client.
So why doesn't some user defined MySQL functions work in PHP's PDO library?

new PDO('mysql:host=localhost', 'user', 'pass');
Missing dbname=mydb?
it will be mydb.CALC_IT
Yep, that's a possibility. There are advantages and disadvantages to selecting a database at connect-time rather than explicitly specifying it in the query. If you're only using a single database on the server it tends to be easier to say so at connect-time and handle the possible access-rights errors then, but if you're doing cross-db work the explicit way “mydb.CALC_IT” will be necessary.

Related

OCI-Lob could not be converted to string error when Importing an Oracle BLOB into a MySQL one, using PHP

For a work, i have to import tables from an Oracle Database into a MySQL one.
Here is my approach to this :
$db = "" //Oracle connection
$connexion = oci_connect(user, login,$db)
$sql2 = "SELECT * FROM table" //SQL query
$requete2 = oci_parse($connexion, $sql);
$result2 = oci_execute($requete); // result is useless i think
if (!$requete2) {
$e = oci_error();
}else {
try {
$conn2 = new PDO("mysql:host=localhost;dbname=db", 'login', 'pass');
$conn2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt2 = $conn2->prepare("INSERT IGNORE INTO table
('field1', 'field2', 'field3')
VALUES (?, ?, ?)");
$conn2->beginTransaction();
while (($row2 = oci_fetch_array($requete2, OCI_NUM+OCI_RETURN_NULLS)) != false) {
$stmt2->execute($row2);
}
}catch(PDOException $e){
$conn2->rollBack();
echo "Connection failed: " . $e->getMessage();
}
$conn2->commit();
}
Here is an example of the var_dump of the $row2 before the execute statement:
array (size=3)
0 => string '2541.pdf' (length=29)
1 =>
object(OCI-Lob)[5]
public 'descriptor' => resource(7, oci8 descriptor)
2 => string 'id' (length=6)
And here are the fields i'm trying to insert in :
field1 : VARCHAR
field2 : BLOB
field3 : VARCHAR
Running this code gives me :
Catchable fatal error: Object of class OCI-Lob could not be converted
to string in line 95
With line 95 being $stmt2->execute($row2);
trying to bind each parameter, in this fashion :
$stmt2->bindParam(1, $row2[0]);
$stmt2->bindParam(2, $row2[1],PDO::PARAM_LOB);
$stmt2->bindParam(3, $row2[2]);
$stmt2->execute();
gives me the same error
Catchable fatal error: Object of class OCI-Lob could not be converted
to string
And just to see, here is the var_dump of the $row2[1] (my blob):
object(OCI-Lob)[5]
public 'descriptor' => resource(7, oci8 descriptor)
Thanks.
EDIT : To add some precision, thsi aprroach works like a charm if i only have Strings (i tried it). The problem here comes specifically from the BLOB
The PDO and OCI8 extensions cannot interact each other the way you intend. More specifically, PDOStatement::execute() expects a flat array of values that are either strings or can be cast to strings. The OCI-Lob class doesn't implement a __toString() magic method, thus the error.
I haven't worked with OCI8 for a while and I can't remember if there's a way to return LOBs as plain strings (which would be one option) but it's pretty straightforward to render a string yourself:
while (($row2 = oci_fetch_array($requete2, OCI_NUM+OCI_RETURN_NULLS)) != false) {
$row2[1] = $row2[1]->load();
$stmt2->execute($row2);
}
... or this (to your liking):
while (($row2 = oci_fetch_array($requete2, OCI_NUM+OCI_RETURN_NULLS)) != false) {
$stmt2->execute(
[
$row2[0],
$row2[1]->load(),
$row2[2],
]
);
}
Beware this is memory intensive since you're loading the complete LOB into RAM. If you need to manipulate large values, you'll have to research on PDO LOBs.
Also, this code will break when you add more columns to the oracle table or alter their position. I suggest you replace SELECT * with a proper column list.

How do I read an output param whose type is uniqueidentifier?

I'm trying to execute a stored procedure on SQL Server using PDO. Everything runs fine, but when I try to read the output parameter (whose type is UNIQUEIDENTIFIER) the only thing I get is a NULL.
I've tried running my script on Debian 9 with PHP 7.0 and Ubuntu 18.10 with PHP 7.2 and changing the PDO type of my parameter, with no success.
$order_uid = null;
$sql = "EXEC spInsertOrder ?, ?, ?, ?...";
$stmt = $db->prepare($sql);
$stmt->bindParam(29, $order_uid, PDO::PARAM_INPUT_OUTPUT | PDO::PARAM_STR, 50);
if ($stmt->execute() === false) {
echo $stmt->errorCode();
print_r($stmt->errorInfo());
}
I expect to get the UUID that SQL Server emits, instead this error raises:
Fatal error: Uncaught PDOException: SQLSTATE[IMSSP]: An invalid type
for parameter 5 was specified. Only booleans, integers, floating point
numbers, strings, and streams may be used as parameters.
Before you read the value of your OUTPUT parameter, you need to consider the following:
If your stored procedure executes SELECT statements, you need to consume all results with PDOStatement::nextRowset, before accessing the value of your output parameter.
If your statement executes INSERT or UPDATE statements, put SET NOCOUNT ON as first line in your procedure to stop SQL Server to return the count of the affected rows as a resultset.
set your PHP variable to null.
Working example (tested with PHP 7.1.12 and PHP Driver for SQL Server (PDO) 4.3.0+9904):
T-SQL:
CREATE PROCEDURE [dbo].[sp_UID]
#id UNIQUEIDENTIFIER OUTPUT
AS BEGIN
SET NOCOUNT ON
SET #id = NEWID()
END
PHP:
<?php
# Connection info
$server = 'server\instance,port';
$database = 'database';
$uid = 'uid';
$pwd = 'pdw';
# Connection
try {
$dbh = new PDO("sqlsrv:server=$server;Database=$database", $uid, $pwd);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch( PDOException $e ) {
die("Error connecting to SQL Server. ".$e->getMessage());
}
# Stored procedure
try {
$sql = "{CALL sp_UID(?)}";
$uid = null;
$stmt = $dbh->prepare($sql);
$stmt->bindParam(1, $uid, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 36);
$stmt->execute();
// If your procedure returns result set, you need to fetch result and then get the value for your output parameter
/*
do {
while ($row = $stmt->fetch( PDO::FETCH_ASSOC )) {
}
} while ($stmt->nextRowset());
*/
} catch( PDOException $e ) {
die( "Error executing stored procedure: ".$e->getMessage());
}
$stmt = null;
# End
$dbh = null;
echo $uid;
?>
Output:
F689A035-C3DB-4D4E-88FB-52F5DA133FA8

PDO executing statement that mysql cli cannot

I have a database table with an unsigned int that represents a wallet balance. this column is called wallet. It is in a table called users.
The following query fails via mysql cli:
UPDATE users set wallet = `wallet` - 550000000 WHERE username = 'user'
With error message:
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in
'(database.users.wallet - 550000000)'
The issue is, when executed via PDO with ERRMODE_EXCEPTION, it brings wallet balance to 0 and continues execution without raising the mentioned error in an exception
SELECT ##sql_mode returns nothing in both the code and mysql cli.
Here is the function that creates my database handle and returns it to the query object
//This function connects to a database
public function connect($dbname,DBConfig $config = NULL)
{
//If the connection is already set return it
if(isset($this->dbs[$dbname]))
return $this->dbs[$dbname];
//If the config object is not already set, and still null, throw exception
if(!isset($this->_config[$dbname]))
{
if($config === NULL)
throw new PDOdatabasesException('Database configuration object is not set',1);
$this->_config[$dbname] = $config;
}
$config = $this->_config[$dbname];
//Create a PDO object
$this->dbs[$dbname] = new PDO(
$config::type .
':host=' . $config::$host .
';port=' . $config::$port .
';dbname=' . $dbname,
$config::$user,
$config::$password
);
//Tell the handle to throw exceptions
$this->dbs[$dbname]->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$this->dbs[$dbname]->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
//Return a reference to the newly created PDO object
return $this->dbs[$dbname];
} //End connect()
Trying to save the need to paste a bunch of unnecessary code.. my query object takes a database handle from above, and prepares and executes this statement:
'UPDATE users SET wallet = wallet - ? WHERE id = ?'
binding the amount (approximately 10 million) and the user id, then executing.
Wallet balance can be at 0 and the query will still execute successfully. Why does this happen when the cli cannot? It does not make sense!!
I believe i need to once again reiterate This query SHOULD fail if it drops wallet below 0!
It succeeds through pdo, but not through mysql cli, my question is WHY??
The result of wallet - 550000000 should be less than 0 and your column is UNSIGNED. Try to change your column type from BIGINT UNSIGNED to BIGINT
This is apparently an unsolved bug in PDO::bindValue() and PDO::bindParam()
EDIT:
I am apparently stupid and did not realize that data_type was REQUIRED to be defined when binding an integer to a query.
I have simplified the code to be the following:
$db = new PDO('mysql:dbname=test;host=127.0.0.1','omitted','omitted');
$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
try{
$statement = $db->prepare("UPDATE test set number = number - ? WHERE id = 1");
$statement->bindValue(1,1000000000);
$statement->execute();
}catch(PDOException $e)
{
die($e->getMessage());
}
changing the statement to (and not using bindValue or bindParam):
$statement = $db->prepare("UPDATE test set number = number - 100000000 WHERE id = 1");
then the code throws the expected exception
CHANGING AGAIN TO:
$statement>bindValue(1,1000000000,PDO::PARAM_INT);
throws as expected

pdo defining a function an then using it in a select statement

My query is somewhat like this :
CREATE FUNCTION func ... ;
SELECT * FROM ....;
I'm using php's PDO which doesn't allow more than 1 statements in a $pdo->query() , so instead I decided that I'd split the query into two, so I execute the CREATE FUNCTION query using $pdo->exec() and then use $pdo->query() on the select statement.
However, I get the error on execution of select statement that FUNCTION database.func does not exist. How do I solve this? when I run it in phpmyadmin as a single query it works fine
Edit : PHP code :
class MyClass {
function connectPDO($user,$pass,$chartset="utf8"){
//simple function for making a new PDO object and mysql connection
$dbname = "my_db";
try {
//creating new pdo
$pdo = new PDO('mysql:host=localhost;dbname='.$dbname, $user,$pass);
//attributes set to throw errors and exceptions which can be caught, can be changed
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//fetch associative arrays
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);
//default chartset
$pdo->exec('SET NAMES "'.$chartset.'"');
} catch (PDOException $e) {
//on some error
$output = "Ay caramba! there's something wrong here!.<br>";
echo $output.$e->getMessage();
exit();
}
$this->db = $pdo;
}
}
$object = new MyClass();
$object->connectPDO("user","pass");
$sqlPreliminary = "
DROP FUNCTION IF EXISTS myFunc;
DELIMITER //
CREATE FUNCTION myFunc (id INT)
RETURNS DECIMAL
BEGIN
RETURN id + 1;
END //
DELIMITER ;
";
$sqlFinal = "
SELECT id, myFunc(id) AS plusOne FROM table;
";
$object->db->exec($sqlPreliminary);
var_dump($object->db->query($sqlFinal)->fetchAll(PDO::FETCH_ASSOC));
Create a user defined function using PDO simply works by exec, no delimiter is required. Here $db is PDO instance.
$db->exec('CREATE FUNCTION ConvertHTMLToText(str LONGTEXT CHARSET utf8)
RETURNS LONGTEXT CHARSET utf8
BEGIN
DECLARE start, end INT DEFAULT 1;
LOOP
SET start = LOCATE("<", str, start);
IF (!start) THEN RETURN str; END IF;
SET end = LOCATE(">", str, start);
IF (!end) THEN SET end = start; END IF;
SET str = TRIM(INSERT(str, start, end - start + 1, ""));
END LOOP;
END');
EDIT:
This is multiple statement. The first part is DROP FUNCTION MyFunc IF EXISTS and the second is CREATE FUNCTION.
This was my previous answer, which was correct, but OP changed the code:
You don't say what $object->db is:
You call connectPDO("user","pass"); and then use $object->db->exec($sqlPreliminary);. $object should be global in function body. At the moment it is not, so it is a local variable, which is lost after end of the function. This $object in rest of the code is something different.
I have faced the same problem. When I removed " DELIMITER // " (also //
DELIMITER ; at the end), it worked. I think DELIMITER is no longer required with PHP PDO.
$sqlPreliminary = "
DROP FUNCTION IF EXISTS myFunc;
CREATE FUNCTION myFunc (id INT)
RETURNS DECIMAL
BEGIN
RETURN id + 1;
END
";
I tested this and worked.

Import from mysqldump not working as expected

I've got an sql dump generated from mysqldump. This file includes mysql-version specific comments (/*!{MySQL version number} {Code} */).
If I insert an sql syntax error after this block, PDO doesn't trigger an exception.
php code
$sql = file_get_contents('FooBar.sql');
$pdo = new \PDO('mysql:host=localhost;dbname=FooBar', 'root');
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$pdo->exec($sql);
FooBar.sql
/*!40101 SET #Foo='Bar' */;
ERROR
INSERT INTO Foo VALUES ('Bar');
This executes without causing any exceptions or errors. If I either remove the /*!40101 SET #Foo='Bar' */; statement, or move the error on line up an PDOException is thrown.
Thanks to the hek2mgl's for putting me on the right path.
PDO doesn't support multiple queries. If you execute a statement containing multiple queries, they get executed, but it seems PDO stops behaving after the first query is executed. The /*!{MySQL version number} {Code} */ style comment gets executed as a regular query by MySql and anything after this gets ignored by PDO, even though it gets executed by MySql.
The same error indicated would trigger by the following query:
SET #Foo='Bar';
ERROR
INSERT INTO Foo VALUES ('Bar');
To make this work using PDO, I need to split up the statements.
$sql = file_get_contents('FooBar.sql');
$lines = explode(';', $sql);
$pdo = new \PDO('mysql:host=localhost;dbname=FooBar', 'root');
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
foreach ($lines as $line) {
$trimmedLine = trim($line, " \t\n\r\0\x0B");
if (strlen($trimmedLine) > 0) {
$pdo->exec($trimmedLine.';');
}
}
EDIT:
An alternative solution is to use pdo prepared statements.
$sql = file_get_contents('FooBar.sql');
$pdo = new \PDO('mysql:host=localhost;dbname=FooBar', 'root');
$stmt = $pdo->prepare($sql);
$stmt->execute();
do {
... Do stuff ...
} while ($stmt->nextRowset());
if ($stmt->errorCode() != '00000') {
... Handle error ...
}
It's because of the ; at the end of the comment. I don't know why at the moment. Will investigate further...
Found this bug report. But don't expect the char encoding to be a problem as I've investigated the network traffic using wireshark and the MySQL returns a Syntax error (as expected.) Still don't know why PDO doesn't handle this correctly.
A workaround would be to use Mysqli which seems to handle this properly. The following example demonstrates this:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'secret');
$result = $pdo->query('
SELECT 1 AS num;
ERROR
SELECT 1 AS num;
');
if(!$result) {
var_dump($pdo->errorInfo); // silence ...
}
$mysqli = new Mysqli('localhost', 'root', 'user', 'secret');
$result = $mysqli->query('
SELECT 1 AS num;
ERROR
SELECT 1 AS num;
');
if(!$result) {
print( $mysqli->error);
// Output: 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 'ERROR SELECT 1 AS num' at line 2
}

Categories