Justin Carter's madfellas.com

Using CFML tags in cfscript (C4X prototype)

Update: I've added Enhancement Request #86199 to the Adobe ColdFusion Bug Tracker. Please go there and vote for it if you would like to see this considered in a future version of ColdFusion!

About 8 months ago I wrote a blog entry which discussed the concept of using XML and CFML tag literals in cfscript, dubbed C4X for want of a better title. (If you missed that entry you might want to read it first before continuing with this one). A few weeks later I wrote some proof of concept code in the form of a function that would let me use this C4X style of syntax in cfscript.

The following code has only been tested on ColdFusion 9, and since it uses the virtual file system along with included virtual files it requires a mapping for the logical path /ram pointing to ram:// (this should also work on Railo though I haven't tried it). The function itself is just 40 simple lines of code:

function c4x(string tagInput)
{
    var c4xResult = "";
    var c4xFilename = "c4x_#createUUID()#.cfm";
    var tag = listFirst(arguments.tagInput, " #chr(9)##chr(10)##chr(13)#<>");
 
    if (left(tag, 2) eq "cf") {
        if (tag eq "cfquery") {
            // query: inject name attribute
            arguments.tagInput = replace(arguments.tagInput, "cfquery", "cfquery name='c4xResult'");            
        }
        else if (tag eq "cfxml") {
            // xml: inject variable attribute
            arguments.tagInput = replace(arguments.tagInput, "cfxml", "cfxml variable='c4xResult'");            
        }
        else {
            // other: capture output using cfsavecontent
            arguments.tagInput = "<cfsavecontent variable='c4xResult'>" & arguments.tagInput & "</cfsavecontent>";
        }    
        // write code to ram disk, execute, clean up
        try {
            fileWrite("ram://#c4xFilename#", arguments.tagInput);
            include "/ram/#c4xFilename#";
        }
        finally {
            fileDelete("ram://#c4xFilename#");
        }
    }
    else {
        if (isXML(arguments.tagInput)) {
            // xml: parse and return xml object
            c4xResult = xmlParse(arguments.tagInput);
        }
        else {
            throw(message="C4X: Invalid syntax. Root tag must be a valid CFML tag or well-formed XML. Application framework and flow control tags are invalid.")
        }
    }
         
    return c4xResult;
}


Essentially this lets us pass some CFML to the c4x() function in a string and we'll get back a result such as a query, XML object or some other type of output (or, in this prototype, nothing if the CFML tag has no output). This method could also handle custom tags, although not custom tags that have bee imported into a namespace from a tag library.

What does C4X look like again?

If C4X support existed natively in CFML, a simple query against the cfartgallery database could look like this in a script-based CFC or <cfscript> block:

artists = <cfquery datasource="cfartgallery">
		SELECT firstname, lastname, email, city
		FROM app.artists
		WHERE
			city IN (<cfqueryparam cfsqltype="cf_sql_varchar" list="true" value="New York,Washington">)
	</cfquery>;


If the C4X proof of concept function above was available in the variables scope then we could use it like this:

artists = c4x('<cfquery datasource="cfartgallery">
		SELECT firstname, lastname, email, city
		FROM app.artists
		WHERE
			city IN (<cfqueryparam cfsqltype="cf_sql_varchar" list="true" value="New York,Washington">)
	</cfquery>');


This code works now, in CF9. The artists variable will now contain the query object resulting from the <cfquery> tag. The only difference between the two pieces of code is simply the function wrapping the CFML code; the 7 unnecessary, but necessary, characters of c4x(' ')...

How do the current script equivalents compare?

Below I have three example pieces of code; a simple query, a dynamic query with logic, and a query of a query. Each of these examples will be compared using code from ColdFusion 9, Railo 3.2, and the mythical C4X :)

As we well know the CF9 script version of performing queries is more verbose than the way CF developers have traditionally done things, with the <cfquery> tag.

What some people might not know is that with Railo 3.2 there is another way of doing queries in script which is different to the CF9 implementation. In my opinion, it seems to strike a nice balance, but perhaps still isn't ideal due to the need for using writeOutput() to build up your SQL statements.

So, on to the comparisons!

A simple query

// CF9 query
qs = new Query(datasource="cfartgallery");
qs.setSQL("SELECT firstname, lastname, email, city
	FROM app.artists
	WHERE
		city IN (:cities )");
qs.addParam(name="cities", cfsqltype="cf_sql_varchar", list="true", value="New York,Washington");
artists = qs.execute().getResult();
 
// Railo 3.2 query
query name="artists" datasource="cfartgallery" {
	writeOutput("SELECT firstname, lastname, email, city
		FROM app.artists
		WHERE
			city IN (?)");
	queryparam cfsqltype="cf_sql_varchar" list="true" value="New York,Washington";
}
 
// C4X query
artists = <cfquery datasource="cfartgallery">
	SELECT firstname, lastname, email, city
	FROM app.artists
	WHERE
		city IN (<cfqueryparam cfsqltype="cf_sql_varchar" list="true" value="New York,Washington">)
</cfquery>;

A dynamic query with logic

// CF9 query with logic
qs2 = new Query(datasource="cfartgallery");
qrySQL = "SELECT firstname, lastname, email, city
	FROM app.artists
	WHERE
		city IN (:cities )";
qs2.addParam(name="cities", cfsqltype="cf_sql_varchar", list="true", value="New York,Washington");
if (myartMember) {
	qrySQL &= " AND email LIKE :email";
	qs2.addParam(name="email", cfsqltype="cf_sql_varchar", value="%@myart.com");
}
qs2.setSQL(qrySQL);
artists2 = qs2.execute().getResult();
 
// Railo 3.2 query with logic
query name="artists" datasource="cfartgallery" {
	writeOutput("SELECT firstname, lastname, email, city
		FROM app.artists
		WHERE
			city IN (?)");
	queryparam cfsqltype="cf_sql_varchar" list="true" value="New York,Washington";
	if (myartMember) {
		writeOutput(" AND email LIKE ?");
		queryparam cfsqltype="cf_sql_varchar" value="%@myart.com";
	}
}
 
// C4X query with logic
artists2 = <cfquery datasource="cfartgallery">
	SELECT firstname, lastname, email, city
	FROM app.artists
	WHERE
		city IN (<cfqueryparam cfsqltype="cf_sql_varchar" list="true" value="New York,Washington">)
		<cfif myartMember>
			AND email LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="%@myart.com">
		</cfif>
</cfquery>;

A query of a query

// CF9 query of a query
qs3 = new Query(dbtype="query");
qs3.setAttributes(artists=artists);
qs3.setSQL("SELECT firstname, lastname
	FROM artists
	WHERE
		lastname LIKE :lastname");
qs3.addParam(name="lastname", cfsqltype="cf_sql_varchar", value="T%");
artistsQoQ = qs3.execute().getResult();
 
// Railo 3.2 query of a query
query name="artistsQoQ" dbtype="query" {
	writeOutput("SELECT firstname, lastname
		FROM artists
		WHERE
			lastname LIKE ?");
	queryparam cfsqltype="cf_sql_varchar" value="T%";
}
 
// C4X query of a query
artistsQoQ = <cfquery dbtype="query">
	SELECT firstname, lastname
	FROM artists
	WHERE
		lastname LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="T%">
</cfquery>;

Comparison results

As you can see from the code above and the table below, the C4X version of the code is a bit more concise than the Railo 3.2 version and significantly more concise than the CF9 version.

  Simple query Dynamic query Query of a query
CF9 276 chars (100%) 435 chars (100%) 269 chars (100%)
  7 lines 12 lines 8 lines
Railo 229 chars (83%) 350 chars (80%) 179 chars (67%)
  7 lines 11 lines 7 lines
C4X 217 chars (79%) 329 chars (76%) 167 chars (62%)
  6 lines 9 lines 6 lines


Better yet, there isn't really any new syntax to learn to be able to write code this way; simply remove the name attribute from the <cfquery> tag, place a variable and assignment operator before the tag and a semicolon after the tag. It's just the same old CFML you've always had to write, used in a new way inside script-based CFCs or <cfscript> blocks.

Why C4X?

As I mentioned in the previous blog entry, the main reason for implementing CFML tag literals is flexibility. It provides access to existing native tags that might not have script equivalents (yet). It's a potential solution for integrating custom tags into script based CFCs. It provides code reuse options. If you think about it, CFML tags and custom tags are now the outcasts in script based CFCs (i.e. they cannot be used), whereas previously cfscript was limited in its functionality compared to the possibilities with CFML tags.

I do like the direction Railo 3.2 has taken, but is it ideal?

To finish, here's a bunch of tweets which show a few reasons why cfscript still needs to be improved: