I could not use student_id in cfoutput with query. My argument didn't pass in getStudentAnswers function. How can I do this?
I want to create JSON and need to add question and questions' answers near exam like this:
[
{
"id": "7",
"type": "2",
"question_id": "20",
"question": "Write a summary by answering the questions below: * What is convergence? * What will be the implications of convergence on classical media? on users? * Which devices/media forms do you think will converge nowadays/in the future? * What will be the opportunities and threats emerged by the convergence?",
"question_type": "0",
"answer": "",
"name": "Assignment3ver2",
"deadline_date": "2014-11-22 00:00:00.0"
}
Functions:
<cffunction name="getQuestion" >
<cfargument name="exam_id" type="string" required="true" >
<cfargument name="student_id" type="string" required="true" >
<cfset mert="#student_id#" >
<cfquery dataSource="mobileCourse" name="questselect">
SELECT id,question,question_type FROM dbo.Questions WHERE exam_id=#exam_id#
</cfquery>
<cfoutput query="questselect">
"question_id": "#id#",
"question": "#question#",
"question_type": "#question_type#",
#getStudentAnswers(mert, id, question)#
</cfoutput>
<cfreturn />
<cffunction name="getStudentAnswers" >
<cfargument name="student_id" type="string" required="true" >
<cfargument name="question_id" type="string" required="true" >
<cfargument name="question" type="string" required="true" >
<cfquery dataSource="mobileCourse" name="questselecta">
SELECT * FROM dbo.ANSWERS WHERE question_id=#question_id# AND student_id=#student_id#
</cfquery>
<cfif #questselecta.recordcount# gte 1>
<cfoutput query="questselecta">
"answer": "#answer#",
</cfoutput>
<cfelse>
"answer": "",
</cfif>
<cfreturn />
This is not a direct answer to your question. However, even ignoring the errors mentioned in the comments, the code above is not an efficient way to get the job done. It is not necessary to run a separate query for each question in the exam. Just run a single query using a JOIN instead. Since you appear to want all questions, even ones without an answer, make it an OUTER JOIN.
Note: Never use raw parameters in a cfquery because it puts your database at risk for sql injection. Instead use cfqueryparam with whichever cfsqltype matches actual data types of your columns (see documentation matrix).
<!--- adjust cfsqltypes as needed --->
<cfquery name="yourQueryName" ....>
SELECT q.id, q.question, q.question_type, a.answer, ... other columns
FROM Questions q LEFT JOIN Answers a
ON a.question_id = q.question_id
AND a.student_id = <cfqueryparam value="#ARGUMENTS.student_id#"
cfsqltype="cf_sql_integer">
WHERE q.exam_id = <cfqueryparam value="#ARGUMENTS.exam_id#"
cfsqltype="cf_sql_integer">
</cfquery>
Once you have the query results, use it to build your result object. The exact structure depends on your application, but assuming there can be multiple questions - an array of structures would be a good example.
Be sure to properly scope all variables. All function local variables should be var/local scoped to prevent potential threading issues: that includes query names.
<!--- localize all variables --->
<cfset var yourQueryName = "">
<cfset var resultArray = "" >
<cfset var data = "" >
.... run query ....
<!--- first initialize the array --->
<cfset resultArray = []>
<!--- loop through your query and build an array of structures --->
<cfloop query="yourQueryName">
<!--- create new structure to store current question --->
<cfset data = {}>
<cfset data["question_id"] = yourQueryName.question_id>
<cfset data["question"] = yourQueryName.question>
<cfset data["answer"] = yourQueryName.answer>
... other keys ....
<!--- append the question to your array --->
<cfset arrayAppend(resultArray, data)>
</cfloop>
Finally, use serializeJSON to return the array of structures in JSON format:
<cfreturn serializeJSON(resultArray)>
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>
I have a query that gets called multiple times depending on the the variable vchrStatus
<cffunction name="getGalleriesByStatus" output="no" returntype="query">
<cfargument name="vchrStatus" type="string" required="yes">
<cfquery
name="getGalleries"
datasource="#Application.dsn#">
/// Long complicated query in here
</cfquery>
<cfset var result="#getGalleries#">
<!--- Return it --->
<cfreturn result>
</cffunction>
I tested the function with cfdump and it is outputing the desired results.
Now I want to loop over the results in a cfoutput
<cfoutput query="getGalleriesByStatus('Pending')">
But I'm getting the error: The value of the attribute query, which is currently getGalleriesByStatus('Pending'), is invalid.
<cfoutput> takes the name of the query variable (ie: a string). It does not take an expression which evaluates to a query. So you need this:
<cfset someVar = getGalleriesByStatus('Pending')>
<cfoutput query="someVar">
This is counter-intuitive, but is the case. It's also not in the docs, which sux.
I have a function which loops over a query and updates a database row for each item. After about 7000 iterations it's throwing an out of memory error - Java heap space.
Is there anything obviously wrong with this code ?
<cfloop query=loc.fixItems>
<cfset loc.count = loc.count + 1>
<cfset var categoryName = loc.fixItems.categoryName>
<cfinvoke component="Item" method="updateCode"
itemId="#loc.fixItems.itemId#" code="#loc.fixItems.newCode#"/>
<!--- Increment counter for category --->
<cfif structKeyExists(categoryMap, categoryName)>
<cfset var inc = structFind(categoryMap, categoryName) + 1>
<cfset structUpdate(categoryMap, categoryName, inc)>
<cfelse>
<cfset structInsert(categoryMap, categoryName, 1)>
</cfif>
</cfloop>
and in the update component:
<cffunction name="updateCode">
<cfargument name="itemId" type="numeric" required="yes">
<cfargument name="code" type="string" required="yes">
<cfset var loc = {}>
<cfquery name="loc.update">
update items
set code = <cfqueryparam value="#code#">
where id = <cfqueryparam value="#itemId#">
</cfquery>
</cffunction>
Don't use cfinvoke to create your Item component every iteration of your fixItems query. Create it once before that using createObject and simply call the updateCode method each time directly on the object.
The following can be done:
Change your <cfqueryparam> to use the appropriate cf_sql type. Are code and id really strings?
Don't give your <cfquery> a name. You are not keeping the result anyway. var loc doesn't help either
Bump up you memory to the JVM Addtional approach Use Java 7 and G1GC
Every 100 to 1000 iterations do a forced Garbage Collect
Update your data in bulk. XML based table variables can do this.
Make your function silent
Consider ORM on this
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 want to get a specific row in a ColdFusion Query object without looping over it.
I'd like to do something like this:
<cfquery name="QueryName" datasource="ds">
SELECT *
FROM tablename
</cfquery>
<cfset x = QueryName[5]>
But it's giving me an error saying that the query isn't indexable by "5". I know for a fact that there are more than 5 records in this query.
You can't get a row in CF <= 10. You have to get a specific column.
<cfset x = QueryName.columnName[5]>
It's been 8 years since I posted this answer, however. Apparently CF11 finally implemented that feature. See this answer.
This can now be accomplished in coldfusion 11 via QueryGetRow
<cfquery name="myQuery" result="myresult" datasource="artGallery" fetchclientinfo="yes" >
select * from art where ARTID >
<cfqueryparam value="2" cfsqltype="CF_SQL_INTEGER">
</cfquery>
<cfdump var="#myQuery#" >
<cfset data = QueryGetRow(myQuery, 1) >
<cfdump var="#data#" >
I think there is a simpler solution...
I am guessing you know your column names and only want this column or that one. Then you don't need to put the whole row in a struct. You can reference the query by row number (remember its 1 based not 0).
yourQueryName["yourColumnName"][rowNumber]
<cfoutput>
#mycontacts["Name"][13]#
#mycontacts["HomePhone"][13]#
</cfoutput>
You have to convert the query to a struct first:
<cfscript>
function GetQueryRow(query, rowNumber) {
var i = 0;
var rowData = StructNew();
var cols = ListToArray(query.columnList);
for (i = 1; i lte ArrayLen(cols); i = i + 1) {
rowData[cols[i]] = query[cols[i]][rowNumber];
}
return rowData;
}
</cfscript>
<cfoutput query="yourQuery">
<cfset theCurrentRow = GetQueryRow(yourQuery, currentRow)>
<cfdump var="#theCurrentRow#">
</cfoutput>
Hope this points you in the right direction.
I know I come back to this thread any time I Google "cfquery bracket notation". Here's a function I wrote to handle this case using bracket notation. Hopefully this can help someone else too:
<cffunction name="QueryGetRow" access="public" returntype="array" hint="I return the specified row's data as an array in the correct order">
<cfargument name="query" required="true" type="query" hint="I am the query whose row data you want">
<cfargument name="rowNumber" required="true" hint="This is the row number of the row whose data you want">
<cfset returnArray = []>
<cfset valueArray = []>
<cfset cList = ListToArray(query.ColumnList)>
<cfloop from="1" to="#ArrayLen(cList)#" index="i">
<cfset row = query["#cList[i]#"][rowNumber]>
<cfset row = REReplace(row, "(,)", " ")>
<cfset returnArray[i] = row>
<cfset i++>
</cfloop>
<cfreturn returnArray>
</cffunction>
The REReplace is optional, I have it in there to cleanse commas so that it doesn't screw up the arrayToList function later on if you have to use it.
I wanted to extract a single row from a query, and keeping the column names (of course). This is how I solved it:
<cffunction name="getQueryRow" returntype="query" output="no">
<cfargument name="qry" type="query" required="yes">
<cfargument name="row" type="numeric" required="yes">
<cfset arguments.qryRow=QueryNew(arguments.qry.columnlist)>
<cfset QueryAddRow(arguments.qryRow)>
<cfloop list="#arguments.qry.columnlist#" index="arguments.column">
<cfset QuerySetCell(arguments.qryRow,arguments.column,Evaluate("arguments.qry.#arguments.column#[arguments.row]"))>
</cfloop>
<cfreturn arguments.qryRow>
</cffunction>
Methods previously described for obtaining query data by column name and row number (variables.myquery["columnName"][rowNumber]) are correct, but not convenient for getting a full row of query data.
I'm running Railo 4.1. And this is a cool solution. Too bad this can't be done the way we would want outright to get a full row of data, but the following method allows us to get what we want through a few hoops.
When you serializeJSON(variables.myquery) it changes the query to a JSON formatted cfml struct object with two items: "Columns" and "Data". Both of these are arrays of data. The "data" array is a two-dimensional array for rows and then columnar data.
The issue is that now we have an unusable string. Then if we re-serialize it it's NOT a query, but rather usable regular struct in the format described above.
Assume we already have a query variable named 'variables.myquery'. Then look at the following code:
<cfset variables.myqueryobj = deserializeJSON(serializeJSON(variables.myquery)) />
Now you get the two dimensional array by getting this:
<cfset variables.allrowsarray = variables.myqueryobj.data />
And you get one query row array by getting this:
<cfset variables.allrowsarray = variables.myqueryobj.data[1] />
OR the last row this way:
<cfset variables.allrowsarray = variables.myqueryobj.data[variables.myquery.recordCount] />
And you can get individual column values by column order number iteration:
<cfset variables.allrowsarray = variables.myqueryobj.data[1][1] />
Now this might be slow and possibly unwise with large query results, but this is a cool solution nonetheless.
Check out the documentation for queryGetRow. It accepts a query object and an index of the row with the first row being referenced with the index of 1 (NOT 0) The index used this way is required to be a positive integer.
<cfquery name="QueryName" datasource="ds">
SELECT *
FROM tablename
</cfquery>
<!---
This would retrieve the first record of the query
and store the record in a struct format in the variable 'x'.
--->
<cfset x = queryGetRow(QueryName, 1) />
<!---
This is an alternative using the member method form of queryGetRow
--->
<cfset x = QueryName.getRow(1) />