Thursday 18 February 2016

ColdFusion 2016: query iteration functions

G'day:
In ColdFusion 11, one of the best new features was the addition of collection iteration methods, ie stuff like Array.map(), Struct.each(), List.listReduce() etc. One glaring omission was the lack of these implemented for queries. I have no idea why they were omitted, but there you go.

Anyway, one of the few decent language features in ColdFusion 2016 is that these have finally been implemented. They mostly work as one would expect, but I'll run through examples of each anyhow.

each()

each() simply iterates over the query, calling the callback for each row. The function singature for the callback is:

void function(struct row, numeric index, query query)

An example of this in action is:

colours = queryNew("id,en,mi", "integer,varchar,varchar", [
    [1,"red","whero"],
    [2,"orange","kakariki"]
]);

colours.each(function(colour, index, colours){
    writeDump(arguments);
});

And the results:



Predictable and no surprises.

map()

map() is a bit more complicated. I'll start with an example to frame things:

colours = queryNew("id,en,mi", "integer,varchar,varchar", [
    [1,"red","whero"],
    [2,"orange","karaka"],
    [3,"yellow","kowhai"],
    [4,"green","kakariki"],
    [5,"blue","kikorangi"],
    [6,"indigo","poropango"],
    [10,"violet","papura"]
]);

maoriColours = colours.map(function(colour, index, colours){
    return {mi=colour.mi};
}, queryNew("mi","varchar"));    

writeDump(var=maoriColours);



The function signature for the callback is the same as before except it returns a struct:

struct function(struct row, numeric index, query query)

Note map() takes a second argument, which is a second query. This query acts as a template for the remapped query returned by map(). The map callback returns a struct with keys that are a subset of the column names in that query. Note here my template query has a column mi, and so does the struct I'm returning from the callback. If I try to return a different column name, I get an error:



It's important to note that that query is only used for the "schema" of the returned query, any rows in it are ignored. That whole second argument is optional, and if omitted the original query's schema is used instead.

reduce()

reduce() holds no surprises. Its callback signature is:

any function(any carry, struct row, numeric index, query query)

And an example is:

week = queryNew("id,en,mi", "integer,varchar,varchar", [
    [1,"Monday","Rāhina"],
    [2,"Tuesday","Rātū"],
    [3,"Wednesday","Rāapa"],
    [4,"Thursday","Rāpare"],
    [5,"Friday","Rāmere"],
    [6,"Saturday","Rāhoroi"],
    [7,"Sunday","Rātapu"]
]);

shortestMaoriDayName = week.reduce(function(shortest,number){
    if (shortest.len() == 0) return number.mi;
    return number.mi.len() < shortest.len() ? number.mi : shortest;
}, "");

writeOutput(shortestMaoriDayName);

This simply returns:

Rātū

sort()


The callback for this takes two elements to compare:

comparison function(any row1, any row2)

Where I say comparison as the return type here, I mean a numeric value that is <0, 0 or >0 indicating whether row1 is less then, equal to or greater than row2, by whatever yardstick is reflected by the callback. Same as any other sort handler. Example:

colours = queryNew("id,en,mi", "integer,varchar,varchar", [
    [1,"red","whero"],
    [2,"orange","karaka"],
    [3,"yellow","kowhai"],
    [4,"green","kakariki"],
    [5,"blue","kikorangi"],
    [6,"indigo","poropango"],
    [10,"violet","papura"]
]);

colours.sort(function(c1,c2){
    return compare(c1.mi, c2.mi);
});

writeDump(colours);

Note that sort() desn't return the sorted query - it'd be a lot better if it did, TBH, I think I might raise a bug for this (4119099) - it sorts the query inline. This prevents this method being chained, which is disappointing. The output for this is predictable:



(it's the Maori column that's sorted there... not so obvious given the ubiquity of Ks in Maori words).

filter()


Yay, here's the first definite bug. The filter() callback has this function signature:

boolean function(struct row, numeric index, query query)

And here's an example:
colours = queryNew("id,en,mi", "integer,varchar,varchar", [
    [1,"red","whero"],
    [2,"orange","karaka"],
    [3,"yellow","kowhai"],
    [4,"green","kakariki"],
    [5,"blue","kikorangi"],
    [6,"indigo","poropango"],
    [10,"violet","papura"]
]);

coloursWithoutK = colours.filter(function(colour, index, query){
writeDump(arguments);abort;
    return !colour.mi.find('k');
});

writeDump(coloursWithoutK);

This obviously (obviously!) returns a query with only the three rows which don't have the letter K in the Maori colours, right?



No. It returns an array. How did this get through QA (NB: there's a good reason why this was not fixed in the pre-release, but I cannot disclose why. It's not a good reason though).

So I'll raise a bug for that (4119103).

That's about it. There's a fail in sort() in that it does not return the sorted query, leaving the original alone, and an outright bug in the implementation of filter().

All in all: a good alpha-quality implementation of these functions though. And a good thing about ColdFusion 2016.

Righto.

--
Adam