A Query Object Maintains Its CurrentRow When Passed Out-Of-Context In Adobe ColdFusion 2021
As I'm attempting to modernize my blogging platform for Adobe ColdFusion 2021, I'm moving a lot of my old-school, inline CFQuery
tags into various "Service" and "Data Access" ColdFusion components where they can be reused across multiple templates. And, as much as I love the ColdFusion Query object, my "service boundaries" deals with Arrays and Structs, not queries. As such, I have code that deals with mapping queries onto other normalized data structures. While writing this code, I was tickled by the fact that the Query object maintains its .currentRow
property even when passed out-of-context. This .currentRow
can then be used a default argument value in Function signatures. This is a really old behavior of ColdFusion; but, I thought it would be fun to demonstrate since it may not be a feature people consider very often.
A ColdFusion query object has two meta-data properties:
.recordCount
- The number of rows in the query..currentRow
- The current iteration row inside a loop (either usingCFQuery
orFor-In
).
Since there is no "row object", these properties exist on the query object itself. And, if you pass the query object out-of-context (ie, pass the query into a Function invocation), these two properties go along with it. These properties can then be used in both the body of the invoked Function as well as in the Function signature itself. Example:
<cfscript>
/**
* I return a struct-representation of the given query row. By default, the row being
* inspected is the "currentRow". But, any index can be used as an override.
*/
public struct function asTransferObject(
required query row,
numeric rowIndex = row.currentRow
) {
return({
id: row.id[ rowIndex ],
name: row.name[ rowIndex ],
createdAt: row.date_created[ rowIndex ].getTime()
});
}
</cfscript>
Notice that the second argument in this Function is the row being inspected. It's an optional argument; and, if omitted, it will default to the .currentRow
value of the first argument (which is the query object).
In fact, if our Function only ever needed to inspect the "current row", we could omit the second argument altogether:
<cfscript>
/**
* I return a struct-representation of the given query row.
*/
public struct function asTransferObject( required query row ) {
return({
id: row.id,
name: row.name,
createdAt: row.date_created.getTime()
});
}
</cfscript>
If we omit the row-index from the column-access syntax, the query object will implicitly use the .currentRow
value of the Query.
To see this all in action, I'm going to run a SQL statement and then map the Query object to an Array-of-Structs using our Function:
<cfscript>
results = queryExecute("
SELECT
e.id,
e.name,
e.date_created
FROM
blog_entry e
WHERE
e.isActive = 1
ORDER BY
e.id ASC
LIMIT
5
");
entries = [];
cfloop( query = results ) {
// NOTE: The query object is STATEFUL. As we pass it around within a loop, it
// maintains the contextual .currentRow value of the iteration.
entries.append( asTransferObject( results ) );
}
cfdump( var = entries );
// -------------------------------------------------------------------------------//
// -------------------------------------------------------------------------------//
/**
* I return a struct-representation of the given query row. By default, the row being
* inspected is the "currentRow". But, any index can be used as an override.
*/
public struct function asTransferObject(
required query row,
numeric rowIndex = row.currentRow
) {
return({
id: row.id[ rowIndex ],
name: row.name[ rowIndex ],
createdAt: row.date_created[ rowIndex ].getTime()
});
}
</cfscript>
As you can see, inside our CFLoop
tag, we're taking the Query object and passing out-of-context into our asTransferObject()
function. Then, within the function, we use the stateful .currentRow
property. And, when we run this Adobe ColdFusion 2021 code, we get the following output:
Works like a charm! The Query object in ColdFusion is one of the most amazing and fundamental parts of the CFML language. It's nice to see that it "just works" in so many cases. It's a huge part of why ColdFusion is just a luxurious language to use in modern web applications.
returnType="array"
and For-In
Query Loops
Epilogue on With Lucee CFML and Adobe ColdFusion 2021, the CFQuery
tag now has a returnType="array"
attribute, which allows the CFQuery
tag and the queryExecute()
function to return an Array-of-Structs instead of a Query object. This can be great in some situations. But, in my case, since I'm also mapping the results onto my own Array-of-Structs, it seems like unnecessary processing to perform two different mappings. I'd essentially be throwing away the first mapping that the CFQuery tag did for me.
For many years, ColdFusion has allowed us to iterate over a Query object using a For-In
loop:
for ( var myRow in queryObject ) { ... }
And while this is awesome in many contexts, in my case, since I'm also generating my own Struct per query row, having ColdFusion generate an intermediary Struct seems unnecessary. Again, I'd just be throwing away the first mapping that ColdFusion was doing for me.
To be clear, I'm not saying that these other techniques are wrong - I use them all the time! I'm just pointing out that in some contexts, they are unnecessary.
Want to use code from this post? Check out the license.
Reader Comments
Euh.. thanks Ben. I have function that actually turns recordsets into an array of structure 😱... I guess it's time to investigate that.
@Frédéric,
Totally. The
returnType="array"
on the query in ACF 2021 (and Lucee, not sure how many releases) will do that automatically. Also, if youfor ( row in query )
, therow
value will automatically be a Struct inside the iteration. Being able tofor-in
over a query object has been such a helpful feature.