Ever since completely refactoring the native Query of Query support in Lucee to be more performant and support better aggregate and grouping, I’ve been doing a steady stream of pulls adding new features and fixing incompats with Adobe’s QoQ. One really nice thing about the native QoQ support is
- We have full control over the SQL parser
- We have full control over the execution and optimization
- We can add whatever functions/expressions we want-- many of which Adobe’s QoQ doesn’t even support such as
isNull()
(already supported) orCASE WHEN THEN END
statements (proposed).
While most everything QoQ does follows the SQL spec, or at least takes into account MSSQL, MySQL, Postgres, and Oracle’s precedent-- CFML’s QoQ has a few nods to CFML since it’s embedded into CFML and made for CF devs. We have a lot of great first-class functions in Lucee’s native QoQ and Implementing new ones is easy and I really love making QoQ more powerful.
One of my re-occurring ideas is to gather a collection of basic string, list, and date/time functions in CFML and make them available first class SQL functions in Lucee’s QoQ which just delegated behind the scenes to the same implementation as the BIFs. This would really unlock a ton of incredibly powerful stuff you can do right from inside your Query without additional loops for mapping of the result set. Some examples could be:
Format data for your reports or use string and number manipulations like regex or lists
SELECT
cf.reReplace( nameCol, '(.*)(Jr|Sr)', '\1' ) as nameCleaned,
cf.listFirst( forwarded_for_IP_List ) as forwarded_for,
cf.dollarFormat( cf.max( productTotal, orderTotal ) ) as maxDollars,
cf.hash( password ) as passwordHashed,
cf.now() as datetimeReport,
cf.val( age ) as age,
cf.dateFormat( dateCreated, 'MM/DD/YYYY' ) as dateCreatedFormatted
from myQry
Or do funky stuff with JSON or lists. Yes, performance considerations apply, but it’s not going to be any different than doing a separate myQry.filter()
or myQry.map()
- in fact it may even be faster.
SELECT
name,
foo,
cf.listToArray( categoryIDList ) as categoryIDArray
from myQry
where cf.structFind( cf.deserializeJSON( strOrderJSON ), 'customerName' ) = 'brad'
The possibilities are near endless, however I would recommend putting some artificial limits on it on purpose so it’s clear that WE’RE WRITING SQL, NOT CFML HERE. i.e. everything above is SQL, being parsed by the SQL parser, following the rules of SQL. And the functions called cf.BIF()
just to happen to delegate to the CFML BIFs of the same name behind the scenes. This means I’d recommend enforcing the following rules (which are really just desired limitations of the SQL parser):
- Support for delegating to CF functions only, no tags
- Positional params only
- We choose what BIFs to expose so it’s only the ones that actually make sense (Keeping in mind a CFML query object can contain a complex value such as an array or a struct inside a table cell)
- No complex object literals allowed in the SQL (this includes array, struct, and closure/UDF literals). Only query columns, string/number literals, and other SQL expressions can be passed.
- There would be no use of
evaluate()
or anything like that. The Lucee native QoQ Java implementation would simply map those SQL functions directly through to the matching CFML BIFs when executing so it would perform the same as you writing that CFML BIF in your source code.
So this is where you come in. The technical implementation of this feature is pretty straightforward. Heck, I’ll probably spend more time writing the tests than the actual java code for this. This would also not be any sort of replacement for functional programming as any of this could also be done with queryMap()
, queryFilter()
, etc-- this would just be another way to skin the cat if you already were doing logic in QoQ in the first place. The questions are
- Does this make sense?
- Is it a good idea?
- Would you use it?
- Is
cf_
a good prefix for the SQL functions that pass through to a BIF? I feel like they need to be name spaced to be explicit, butcf_foo
also looks a lot like a custom tag call and a little bit like Adobe’s tag-in-script syntax. Better ideas? Update: I’ve updated the examples to usecf.BIF()
as it matches SQL schema much better. - What should the final list of functions be that we support? I have several examples above, but honestly there are a ton of string/number/list/date functions and that doesn’t even count if we want to get funky with struct/array/JSON functions to lean on CFML queries’ abilities to hold complex values in their cells. And then there’s bit-wise operations, decision functions, conversion functions, and math functions! I can imagine use cases for nearly any of those, but I’m not sure we should support everything just because it’s there.