There are several ActiveRecord styled query builder libraries out there. Some are stand alone and some come built into frameworks. However, they really have trouble with WHERE a
I know this is an extremely old posting, but I'm going to reply to it anyway, because I'm in the process of developing my own classes to meet similar needs to what the question asks.
After looking into it, I've found that the problem with Zend-Db and other such engines is that they try to be all things to all people. To appeal to the largest audience, they need to offer the most general functionality, which becomes their own undoing as far as I can see (and as expertly explained by Bill Karwin).
One of the most obvious over-complications that many engines do, is to confuse the generation of SQL code with its execution (making it easier to write dirty SQL). In many applications, it's a good idea to separate both of these quite explicitly, encouraging the developer to think about injection attacks etc.
In building an SQL engine, the first thing to try to do, is to limit the scope of what SQL your engine can produce. You should not allow it to produce a select * from table
for example; the engine should require the developer to define each select
, where
and having
column explicitly. As another example, it's often useful to require every column to have an alias (normally not required by the database).
Notice that limiting the SQL in these ways does not limit what you can actually get out of the database. Yes, it makes the up-front coding more verbose on occasion, but it also makes it more structured, and lets you dump hundreds of lines of library-code which were only ever there in the first place to deal with complicated exceptions and provide (ahem) "flexibility".
The libraries I've written so far are about 600 lines of code (~170 lines of which is error-handling). It deals with ISO joins, sub-statements (in the SELECT
, FROM
and WHERE
clauses), any 2-sided comparison clause, IN
, EXISTS
and BETWEEN
(with sub-statements in the WHERE clause). It also implicitly creates bindings, instead of directly injecting values into the SQL.
Limitations (other than those already mentioned): the SQL is written expressly for Oracle. Untested on any other database platform.
I'm willing to share the code, assuming that any improvements are sent back.
As an example of what the libraries let me produce, I hope that the following is simple enough to be intuitive, while also being complex enough to show the expandability potential:
AddVarcharCol ('value','VALUE')
->AddVarcharCol ('identity','UID',false)
->AddVarcharCol ('type','info_type',false)
->AddFrom ('schemaa.user_propertues','up')
->AddWhere ('AND')
->AddComparison ('UID', '=', 'e.identity', 'column')
->AddComparison ('info_type', '=', 'MAIL_ADDRESS');
$stmt = new OraSqlStatement;
$stmt->AddVarcharCol ('company_id', 'Company')
->AddVarcharCol ('emp_no', 'Emp Id')
->AddVarcharCol ('person_id', 'Pers Id')
->AddVarcharCol ('name', 'Pers Name')
->AddDateCol ('employed_date', 'Entry Date')
->AddDateCol ('leave_date', 'Leave Date')
->AddVarcharCol ('identity', 'User Id')
->AddVarcharCol ('active', 'Active')
->AddVarcharCol ($substmt, 'mail_addy')
->AddFrom ('schemab.employee_tab', 'e')
->AddFrom ('schemaa.users_vw','u','INNER JOIN','u.emp_no=e.emp_number')
->AddWhere ('AND')
->AddComparison ('User Id', '=', 'my_user_id')
->AddSubCondition ('OR')
->AddComparisonNull ('Leave Date', false)
->AddComparisonBetween ('Entry Date', '2011/01/01', '2011/01/31');
echo $stmt->WriteSql();
var_dump($stmt->GetBindArray());
?>
Which produces:
SELECT
company_id "Company", emp_no "Emp Id", person_id "Pers Id", name "Pers Name",
employed_date "Entry Date", leave_date "Leave Date", identity "User Id", active "Active",
( SELECT value "VALUE" FROM schemaa.user_propertues up
WHERE upper(identity) = upper(e.identity)
AND upper(TYPE) = upper (:var0)
) "mail_addy"
FROM
schemab.employee_tab e
INNER JOIN schemaa.users_vw u ON u.emp_no = e.emp_number
WHERE
upper (identity) = upper (:var1)
AND ( leave_date IS NOT NULL OR
employed_date BETWEEN to_date (:var2,'YYYY/MM/DD') AND to_date (:var3,'YYYY/MM/DD')
)
Along with the bind array:
array
0 => string 'MAIL_ADDRESS' (length=12)
1 => string 'my_user_id' (length=10)
2 => string '2011/01/01' (length=10)
3 => string '2011/01/31' (length=10)