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

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.

Related

Using LOB data in PHP with PDO instead of OCI_Connect

I was using OCI_Connect to connect to my Oracle database.
Because of some internal plicies, i need to change it to PDO.
With OCI_Connect, i can read LOB data from database with "->load()" function in the result, something like this:
$this->Conn = oci_connect($this->User, $this->Pass, $this->Name, 'AL32UTF8');
$sql = "select field from table";
$s = oci_parse($this->Conn, $sql);
$res = oci_fetch_array($s, OCI_ASSOC + OCI_RETURN_NULLS)
echo $res[0]['FIELD']->load();
and it worked very well.
Now i need to do the same stuff with PDO, and because all my queries may change the number and name of the fields, i cannot bind the variables before executing it.
What i'm using now to connect:
$dbTns = "(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = $server)(PORT = $port)) (CONNECT_DATA = (SERVICE_NAME = $service_name) (SID = $sid)))";
$paramArray = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
$this->PDO = new PDO("oci:dbname=" . $dbTns . ";charset=utf8", $db_username, $db_password, $paramArray );
With PDO, everything works fine, but i can't use the "->load()" function in the LOB field, as it does not exists here.
Is there an equivalent way to get the data after the query run?
Any suggestions are welcome.
(yes, i did search for a solution before posting that question here)

Connecting to PGSQL over SSL via Red Bean PHP

$dbh = new PDO('pgsql:localhost=host;port=26257;dbname=bank;sslmode=require;sslcert=[path]/client.maxroach.crt;sslkey=[path]/client.maxroach.key;sslrootcert=[path]/ca.crt;',
'maxroach', null, array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => true,
));
This a pdo method, i need configure red bean connection for ssl pgsql connection
R::setup( "pgsql:host=$ip;port=$port;dbname=$dbname",$user, $password, $frozen ); ?
You definitely need to write the full path to certificates and keys,
otherwise nothing will work.
$crt = $_SERVER["DOCUMENT_ROOT"]."/client.crt";
$key = $_SERVER["DOCUMENT_ROOT"]."/client.key";
$ca = $_SERVER["DOCUMENT_ROOT"]."/ca.crt";
R::setup( "pgsql:host=$ip;port=$port;dbname=$dbname;sslmode=verify-ca;sslcert=$crt;sslkey=$key;sslrootcert=$ca;",$user, $password, $frozen );
sslmode=verify-ca; better use sslmode=verify-full

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_SQLSRV: encoding error with column containing accented chars

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

How do I shorten the MySQL Connect Time Out - Failover Scenario

I have a fail-over scenario in a Wordpress plugin, where a PHP script on my web server tries to connect to a backup database if the production one is offline.
Currently the fallback seems to take up to 60 seconds. Is this the PHP default? How would I set the time out to something like 10 seconds?
Here is the relevant portion of the script...
try
{
$DBblue = new \PDO('mysql:host='.$samhost.';'.'dbname='.$DBblue, $samuser, $sampass);
$DBgreen = new \PDO('mysql:host='.$samhost.';'.'dbname='.$DBgreen, $samuser, $sampass);
}
catch (\PDOException $pde)
{
// Fallback Database connection
$althost = get_option('fallback_host');
$altuser = get_option('fallback_user');
$altpass = get_option('fallback_password');
$DBblue = new \PDO('mysql:host='.$althost.';'.'dbname='.$DBblue, $altuser, $altpass);
$DBgreen = new \PDO('mysql:host='.$althost.';'.'dbname='.$DBgreen, $altuser, $altpass);
}
Try this:
$DBblue = new \PDO('mysql:host='.$samhost.';'.'dbname='.$DBblue, $samuser, $sampass, array(
PDO::ATTR_TIMEOUT => "10",
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
));
$DBgreen = new \PDO('mysql:host='.$samhost.';'.'dbname='.$DBgreen, $samuser, $sampass, array(
PDO::ATTR_TIMEOUT => "10",
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
));
PDO::ATTR_TIMEOUT => "10" ->>> Set timeout to 10 seconds.
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ->>> Throw exception.
More Information : http://php.net/manual/en/pdo.setattribute.php

Categories