Best practice for cftry/cfcatch - coldfusion

In ColdFusion 8 or below, is the marked line in the right place?
<cftry>
<cfquery name="local.qry" datasource="myDatasource">
SELECT ID FROM TableName
WHERE ...
</cfquery>
<cfset local.result = local.qry.ID><!--- this line! --->
<cfcatch>
<cfset local.result = Variables.objDatabase.CatchError(cfcatch)>
</cfcatch>
</cftry>
<cfreturn local.result>

Yes. You could set it outside of the block, but why? At least this way, you'll catch any bizarre errors that might occur during assignment. It's not likely, but the query could succeed and the assignment could fail, so why not trap that potential issue?
You've already got the overhead of a try/catch, might as well add the assignment to the try block as well.

Related

Coldfusion Complex Construct

I am trying to construct a coldfusion conditional statement that looks for incremental form ID checkboxes to have been selected. All checkboxes are defined as Component[component number]. I have established a loop that is looking for a URL variable that is different for every form that calls on the condition as seen below. The issue I am having is that I recieve an error when executing that tells me "Complex constructs are not supported with function parameterexists."
Clearly it has to do with the dynamic nature of the parameterexists statement, but I do not fully know what this means. Can anyone explain this and also offer a solution? I am fairly new to coldfusion and coding, so take it easy on me.
<cfloop from="1" to="#URL.loopcounter#" index="loopvar">
<cfif parameterexists(Form.Component#loopvar#)>
INSERT INTO Results (MP_Barcode, Reworked, Reworked_By)
VALUES ('#Form.MontaplastBarcode#', 'YES', '#URL.BadgeNumber#')
</cfloop>
<cfoutput>
<p class="success">YOUR REWORK HAS BEEN SUBMITTED SUCCESSFULLY.</p>
</cfoutput>
<cfelse>
<p class="error">NO REWORK WAS SUBMITTED. NO COMPONENTS SELECTED.</p>
</cfif>
Depending on the form that calls on this action, the URL loopcounter variable could range from 1 to 50.
To answer the question, there are several ColdFusion functions that won't allow you to create a dynamic name before the function evaluates it. parameterExists() was one of those. Both isDefined() and structKeyExists() will allow dynamic variables. So will the member function of structKeyExists() > structName.keyExists("theKey").
Again, if you are new to ColdFusion, I'd simply pretend you never saw parameterExists(). I believe it has been listed as "deprecated" since CF 4.5 or somewhere around there. That's almost 20 years ago. That function has actually become somewhat of a joke about how Adobe never really throws away their trash.
As I pointed out above, I'd get rid of it completely and go with structKeyExists(). I also don't know what your whole page is doing, but with the code you provided, I'd change it to something like this:
<cfloop from="1" to="#url.loopcounter#" index="loopvar">
<cfoutput>
<cfif structKeyExists(form,"Component#loopvar#")>
<!--- SANITIZE INPUTS --->
<cfset inMontplastBarcode = sanitizingFunction(FORM.MontaplastBarcode)>
<cfset inBadgeNumber = sanitizingFunction(URL.BadgeNumber)>
<!--- Now use sanitized inputs in query with queryparams --->
<cfquery name="InsertStuff" datasource="myds">
INSERT INTO Results (MP_Barcode, Reworked, Reworked_By)
VALUES (
<cfqueryparam value="#inMontaplastBarcode#" cfsqltype="cf_sql_varchar" maxlength="50">
, 'YES'
, <cfqueryparam value="#inBadgeNumber#" cfsqltype="cf_sql_varchar" maxlength="20">
)
</cfquery>
</cfif>
</cfoutput>
</cfloop>
In your database, Reworked should be a boolean datatype. It appears that it may be a 'Yes' or 'No' string. A true boolean will be a) smaller and b) easier to validate. In the cfqueryparams, if you are using a cf_sql_varchar datatype, make sure you set an appropriate max length. You'll need to look at the available CF datatypes and see how they match up to your database datatypes. (Also see https://cfdocs.org/cfqueryparam)
For your sanitizingFunction() that you'll use to sanitize your input variables, you'll want to write a function that will follow through the steps to clean up your variables to strip out unsafe characters or other things you don't want. That is an entirely different, extremely large topic all on its own.
In your form, name your checkboxes simpler. Like reworked01 through reworked50.
On the action page use cfparam to default them to zero (since html forms don't post unchecked boxes):
<cfloop from="1" to="#url.loopCounter#" index="i">
<cfparam name="form.reworked#numberFormat(i, 00)#" default="0">
</cfloop>
Then instead of fumbling with whether or not a variable exists, you can instead look for the value:
<cfloop from="1" to="#url.loopCounter#" index="i">
<cfif evaluate("form.reworked"&i) eq 1>
<!--- some logic here --->
<cfelse>
<!--- some other logic here --->
</cfif>
</cfloop>

cftransaction will only run first cfquery, skips over second

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.

ColdFusion having multiple cfqueries in cffunction

I am trying to write a function for a survey where it pulls questions from a database. The catch is that there are both active and unactive questions. I need older questions to show up when someone views the results from an old survey.
Here is the code I am trying within a CFC:
<cffunction name="getAllQuestions" access="public" returntype="query">
<cfargument name="survey" default=0>
<cfif len(#survey#) gt 0>
<cfquery name="getsdate" datasource="blah.database">
select * from past_survey
where survey_id = #survey#
</cfquery>
<cfreturn getsdate>
</cfif>
<cfquery name="getquestions" datasource="blah.database">
select * from pool_questions
<cfif len(#survey#) eq 0>
where active_flag='Y'
<cfelse>
where <cfqueryparam value="#dateformat
(getsdate.survey_date, "yyyy/mm/dd")#"> BETWEEN start_date AND
end_date
</cfif>
order by qtn_nb
</cfquery>
<cfreturn getquestions>
</cffunction>
#survey# is the survey id which is generated by the form. What I am trying to do is that if survey has a value to run query getsdate. Then the second query would run no matter if survey has a value or not. If there is not value it should pull all active questions. If there is a value then it should check if the survey date is between the start date and end date for past questions.
Any advice on how to make this work would be greatly appreciated. Thank you in advance!
<cffunction name="getAllQuestions" access="public" returntype="struct">
<cfargument name="survey" required="true" default="0" type="numeric">
<cfset var qryReturn = ""> <!---Always var scope your variables to prevent them from leaking to other functions --->
<cfset var structReturn = structNew()>
<cfset structReturn.pastSurvey = "">
<cfset structReturn.surveyQuestions = "">
<cfif survey GT 0>
<cfquery name="qryReturn" datasource="blah.database">
SELECT *
FROM past_survey
<!--- Always cfqueryparam to prevent SQL injection attacks & also always reference the scope to prevent confusion --->
WHERE survey_id = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.survey#">
</cfquery>
<cfset structReturn.pastSurvey = qryReturn>
<cfelse>
<cfquery name="qryReturn" datasource="blah.database">
SELECT *
FROM pool_questions
<cfif arguments.survey EQ 0>
WHERE active_flag = 'Y'
<cfelse>
WHERE <cfqueryparam value="#dateformat
(getsdate.survey_date, "yyyy/mm/dd")#"> BETWEEN start_date AND
end_date
</cfif>
ORDER BY qtn_nb
</cfquery>
<cfset structReturn.surveyQuestions = qryReturn>
</cfif>
<cfreturn structReturn>
</cffunction>
You probably should be doing this in two separate functions, but I will attempt to answer your question.
My code will return a struct of queries (you can change to an array if you prefer) that returns a past survey and the survey questions
Note: In your example code, you have a few bad practices.
You are checking the length of the survey value rather than checking the value itself.
If you want to ensure that survey always has a value regardless of if it is passed or not, set requried=true and give it a default value.
Use cfqueryparam to prevent sql injection attacks
Any variables created in the function need to be var scoped to prevent them from leaking to other cffunctions in the same cfcomponent. I always do this at the top. Yes, even the name you give a cfquery needs to be var scoped.
Since you are doing a return after your first query, if the survey value is greater than 0 it will never get to the second query where it has the date check.
I see the following problems you need to address.
First, your survey argument has a default value of 0 and you are doing conditional logic on the length of it. The length of "0" is 1, so that condition will always return true.
Next, you state that you want the 2nd query to run whether the first one runs or not, but you refer to a value from the 1st query in the 2nd one. That means if the 1st query does not run, the 2nd one will crash due to an undefined variable.
Next, dateformat returns a string. Applying it the way you do in the 2nd query is at best unnecessary, and at worse, and indication that you are storing the start and end dates in pool_questions as strings. If you are attempting to strip out the time portion of the datefield in the first query, ColdFusion has a cast() function for that.
Also, scope your variables. ie - instead of <cfif len(survey), do this <cfif len(arguments.survey).
Also, var your local variables. In this case, it's the names of your two queries.
That should get you started.

Is it possible to have dynamically generated query names in ColdFusion?

What I am trying to do is
<cfloop array="#LOCAL.someArray" index="LOCAL.aString">
<cfset LOCAL.queryName = "uniqueQueryName_" & LOCAL.aString />
<cfquery name="#LOCAL.queryName#" datasource="db" cachedwithin="#CreateTimeSpan(1,0,0,0)#">
SELECT count(*) AS c FROM someTable
</cfquery>
<cfdump var="#LOCAL.queryName#" />
</cfloop>
is this possible, or is there a better way to do it?
Edit
This works with <cfloop query="LOCAL.queryName"> but not when I try to do <cfset ArrayAppend(LOCAL.returnArray, LOCAL.queryName.c) />
There is no need to use evaluate() to do this, and one shouldn't (so I've down-voted that answer, sorry).
All you need to do is use associative array notation:
<cfdump var="#local[qname]#">
If one wants to access a column of that query, it's:
#local[qname][columnName]#
And for a specific cell:
#local[qname][columnName][rowNumber]#
There are very very very few situations in which evaluate() is the correct answer to anything. One cannot rely on the Adobe docs because - unfortunately - an awful lot of it was not written by very experienced ColdFusion developers.
You can dump the query, and I imagine also access it by doing something like this:
<cfloop list="q1,q2,q3" index="qname">
<cfquery name="#qname#" datasource="dsn">
SELECT * from some_table;
</cfquery>
<cfdump var="#Evaluate('#qname#')#" />
</cfloop>
The Evaluate function is what allows you to do what you want.

Building a query param dynamically

Hi I am trying to build a query parameter dynamically, and am getting an error, i am using the code below,
<cfset featQuery="">
<cfloop list="#arguments.uid_features#" index="x">
<cfif x neq "0">
<cfif Len(featQuery) gt 0>
<cfset featQuery = featQuery& " AND ">
</cfif>
<cfset featQuery = featQuery & 'uid_prodf_featid = <cfqueryparam cfsqltype="CF_SQL_INTEGER" value="' & x & '">'>
</cfif>
</cfloop>
I get this error message from coldfusion;
[Macromedia][SQLServer JDBC Driver][SQLServer]Incorrect syntax near '<'.
If i look at the output, it looks correct, but normaly using cfquerypram, you just get (param1), uid_prodf_featid=(param1) in the error message it displays the following;
uid_prodf_featid = <cfqueryparam cfsqltype="CF_SQL_INTEGER" value="5">
Jason
You can't really build and execute CFML dynamically like you're attempting to do. It looks to me like you're trying to build a SQL query outside of a cfquery tag context; this would be fine, except for your need to parameterize it. If possible, change your code to run within a cfquery tag pair:
<cfquery...>
SELECT * FROM tableFoo
<cfif ListLen(arguments.uid_features)>
WHERE uid_prodf_featid IN (<cfqueryparam value="#arguments.uid_features#" list="true" cfsqltype="CF_SQL_INTEGER">)
</cfif>
</cfquery>
Also, as you can see I've changed your query structure a bit - you had a lot of code to do something that is much more easily accomplished as I show above.
edit
I see that you actually are doing AND operations with each item in your uid_features list... I have a hard time imagining there being a valid logical reason for that (rather than OR), but if so, my example won't work for that - instead change it back to a series of AND conditions within the loop.