Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

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

By Ben Nadel on
Tags: ColdFusion

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.



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.

Reply to this Comment

@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.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.