LOB support added to PDO_OCI in PHP 5.1 CVS (finally)
[update: corrected information about STRINGIFY_FETCHES]
It's been a looong time coming, but it's finally here. Here's how to insert a BLOB via PDO_OCI:
<?php $db = new PDO("oci:", "scott", "tiger"); $db->beginTransaction(); // Essential! $stmt = $db->prepare( "INSERT INTO blobtest (id, contenttype, blob) ". "VALUES (:id, :type, EMPTY_BLOB()) ". "RETURNING blob INTO :blob"); $stmt->bindParam(':id', $id); $stmt->bindParam(':type', $type); $stmt->bindParam(':blob', $blob, PDO::PARAM_LOB); $type = 'image/gif'; $id = 1; // generate your own unique id here $blob = fopen('/path/to/a/graphic.gif', 'rb'); $stmt->execute(); $stmt->commit(); ?>
This will suck the contents of the graphic.gif up and store it into the newly inserted row. This syntax most closely matches the generic blob insert syntax that I talk about in my PDO presentation, there are two differences that are peculiar to Oracle. The first is the RETURNING blob INTO :blob that's tacked onto the end of the INSERT query. The reason for this is that Oracle stores "LOB Locators" rather than LOB contents in its table rows.
A LOB Locator tells Oracle where it stashed the actual LOB contents without making the table rows overly large, and allows some clever optimizations when manipulating LOBs. You can't just conjure up a LOB Locator though, so you need to insert a brand new empty LOB into a table and then fetch it's locator back out before you can start modifying it. (If you're coming from a mysql background, you can think of the locator as being something like a mysql auto-increment field; you need to insert a row before you find out what the value of the field is.)
Rather than issuing 2 queries just to make an insert, Oracle provides the RETURNING ... INTO syntax as a shortcut; it's equivalent to SELECTing the columns back out again, but it bundled up into a single query, saving the effort of parsing multiple queries and the overhead of multiple network round-trips to get everything where it needs to be.
This means that the :blob parameter is actually an output parameter, even though it smells like an input parameter. There's some intuitive magic at work here; if you bind a stream or a string to a PDO::PARAM_LOB parameter, the PDO_OCI driver will assume that you want to store the contents of that stream-or-string into the LOB that gets returned after the execute. So, that's what it does. Post-execute, all the LOB parameters are checked to see what PHP-space variables were bound, and data is written to the LOBs. This has an important implication; if you're doing this, you'd better have a transaction open, otherwise your new LOBs will be committed as part of the execute--before the PDO_OCI driver can write the data into the LOBs.
So, we can insert just fine. What about binding a LOB for output? Here's how:
<?php $db = new PDO("oci:", "scott", "tiger"); $db->beginTransaction(); // Essential! $stmt = $db->prepare( "INSERT INTO blobtest (id, contenttype, blob) ". "VALUES (:id, :type, EMPTY_BLOB()) ". "RETURNING blob INTO :blob"); $stmt->bindParam(':id', $id); $stmt->bindParam(':type', $type); $stmt->bindParam(':blob', $blob, PDO::PARAM_LOB); $type = 'image/gif'; $id = 1; // generate your own unique id here $blob = null; $stmt->execute(); // now $blob is a stream fwrite($blob, "GIF89a"); ... fclose($blob); $stmt->commit(); ?>
OK, this sample is still inserting data into the LOB, but it's doing it by binding the LOB for output, and giving you access to the LOB stream so that you can manually do things with it. The same approach will work if you issue a query that returns an existing read-only LOB. This syntax is closer to the traditional oci8 extension LOB support, except that the LOB is mapped as a PHP stream, so that you can use all the usual PHP streams functions to work with it.
LOBs are also handled for simple SELECTs that return rows with LOB columns. The PDO_OCI driver returns each LOB column as a stream; no data is transferred from that column until you start to read from it:
<?php $stmt = $db->prepare('select blob from blobtest where id = ?'); $stmt->execute(array($id)); $row = $stmt->fetch(); var_dump($row); var_dump(stream_get_contents($row[0])); ?>
this will output something like:
array(2) {
["BLOB"]=>
resource(7) of type (stream)
[0]=>
resource(7) of type (stream)
}
string(886) ".....BLOBDATAHERE..."
Notice that I'm using stream_get_contents() to transform the LOB stream into a string. If you're writing a portable application (good luck!) you need to be prepared to handle columns coming back as a stream, even if you didn't explicitly bindColumn and ask for it to be delivered as a LOB. If you're not looking forward to handling that dynamically, you might be interested in setting the STRINGIFY_FETCHES database attribute:
<?php $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); // now ALL non-NULL columns will be converted to strings when fetched ?>
This will convert all columns to strings, regardless of their original type, when fetches. This does not include NULL columns. This does NOT translate data being inserted. Use your brain before deploying this setting!
Would you like to work
with me?
I have positions open for server/infrastructure software development (C) and QA.

Good news
Thanks Wez!
Cant use pdo OCI on Windows anymore :(
Hi Wez,
I got the PHP Win32 Package at http://snaps.php.net/ (php5.1-win32-200511041130.zip).
Now I am trying to execute the following code:
<?php error_reporting(E_ALL); ini_set( "display_errors", "1" ); ini_set( "track_errors", "1" );
while( $row = $sth->fetch() ){ print "<BR>NOME: " . $row[NOME]; }And I get the output:
Array ( [0] => HY000 [1] => 24374 [2] => OCIStmtFetch: ORA-24374: define not done before fetch or execute and fetch (ext\pdo_oci\oci_statement.c:446) )
If I try to execute an Update Statement I get:
Array ( [0] => HY000 [1] => 24374 [2] => OCIStmtFetch: ORA-24374: zero iteration count (ext\pdo_oci\oci_statement.c:446) )
It was working fine...
Pls help, I think its a PHP Windows bug.
Best Regards, Junior
Bug fixed
http://pecl.php.net/bugs/bug.php?id=5868
please report bugs using the bug tracker next time!
Random (?) Segmentation Faults when SELECTing BLOBs
When SELECTing BLOBs with PDO_OCI from an Oracle XE the apache process dies an the apache error.log show segmentation faults like this:
[Fri Mar 28 16:19:27 2008] [notice] child pid 20674 exit signal Segmentation fault (11)
This does not happen on every request but reproducably often.
Someone did report exactly the same as a bug several months ago but nothing happened.
http://pecl.php.net/bugs/bug.php?id=11791
Sorry for reporting it here but I really need to get it working :-(
Clob vs Blob
I would have posted this as a bug as you suggest above, except that someone else did that three years ago.
http://pecl.php.net/bugs/bug.php?id=7943
This bug has caused much grief in the enterprise world, I've just spent 2 days trying to get our apps moved to Symfony 1.2. Since both Propel 1.3 and Doctrine are based on PDO I'm left with 3 unpleasant choices:
- change our years old schema to use Blobs instead of Clobs and write an update function based on this great article. (what I'm doing on my test system)
- revert to Symfony 1.1 and Creole.
- revert to J2EE.
Please either fix this or point me in the right direction to fix it myself. I've seen a number of very unpleasant looking kludges to fix this, there must be a better way.