Skip to content

Fix PDO OCI Bug #60994 (Reading a multibyte CLOB caps at 8192 chars) #8018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ext/pdo_oci/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ if test "$PHP_PDO_OCI" != "no"; then
-L$PDO_OCI_LIB_DIR $PDO_OCI_SHARED_LIBADD
])

dnl Can handle bytes vs. characters?
PHP_CHECK_LIBRARY(clntsh, OCILobRead2,
[
AC_DEFINE(HAVE_OCILOBREAD2,1,[ ])
], [], [
-L$PDO_OCI_LIB_DIR $PDO_OCI_SHARED_LIBADD
])

PHP_CHECK_PDO_INCLUDES

PHP_NEW_EXTENSION(pdo_oci, pdo_oci.c oci_driver.c oci_statement.c, $ext_shared,,-I$pdo_cv_inc_path)
Expand Down
32 changes: 23 additions & 9 deletions ext/pdo_oci/oci_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,7 @@ struct oci_lob_self {
OCILobLocator *lob;
oci_lob_env *E;
ub4 offset;
ub1 csfrm;
};

static ssize_t oci_blob_write(php_stream *stream, const char *buf, size_t count)
Expand All @@ -645,23 +646,34 @@ static ssize_t oci_blob_write(php_stream *stream, const char *buf, size_t count)
static ssize_t oci_blob_read(php_stream *stream, char *buf, size_t count)
{
struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract;
ub4 amt;
sword r;
#if HAVE_OCILOBREAD2
oraub8 byte_amt = (oraub8) count;
oraub8 char_amt = 0;

amt = (ub4) count;
r = OCILobRead(self->E->svc, self->E->err, self->lob,
&amt, self->offset, buf, (ub4) count,
sword r = OCILobRead2(self->E->svc, self->E->err, self->lob,
&byte_amt, &char_amt, (oraub8) self->offset, buf, (oraub8) count,
OCI_ONE_PIECE, NULL, NULL, 0, self->csfrm);
#else
ub4 byte_amt = (ub4) count;

sword r = OCILobRead(self->E->svc, self->E->err, self->lob,
&byte_amt, self->offset, buf, (ub4) count,
NULL, NULL, 0, SQLCS_IMPLICIT);
#endif

if (r != OCI_SUCCESS && r != OCI_NEED_DATA) {
return (size_t)-1;
return (ssize_t)-1;
}

self->offset += amt;
if (amt < count) {
#if HAVE_OCILOBREAD2
self->offset += self->csfrm == 0 ? byte_amt : char_amt;
#else
self->offset += byte_amt;
#endif
if (byte_amt < count) {
stream->eof = 1;
}
return amt;
return byte_amt;
}

static int oci_blob_close(php_stream *stream, int close_handle)
Expand Down Expand Up @@ -728,6 +740,8 @@ static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLoca
self->E->svc = self->S->H->svc;
self->E->err = self->S->err;

OCILobCharSetForm(self->S->H->env, self->S->err, self->lob, &self->csfrm);

stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b");

if (stm) {
Expand Down
31 changes: 21 additions & 10 deletions ext/pdo_oci/tests/bug60994.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@ $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
$dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);

@$dbh->exec('DROP TABLE pdo_oci_bug60994');
$dbh->exec('CREATE TABLE pdo_oci_bug60994 (id NUMBER, data CLOB)');
$dbh->exec('CREATE TABLE pdo_oci_bug60994 (id NUMBER, data CLOB, data2 NCLOB)');

$id = null;
$insert = $dbh->prepare('INSERT INTO pdo_oci_bug60994 (id, data) VALUES (:id, :data)');
$insert = $dbh->prepare('INSERT INTO pdo_oci_bug60994 (id, data, data2) VALUES (:id, :data, :data2)');
$insert->bindParam(':id', $id, \PDO::PARAM_STR);
$select = $dbh->prepare("SELECT data FROM pdo_oci_bug60994 WHERE id = :id");
$select = $dbh->prepare("SELECT data, data2 FROM pdo_oci_bug60994 WHERE id = :id");


echo PHP_EOL, 'Test 1: j', PHP_EOL;
$string1 = 'abc' . str_repeat('j', 8187) . 'xyz'; // 8193 chars total works fine here (even 1 million works fine, subject to memory_limit)
$id = 1;
$insert->bindParam(':data', $string1, \PDO::PARAM_STR, strlen($string1)); // length in bytes
$insert->bindParam(':data2', $string1, \PDO::PARAM_STR, strlen($string1));
$insert->execute();
$select->bindParam(':id', $id, \PDO::PARAM_STR);
$select->execute();
Expand All @@ -41,12 +42,15 @@ echo 'size of string1 is ', strlen($string1), ' bytes, ', mb_strlen($string1), '
echo 'size of stream1 is ', strlen($stream1), ' bytes, ', mb_strlen($stream1), ' chars.', PHP_EOL;
echo 'beg of stream1 is ', $start1, PHP_EOL;
echo 'end of stream1 is ', $ending1, PHP_EOL;

if ($string1 != $stream1 || $stream1 != stream_get_contents($row['DATA2'])) {
echo 'Expected nclob value to match clob value for stream1', PHP_EOL;
}

echo PHP_EOL, 'Test 2: £', PHP_EOL;
$string2 = 'abc' . str_repeat('£', 8187) . 'xyz'; // 8193 chars total is when it breaks
$id = 2;
$insert->bindParam(':data', $string2, \PDO::PARAM_STR, strlen($string2)); // length in bytes
$insert->bindParam(':data2', $string2, \PDO::PARAM_STR, strlen($string2));
$insert->execute();
$select->bindParam(':id', $id, \PDO::PARAM_STR);
$select->execute();
Expand All @@ -58,12 +62,15 @@ echo 'size of string2 is ', strlen($string2), ' bytes, ', mb_strlen($string2), '
echo 'size of stream2 is ', strlen($stream2), ' bytes, ', mb_strlen($stream2), ' chars.', PHP_EOL;
echo 'beg of stream2 is ', $start2, PHP_EOL;
echo 'end of stream2 is ', $ending2, PHP_EOL;

if ($string2 != $stream2 || $stream2 != stream_get_contents($row['DATA2'])) {
echo 'Expected nclob value to match clob value for stream2', PHP_EOL;
}

echo PHP_EOL, 'Test 3: Җ', PHP_EOL;
$string3 = 'abc' . str_repeat('Җ', 8187) . 'xyz'; // 8193 chars total is when it breaks
$id = 3;
$insert->bindParam(':data', $string3, \PDO::PARAM_STR, strlen($string3)); // length in bytes
$insert->bindParam(':data2', $string3, \PDO::PARAM_STR, strlen($string3));
$insert->execute();
$select->bindParam(':id', $id, \PDO::PARAM_STR);
$select->execute();
Expand All @@ -75,12 +82,15 @@ echo 'size of string3 is ', strlen($string3), ' bytes, ', mb_strlen($string3), '
echo 'size of stream3 is ', strlen($stream3), ' bytes, ', mb_strlen($stream3), ' chars.', PHP_EOL;
echo 'beg of stream3 is ', $start3, PHP_EOL;
echo 'end of stream3 is ', $ending3, PHP_EOL;

if ($string3 != $stream3 || $stream3 != stream_get_contents($row['DATA2'])) {
echo 'Expected nclob value to match clob value for stream3', PHP_EOL;
}

echo PHP_EOL, 'Test 4: の', PHP_EOL;
$string4 = 'abc' . str_repeat('の', 8187) . 'xyz'; // 8193 chars total is when it breaks
$id = 4;
$insert->bindParam(':data', $string4, \PDO::PARAM_STR, strlen($string4)); // length in bytes
$insert->bindParam(':data2', $string4, \PDO::PARAM_STR, strlen($string4));
$insert->execute();
$select->bindParam(':id', $id, \PDO::PARAM_STR);
$select->execute();
Expand All @@ -92,13 +102,14 @@ echo 'size of string4 is ', strlen($string4), ' bytes, ', mb_strlen($string4), '
echo 'size of stream4 is ', strlen($stream4), ' bytes, ', mb_strlen($stream4), ' chars.', PHP_EOL;
echo 'beg of stream4 is ', $start4, PHP_EOL;
echo 'end of stream4 is ', $ending4, PHP_EOL;
if ($string4 != $stream4 || $stream4 != stream_get_contents($row['DATA2'])) {
echo 'Expected nclob value to match clob value for stream4', PHP_EOL;
}
?>
--XFAIL--
Fails due to Bug 60994
--EXPECT--
Test 1: j
size of string1 is 1000006 bytes, 1000006 chars.
size of stream1 is 1000006 bytes, 1000006 chars.
size of string1 is 8193 bytes, 8193 chars.
size of stream1 is 8193 bytes, 8193 chars.
beg of stream1 is abcjjjjjjj
end of stream1 is jjjjjjjxyz

Expand Down