I'm trying to write to two different tables, both in the same database. In one CFTRANSACTION with two CFQUERY, the first CFQUERY will INSERT properly but the second (also INSERT) is just skipped over. No errors are thrown, I can see the data in the first table, and commenting out the first INSERT will allow the second to go through as desired.
A simplified version of my code is:
<cffunction name="insertReport">
<cfset var strReturn="">
<cftransaction>
<cftry>
<cfquery name="updateTable1" datasource="DB1">
...
</cfquery>
<cfquery name="UpdateTable2" datasource="DB1">
...
</cfquery>
<cfcatch type="any">
<cfset errMsg = "#cfcatch.Message#">
</cfcatch>
</cftry>
<cfif trim(errMsg) eq ''>
<cftransaction action="commit">
<cfelse>
<cftransaction action="rollback">
<cfset strReturn = "Error: #errMsg#.">
</cfif>
</cftransaction>
<cfreturn strReturn>
</cffunction>
This is probably something really simple, but I'm stuck. Any help is appreciated.
Update
I just tried the below code on CF11 and it worked fine. The only error I received was that errMsg was undefined, which it isn't in your code until the <cfcatch> occurs. I defined errMsg and re-ran -- it was successful.
<cffunction name="insertReport">
<cfset var strReturn="">
<cfset errMsg = "">
<cftransaction>
<cftry>
<cfquery name="updateTable1" datasource="DS1">
INSERT INTO ...
</cfquery>
<cfquery name="UpdateTable2" datasource="DS1">
INSERT INTO ...
</cfquery>
<cfcatch type="any">
<cfset errMsg = "#cfcatch.Message#">
</cfcatch>
</cftry>
<cfif trim(errMsg) eq ''>
<cftransaction action="commit">
<cfelse>
<cftransaction action="rollback">
<cfset strReturn = "Error: #errMsg#.">
</cfif>
</cftransaction>
<cfreturn strReturn>
</cffunction>
<cfoutput>#insertReport()#</cfoutput>
Regarding multiple datasources
According to this Adobe forum and this SO post, you must commit changes to each datasource before continuing to the next.
According to the SO post, this will work:
<cftransaction action="begin">
<cfquery datasource="foo">
select * from foo_test
</cfquery>
<cftransaction action="commit"><!-- Commit before switching DSNs --->
<cfquery datasource="bar">
select * from bar_test
</cfquery>
</cftransaction>
Note that if you can access your data via one datasource using three part names, (e.g. fooDB.dbo.table ) you can write to different databases in one <cftransaction>
Thanks to everyone who helped. It turns out the problem was the fact that we're using CFQUERYPARAMs in the CFQUERY instead of hard-coding the values, and they were throwing Null Pointer exceptions.
It seems like those CFQUERYPARAM elements would write to the database properly when we only had one CFQUERY, but still threw that Null Pointer exception that would skip over the second CFQUERY. Because the Null Pointer only threw a "#cfcatch.type#" value, not "#cfcatch.Message#" or "#cfcatch.detail#" that our error-checking was looking for, we didn't flag the problem.
We're looking unto using jTDS now to see if that will solve the issue.
Related
Recently, a security risk company we hired identified a SQL injection risk for a specific parameter of a function in our Coldfusion application. The argument is correctly captured as a cfargument:
<cfargument name="status" required="false" type="string">
and also correctly paramed as a cfprocparam:
<cfif isDefined('ARGUMENTS.status') AND ARGUMENTS.status NEQ "">
<cfprocparam type="in" value="#ARGUMENTS.status#" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
<cfelse>
<cfprocparam type="in" null="yes" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
</cfif>
And then it's used in the stored proc this way, building a where clause for a SQL query:
if (len(#search_status) > 0)
begin
set #strWhere = #strWhere + ' AND e1.cps_txt LIKE '+ '''%'+#search_status+'%'''
end
Of course, paraming to make sure the value is a varchar can't prevent a string of malicious code being entered and passed. This seems like it could easily be a larger problem, too, since it's far from the only place in our application where string arguments are passed to cfc functions and then used in SQL queries.
I'm wondering if the best approach to remedy the security risk in this one case is to compare the argument's value to all of the possible expected values (there's about twenty of them, stored in a table), and if it doesn't match one of them, then to throw an error, or just pass to the stored proc as null.
Had to tackle this at a previous employer. The trick is to pass true or false to the null argument and to avoid writing all this conditional code that calls different versions of the procparam with or without the attribute. Just always have the attribute and put the conditional logic in the value.
<cfprocparam type="in"
dbvarname="#search_status"
cfsqltype="cf_sql_varchar"
value="#ARGUMENTS.status#"
null="#!(structKeyExists(arguments, "status") AND (len(arguments.status) NEQ 0))#">
Depending on the complexity of the condition, you can simply pass it as a variable.
<cfset statusNull = true>
<cfif structKeyExists(arguments, "status") AND (len(arguments.status) NEQ 0)>
<cfset statusNull = false>
</cfif>
<cfprocparam type="in"
dbvarname="#search_status"
cfsqltype="cf_sql_varchar"
value="#ARGUMENTS.status#"
null="#statusNull#">
And I'd like to reiterate the comment from SOS that mentions dynamic SQL. We also eliminated dynamic SQL from thousands of stored procedures and replaced it with the conditional check from his linked message. This is so much easier to read and debug than dealing with acres of string concatenation code.
WHERE ( #OrderId IS NULL OR tblOrder.OrderId = #OrderId )
AND ( #OrderCustomer IS NULL OR tblOrder.OrderCustomer = #OrderCustomer )
I would also avoid adding a query just to look up valid values for status before passing the argument to the this function/proc. That just adds overhead to you system for every call to this process.
I think you pretty much answered your own question. Here's an approach I've used in the past to handle validations using accepted values stored in a small lookup table. Do this before calling your stored procedure. I'll use generic names since I don't know your table and column names.
<!--- Validation code prior to running stored proc --->
<cfif isDefined('ARGUMENTS.status') AND ARGUMENTS.status NEQ "">
<!--- Query acceptable values --->
<cfquery name="qValidateSearchStatus" datasource="myDsn">
select
search_status
from
lookup_table
</cfquery>
<cfif listFind(valueList(qValidateSearchStatus.search_status), ARGUMENTS.status)>
<cfset statusValid = true>
<cfset statusNull = false>
<cfelse>
<cfset statusValid = false>
<cfset statusNull = false>
</cfif>
<cfelse>
<cfset statusValid = true>
<cfset statusNull = true>
</cfif>
<cfif statusValid>
<!--- Call stored procedure --->
<cfstoredproc procedure="myStoredProc" datasource="myDsn">
<cfif statusNull>
<cfprocparam type="in" null="yes" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
<cfelse>
<cfprocparam type="in" value="#ARGUMENTS.status#" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
</cfif>
</cfstoredproc>
<cfelse>
<!--- Process error handling --->
</cfif>
Preamble: So we have some working applications that we need to move over to a new server because we are retiring an older one, and as a result I've had to install a new instance of CF.
This application works fine on the older server, which is running ColdFusion version "9,0,0,251028" Standard edition (via ColdFusion Administrator).
On the newer server, I'm using CF 2016 version 2016.0.0.298074 Developer edition (It's the first thing that popped up on a google search so I went with it).
Now the issue: there is a piece of code giving an error that says:
Complex object types cannot be converted to simple values.
The expression has requested a variable or an intermediate expression result as a simple value. However, the result cannot be converted to a simple value. Simple values are strings, numbers, boolean values, and date/time values. Queries, arrays, and COM objects are examples of complex values.
The most likely cause of the error is that you tried to use a complex value as a simple one. For example, you tried to use a query variable in a cfif tag.
The error occurred in G:/Gumbo/components/modules/resource/ResourceAdmin.cfc: line 282
Called from G:/Gumbo/admin/modules/resource/action.cfm: line 34
Called from G:/Gumbo/admin/action.cfm: line 19
281 cfloop query="getseq">
282 <cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
283 </cfloop>
The offending line is 282. The code in question:
<cfloop query="getseq">
<cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
</cfloop>
From my research, I've noted that apparently cfloop doesn't work with query parameters on some versions of ColdFusion, but what I don't understand is why a NEWER version is therefore causing me this error.
So my question is:
Is there a way to reacquire this older version of CF somehow? Keep in mind I have the CF9 source folder on my older computer, but I wasn't sure if there's a way I can take the source files and move them over or manually install it or the ins and outs of that. Could it be as easy as copying the older source files into the new CF source on the newer server?
What would be an easy alternative to change the code mentioned? I am completely unfamiliar with CF as this is an older project that I inherited upon taking this job. I'd prefer to get the exact version on the newer system, but changing the code is the only viable alternative.
Any insight would be appreciated.
EDIT:
Here is the entire offending function:
<cffunction name="updateResource" access="public" output="false" displayname="Update a Resource">
<cfset VARIABLES.dateUpdated=DateAdd("d", 0, arguments.dateUpdated)>
<cfquery datasource="#this.datasource#">
update md_rlm_resource
set published=<cfqueryparam cfsqltype="cf_sql_tinyint" value="#arguments.published#">,
resourceName=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceName#">,
resourceNumber=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceNumber#">,
resourceAuthor=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceAuthor#">,
resourceFile=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceFile#">,
dateUpdated=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#VARIABLES.dateUpdated#">,
shortDescription=<cfqueryparam cfsqltype="cf_sql_longvarchar" value="#arguments.shortDescription#">
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfquery name="getseq" datasource="#this.datasource#">
select displaySeq, resourceCategoryID
from md_rlm_resourcecategoryrel
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfquery datasource="#this.datasource#">
delete from md_rlm_resourcecategoryrel
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfif IsDefined("arguments.resourceCategoryIDs")>
<cfset resourceCategoryID = ArrayNew(1)>
<cfset resourceCategoryID = ListToArray(arguments.resourceCategoryIDs)>
<cfif #ListLen(arguments.resourceCategoryIDs)# gt 1>
<cfset tmp1 = #ArrayLen(resourceCategoryID)#>
<cfelse>
<cfset tmp1 = "1">
</cfif>
<cfloop INDEX="idx" FROM="1" TO="#tmp1#">
<cfset newseq = 1>
<cfif #tmp1# gt 1>
<cfset temp=resourceCategoryID[idx]>
<cfelse>
<cfset temp=resourceCategoryID>
</cfif>
<cfloop query="getseq">
<cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
</cfloop>
<cfquery datasource="#this.datasource#">
insert into md_rlm_resourcecategoryrel
(resourceCategoryID, resourceID, displaySeq)
values
(
<cfif #tmp1# gt 1>
<cfqueryparam CFSQLTYPE="cf_sql_integer" VALUE="#resourceCategoryID[idx]#">,
<cfelse>
<cfqueryparam CFSQLTYPE="cf_sql_integer" VALUE="#resourceCategoryID#">,
</cfif>
<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.resourceID#">,
<cfqueryparam cfsqltype="cf_sql_float" value="#newseq#">)
</cfquery>
</cfloop>
</cfif>
<cfquery datasource="#this.datasource#">
DELETE FROM md_rlm_resourceregionrel
WHERE resourceID=<cfqueryparam value="#arguments.resourceid#" cfsqltype="cf_sql_integer">
</cfquery>
<cfloop index="regionele" list="#arguments.regionID#" delimiters=",">
<cfquery datasource="#this.datasource#">
INSERT INTO md_rlm_resourceregionrel (resourceID, regionID)
VALUES (<cfqueryparam value="#arguments.resourceid#" cfsqltype="cf_sql_integer">, #regionele#)
</cfquery>
</cfloop>
</cffunction>
From this:
<cfset resourceCategoryID = ListToArray(arguments.resourceCategoryIDs)>
...
<cfif #tmp1# gt 1>
<cfset temp=resourceCategoryID[idx]>
<cfelse>
<cfset temp=resourceCategoryID><!--- temp is now an array --->
</cfif>
temp (terrible variable name, btw) could be an array.
And later you do this:
<cfif getseq.resourceCategoryID IS temp>
You cannot compare arrays with the IS operator: IS compares simple values. That's why you're seeing the error.
As an aside, you're not var-ing any of your variables in that code, which is fairly poor form, and has potential for "unexpected behaviour" in your code.
I have a query-of-queries that performs a LIKE condition on a variable string:
When the variable contains a single word that includes a single quote, some results are returned, but not all:
<cfset _myVar = "Women's" />
<cfquery name="_qData" dbtype="Query">
SELECT
ID
FROM MyQoQ
WHERE NAME LIKE '%#_myvar#%'
OR DESCRIPTION LIKE '%#_myvar#%'
</cfquery>
When the variable contains more than one word, and one of those words includes a single quote, no records are returned:
<cfset _myVar = "Women's Initiative" />
<cfquery name="_qData" dbtype="Query">
SELECT
ID
FROM MyQoQ
WHERE NAME LIKE '%#_myvar#%'
OR DESCRIPTION LIKE '%#_myvar#%'
</cfquery>
I've tried PreserveSingleQuotes() as well as wrapping the varaibles with CFQUERYPARAM, but, to no avail - I get the same results.
Is there a way to make this work?
Adding in a repro case
<cfset myQuery = queryNew('hello')>
<cfset queryAddRow(myQuery,5)>
<cfset querySetCell(myQuery,"hello","what up",1)>
<cfset querySetCell(myQuery,"hello","what's up",2)>
<cfset querySetCell(myQuery,"hello","what's up friends",3)>
<cfset querySetCell(myQuery,"hello","what u",4)>
<cfset querySetCell(myQuery,"hello","what",5)>
<cfdump var="#myQuery#">
<cfquery name="res" dbtype="query">
SELECT *
FROM myQuery
WHERE hello LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="%$what's up%">
</cfquery>
<cfdump var="#res#">
Railo 4.1.1.009 - returns both results (rows 2 and 3)
ColdFusion 10,0,13,287689 - returns no results
If I change my SQL to
WHERE hello LIKE '%what''s up%'
I still get no results
aarh!! a classic case of royal pain in the rear.
To solve this, you have to add an extra ' to every ' in your search term, there by escaping it.
<cfset myQuery = queryNew('hello')>
<cfset queryAddRow(myQuery,5)>
<cfset querySetCell(myQuery,"hello","what up",1)>
<cfset querySetCell(myQuery,"hello","what's up",2)>
<cfset querySetCell(myQuery,"hello","what's up friends",3)>
<cfset querySetCell(myQuery,"hello","what u",4)>
<cfset querySetCell(myQuery,"hello","what",5)>
<cfdump var="#myQuery#">
<cfset x = "what's up" />
<cfquery name="res" dbtype="query">
SELECT *
FROM myQuery
WHERE hello LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="#replace(x, "'", "''", "all")#%">
</cfquery>
<cfdump var="#res#">
if you do this directly, as you mentioned,
WHERE hello LIKE '%what''s up%'
the parser is going bonkers. But, if you pass the value via a function return value, the run time assignment of values via a variable somehow makes the parser happy.
I remember using such tricks in sending multiple SQL statements delimited with ';' in cfquery. Directly writing
"DECLARE x NUMBER; SELECT 2 INTO x FROM DUAL;"
inside cfquery fails, but assigning them to a string and then sending the string as a return value for any string manipulator function (lcase, ucase, etc) worked perfectly.
Note: the problem is solved, but if my explanation and approach is diff, feel free to correct and comment.
I'm running ColdFusion 10 u13.
Modifying your repo code this seemed to work:
<cfset myQuery = queryNew('hello')>
<cfset queryAddRow(myQuery,5)>
<cfset querySetCell(myQuery,"hello","what up",1)>
<cfset querySetCell(myQuery,"hello","what's up",2)>
<cfset querySetCell(myQuery,"hello","what's up friends",3)>
<cfset querySetCell(myQuery,"hello","what u",4)>
<cfset querySetCell(myQuery,"hello","what",5)>
<cfdump var="#myQuery#">
<cfquery name="res" dbtype="query">
SELECT *
FROM [myQuery]
WHERE [hello] LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="%what''s up%">
</cfquery>
Note the double single quotes in the <cfqueryparam> tag. Like Dan I would have thought that the <cfqueryparam> tag would have taken care of this for you automatically. Perhaps this is a bug in QoQ?
I have set of inserts that are wrapped in a <cftransaction> block, and I am getting a error and the insert is being rolled back.
Here is the code in question stubbed for space:
<cffunction name="InsertTCUV" access="public">
<cfargument name="vehicle required="true" type="xml" />
//Parsing the xml document here
<cftransaction>
<cfquery name="TCUVinsert datasource="mydb">
INSERT INTO tcuv
VALUES(...)
<cfquery>
<cfquery name="qLatestTCUVID" datasource="mydb">
SELECT TOP 1 tcuv_id FROM dbo.tcuv ORDER BY tcuv_id DESC
</cfquery>
<cfset curTCUVID = qLatestTCUVID.tcuv_ID>
<cfset optionsResult = insertOptions(curTCUVID,vehicle>
<cfset imagesResult = insertImages(curTCUVID,vehicle)>
<cfset standardFeaturesResult = insertStandardFeatures(curTCUVID,vehicle
</cftransaction>
</cffunction>
<cffunction name="insertOptions" access="private">
<cfargument name="TCUVID required="true type="numeric" />
<cfargument name="vehicleInfo" required="true" type="xml" />
<cfset var result = "good">
<cftry>
<cfset optionNode = xmlSearch(arguments.vehicleInfo[1], "p:RemarketingOption">
<cfloop index="i" from="1" to="#arrayLen(optionNode)#">
<cfset optionNodeNotes = XmlSearch(optionNode[#i#], "p:OptionNotes")>
<cfset optionNotes = "">
<cfloop index="j" from="1" to="#ArrayLen(optionNotesNodes)#">
<cfoutput>
<cfset optionNotes = optionNotes & " " & #optionNotesNodes[j].xmlText#>
</cfoutput>
</cfloop>
<cfquery name="insertOptions" datasource="mydb">
INSERT INTO dbo.tcuv_options
VALUES (
<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.TCUVID#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionID")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionTypeCode")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionShortDescription")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#optionNotes#">
)
</cfquery>
</cfloop>
<cfcatch type="database">
//dumping cfcatch.* information
<cfset result = "error"
</cfcatch>
</cftry>
<cfreturn result>
</cffunction>
This whole thing is in a loop on the calling page, and everything works fine the first time through the loop. One the second pass, the TCUVInsert works, but I get to the insertOptions function, a coldfusion error is thrown saying that, variable insertOptions is undefined, and when I get the database, the second row isn't there, which tells me there was an error and the insert rolled back. So, there is an error with the insert of the options, and coldfusion isn't giving me the right error to diagnose it. So either I have to look in the database logs, which apparently are not setup, or try to extract the error from the cftransaction block, which I'm not sure how to do.
coldfusion 9, sql server 2008 r2
Any thoughts?
<cffunction name="insertOptions" access="private">
You are overwriting the function by using the same name for a query variable:
<cfquery name="insertOptions" datasource="imports">
Interestingly it is all because the query name was not var scoped. Functions are stored in the variables scope of the component. So by failing to localize the query name, you end up overwriting the function stored in variables.insertOptions when you run the query. Because insert statements do not return a resultset, that variable ends up being undefined. Hence the error. In this case the solution is to either scope the query name, or better yet remove it entirely (since it is not populated anyway).
Just one more reason to always var/local scope function variables - yes, query names too!
I am trying to set a variable dynamically into a structure via CFLOOP. I have read Ben Nadal's blog post but cant seem to get the assignment correct. I would like to use dot notation and make the VIN a sub structure of values.
Here is my code:
<cfloop query="VINs">
<cfquery name="carsQue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
WHERE (VIN = #VIN#)
</cfquery>
<cfset carsStruct= StructNew()>
<cfset carsStruct.[VIN].MAKE = '#carsQue.MODEL#'>
<cfset carsStruct.[VIN].MODEL = '#carsQue.MAKE#'>
</cfloop>
Any guidance would be greatly appreciated,
Thanks
Running a query inside a loop is almost always a bad idea. In your case, a better option would be:
<cfif ListLen(valuelist(vins.vin)) gt 0>
<cfquery name=CarsQue datasource = "carsData">
select vin, model, make, etc
from carsDB
where vin in (<cfqueryparam cfsqltype="cf_sql_varchar"
value="#valuelist(vins.vin)#" list="true">)
</cfquery>
<cfset carsStruct = StructNew()>
<cfloop query="carsQue">
code for your struct
</cfloop>
<cfelse>
code for vins query returning no data
</cfif>
Better yet would be to get all the data with one query. You didn't provide enough information to determine if this was possible, but it often is.
Create a structure outside loop and and setting variable within loop can solve the problem. in a current scenario each time loop run its create a new structure.
you can do some thing like this
<cfset carsStruct= StructNew()>
<cfloop query="VINs">
<cfquery name="carsQue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
WHERE VIN = <cfqueryparam cf_sql_type="cf_sql_varchar" value="#VINs.VIN#">
</cfquery>
<cfset carsStruct[VINs.VIN].MAKE = carsQue.MODEL>
<cfset carsStruct[VINs.VIN].MODEL = carsQue.MAKE>
</cfloop>
Based on the limited information you've given you should be able to run one query and loop through that to add to your struct.
<cfset carsStruct= {}> //new struct
<cfif VINs.RecordCount> //your VINs query has records
<cfquery name="carsQueue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
// Quoted list of all your VINs. cfqueryparam prevents against SQL injection
WHERE VIN IN (<cfqueryparam cf_sql_type="cf_sql_varchar" value="#ValueList(VINs.VIN)#" list="true">
</cfquery>
<cfloop query="carsQueue">
<cfset carsStruct.[carsQueue.VIN].MAKE = carsQueue.MODEL>
<cfset carsStruct.[carsQueue.VIN].MODEL = carsQueue.MAKE>
</cfloop>
<cfelse>
// if VINs query return nothing a blank struct will be returned.
//You do NOT need this <cfelse> unless you are returning something when the query is blank
</cfif>