Skip to main content
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Rick Stumbo
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Rick Stumbo

A Query Object Maintains Its CurrentRow When Passed Out-Of-Context In Adobe ColdFusion 2021

By
Published in Comments (2)

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 using CFQuery or For-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:

A ColdFusion query object mapped onto an array of structs.

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.

Epilogue on returnType="array" and For-In Query Loops

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

15,848 Comments

@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 you for ( row in query ), the row value will automatically be a Struct inside the iteration. Being able to for-in over a query object has been such a helpful feature.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel