Monday 15 October 2012

Probable backwards compat bug in CF10's cachedwithin behaviour

G'day:
I'm following up a post I noticed in the Adobe ColdFusion Forums. I'm gonna write up my findings here, and then summarise / crosspost back to the forums, and perhaps raise a bug depending on what people think about it.

The general gist of the situation is that CF10's query cache seems to be bound to an application, whereas it never used to be. I can see an argument both ways for this, but it's something Adobe seem to have arbitrarily changed without telling us (I have not looked, but the person posting on the forums has, and has drawn a blank).

Update:
Rob Brooks-Bilson has pointed out that there's an Application.cfc setting that controls this:

<cfset this.cache.useinternalquerycache = true>

That about solves this one, except I think TRUE should have been the default. Mileage might vary on that one, I guess.

It's also not been documented in a fashion that's particularly easy to find, I think.  Oh well: at least it's not a bug!

Cheers for that, Rob.



Here's my repro case.

<!--- /differentapps/createData.cfm --->


<!---
DROP TABLE IF EXISTS `scratch`.`numbers`;
CREATE TABLE  `scratch`.`numbers` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `english` varchar(45) NOT NULL,
  `maori` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
 --->

<cfquery datasource="scratch_mysql">
    TRUNCATE TABLE numbers
</cfquery>

<cfloop index="record" array="#[
    {english="one", maori="tahi"},
    {english="two", maori="two"},
    {english="three", maori="three"},
    {english="four", maori="four"}
]#">
    <cfquery datasource="scratch_mysql">
        INSERT INTO numbers (
            english, maori
        ) values (
            <cfqueryparam value="#record.english#">,
            <cfqueryparam value="#record.maori#">
        )
    </cfquery>
</cfloop>

<cfquery name="numbers" datasource="scratch_mysql">
    SELECT    *
    FROM    numbers
</cfquery>
<cfdump var="#numbers#">



<!--- /differentapps/app1/Application.cfc --->

<cfcomponent>
    <cfset this.name = listLast(getDirectoryFromPath(getCurrentTemplatePath()), "\")>
</cfcomponent>


<!--- /differentapps/app1/getNumbers.cfm --->

<cfquery name="numbers" datasource="scratch_mysql" cachedwithin="#createTimeSpan(0,0,0,30)#">
    SELECT        id, english, maori
    FROM        numbers
    ORDER BY    id
</cfquery>
<cfdump var="#numbers#">


<!--- /differentapps/app2/Application.cfc --->

<cfcomponent>
    <cfset this.name = listLast(getDirectoryFromPath(getCurrentTemplatePath()), "\")>
    <!---<cfset this.name = "app1">--->
</cfcomponent>


<!--- /differentapps/app2/clearCachedNumbers.cfm --->

<cfquery datasource="scratch_mysql">
    INSERT INTO numbers (
        english, maori
    ) values (
        <cfqueryparam value="five">,
        <cfqueryparam value="rima">
    )
</cfquery>
<cfquery name="numbers" datasource="scratch_mysql" cachedwithin="#createTimeSpan(0,0,0,0)#">
    SELECT        id, english, maori
    FROM        numbers
    ORDER BY    id
</cfquery>
<cfdump var="#numbers#">

So what we have here is a directory structure like this:


\differentapps\
    createData.cfm
    \app1\
        Application.cfc
        getNumbers.cfm
    \app2\
        Application.cfc
        clearCachedNumbers.cfm

createData.cfm truncates an existing table (the DDL for which is included up there too), and puts rows 1-4 in the test table.

Then there are two "applications" in sub-directories.  Each Application.cfc uses its parent dir name as its name, so they are distinct applications.

app1 one has a file getNumbers.cfm that queries the four rows of data, and caches it for 30sec (the 30sec is just to help with the expediency of my testing).

app2 has a file clearCachedNumbers.cfm which adds a row five to the table, and requeries - using exactly the same SQL as getNumbers.cfm - setting the cache time to zero.

The test is this:
  1. switch debugging output on, to see the DB activity;
  2. hit createData.cfm - rows 1-4 are created;
  3. hit getNumbers.cfm - rows 1-4 are queried and cached;
  4. hit getNumbers.cfm again to make sure the data is coming from a cached query;
  5. hit clearCachedNumbers.cfm, which adds row 5, and requeries the table, resetting the cache: we see five records;
  6. hit getNumbers.cfm again, we see the five records.
On CF8 and CF9, this works as described.  On CF10 we still only see four records coming back at step six.  If, however, we change app2's Application.cfc to use the app1 application name, then we see the test running correctly.  This - to me - suggests that the cachedwith query cache has been changed to be application-specific.

Why is this a problem?  Well I think most of the time it should not be, but there's definitely a case where it could be (as per the person posting on the forums). And Adobe are supposed to preserve backwards compatibility in ColdFusion.  They have not, here.

I think if Adobe wanted to change this behaviour they should have made it backwards compatible, and perhaps have a "cacheat" setting, which defaults to SERVER, but could take a value for APPLICATION if one so wanted.  They should not have simply unilaterally changed this.

Thoughts?

--
Adam