This is one of the major reasons I developed xPDO. IMHO, the DBAPI is simply not a robust enough solution to handle abstracting the complexities of many of these queries. The DBAPI is also heavily based on the mysql extension and would need a lot of work to really make it database independent. This is why each class that defines a table in xPDO has a subclass (or derivative class) that is specific to the database engine. If a particular query needs to be modified for a particular database engine, you simply "override" a function in that engine’s subclass. This detail is never exposed to the developer who is using your API; it all takes place behind the scenes, making it super easy to develop with. This has the added benefit of allowing you to optimize the behavior of individual functions for a specific database engine (e.g. using the MySQL-specific REPLACE statement), without having to copy-and-paste an entire file and modify it for each one (which would simply be ignoring the "DRY", or "Don’t Repeat Yourself" principle).
xPDO also includes xPDOQuery, which provides functions to perform various kinds of JOINs, ORDER BY and other statements in simple programmatic ways. The queries become very easy to construct in a script, and are automatically abstracted for each database engine. Here are a few examples; first, a simple example of getting all the user groups and sorting them based on criteria passed in the request...
<?php
$c = $modx->newQuery('modUserGroup');
$c->sortby($_REQUEST['sort'],$_REQUEST['dir']);
$groups = $modx->getCollection('modUserGroup',$c);
?>
...or a more complex example demonstrating some of the more advanced query building capabilities (this retrieves a single "Catalog" along with all of the "Items" it is associated with and the details of each "Item"...
<?php
// ...
$catalogCriteria = $model->newQuery('cpCatalog', $catalogId);
$catalogCriteria->bindGraph('{"Items":{"Item":{}}}');
$catalogCriteria->where(array('Item.active' => 1));
$catalogCriteria->limit($pageLimit, $pageOffset);
$catalog = $model->getObjectGraph('cpCatalog', '{"Items":{"Item":{}}}', $catalogCriteria);
// ...
?> Note that the bindGraph() function is a shortcut for using JOINs using the relationships that can be defined between tables in your xPDO model that represents them.
...and finally an even more complex scripted query...
<?php
// ...
$catalogCriteria = $model->newQuery('cpCatalogItemXref', array('catalog' => $catalogId));
$catalogCriteria->bindGraph('{"Item":{}}');
if ($hideUnaffordable) {
$catalogCriteria->innerJoin('cpCatalogItemAttribute', 'attr', array(
"attr.item = Item.id",
"attr.name = 'cost'",
"CAST(attr.value AS UNSIGNED INTEGER) <= {$cost}",
));
}
$catalogCriteria->where(array(
'Item.active' => 1
));
$countCriteria = $model->newQuery('cpCatalogItem', array('active' => 1));
$countCriteria->innerJoin('cpCatalogItemXref', 'xref', array(
"xref.catalog = {$catalogId}",
"cpCatalogItem.id = xref.item",
));
if ($hideUnaffordable) {
$countCriteria->innerJoin('cpCatalogItemAttribute', 'attr', array(
"attr.item = cpCatalogItem.id",
"attr.name = 'cost'",
"CAST(attr.value AS UNSIGNED INTEGER) <= {$cost}",
));
}
$itemCount = $model->getCount('cpCatalogItem', $countCriteria);
$modx->setPlaceholder("catalog.itemCount", $itemCount);
$catalogCriteria->limit($pageLimit, $pageOffset);
// uncomment these 2 lines to view the sql that is prepared
//$catalogCriteria->prepare();
//print_r($catalogCriteria->sql);
$catalogs = $model->getCollectionGraph('cpCatalogItemXref', '{"Item":{}}', $catalogCriteria);
// ...
?>