Link to home
Start Free TrialLog in
Avatar of avatraxiom
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.getUserID()#'
)


"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.getUserID()" 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.userID") 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
Avatar of Renante Entera
Renante Entera
Flag of Philippines image

Yah! When you are storing that data in the database, tabs and carriage returns are included.

But if you are just calling it then displaying it something like this :

   <cfoutput>#Arguments.contactUser.getUserID()#</cfoutput> Then no worth since tabs and carriage returns are not included.

To include those special characters, you may do it this way using the Replace() function in ColdFusion :

   <cfset temp = Arguments.contactUser.getUserID() >
   <cfset temp = Replace(temp,chr(10),'<br>','all')> <!--- Replacing all carriage returns with break line tag--->
   <cfset temp = Replace(temp,chr(9),'&nbsp;&nbsp;&nbsp;','all')> <!--- Replacing all tabs with specified no. of spaces --->
   <cfoutput>#temp#</cfoutput> <!--- Display the corresponding data --->

I hope this will give you an idea .

Goodluck!
eNTRANCE2002 :)
Avatar of avatraxiom
avatraxiom

ASKER

There are no tabs or carriage returns in the data that "Arguments.contactUser.getUserID()" returns.

Here's an example, for clarification:

If the "userID" is "maxka", then

<cfoutput>
[#Arguments.contactUser.getUserID()#]
</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.
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
could you post your code for the function getUserID?
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.initGenericUser(): You must specify a non-blank userID.">
                  </cfif>
                  <cfif Len(Arguments.fullName) LT 1>
                        <cfabort showError="GenericUser.initGenericUser(): You must specify a non-blank fullName.">
                  </cfif>
                  <cfif Len(Arguments.firstName) LT 1>
                        <cfabort showError="GenericUser.initGenericUser(): You must specify a non-blank firstName.">
                  </cfif>
                  <cfif Len(Arguments.myLocation.serialize()) LT 1>
                        <cfabort showError="GenericUser.initGenericUser(): 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.StaffUser" 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="InvalidUserIDException" message="StaffUser.load(): There is no saved record for '#Arguments.userID#'.">
            </cfif>

            <cfobject component="cfc.Location" name="userLocation">
            <cfset userLocation.initLocation(readUser.location)>

            <cfobject component="cfc.StaffUnit" name="userUnit">
            <cfset userUnit = userUnit.load(readUser.deptID)>

            <cfobject component="cfc.Users.StaffUser" name="returnMe">
            <cfset returnMe.initStaffUser(Arguments.userID, 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
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
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.testC")>
      <cflock scope="Application" type="Exclusive" timeout="30">
            <cfset Application.testC = CreateObject("component", "Max.testComponent")>
      </cflock>
</cfif>

<cfscript>Application.testC.insertData();</cfscript>

<cflock scope="Application" type="read-only" timeout="30">
<cfoutput>
<pre>
[#Application.testC.readOutData()#]
</pre>
</cfoutput>
</cflock>


Output (On First Load)
----------------------
[Inserting Some Data]


Output (On Second Load)
-----------------------
[
            
            
            Inserting Some Data]
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
have you tried this in CFMX 6.1 yet?
ASKER CERTIFIED SOLUTION
Avatar of GhostMod
GhostMod
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial