PHP PDO_SQLSRV: encoding error with column containing accented chars - php

I'm getting back "�" instead of accented chars in my PSO_SQLSRV querys. This is how i connect to the db:
$dsn = 'sqlsrv:Server=' . $this->host . ';Database=' . $this->dbname;
`$options` = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try {
$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);
} catch (PDOException $e) {
new DatabaseErrorHandler($e);
}
I tried adding PDO::SQLSRV_ATTR_ENCODING => PDO::SQLSRV_ENCODING_UTF8 to my $options array, but nothing changes. Db collation is Latin1_General_CI_AS.
I'm using Microsoft driver php_pdo_sqlsrv_56_nts.dll with PHP 5.6.14

Related

Connect to MariaDB by using PDO with accept server cert

I can connect the database with Maria client with --ssl options on application server:
mariadb -h [Address] -P [Port] -u [DB admin user] -p --ssl
But tried to connect the same database on same server with PHP PDO, we got the error message with:
SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client[client_ed25519]
I have added below options to PDO connection for simulate the --ssl options
PDO::MYSQL_ATTR_SSL_CA => true,
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
Here is my testing code piece:
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$options = [
PDO::MYSQL_ATTR_SSL_CA => true,
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
];
try {
$conn = new PDO("mysql:host=$servername;dbname=myDB", $username, $password, $options);
// set the PDO error mode to exception
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connected successfully";
} catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
?>
Database version: MariaDB 10.5

Stream very large Blob from MySQL to PHP and create a file

We have a MySQL database that has some very large files stored in blob fields, such as some videos that are over 700MB. File sizes range from .5 MB PDFs to JPEGS, etc...
I'm trying to use PHP to retrieve these columns and create a file on the server that will be later offered up as a download.
I'm currently using the following method:
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_MAX_BUFFER_SIZE => 1024*1024*500
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
$stmt = $pdo->prepare('SELECT a.TITLE, a.ATTVERSION, a.ATTACHMENTID, a.CONTENTTYPE, a.FILESIZE, d.DATA FROM ATTACHMENTS a
LEFT JOIN ATTACHMENTDATA d ON d.ATTACHMENTID=a.ATTACHMENTID
WHERE a.ATTACHMENTID= ?');
$stmt->execute([$fileid]);
$file = $stmt->fetch();
file_put_contents($storage_dir . "/" . $filename, $file['DATA']);
This works for smaller files (note I'm setting the buffer size to 500MB), but larger files get truncated and corrupted.
I next tried the LOB and unbuffered query approach:
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
$stmt = $pdo->prepare('SELECT a.TITLE, a.ATTVERSION, a.ATTACHMENTID, a.CONTENTTYPE, a.FILESIZE, d.DATA FROM ATTACHMENTS a
LEFT JOIN ATTACHMENTDATA d ON d.ATTACHMENTID=a.ATTACHMENTID
WHERE a.ATTACHMENTID= ?');
$stmt->execute([$fileid]);
$stmt->bindColumn(1, $title, PDO::PARAM_STR, 256 );
$stmt->bindColumn(2, $attversion, PDO::PARAM_INT);
$stmt->bindColumn(3, $attid, PDO::PARAM_INT);
$stmt->bindColumn(4, $contenttype, PDO::PARAM_STR, 256);
$stmt->bindColumn(5, $filesize, PDO::PARAM_INT);
$stmt->bindColumn(6, $data, PDO::PARAM_LOB);
$stmt->fetch(PDO::FETCH_BOUND);
file_put_contents($storage_dir . "/" . $filename, $data)
With this option, I only get 1MB files, and it seems to be ignoring the MYSQL_ATTR_USE_BUFFERED_QUERY => false and since I'm not setting a buffer size, it's defaulting to 1MB. Can anyone offer any advice or see anything glaring? MySQL is on a different server, but I'd be open to doing this another way as well and calling a bash script or something via PHP.
This was initially done on php 5.4.16, which seemed to not support the unbuffered stream. After upgrading to php 8.0.11, I now have this working, with the exception of increasing PHP memory using ini_set.

Connecting to AWS RDS via PDO

I have been trying to connect my PHP application to a MySQL database on AWS RDS via PDO. I have seen a similar question here: Unable to connect to AWS RDS through PDO but this is over 4 years old with no definitive answers.
I have tried this a couple of ways. Firstly, passing the host name as '<my-db-name.eu-west-2.rds.amazonaws.com:3306' and secondly passing the port explicitly in the dsn string via
$dsn = $dsn = "mysql:host=" . $this->host . ";port=". $this->port . ";dbname=" . $this->name . ";charset=utf8";
(commented out below). Neither works!
The code snippet is:
$dsn = null;
$options = null;
$this->host = SYSTEM_CONFIG["database"]["host"];
$this->type = SYSTEM_CONFIG["database"]["type"];
$this->name = SYSTEM_CONFIG["database"]["name"];
$this->user = SYSTEM_CONFIG["database"]["user"];
$this->pass = SYSTEM_CONFIG["database"]["pass"];
/* New */
$this->port = SYSTEM_CONFIG["database"]["port"];
switch ($this->type) {
case "SQLSRV":
$dsn = "sqlsrv:Server=" . $this->host . ";Database=" . $this->name;
$options = [
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true,
PDO::ATTR_STRINGIFY_FETCHES => false
];
break;
default:
$dsn = "mysql:host=" . $this->host . ";dbname=" . $this->name;
//$dsn = "mysql:host=" . $this->host . ";port=". $this->port . ";dbname=" . $this->name . ";charset=utf8";
$options = [
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false
];
}
try {
$this->pdo = new PDO($dsn, $this->user, $this->pass, $options);
} catch (PDOException $e) {
$this->logError($e);
} catch (Exception $e) {
$this->logError($e);
}
One thing that does work is to pass the IP address and port as the host name in the form
$this->host = "<IP-address>:3306"
However, I only found the IP address by pinging the host name and I am not sure if this is static or a dynamic IP address (the latter would be no good in a config file!).
Any help on this would be much appreciated!
Have got the code working now, although quite frustratingly I never got to the bottom of why it wasn't working in the first place! I suspect it was something to do with not picking up on the port number properly - maybe a typo somewhere that got 'accidently' corrected (rather than deliberately) when I was trying things out. This code now works (just for MySQL):
$dsn = null;
$options = null;
$this->host = SYSTEM_CONFIG["database"]["host"];
$this->type = SYSTEM_CONFIG["database"]["type"];
$this->name = SYSTEM_CONFIG["database"]["name"];
$this->user = SYSTEM_CONFIG["database"]["user"];
$this->pass = SYSTEM_CONFIG["database"]["pass"];
$this->port = SYSTEM_CONFIG["database"]["port"];
switch ($this->type) {
case "SQLSRV":
// Other untested code...
break;
default:
$dsn = "mysql:host={$this->host};port={$this->port};dbname={$this->name};charset=utf8";
$options = [
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false
];
}
try {
$this->pdo = new PDO($dsn, $this->user, $this->pass, $options);
} catch (PDOException $e) {
$this->logError($e);
} catch (Exception $e) {
$this->logError($e);
}

Optimize PHP PDO Transaction from CURL Stream

I'm using CURL to request large XML Files from an API.
To prevent memory leaks I use this CURL option to stream the data and send it to the function curlCallback:
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($splitter, 'curlCallback'));
In the curlCallback I prepare the incoming XML Stream and call the function below to store every main XML Node in the MySQL Database. Everything works well but:
I want to optimize the efficiency to store the data in the MySQL Database. This is the actual code:
public function processLine($str) {
$prdData = simplexml_load_string($str);
// connect to mysql db
$servername = "localhost";
$username = "";
$password = "";
$dbname = 'temp';
$db = new \PDO('mysql:host=' . $servername . ';dbname=' . $dbname . ';charset=utf8mb4',
$username,
$password,
array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_PERSISTENT => false
)
);
try {
$stmt = $db->prepare("INSERT IGNORE INTO Product (PRDNO, DSCRD ,DSCRF, DSCRLONGD, DSCRLONGF, PRTNO, SMCAT, DEL, BNAMD) VALUES (:prdno, :dscrd, :dscrf, :dscrlongd, :dscrlongf, :prtno, :smcat, :del, :bnamd)");
// MySQL Transaction
$db->beginTransaction();
$stmt->bindParam(':prdno', $prdData->PRDNO);
$stmt->bindParam(':dscrd', $prdData->DSCRD);
$stmt->bindParam(':dscrf', $prdData->DSCRF);
$stmt->bindParam(':dscrlongd', $prdData->DSCRLONGD);
$stmt->bindParam(':dscrlongf', $prdData->DSCRLONGF);
$stmt->bindParam(':prtno', $prdData->PRTNO);
$stmt->bindParam(':smcat', $prdData->SMCAT);
$stmt->bindParam(':del', $prdData->DEL);
$stmt->bindParam(':bnamd', $prdData->BNAMD);
$stmt->execute();
$db->commit();
} catch (PDOException $e) {
error_log(date("d.m.Y H:i:s") . ' | ' . $e->getMessage() . PHP_EOL, 3, '/var/www/html/log/import.log');
$db->rollBack();
}
}
How can I optimize this to just send one transaction including for example 100 Rows?

Php pdo unixOBDC to sqlserver 2008: String data, length mismatch when execute prepared stmt

I have the following setup: Apache / php 5.3 / pdo with odbc with installed Microsoft SQL Server ODBC Driver 1.0 for Linux on server.
My script rises an error with the following stacktrace when trying to execute a statement:
(UTC) 2013-12-16 12:07:40: Thrown exception log ->
'Error message: SQLSTATE[22026]: String data, length mismatch: 0 [Microsoft][SQL Server Native Client 11.0]String data, length mismatch (SQLExecute[0] at /tmp/pear/temp/PDO_ODBC/odbc_stmt.c:133)
Arisen in Core_Db->select(array (
0 =>
'SELECT *
FROM TMS.dbo.TEST_user
WHERE email = ? AND status_id = ?',
1 =>
array (
0 => 'support#mail.com',
1 => 2
),
))
For testing I use php 5.3 on windows with pdo sqlsrv and nothing went wrong there.
Connection code is
// for unixODBC (production)
if (DB_DRIVER == 'odbc') {
$this->_link = new PDO(
"odbc:DRIVER={SQL Server Native Client 11.0};SERVER=" . DB_HOST . ";"
. "PORT=" . DB_PORT . ";DATABASE=" . DB_NAME . ";PROTOCOL=TCPIP;UID=" . DB_LOGIN . ";"
. "PWD=" . DB_PASSWD . ";"
);
// for sqlsrv (development)
} else {
$this->_link = new PDO(
"sqlsrv:Server=" . DB_HOST . "," . DB_PORT . ";Database=" . DB_NAME,
DB_LOGIN,
DB_PASSWD
);
}
$this->_link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
There is some recommendation regarding this issue: ODBC and SQL Server 2008: Can't use prepared statements?. But I can't check it. When I try to add an attribute in production, the script rises the following error:
(UTC) 2013-12-16 13:19:44: SQLSTATE[IM001]: Driver does not support this function: driver does not support setting attributes
Does anyone know how to resolve this problem?
UPDATED 2013-12-18
// unixODBC connection
$this->_link = new PDO(
"odbc:DRIVER={SQL Server Native Client 11.0};SERVER=xxx.xxx.xxx.xxx;"
. "PORT=1433;DATABASE=TMS;PROTOCOL=TCPIP;",
DB_LOGIN,
DB_PASSWD,
array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // this attr is recognized by ODBC
PDO::ATTR_EMULATE_PREPARES => false // but if this is added ODBC throws PDOException with message "driver does not support setting attributes"
)
);
String's transfer from ODBC to Client leads to string's corruption while prepared statement is used. So I only guess the matter is in PDO::ATTR_EMULATE_PREPARES.
The error
(UTC) 2013-12-16 13:19:44: SQLSTATE[IM001]: Driver does not support
this function: driver does not support setting attributes
is telling you that this line:
$this->_link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
is invalid. Instead, do this:
$driver_options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => FALSE);
if (DB_DRIVER == 'odbc'){ // for unixODBC (production)
$dsn = 'odbc:DRIVER={SQL Server Native Client 11.0};SERVER='. DB_HOST .';PORT='. DB_PORT .';DATABASE='. DB_NAME .';PROTOCOL=TCPIP;';
$this->_link = new PDO($dsn, DB_LOGIN, DB_PASSWD, $driver_options);
}else{ // for sqlsrv (development)
$this->_link = new PDO(
"sqlsrv:Server=" . DB_HOST . "," . DB_PORT . ";Database=" . DB_NAME,
DB_LOGIN,
DB_PASSWD
);
}

Categories