avatraxiom
asked on
Query input mangled in persistent-scope CFC
I have an Application-scope object (implemented as a component).
Deep inside one object, this SQL code is contained inside a <cfquery> inside of a function:
INSERT INTO _queueTickets (
contactUser
) VALUES (
'#Arguments.contactUser.ge tUserID()# '
)
"Arguments.contactUser" is another component, one which was passed into this function as an object. It represents a staff member of our organization, and "userID" represents their user-name on our system.
"getUserID" is a function with output="no".
The value that is being inserted contains a few tabs and carriage returns.
When I call "Arguments.contactUser.get UserID()" directly, right inside of the same function but outside of the <cfquery>, I get no tabs or carriage returns. It only happens inside the <cfquery>.
What the heck is going on? It looks like the persistent object is mangling my data because it forgets that "getUserID()" has output="no".
This doesn't happen when the Application-scope object that contains the code has been loaded on this page request. That is, it doesn't happen when the object isn't persistent.
It's a similar problem to the "Persistent objects lose pagecontext, and can't write output directly" bug.
------
I should also note that "getUserID()" just returns a field called "userID" from the contactUser.
If I change the code to access "userID" directly (that is, change it to "Arguments.contactUser.use rID") there is no longer a problem.
However, I can't go and change everywhere I've done this in the code -- there's ten or fifteen thousand lines now, and no way to identify all the occurrences with a regular expression.
Also, doing so would violate the design pattern of my application, where public fields aren't supposed to be accessed directly, but only through "get" functions, like a JavaBean.
-------
-M
Deep inside one object, this SQL code is contained inside a <cfquery> inside of a function:
INSERT INTO _queueTickets (
contactUser
) VALUES (
'#Arguments.contactUser.ge
)
"Arguments.contactUser" is another component, one which was passed into this function as an object. It represents a staff member of our organization, and "userID" represents their user-name on our system.
"getUserID" is a function with output="no".
The value that is being inserted contains a few tabs and carriage returns.
When I call "Arguments.contactUser.get
What the heck is going on? It looks like the persistent object is mangling my data because it forgets that "getUserID()" has output="no".
This doesn't happen when the Application-scope object that contains the code has been loaded on this page request. That is, it doesn't happen when the object isn't persistent.
It's a similar problem to the "Persistent objects lose pagecontext, and can't write output directly" bug.
------
I should also note that "getUserID()" just returns a field called "userID" from the contactUser.
If I change the code to access "userID" directly (that is, change it to "Arguments.contactUser.use
However, I can't go and change everywhere I've done this in the code -- there's ten or fifteen thousand lines now, and no way to identify all the occurrences with a regular expression.
Also, doing so would violate the design pattern of my application, where public fields aren't supposed to be accessed directly, but only through "get" functions, like a JavaBean.
-------
-M
ASKER
There are no tabs or carriage returns in the data that "Arguments.contactUser.get UserID()" returns.
Here's an example, for clarification:
If the "userID" is "maxka", then
<cfoutput>
[#Arguments.contactUser.ge tUserID()# ]
</cfoutput>
looks like:
[maxka]
on output.
What is stored in the database looks more like:
[
maxka
]
(With brackets added to make it clear where the data starts and ends.)
Normally that would happen if "getUserID()" had output="yes" or if I just didn't specify the output attribute on the <cffunction> tag. However, I most specifically set output="no" on getUserID(), as I do in all my functions.
Also, note that when the CFC isn't in a persistent scope, then the data stored in the database looks like this:
[maxka]
(With brackets added to make it clear where the data starts and ends.)
-M
Here's an example, for clarification:
If the "userID" is "maxka", then
<cfoutput>
[#Arguments.contactUser.ge
</cfoutput>
looks like:
[maxka]
on output.
What is stored in the database looks more like:
[
maxka
]
(With brackets added to make it clear where the data starts and ends.)
Normally that would happen if "getUserID()" had output="yes" or if I just didn't specify the output attribute on the <cffunction> tag. However, I most specifically set output="no" on getUserID(), as I do in all my functions.
Also, note that when the CFC isn't in a persistent scope, then the data stored in the database looks like this:
[maxka]
(With brackets added to make it clear where the data starts and ends.)
-M
Tough trouble shooting. One thing to look into though. (I'm creating a similarly designed app myself) is that non-scoped variables are available throughout the object, no matter where they are instantiated. This is very different from other languages and caused me some problems until I figure it out. In other words, something like this:
<cffunction name="func1">
<cfset retString = "[" & func2() & "]">
<cfreturn retString>
</cffunction>
<cffunction name="func2">
<cfsavecontent variable="answer">
<cfif answer="false">
no
<cfelse>
yes
</cfif>
<cfset retString = retString & answer>
<cfreturn trim(retString)>
</cffunction>
returns
[
no
]
or something like it.
I know you're not actually including the brackets, I'm just using them to make a point too. And I have no idea why you would create two funtions like this, but again, just making a point.
I don't know if that's your problem or not, but it should definitely be checked out, as I was getting similar results to yours because of this problem. To counteract this, you have to delcare all function-local variables at the head of the function, after all arguments, before any other lines of code, and using the var keyword. e.g.
<cffunction name="func3>
<cfargument name="myArg">
<cfset var retString = "">
<cfif etc....>
</cffunction>
Just one thing to check out. Good luck.
<cffunction name="func1">
<cfset retString = "[" & func2() & "]">
<cfreturn retString>
</cffunction>
<cffunction name="func2">
<cfsavecontent variable="answer">
<cfif answer="false">
no
<cfelse>
yes
</cfif>
<cfset retString = retString & answer>
<cfreturn trim(retString)>
</cffunction>
returns
[
no
]
or something like it.
I know you're not actually including the brackets, I'm just using them to make a point too. And I have no idea why you would create two funtions like this, but again, just making a point.
I don't know if that's your problem or not, but it should definitely be checked out, as I was getting similar results to yours because of this problem. To counteract this, you have to delcare all function-local variables at the head of the function, after all arguments, before any other lines of code, and using the var keyword. e.g.
<cffunction name="func3>
<cfargument name="myArg">
<cfset var retString = "">
<cfif etc....>
</cffunction>
Just one thing to check out. Good luck.
ASKER
Thanks for that attempt, but all my function-local variables are always var'ed.
And I'm not returning any <cfsavecontent> from that function.
Remember, this _doesn't happen_ when it's not persistent. That means that there's nothing -normal- wrong with the code...
-M
And I'm not returning any <cfsavecontent> from that function.
Remember, this _doesn't happen_ when it's not persistent. That means that there's nothing -normal- wrong with the code...
-M
could you post your code for the function getUserID?
ASKER
GetUserID:
<cffunction name="getUserID" returnType="string" access="public" output="no">
<cfif NOT this.isInited>
<cfabort showError="GenericUser: You must call init() before you use a get function.">
</cfif>
<cfreturn this.userID>
</cffunction>
And here's some other functions that are involved:
this.userID gets set in "initGenericUser":
<cffunction name="initGenericUser" returnType="void" access="package" output="no">
<cfargument name="userID" type="string" required="yes">
<cfargument name="fullName" type="string" required="yes">
<cfargument name="firstName" type="string" required="yes">
<cfargument name="lastName" type="string" required="yes">
<cfargument name="myLocation" type="cfc.Location" required="yes">
<cfif NOT this.isNobody()>
<cfif Len(Arguments.userID) LT 1>
<cfabort showError="GenericUser.ini tGenericUs er(): You must specify a non-blank userID.">
</cfif>
<cfif Len(Arguments.fullName) LT 1>
<cfabort showError="GenericUser.ini tGenericUs er(): You must specify a non-blank fullName.">
</cfif>
<cfif Len(Arguments.firstName) LT 1>
<cfabort showError="GenericUser.ini tGenericUs er(): You must specify a non-blank firstName.">
</cfif>
<cfif Len(Arguments.myLocation.s erialize() ) LT 1>
<cfabort showError="GenericUser.ini tGenericUs er(): You must specify a non-empty myLocation.">
</cfif>
</cfif>
<cfif (NOT fullName CONTAINS firstName) OR (NOT fullName CONTAINS lastName)>
<cfthrow message="For GenericUser.init(), fullName must contain both firstName and lastName." detail="firstName: #Arguments.firstName# lastName: #Arguments.lastName# fullName: #Arguments.fullName#">
</cfif>
<cfscript>
this.userID = Arguments.userID;
this.fullName = Arguments.fullName;
this.firstName = Arguments.firstName;
this.lastName = Arguments.lastName;
this.myLocation = Arguments.myLocation;
this.isInited = TRUE;
</cfscript>
</cffunction>
The only function that ever calls initGenericUser is load( ):
<cffunction name="load" returnType="cfc.Users.Staf fUser" access="public" output="no">
<cfargument name="userID" type="string" required="yes">
<cfset var returnMe = "">
<cfset var userLocation = "">
<cfset var userUnit = "">
<cfset var readUser = "">
<cfquery name="readUser" dataSource="CHDCCS">
SELECT firstName, lastName, location, deptID, email, phone1
FROM vu_StaffProfiles
WHERE UserName = <cfqueryparam CFSQLType="CF_SQL_VARCHAR" value="#Arguments.userID#" >
</cfquery>
<cfif readUser.RecordCount LT 1>
<cfthrow type="InvalidUserIDExcepti on" message="StaffUser.load(): There is no saved record for '#Arguments.userID#'.">
</cfif>
<cfobject component="cfc.Location" name="userLocation">
<cfset userLocation.initLocation( readUser.l ocation)>
<cfobject component="cfc.StaffUnit" name="userUnit">
<cfset userUnit = userUnit.load(readUser.dep tID)>
<cfobject component="cfc.Users.Staff User" name="returnMe">
<cfset returnMe.initStaffUser(Arg uments.use rID, readUser.firstName & " " & readUser.lastName, readUser.firstName, readUser.lastName, userLocation, readUser.phone1, readUser.email, userUnit)>
<cfreturn returnMe>
</cffunction>
load() is basically a de-serialization method that extracts a "StaffUser" from the database and makes it into an object.
-M
<cffunction name="getUserID" returnType="string" access="public" output="no">
<cfif NOT this.isInited>
<cfabort showError="GenericUser: You must call init() before you use a get function.">
</cfif>
<cfreturn this.userID>
</cffunction>
And here's some other functions that are involved:
this.userID gets set in "initGenericUser":
<cffunction name="initGenericUser" returnType="void" access="package" output="no">
<cfargument name="userID" type="string" required="yes">
<cfargument name="fullName" type="string" required="yes">
<cfargument name="firstName" type="string" required="yes">
<cfargument name="lastName" type="string" required="yes">
<cfargument name="myLocation" type="cfc.Location" required="yes">
<cfif NOT this.isNobody()>
<cfif Len(Arguments.userID) LT 1>
<cfabort showError="GenericUser.ini
</cfif>
<cfif Len(Arguments.fullName) LT 1>
<cfabort showError="GenericUser.ini
</cfif>
<cfif Len(Arguments.firstName) LT 1>
<cfabort showError="GenericUser.ini
</cfif>
<cfif Len(Arguments.myLocation.s
<cfabort showError="GenericUser.ini
</cfif>
</cfif>
<cfif (NOT fullName CONTAINS firstName) OR (NOT fullName CONTAINS lastName)>
<cfthrow message="For GenericUser.init(), fullName must contain both firstName and lastName." detail="firstName: #Arguments.firstName# lastName: #Arguments.lastName# fullName: #Arguments.fullName#">
</cfif>
<cfscript>
this.userID = Arguments.userID;
this.fullName = Arguments.fullName;
this.firstName = Arguments.firstName;
this.lastName = Arguments.lastName;
this.myLocation = Arguments.myLocation;
this.isInited = TRUE;
</cfscript>
</cffunction>
The only function that ever calls initGenericUser is load( ):
<cffunction name="load" returnType="cfc.Users.Staf
<cfargument name="userID" type="string" required="yes">
<cfset var returnMe = "">
<cfset var userLocation = "">
<cfset var userUnit = "">
<cfset var readUser = "">
<cfquery name="readUser" dataSource="CHDCCS">
SELECT firstName, lastName, location, deptID, email, phone1
FROM vu_StaffProfiles
WHERE UserName = <cfqueryparam CFSQLType="CF_SQL_VARCHAR"
</cfquery>
<cfif readUser.RecordCount LT 1>
<cfthrow type="InvalidUserIDExcepti
</cfif>
<cfobject component="cfc.Location" name="userLocation">
<cfset userLocation.initLocation(
<cfobject component="cfc.StaffUnit" name="userUnit">
<cfset userUnit = userUnit.load(readUser.dep
<cfobject component="cfc.Users.Staff
<cfset returnMe.initStaffUser(Arg
<cfreturn returnMe>
</cffunction>
load() is basically a de-serialization method that extracts a "StaffUser" from the database and makes it into an object.
-M
not that this helps any, but at least you know you're not alone.
http://www.houseoffusion.com/cf_lists/index.cfm?method=messages&threadid=22079&forumid=4
http://www.mail-archive.com/dev@lists.cfdeveloper.co.uk/msg05375.html
http://www.houseoffusion.com/cf_lists/index.cfm?method=messages&threadid=22079&forumid=4
http://www.mail-archive.com/dev@lists.cfdeveloper.co.uk/msg05375.html
ASKER
Indeed. They're not having exactly the same problem as I am, but it's somewhat similar. The difference is that my persistent objects seem to -lose- "output = no".
-M
-M
ASKER
Here is some sample code that generates the problem. This is an entirely self-contained example. (A "reduced testcase" in Q/A-speak)
Application.cfm
---------------
<cfapplication name="maxTestApp">
Component (/Max/testComponent.cfc)
---------
<cfcomponent output="no">
<cffunction name="insertData" returnType="void" access="public" output="no">
<cfquery dataSource="MaxDB">
INSERT INTO testTable (
testField
) VALUES (
'#getSomeData()#'
)
</cfquery>
</cffunction>
<cffunction name="getSomeData" returnType="string" access="package" output="no">
<!--- Make some space so that there will be some bad output. --->
<cfset var returnMe = "Inserting Some Data">
<cfreturn returnMe>
</cffunction>
<cffunction name="readOutData" returnType="string" access="public" output="no">
<cfset var outData = "">
<cfquery name="outData" dataSource="MaxDB">
SELECT TOP 1 testField
FROM testTable
ORDER BY entryDate DESC
</cfquery>
<cfreturn outData.testField>
</cffunction>
</cfcomponent>
Page (/Max/tester.cfm)
----
<cfif NOT IsDefined("Application.tes tC")>
<cflock scope="Application" type="Exclusive" timeout="30">
<cfset Application.testC = CreateObject("component", "Max.testComponent")>
</cflock>
</cfif>
<cfscript>Application.test C.insertDa ta();</cfs cript>
<cflock scope="Application" type="read-only" timeout="30">
<cfoutput>
<pre>
[#Application.testC.readOu tData()#]
</pre>
</cfoutput>
</cflock>
Output (On First Load)
----------------------
[Inserting Some Data]
Output (On Second Load)
-----------------------
[
Inserting Some Data]
Application.cfm
---------------
<cfapplication name="maxTestApp">
Component (/Max/testComponent.cfc)
---------
<cfcomponent output="no">
<cffunction name="insertData" returnType="void" access="public" output="no">
<cfquery dataSource="MaxDB">
INSERT INTO testTable (
testField
) VALUES (
'#getSomeData()#'
)
</cfquery>
</cffunction>
<cffunction name="getSomeData" returnType="string" access="package" output="no">
<!--- Make some space so that there will be some bad output. --->
<cfset var returnMe = "Inserting Some Data">
<cfreturn returnMe>
</cffunction>
<cffunction name="readOutData" returnType="string" access="public" output="no">
<cfset var outData = "">
<cfquery name="outData" dataSource="MaxDB">
SELECT TOP 1 testField
FROM testTable
ORDER BY entryDate DESC
</cfquery>
<cfreturn outData.testField>
</cffunction>
</cfcomponent>
Page (/Max/tester.cfm)
----
<cfif NOT IsDefined("Application.tes
<cflock scope="Application" type="Exclusive" timeout="30">
<cfset Application.testC = CreateObject("component", "Max.testComponent")>
</cflock>
</cfif>
<cfscript>Application.test
<cflock scope="Application" type="read-only" timeout="30">
<cfoutput>
<pre>
[#Application.testC.readOu
</pre>
</cfoutput>
</cflock>
Output (On First Load)
----------------------
[Inserting Some Data]
Output (On Second Load)
-----------------------
[
Inserting Some Data]
ASKER
All right. It's a ColdFusion bug. Apparently it's fixed in Red Sky.
In the mean time, the workaround is to use <cfscript> functions instead of CFML functions. You just have to be sure that those CFScript functions don't call -any- CFML functions.
-M
In the mean time, the workaround is to use <cfscript> functions instead of CFML functions. You just have to be sure that those CFScript functions don't call -any- CFML functions.
-M
have you tried this in CFMX 6.1 yet?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
But if you are just calling it then displaying it something like this :
<cfoutput>#Arguments.conta
To include those special characters, you may do it this way using the Replace() function in ColdFusion :
<cfset temp = Arguments.contactUser.getU
<cfset temp = Replace(temp,chr(10),'<br>
<cfset temp = Replace(temp,chr(9),' 
<cfoutput>#temp#</cfoutput
I hope this will give you an idea .
Goodluck!
eNTRANCE2002 :)