Implemented handling BLOBs represented as stream resources for IBM DB2

parent 091e2642
...@@ -13,8 +13,10 @@ use ReflectionObject; ...@@ -13,8 +13,10 @@ use ReflectionObject;
use ReflectionProperty; use ReflectionProperty;
use stdClass; use stdClass;
use const CASE_LOWER; use const CASE_LOWER;
use const DB2_BINARY;
use const DB2_CHAR; use const DB2_CHAR;
use const DB2_LONG; use const DB2_LONG;
use const DB2_PARAM_FILE;
use const DB2_PARAM_IN; use const DB2_PARAM_IN;
use function array_change_key_case; use function array_change_key_case;
use function db2_bind_param; use function db2_bind_param;
...@@ -28,14 +30,21 @@ use function db2_num_fields; ...@@ -28,14 +30,21 @@ use function db2_num_fields;
use function db2_num_rows; use function db2_num_rows;
use function db2_stmt_error; use function db2_stmt_error;
use function db2_stmt_errormsg; use function db2_stmt_errormsg;
use function error_get_last;
use function fclose;
use function func_get_args; use function func_get_args;
use function func_num_args; use function func_num_args;
use function fwrite;
use function gettype; use function gettype;
use function is_object; use function is_object;
use function is_resource;
use function is_string; use function is_string;
use function ksort; use function ksort;
use function sprintf; use function sprintf;
use function stream_copy_to_stream;
use function stream_get_meta_data;
use function strtolower; use function strtolower;
use function tmpfile;
class DB2Statement implements IteratorAggregate, Statement class DB2Statement implements IteratorAggregate, Statement
{ {
...@@ -45,6 +54,14 @@ class DB2Statement implements IteratorAggregate, Statement ...@@ -45,6 +54,14 @@ class DB2Statement implements IteratorAggregate, Statement
/** @var mixed[] */ /** @var mixed[] */
private $bindParam = []; private $bindParam = [];
/**
* Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
* and the temporary file handle bound to the underlying statement
*
* @var mixed[][]
*/
private $lobs = [];
/** @var string Name of the default class to instantiate when fetching class instances. */ /** @var string Name of the default class to instantiate when fetching class instances. */
private $defaultFetchClass = '\stdClass'; private $defaultFetchClass = '\stdClass';
...@@ -61,16 +78,6 @@ class DB2Statement implements IteratorAggregate, Statement ...@@ -61,16 +78,6 @@ class DB2Statement implements IteratorAggregate, Statement
*/ */
private $result = false; private $result = false;
/**
* DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
*
* @var int[]
*/
static private $typeMap = [
ParameterType::INTEGER => DB2_LONG,
ParameterType::STRING => DB2_CHAR,
];
/** /**
* @param resource $stmt * @param resource $stmt
*/ */
...@@ -92,21 +99,48 @@ class DB2Statement implements IteratorAggregate, Statement ...@@ -92,21 +99,48 @@ class DB2Statement implements IteratorAggregate, Statement
*/ */
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
{ {
$this->bindParam[$column] =& $variable; switch ($type) {
case ParameterType::INTEGER:
$this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG);
break;
if ($type && isset(self::$typeMap[$type])) { case ParameterType::LARGE_OBJECT:
$type = self::$typeMap[$type]; if (isset($this->lobs[$column])) {
} else { [, $handle] = $this->lobs[$column];
$type = DB2_CHAR; fclose($handle);
} }
if (! db2_bind_param($this->stmt, $column, 'variable', DB2_PARAM_IN, $type)) { $handle = $this->createTemporaryFile();
throw new DB2Exception(db2_stmt_errormsg()); $path = stream_get_meta_data($handle)['uri'];
$this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY);
$this->lobs[$column] = [&$variable, $handle];
break;
default:
$this->bind($column, $variable, DB2_PARAM_IN, DB2_CHAR);
break;
} }
return true; return true;
} }
/**
* @param int|string $parameter Parameter position or name
* @param mixed $variable
*
* @throws DB2Exception
*/
private function bind($parameter, &$variable, int $parameterType, int $dataType) : void
{
$this->bindParam[$parameter] =& $variable;
if (! db2_bind_param($this->stmt, $parameter, 'variable', $parameterType, $dataType)) {
throw new DB2Exception(db2_stmt_errormsg());
}
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -177,8 +211,24 @@ class DB2Statement implements IteratorAggregate, Statement ...@@ -177,8 +211,24 @@ class DB2Statement implements IteratorAggregate, Statement
} }
} }
foreach ($this->lobs as [$source, $target]) {
if (is_resource($source)) {
$this->copyStreamToStream($source, $target);
continue;
}
$this->writeStringToStream($source, $target);
}
$retval = db2_execute($this->stmt, $params); $retval = db2_execute($this->stmt, $params);
foreach ($this->lobs as [, $handle]) {
fclose($handle);
}
$this->lobs = [];
if ($retval === false) { if ($retval === false) {
throw new DB2Exception(db2_stmt_errormsg()); throw new DB2Exception(db2_stmt_errormsg());
} }
...@@ -372,4 +422,45 @@ class DB2Statement implements IteratorAggregate, Statement ...@@ -372,4 +422,45 @@ class DB2Statement implements IteratorAggregate, Statement
return $destinationClass; return $destinationClass;
} }
/**
* @return resource
*
* @throws DB2Exception
*/
private function createTemporaryFile()
{
$handle = @tmpfile();
if ($handle === false) {
throw new DB2Exception('Could not create temporary file: ' . error_get_last()['message']);
}
return $handle;
}
/**
* @param resource $source
* @param resource $target
*
* @throws DB2Exception
*/
private function copyStreamToStream($source, $target) : void
{
if (@stream_copy_to_stream($source, $target) === false) {
throw new DB2Exception('Could not copy source stream to temporary file: ' . error_get_last()['message']);
}
}
/**
* @param resource $target
*
* @throws DB2Exception
*/
private function writeStringToStream(string $string, $target) : void
{
if (@fwrite($target, $string) === false) {
throw new DB2Exception('Could not write string to temporary file: ' . error_get_last()['message']);
}
}
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Doctrine\Tests\DBAL\Functional; namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\Driver\OCI8\Driver as OCI8Driver;
use Doctrine\DBAL\Driver\PDOSqlsrv\Driver as PDOSQLSrvDriver; use Doctrine\DBAL\Driver\PDOSqlsrv\Driver as PDOSQLSrvDriver;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\ParameterType;
...@@ -10,7 +11,6 @@ use Doctrine\DBAL\Schema\Table; ...@@ -10,7 +11,6 @@ use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Type;
use Doctrine\Tests\DbalFunctionalTestCase; use Doctrine\Tests\DbalFunctionalTestCase;
use function fopen; use function fopen;
use function in_array;
use function str_repeat; use function str_repeat;
use function stream_get_contents; use function stream_get_contents;
...@@ -55,10 +55,9 @@ class BlobTest extends DbalFunctionalTestCase ...@@ -55,10 +55,9 @@ class BlobTest extends DbalFunctionalTestCase
public function testInsertProcessesStream() public function testInsertProcessesStream()
{ {
if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { // https://github.com/doctrine/dbal/issues/3290
// https://github.com/doctrine/dbal/issues/3288 for DB2 if ($this->connection->getDriver() instanceof OCI8Driver) {
// https://github.com/doctrine/dbal/issues/3290 for Oracle $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters');
$this->markTestIncomplete('Platform does not support stream resources as parameters');
} }
$longBlob = str_repeat('x', 4 * 8192); // send 4 chunks $longBlob = str_repeat('x', 4 * 8192); // send 4 chunks
...@@ -112,10 +111,9 @@ class BlobTest extends DbalFunctionalTestCase ...@@ -112,10 +111,9 @@ class BlobTest extends DbalFunctionalTestCase
public function testUpdateProcessesStream() public function testUpdateProcessesStream()
{ {
if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { // https://github.com/doctrine/dbal/issues/3290
// https://github.com/doctrine/dbal/issues/3288 for DB2 if ($this->connection->getDriver() instanceof OCI8Driver) {
// https://github.com/doctrine/dbal/issues/3290 for Oracle $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters');
$this->markTestIncomplete('Platform does not support stream resources as parameters');
} }
$this->connection->insert('blob_table', [ $this->connection->insert('blob_table', [
...@@ -141,10 +139,8 @@ class BlobTest extends DbalFunctionalTestCase ...@@ -141,10 +139,8 @@ class BlobTest extends DbalFunctionalTestCase
public function testBindParamProcessesStream() public function testBindParamProcessesStream()
{ {
if (in_array($this->connection->getDatabasePlatform()->getName(), ['oracle', 'db2'], true)) { if ($this->connection->getDriver() instanceof OCI8Driver) {
// https://github.com/doctrine/dbal/issues/3288 for DB2 $this->markTestIncomplete('The oci8 driver does not support stream resources as parameters');
// https://github.com/doctrine/dbal/issues/3290 for Oracle
$this->markTestIncomplete('Platform does not support stream resources as parameters');
} }
$stmt = $this->connection->prepare("INSERT INTO blob_table(id, clobfield, blobfield) VALUES (1, 'ignored', ?)"); $stmt = $this->connection->prepare("INSERT INTO blob_table(id, clobfield, blobfield) VALUES (1, 'ignored', ?)");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment