PDO versus MDB2
December 26th, 2006I was just putting together a small test program and I thought I would try using PDO. I really haven’t done anything serious with PDO, just try it a couple times. After recompiling PHP to include the mysql driver for PDO, I coded up the first version of my test program:
$db = new PDO('mysql:host=localhost;dbname=example', 'example', 'secret'); $tags = $db->prepare(" SELECT * FROM bookmark_tags, tags WHERE bookmark_tags.bookmark_id = ? AND tags.id = bookmark_tags.tag_id ORDER BY tags.name"); $bookmarks = $db->prepare("SELECT * FROM bookmarks ORDER BY Title"); $bookmarks->execute(); while ($bookmark = $bookmarks->fetchObject()) { echo "<li><a href='{$bookmark->url}'>{$bookmark->title}</a> "; $tags->execute(array($bookmark->id)); while ($tag = $tags->fetchObject()) { echo $tag->name, " "; } echo "</li>\n"; } echo "\n";
Unfortunately, this didn’t work and it took me a few minutes to figure out why. Actually, I still don’t know exactly why it doesn’t work, but I did find a way to make it work: by using two separate connections, one for each prepared statement. It doesn’t seem like you can have two active statements at the same time on the same connection. I find this hard to believe, so I’m probably doing something wrong.
The other thing I didn’t care for with this PDO code is the non-standard method of iteration with the while loop. Well, the while loop is perfectly standard if you are coming from the PHP 4 style functional DB APIs. However, it doesn’t seem to fit in with the PHP 5 Iterator and foreach integration. PDO doesn’t seem to provide a distinct result set object, or a method of iterating over a result set using the standard PHP Iterator interface.
Now, I can understand why this may be the case. The PDO interface seems to be designed to bind to php variables. Thats not going to work with the Iterator interface. However, I am not using that mode and don’t want to use that mode. It would be nice to be able to acquire an iterator for an example of use such as the one above without having to use fetchAll and ArrayIterator.
Using the Iterator style makes it easier for me to decouple my code from the data source and makes it easier to write test cases for that code.
I was a little bit disappointed. So I moved on to MDB2 with the same code …
require_once 'MDB2.php'; require_once 'MDB2/iterator.php'; $db = MDB2::connect("mysql://example:secret@localhost/example"); $tagLookup = $db->prepare(" SELECT * FROM bookmark_tags, tags WHERE bookmark_tags.bookmark_id = ? AND tags.id = bookmark_tags.tag_id ORDER BY tags.name"); $bookmarkFinder = $db->prepare("SELECT * FROM bookmarks ORDER BY Title"); $bookmarks = new MDB2_Iterator($bookmarkFinder->execute(), MDB2_FETCHMODE_OBJECT); echo "<ul>\n"; foreach($bookmarks as $bookmark) { echo "<li><a href='{$bookmark->url}'>{$bookmark->title}</a> "; $tags = new MDB2_Iterator($tagLookup->execute($bookmark->id), MDB2_FETCHMODE_OBJECT); foreach($tags as $tag) { echo $tag->name, " "; } echo "</li>\n"; } echo "</ul>\n";
I have about the same level of non-experience with MDB2 as with PDO, but my code worked perfectly on the first try and allowed me to use Iterator, which will be helpful to the next stage of my test.
I was a bit impressed. I’ll see how well it handles the next stage of what I want to do.
(And yes, I know there is no error checking in the above code.)
December 26th, 2006 at 3:49 pm
The requirement is actually ‘need to finish consuming one query before using the same connection for another’, and is a characteristic of using unbuffered mysql connections (i.e. unavoidable aspect of the C client library). Unbuffered connections are used by default in PDO_MySQL.
December 26th, 2006 at 4:21 pm
Have you tried ADOdb?
December 26th, 2006 at 5:15 pm
Some useful PDO links covering buffered mysql queries and iterators.
http://ilia.ws/archives/53-PDO_MySQL-Buffered-Query-Support.html
http://netevil.org/node.php?uuid=420ccd50-adb9-5159-5209-20ccd507e106
December 26th, 2006 at 5:15 pm
You’ve discover the limitation in mysql client api.
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
will allow nested loops.
December 26th, 2006 at 5:42 pm
In MDB2 unbuffered queries are optional, the default is buffered.
December 26th, 2006 at 7:30 pm
I begun experimenting with PDO in the last two weeks, and i actually liked it.
As was comented before, the problem you had is just that, can’t begin a new query before finishing the last one.
One solution is just one that you missed and complained about short after, the method $query->fetchAll(), which does what you are looking for, dumps all the results and you can go through with a foreach loop.
PDO has some hidden gems in the documentation, take a deeper look and you might see more interesting things.
I actually compared a prepared statment looped using WHILE and the FOREACH, and actully came back with a 0.0001 to 0.0002 diference, making while faster than foreach, so i added while to my PDO best practices, i should post something about this on my blog soon.
Cheers!
December 26th, 2006 at 7:40 pm
You can only have one prepared statement because PDO uses mysql’s native prepared statements, which suck anyway (see http://netevil.org/node.php?uuid=444a6017-0548-2459-2943-44a601714d58).
Calling
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);after setting up the connection should make that problem go away.December 27th, 2006 at 2:46 am
One more thing .. you can tell MDB2 to always return result sets as an instance of the iterator (or even custom iterator) class.
Similarly PDO also allows for fairly flexible “casting” to a class or even class instance in its fetch methods. The API was unfortunately designed to be very “I know what I am doing”. The idea was that PDO was to be light and efficient, leaving the work of adding convenience to layers on top of PDO. I think this was overdone in some places. Like reusing the rowCount() method for affected rows when doing manipulation queries.
The fact that there is no separate result object is again a speed thing, but also because most RDBMS with proper prepared statement support do not allow result sets to live longer than the prepared statement they were created with. Also remember that PDO by default always uses the prepared statement API. Personally I think a separate object makes more sense, and that is why I stuck with it in MDB2.
Unfortunately my API concerns were of little interest to the PDO developers. Which I found kind of disappointing given that I had spend the previous 4 years studying all the database API’s in PHP (and other scripting languages). So it goes.
That being said PDO does make life a lot easier for people who want to build convenience/abstraction layers and even while it is slightly slower for many common operations compared to the native API’s its still what I would base any future work on.
December 27th, 2006 at 7:42 am
I haven’t tried running this myself, but the following code should address both your concerns about buffered queries and iteration.
The fact that people keep asking these same questions about buffering and iteration highlights that we have a documentation problem. May I suggest that people considering PDO take a look at the slides from one of my talks on PDO (try the latest set)
$db = new PDO('mysql:host=localhost;dbname=example', 'example', 'secret');
$tags = $db->prepare("
SELECT
*
FROM
bookmark_tags, tags
WHERE
bookmark_tags.bookmark_id = ? AND
tags.id = bookmark_tags.tag_id
ORDER BY
tags.name");
$tags->setFetchMode(PDO::FETCH_OBJECT);
foreach ($db->query("SELECT * FROM bookmarks ORDER BY Title", PDO::FETCH_OBJECT)->fetchAll() as $bookmark) {
echo "<li><a href='{$bookmark->url}'>{$bookmark->title}</a> ";
$tags->execute(array($bookmark->id));
foreach ($tags as $tag) {
echo $tag->name, " ";
}
echo "</li>\n";
}
echo "\n";
PS: I’m clearly a combination of too {retarded,impatient} to make a code sample readable on your blog. I can’t remedy either condition (and perhaps a portion of that is due to your blog software) so you get what you’ve got
December 27th, 2006 at 4:57 pm
Wow. Thanks, George for the quick answer. I remember reading that, but it never occurred to me that was applicable in this case. I thought someone would know if I posted here.
Bernislav, I have used ADODB, I just wanted to try something new.
PDO does have some interesting API elements.
Wez, sorry about the formatting and thanks for responding. Stock WordPress sucks for code and I’ve just recently switched code formatting plugins and haven’t updated the commenting directions to reveal the correct syntax, which is a BBCODE style [ CODE=PHP ] [ /CODE ] but without the spaces. Sorry again.
Thanks for the links. I’m reading them now.
May 18th, 2007 at 6:33 am
Why on earth wouldn’t you be using an JOIN there anyway? I can’t see any point at all to having those queries separated.