In which scope would you store a cachedWithin query within a function?

Noted. Thanks.

I know I’m joining in late, folks. And I’m about to suggest something that no else has in this thread, which I do with trepidation. And I may be bringing an ACF bias, so I’m open to being schooled, but I’m not sure that’s the case. :slight_smile:

So correct me if I’m wrong, folks, but it seems to me that the var in which one stores the RESULT of a query using cachedwithin should have no bearing at all on how the caching mechanism itself works, including any NPE one may get from the caching engine.

Doesn’t the variable simply hold the RESULT of the query, for subsequent processing in that request (or others, if a shared scope), regardless of whether the cachedwithin resulted in the query result being cached or obtained from cache when it was put in that variable? I’d think the var’s scope really should have no bearing on that CACHE process on the right-hand side of the variable assignment.

And I think that’s what Zac’s reply was getting at, when he said that “when you use cachedWithin, it creates a hash of the arguments passed in the caching mechanism”. I do think he was speaking there specifically of using cachedwithin in a function (which for now ACF does not support), given the context at the time and his mention of" arguments". But it would seem to also suggest there’s no connection to the var holding that function result. Can you confirm, Zac?

And caching a query in Lucee would seem similar. I can say that in acf, the key for caching a query would have be the sql as evaluated, as well as the dsn and any user name. It literally has nothing at all to do with the “name” in a cfquery. (Nor would one need technically to even assign the queryexecute to a variable, to have it do its cache processing–though of course you would need either if you wanted to process the query results.) Could Lucee work differently? That’s one reason I make my comment here, to ask.

As for the NPE BKBK said he got in ACF, there may well be reasons having nothing to do with this variable assignment, specifically (and I’d think there must be).

As for the NPE you’re now getting, BKBK, it could help to hear if you have found something that can “prevent” that NPE in Lucee, with this demo of caching a query. That may lead to some other solution, or perhaps identification of a bug, or it might well contend against (or confirm) my assertion here.

And to be clear, I’m not at all addressing above the pros cons of what scope one may choose to use to hold the result of something, whether that things was cached or not. There can certainly be value in saving a query result or function in the application scope, for instance, for subsequent use by others. (How one goes about that has its own challenge and options, of course.) I’m just saying I’d not at all expect there to be any impact of that choice of scope on the process of CACHING of that result, or even the OBTAINING of that result from cache.

Does that make sense, or do you or does anyone feel I have things wrong? I really am trying to help, not merely to be argumentative.

100% correct

Agreed. One could easily confirm by running a simple cached query select 1 and dump.
Run the same (different variable) and dump it.
Check out the Cached: Yes/No and then you will know what to expect. I believe I suggested this and it was perhaps missed / misunderstood.

firstly, i know coz i’ve read the source code,
secondly, that would be quite a lot more work to implement something so quirky :wink:

I know the later, coz I’d like to improve some types of exception in lucee, but we don’t track that info

Thanks for that confirmation, Zac.

@carehart , thanks for chiming in.

I should make something clear. I am aware of how the cachedWithin mechanism works in Lucee and in Adobe ColdFusion. That is NOT what my latest question is about. The caching discussion, however interesting, is a diversion. That’s how I see it anyway.

My question is about the fundamental principles on which Lucee and Adobe ColdFusion are designed. I think that it is wrong to use the cache-provider to explain the behaviour of queryExecute. A well-designed function should encapsulate its inner workings. The caller shouldn’t be aware of the wheels turning underneath.

Let me take it again from the top, using bare minimum code for illustration.

query  function getRecords() {

    var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 

}

Assume we’re currently within the context of the function getRecords. The function getRecords in turn calls the function queryExecute.

In software design terms we may consider getRecords to be the client calling the queryExecute service. Normally, assuming all goes well, the client (getRecords) expects the service (queryExecute) to return the following result:

  1. a query object.
  2. the value of the query object remains unchanged in memory for 1 day.

The client (getRecords) needn’t know anything about and shouldn’t care about how the service (queryExecute) got the result. In other words, getRecords is - or, should be - totally oblivious to any underlying machinery responsible for producing the result.

If you’re an object and you, in turn, call getRecords, then you will be the client of the getRecords service. You, too, will expect from getRecords the same result as getRecords expected from queryExecute: a query object, with the same value available in memory for a day.

To repeat: your contract with the service is that the query object be available in memory for a day. You, the client, needn’t know anything about caching.

Software principles such as responsibility (GRASP), design-by-contract and separation-of-concerns come into play here. These may sound pretentious and highfalutin. But they come from real life.

You get a pint of milk at the supermarket. It is fresh, has a temperature of 3 degrees Celsius and will keep for 2 to 3 days. That’s all you need to know about that service.

You needn’t know about the number of cows involved, the state of their udders, whether the farmers used manual or mechanical pump action and how the milk got to you.

I hope I have been able to convince you that, the moment you use the cache-provider to explain the behaviour of queryExecute or getRecords, something is amiss.

With that out of the way, on to the question.

query  function getRecords() {
    var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 

}

The right-hand-side of the assignment represents a persistent object in memory. So, we would expect it to be in memory when getRecords() is repeatedly called, say, within an hour. However, the left-hand-side defines a transient variable which may no longer exist between getRecords() calls. Isn’t this inconsistent?

If so, then Lucee and Adobe ColdFusion needn’t worry about the NullPointerException. For, assigning a cachedWithin query to var scope would simply be bad coding practice.

Yep Jason, I caught your mention of that earlier in the thread, sure. And I agree with what you said. I just didn’t see it as specifically addressing the point I’m now raising: that the scope of the variable used does not affect the caching. I see you’re saying now that one could use the dump to confirm the state of caching. Yep, on that.

I’ll have more to say in another comment–which probably should be split off into a separate topic. I’ll leave others to judge/do that.

1 Like

Zac, it’s unclear who or what that is referring to. I don’t see anyone proposing any new ideas between your previous comments here and this one. I almost think you may have been meaning to reply to some other thread, or perhaps a deleted comment from someone there. Can you clarify?

And I really want to clarify that because I am just about to make a comment that WILL propose something
and I just want to be clear to readers that his comment can’t (yet) be referring to that. :slight_smile:

In recent years, we saw hundreds of NullPointerExceptions resulting from cachedWithin queries being assigned to var scope in Adobe ColdFusion.

Here are 2 examples, on CF2018:

“Error”,“ajp-nio-127.0.0.1-8030-exec-14”,“11/02/20”,“07:28:22”,“stxxxmeterLive2018”,
"‘’ The specific sequence of files included or processed is:
E:\wwwroot\data_stxxxmeter_nl\htdocs\scheduled\ImportNatschool.cfm, line: 51
"java.lang.NullPointerException
at coldfusion.tagext.sql.QueryTag.setupCachedQuery(QueryTag.java:1186)
at coldfusion.tagext.sql.QueryTag.startQueryExecution(QueryTag.java:833)
at coldfusion.tagext.sql.QueryUtils.executeQuery(QueryUtils.java:72)
at coldfusion.runtime.CFPage.QueryExecute(CFPage.java:12450)
at cfUserMethods2ecfc802731659$funcSETMETHODINFO.runFunction(E:\wwwroot\data_stxxxmeter_nl\htdocs\nl\devxxxx\stxxxmeter\orm\UserMethods.cfc:51)

var qProductMethod=queryExecute(sSql, stSqlParams, {cachedwithin: createTimeSpan(0, 24, 0, 0)});

“Error”,“ajp-nio-8014-exec-6”,“09/19/19”,“13:49:41”,“stxxxmeterStaging2018”,
"‘’ The specific sequence of files included or processed is:
E:\wwwroot\staging_stxxxmeter_nl\htdocs\nl\devxxxx\stxxxmeter\facades\UserDataFacade.cfc, line: 239 "
java.lang.NullPointerException
at coldfusion.tagext.sql.QueryTag.setupCachedQuery(QueryTag.java:1186)
at coldfusion.tagext.sql.QueryTag.startQueryExecution(QueryTag.java:833)
at coldfusion.tagext.sql.QueryUtils.executeQuery(QueryUtils.java:72)
at coldfusion.runtime.CFPage.QueryExecute(CFPage.java:12254)
at cfUserData2ecfc795832988$funcGETUSERRESULTSTOTALS.runFunction(E:\wwwroot\staging_stxxxmeter_nl\htdocs\nl\devxxxx\stxxxmeter\service\UserData.cfc:239)

var qUserResultsTotals=queryExecute(sSql, stSqlParams, {cachedwithin: createTimeSpan(0, 24, 0, 0)});

BKBK, I don’t see that as “inconsistent”. I do see it as potentially confusing, sure. But to be clear, the result of the queryexecute on the right will always produce some query result: either it will come from cache or it will not, depending on things like that cachedwithin timespan.

But then that query result (however it was obtained on that right-handside queryexecute) will in this case be put in a variable whose life will only be within the method, given the var scope.

I suppose one could argue that the inconsistency comes from a seeming mix of “lifetimes”. But the two things (on the right and left) are very different. To use your analogy, one is the producer (rhs) and one is the consumer (lhs).

As for the caller to the method, sure, they would seemingly not really care at all about ANY of this (lifetime of the var scoped variable, or whether the result was or wasn’t cached).

But this gets muddled by a couple of things. First, your function isn’t returning anything at all. So really, one could say that the variable assignment here is entirely moot.

On the other hand, the caching of the queryexecute is really a side-effect. One might make a purist argument that a method being self-contained should not CAUSE such a side-effect, sure (which it will, in possibly caching the result). But as soon as you choose to use caching in the method, you’ve “bought that milk”, in your analogy.

But back to the point I was making, it really makes no difference at all (to the CACHING of the query on the rhs) whether you do or do not use a var scoped variable for holding its result.

As I did touch on, one could certainly argue that if the lhs variable were an application scope (or any shared scope, like session or server or Lucee’s cluster), that WOULD of course make the result be available “outside” the function. And that, too, could be seen to be an undesired side-effect, to those who want to prevent such things.

Hope that helps clarify something.

BKBK, in those cases (or in the case you are now working with here in Lucee), are you confirming that as soon as you change it to NOT be a var scoped variable, the NPE goes away? That would really surprise me, again because to me there’s no bearing on the caching process on the rhs of the assignment (the queryexecute), relative to then saving the result (whether it was in cache or not, or saved to cache or not) in the var on the lhs.

(And if anyone reading along may be confused about lhs/rhs acronyms, that refers to left-hand side and right-hand side of an assignment. I clarified it a couple of times earlier, but dropped into the acronyms later. And I realize some folks don’t read every message. No disrespect meant to anyone in clarifying lhs/rhs. We can’t know who may read this days, weeks, or months later.)

There are several things being discussed here.

Regarding NPEs in Adobe, this is a bug in Adobe and you should report it in their ticket tracker. It has nothing to do with scopes, is not by design, and has no relation to any NPEs you may have encountered in Lucee or 3rd party Ortus extensions.

Regarding NPEs in the Ortus Redis cache extension, this was a bug in Ortus’ extension that has been fixed in the latest version. It had nothing to do with scopes, was not by design, and had no relation to any NPEs you may have encountered in Adobe or Lucee.

Regarding using scopes to cache data. This is fine, but it’s not really a matter of which scope is “better”. Given the documentation of the live cycle of each scope, you use the one that fits your needs. I.E

  • server if you want to cache for as long as the server is up
  • application if you want each application to have a copy and you want to cache for as long as the application exists
  • session if you want each session to have a copy and you want to cache for as long as the session exists
  • request if you want each request to have a copy and you want to cache for as long as the request exists
  • etc, etc.

Now, that said, none of that is related to using a caching implementation which uses an out-of-process store which seems to be what you’re doing.

Regarding using cached within (whether on the function or the query). There is also nothing wrong with this. It does not necessitate you using any scope other than local to hold the result, and the NPEs you experienced in Adobe and the Ortus extension were just unfortunate programming bugs.

Just to clarify, this is not quite true. The caching assures you the same equivalent query data will be returned every time you run it. There is no guarantee of where or how the cache has decided to (possibly serialize) and store your query.

I don’t know what you think Zac said, but you’re arguing against a vapor cloud. Everyone here understands encapsulation-- Zac was more making commentary on how the caching is deterministic against the inputs (which should be fairly obvious, otherwise it would be pretty useless.)

You’re conflating your local scoped variable which contains a pointer to the object in cache. Just because the local scope goes away after the function is done executing doesn’t mean the query isn’t in the cache.

application.longLivedData = [ 'foo' ];
session.bar=application.longLivedData;
request.baz=application.longLivedData;
local.bum=application.longLivedData;

There is one array in memory and it will exist in memory until there are no more references to it. The demise of the local or request scopes have no effect on that.

No, the correlation of your NPEs is not a causation. The NPEs are nothing more than bugs that are unrelated to the scopes you were using to store the cached query.

Nope, totally and completely unrelated. And a bug.

Incorrect. The reference in the local scope will be removed, but the references the cache holds to the object still exist. Destroying a reference does not remove the actual data being referenced from memory so long as there are other references.

// create data and a reference so it
application.foo={ 'brad' : now() };
//Create another reference to it
local.bar=application.foo;
// delete the local reference
structDelete( local, 'bar' )
// The data is still here!
dump( application.foo )

No, this is incorrect.

1 Like

@bdw429s , thanks for chiming in. Thanks also for your thoughts on the appropriate use of scopes.

I don’t quite get the rest of your post. In particular, I do not see how your answer connects to my var-scoped-cachedWithin question.

Your answer is based on

local.bar=application.foo;

Or, equivalently, in the style of my question:

var bar=application.foo;

You started with that, and developed your answer further along that path. But that code doesn’t quite match the one in my question, namely

var bar=a_cachedWithin_query_resulting_from_queryExecute;

In ACF and Lucee, queryExecute is the script variant of the cfquery tag. So, here, bar is the equivalent of the name attribute in a cfquery tag.

I don’t see any such consideration in your var bar=application.foo; example. Yet the rest of your answer relies on this foo-bar example.

@bdw429s
I don’t know what you think Zac said, but you’re arguing against a vapor cloud.
Everyone here understands encapsulation-- Zac was more making commentary
on how the caching is deterministic against the inputs (which should be fairly
obvious, otherwise it would be pretty useless.)

You say you don’t know what I think, so I shall repeat it: the caller of a function should be totally unaware of those internal implementation details of the function that have nothing to do with the interface available to the caller.

Deterministic? My var-scope-cachedWithin question is whether we’re in fact dealing with a Schrödinger cat. :slight_smile:
Again, I’m just asking.

In addition, you’ve repeated what I considered to be a design mistake. However, when I re-read it I realized we may perhaps be talking about the same thing, but in 2 different ways.

You respond to my point, “The cachedWithin implies you may assume the object is held in memory” with:

@bdw429s
Just to clarify, this is not quite true. The caching assures you the same equivalent query data
will be returned every time you run it. There is no guarantee of where or how the cache has
decided to (possibly serialize) and store your query.

I think you’ve misread me. I haven’t said a cachedWithin query is stored in memory. My thiniking is that, by design, the caller expects the query to be in memory on subsequent calls of queryExecute.

You’re talking about the internals of the query-caching process. I am talking from the point of view of the caller of the queryExecute() service. The caller doesn’t - and shouldn’t know - anything about the caching internals.

In fact, the query may equally be stored using some technology other than a cache-provider. All the caller expects is that subsequent queryExecute calls will take zero time.
Hence my statement, “cachedWithin implies you may assume the object is held in memory”.

@Zackster
Incorrect. The reference in the local scope will be removed, but the references
the cache holds to the object still exist. Destroying a reference does not remove
the actual data being referenced from memory so long as there are other references.

You’ve said “incorrect”, then agreed with my point immediately after. :slight_smile:

I said, “when you var-scope a variable within a function, you’re telling the application that the corresponding object may be deleted from memory after the function returns.”. Which is the same as your statement, “The reference in the local scope will be removed”. To which anyone in ACF or Lucee Land would say “Duh”, I guess. :wink:

I am only talking about the consequence of the var scope within the context of a function. I made no connection to caching. You’re the one who brings in references from elsewhere. Here again, you’re attributing responsibility where it shouldn’t exist. The caller of queryExecute does not - and should not - know or care about what the cache-provider does.

In any case, we agree on one point. Namely, that the var-scoped variable is independent of any references that exist elsewhere, outside the function.

Because it’s irrelevant to my example where the long-lived data is stored. Whether it’s in the application scope, or pulled form the cache, your question was about the implications of storing a reference to a long-lived variable with a short-lived variable. So whether we’re talking about

var myVar = longLivedScope.foo;

or

var myVar = functionThatReturnsLongLivedValue()

the behavior of the local-scoped variable will be the same.

No, I said I didn’t know what you thought Zac said because your reply to him before and now is completely off-topic. Zac just made a simple side comment about how the cached values are internally dependent upon the inputs and you typed an incredibly long reply about design patterns and such. To be clear, Zac wasn’t implying that the internal behavior of the cache had anything at all to do with the behavior of scopes. He was just providing some contextual information about how the caching works, probably because he thought it would be useful to you. But it was unrelated to how scopes work.

It is. So the good news is you’re correct there. The confusing part for me was that no one ever suggested otherwise, so wasn’t sure why you even thought that may be the case.

I’m
 not really sure what you mean by that (and yes, I do know what Schrödinger’s cat is :wink: ). The scope behavior has been explained. The Ortus Redis extension bug has been fixed (and was related to a connection timeout causing an internal reference in the Redis SDK to become null) and the Adobe bug we’ll probably never know about since it’s closed source :laughing:

Thanks for clarifying, I think I understand what what you mean now. And yes, it is a reasonable expectation that code like this

var foo = functionThatReturnsAValue();

will result in the foo variable existing with a value in it,. That said, I’m not entirely certain why we’re covering such basic ground here. Setting variables is a thing that works in CFML. Now, in case you thought the NPE was somehow indicating that foo itself was null, that was not what was happening. The NPE was coming from deep inside the cache-specific code that just happens to run as part of functionThatReturnsAValue().

I don’t think it’s the same, even though the two statements may sound similar. Your original premise was what the scope of the variable you declared in the function was directly responsible for the NPE you were seeing-- and that was what was incorrect. Also, when we use ambigious terms such as “corresponding object in memory”, it’s not clear if we’re talking about

  • the long lived data itself (a query in this case)
  • the variable reference that the caching mechanism may hold pointing to it (for an in-process cache at least)
  • the variable reference that your function holds pointing to it.

It sounded like me that you were surmising that the scope of bullet #3 may be affecting the cached lifespan of bullet #1 and causing the NPE. But in reality, there is no connection whatsoever between any of those things.

Thanks for clarifying, because it sure seemed like you were., In fact, this entire thread is about caching.

I haven’t attributed any responsibility anywhere. I’ve simply explained the errors you saw and explained how scopes work. But at this point, I guess I’m not even clear what your question is since everything you appeared to be saying, you’re now claiming you never said :slight_smile:

That is correct, and to be very clear, again, no one said it did. So I don’t understand why you keep talking about it.

Yep, we’ll take this as progress :+1: And at this point, it may be useful to start a new, more narrow topic, if there are still any outstanding points of confusion. But it sounds like after initially misunderstanding everyone, we’re mostly on the same page, lol.

2 Likes

Hi @bdw429s ,
I shall leave it here. We have given the discussion a good go.

I came in with 2 questions. They have both been answered along the way. :slight_smile:

  1. If a query is to be cached throughout the lifetime of an application (e.g. 1 day), then using the application scope as follows is good practice:
query  function getRecords() {

    application.recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 
    return application.recordsQuery;
}
  1. The NullPointerException was due to a bug that has since been fixed. If a query is to be cached throughout the lifetime of an application (e.g. 1 day), then using the var scope as follows is fine, too:
query  function getRecords() {

    var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 
    return recordsQuery;
}

@bdw429s , @carehart , @markdrew , @Phillyun , @Zackster , I owe you an immense thank you for your explanations and for your time.

3 Likes

To be clear, no one suggested this or backed up this idea. Storing the result is not only unnecessary to place in a long-lived scope, but could cause bugs related to race conditions if there were several versions of the cache query that could come back from the cache (overwriting the same shared variable). I would actively discourse this idea :slight_smile: Either cache the query manually in the application scope, or let queryExecute() handle the caching, but pick one or the other. It’s breaking encapsulation to create an application-scoped variable in this way as part of your function where only a temporary local var is required.

@bdw429s ,
Thanks for the clarification. My apologies. In fact, I still had doubts about that, following a discussion with a colleague.

So, let me see whether I am leaving with the correct take-aways from this discussion:

The correct possibilities:

  1. Assigning the cachedWithin query to a function-local variable:
query  function getRecords()  {

    var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin=createTimeSpan(1,0,0,0)}); 
    return recordsQuery;

}
  1. Calling a function whose result happens to be the query (the result will be cached):
query  function getRecords() cachedWithin=createTimeSpan(1,0,0,0) {

var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {}); 
return recordsQuery;

}
  1. Assigning the cachedWithin query directly to Application scope:
application.recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin=createTimeSpan(1,0,0,0)}); 

// Alternatively, using one of the functions in 1. or 2.
application.recordsQuery=getRecords(); 

  1. only works when your running it every request, and I’d never that way myself

Thanks, @Zackster .

  1. only works when your running it every request, and I’d never that way myself

Precisely. I share your thinking. To my mind, you would, ideally, want to set application variables once. For example, in onApplicationStart.

I included case 3. just for completeness, imagining a use-case such as:

if (! structKeyExists(application, "recordsQuery")) {
    application.recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin=createTimeSpan(1,0,0,0)}); 

    // Alternatively, using one of the functions in 1. or 2.
    application.recordsQuery=getRecords(); 

}