Character dropped in BIND expression - coldfusion

I have run into an interesting situation while upgrading some legacy code from CF10 to CF2018. A query is segmented in string pieces in a webpage and passed through a CFGRID BIND statement to a query in a .cfc file.
The .CFC handles several webpages for the CFGRID. The various webpages provide input fields for filtering the data in the grid, such as searches, dates, etc.
Several of the queries use the JOIN (+) shortcut notation in the WHERE statement.
My problem is that somewhere between the CFGRID and the .CFC, the "+" is getting dropped and replaced with a space.
DisplayHistoryReceived.cfm
Conditions = "t.CPNId = pn.CPNId(+)
AND t.PartId = p.PartId
AND t.POId = po.POId(+)
AND t.LocationId = l.LocationId
AND l.BuildingId = b.BuildingId
AND t.StatusCodeId = sc.StatusCodeId(+)
AND t.TransTypesId = 1
AND po.OwnerId = o.OwnerId"
DisplayGrid-HTML.cfm
<CFGRID
BIND="cfc:#application.area#.Components.Global.DrillDown.SearchGrid({dSource},{queryFieldNames},
{tableNames},{Conditions},{SearchField#change},SearchInput#keyup},{BooleanSelect#change},{BoolField},
{DFilters},DFilterLine1},{DFilterLine2},{DFilterLine3},{DateRange},{DateAfter},{DateBefore},{OrderBy},
{GroupBy},{pSize},{cfgridsortcolumn},{cfgridsortdirection},{cfgridpage},{cfgridpagesize})"
DrillDown.cfc
<CFQUERY NAME="grd" DATASOURCE="#dSource#">
SELECT DISTINCT #Fields#
FROM #PreserveSingleQuotes(Tables)#
WHERE #PreserveSingleQuotes(Conditions)#
<!--- Used to filter based on a TEXT search --->
<cfif '#sInput#' neq ''>
AND #sField# like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#sInput#%"/>
</cfif>
[more CFIF statements follow]
The error message that I get is: [Macromedia][Oracle JDBC Driver][Oracle]ORA-00904: "SC"."STATUSCODEID": invalid identifier
But this excerpt from the error message leads me to think that something may have happened in CF2018 instead:
WHERE t.CPNId = pn.CPNId( )
AND t.PartId = p.PartId
AND t.POId = po.POId( )
AND t.LocationId = l.LocationId
AND l.BuildingId = b.BuildingId
AND t.StatusCodeId = sc.StatusCodeId( )
AND t.TransTypesId = 1
AND po.OwnerId = o.OwnerId
Any ideas, besides rewriting all the queries with standard JOINS? There are several pages scattered throughout the site that use this.

Related

Issue with ColdFusion Query of Query after upgrading ColdFusion server from CF9 to CF21

I cannot figure this out. My company recently upgraded our ColdFusion Server from CF9 to CF2021. One of my pages uses queries of queries to run. Prior to the upgrade the page worked fine. However after the upgrade the query of query where statement is not returning any results. Even though the original query has the data. The data is coming from DB2 data source in case that changed with the new version.
<cfquery name = "query_data_sample" dbtype="query">
SELECT
SAMPLE_DATE
, SAMPLE_NBR
, SAMPLE_MISC_NBR
, SUM (RECD_QTY) AS TTL_RECD_QTY
, SUM (MISC_QTY_1) AS TTL_MISC_QTY_1
, SUM ( 1 ) AS TTL_COUNT
FROM original_query_result
WHERE 1=1
AND SAMPLE_DATE = <cfqueryparam value = "#original_query_result.SAMPLE_DATE#" cfsqltype = "CF_SQL_DATE">
AND SAMPLE_NBR = <cfqueryparam value = "#trim(original_query_result.SAMPLE_NBR)#" cfsqltype = "CF_SQL_CHAR">
AND SAMPLE_MISC_NBR = <cfqueryparam value = "#trim(original_query_result.SAMPLE_MISC_NBR)#" cfsqltype = "CF_SQL_CHAR">
GROUP BY
SAMPLE_DATE
, SAMPLE_NBR
, SAMPLE_MISC_NBR
</cfquery>
When I comment out the AND statements and dump out the above query I can see the output of the original_query_result with all the TTL values and Sample rows. However if I want to grab just one row with the AND statements then the query comes back as not finding the row. I am doing a cfoutput query = original_query_result and trying to get the total quantity to display on each row in my output grid table.
Any help would be greatly appreciated. I have been trying to figure this out for more then a week now.
I saw something similar a while back and found this post:
https://web.archive.org/web/20180210052449/http://blog.dkferguson.com/index.cfm/2010/2/17/Using-Querie-of-Queries-with-CFDirectory
Basically I had to CAST the date and use cfqueryparam (as you are):
SAMPLE_DATE = CAST(<cfqueryparam value="#original_query_result.SAMPLE_DATE#" cfsqltype="CF_SQL_DATE"> AS date)
The post said they had to cast both sides but I didn't find this.

Retrieving a report stored on disk

I have a report stored as an .cfm file. I have been able to retrieve it fine with a cffile read. Now I want the option of retrieving only part of the report, say the first 50 lines. I decided to try a fileReadLine():
<cfset repname = url['rep']>
<cfset type = url['type']>
<cfset dataFile = fileOpen("/var/www/reports/moxrep/#repname#.cfm", "read" ) >
<cfset i = 0>
<cfoutput>
<cfloop
condition = "NOT FileIsEOF(dataFile) AND i LT 100">
<cfset i = i + 1>
<cfset inf = fileReadLine( dataFile ) >
#inf#
</cfloop>
</cfoutput>
<cfset fileClose( dataFile ) >
It did not retrieve things at all correctly. The formatting was messed up. All the dynamic data in the report was missing. The CSS links did not operate. And there were many extra blank lines.
Am I doing something wrong? Or is fileReadLine just not meant for retrieving a formatted report? And if not, is there any way to retrieve just part of the report with cffile?
Use cfhttp to get the report, then take that result and strip it down to what you need.
I am not sure you realize that the FileOpen() function is actually reading the raw CFML template and not actually executing the queries that populate your report. Using the CFHTTP tag is definitely a better approach, but be careful because your rendered page is likely to contain all your CSS that would be necessary for the report to render properly so use View Source on your report to see if you want just 50 lines.
The question burning in my mind is "why" do you want just 50 lines? are you previewing the report? it is only 1 page long? Are you embedding it into a dashboard? You may want to consider modifying the .cfm "report" so that the area you want to display elsewhere is wrapped with a specific tag (such as a Span or even something custom) and then when you fetch the report using CFHTTP, then you can parse the results with XMLParse() function (assuming it is properly formatted) and render the section of the report you actually want.

Using dynamic variables in query

I have a page that has an variable number of input fields (this number of fields is saved in the database). Upon submit, the information in these fields is to be saved into a database. The names of the fields are looped through and the current loop number is attached to the end of the field. I then save this into a variable and use the variable in the query. It's getting the correct information from the variable, but isn't actually evaluating the form fields. For instance, the values put into the form fields are:
"#FORM.TEST_LOCATION_1#=two"<br>
"#FORM.TEST_LOCATION_2#=four"<br>
"#FORM.TEST_LOCATION_3#=six"<br>
"#FORM.TEST_LOCATION_4#=eight"<br>
"#FORM.TEST_LOCATION_5#=ten"<br>
"#FORM.TEST_NAME_1#=one"<br>
"#FORM.TEST_NAME_2#=three"<br>
"#FORM.TEST_NAME_3#=five"<br>
"#FORM.TEST_NAME_4#=seven"<br>
"#FORM.TEST_NAME_5#=nine"<br>
The variable that is used in the query gets:
test_location_1 = '#form.test_location_1#', test_name_1 = '#form.test_name_1#', test_location_2 = '#form.test_location_2#', test_name_2 = '#form.test_name_2#', test_location_3 = '#form.test_location_3#', test_name_3 = '#form.test_name_3#', test_location_4 = '#form.test_location_4#', test_name_4 = '#form.test_name_4#', test_location_5 = '#form.test_location_5#', test_name_5 = '#form.test_name_5#',
but instead of putting the values actually entered in the input field in to database, it puts:
"#form.test_location_1#"
"#form.test_location_2#"
"#form.test_location_3#"
"#form.test_name_1#"
"#form.test_name_2#"
"#form.test_name_3#"
etc
The code I'm using right now is:
<cfset session.updque = ''>
<cfloop from="1" to="#session.test_numfields#" index="numf">
<cfset session.updque &= "test_location_" & #numf# &" = '##form.test_location_" & #numf# & "##', ">
<cfset session.updque &= "test_name_" & #numf# &" = '##form.test_name_" & #numf# & "##', ">
</cfloop>
<cfquery DATASOURCE="#ODSN#" NAME="uptest" >
UPDATE redbook_test SET
<cfoutput>#PreserveSingleQuotes(updque)#</cfoutput>
test_date_last_mod='#datecompleted#',
test_status='C',
where buildno = '#session.buildno#'
</CFQUERY>
What do I need to do to actually get the form variable saved into the database??
FORM is a structure. To evaluate dynamically named fields, use associative array notation:
<cfset value = FORM["test_location_" & numf]>
Not directly related to your question, but your current query is unsafe. CF automatically escapes single quotes inside query parameters to protect against sql injection. PreserveSingleQuotes disables that protection, putting your database at risk. To avoid that risk, always use cfqueryparam on user supplied values:
UPDATE Table
SET Column = <cfqueryparam value="#form.fieldName#" cfsqltype="...">
WHERE ...
Also, when you have multiple columns with the same name, ie test_location_1, test_location_2, ..., location_n it is usually a good sign you need to normalize your tables. Typically, you want to create a secondary table and store the duplicated information in rows, not columns, with a link back to the main table.
This sort of syntax will get you started.
update redbook_test
set test_date_last_mod <cfqueryparam value="#test_date_last_mod#">
, test_status='C'
<cfloop list="#form.fieldnames#" index="ThisField">
<cfif left(ThisField, 5) is "test_">
, #ThisField# = <cfqueryparam value = "#form[ThisField]#">
</cfif>
</cfloop>
where buildno = <cfqueryparam value='#session.buildno#'>
It's not complete. You have to consider how to handle empty strings, different datatypes, and such.

coldfusion struct syntax and query data

I’ve mostly only used coldfusion for queries before never needed structs or any object notation until now. The server I am working on doesn’t have debugging turned on just a “500- internal server error.” so I am unable to see why my code is not working and sadly I do not have the ability to turn debugging on.
By trial and error with commenting blocks out I’ve noticed the errors are occurring in my struct line, and adding the struct to my array. From what I’ve read of the CF documentation I do not see any syntax errors but any help would be much appreciated as to if I have any bad logic or what could be wrong.
<cfset dataArray = []>
<cfset i = 0>
<cfloop query="getMembers">
<cfquery name="getmaps" datasource=“a" dbtype="odbc">
SELECT member_id, mlong, mlat
FROM maps
WHERE member_id = '#getMembers.MemberID#'
</cfquery>
<cfif getmaps.recordcount eq 1>
<!--- temp structure to insert into array --->
<cfset dataTemp = {
memberID = getMemebers.memberID,
name = getMemebers.MemberName,
long = getmaps.mlong,
lat = getmaps.mlat
}>
<cfset dataArray[i] = dataTemp>
<cfset i++>
</cfif>
</cfloop>
I addition to Shawn's comment, I believe you'll have a problem with starting your array index at 0, rather than 1. Coldfusion begins array indices at 1.
edit Some more suggestions:
<cfset dataArray = []>
<cfloop query="getMembers">
<!--- Not usually a good idea to query each time through a loop - should be able to do a single query outside it --->
<cfquery name="getmaps" datasource=“a" dbtype="odbc">
SELECT member_id, mlong, mlat
FROM maps
WHERE member_id = <cfqueryparam value='#getMembers.MemberID#' cfsqltype="cf_sql_varchar"><!--- assuming varchar since you had quotes around it --->
</cfquery>
<cfif getmaps.recordcount eq 1>
<!--- temp structure to insert into array --->
<cfset dataTemp = {
memberID = getMembers.memberID,
name = getMembers.MemberName,
long = getmaps.mlong,
lat = getmaps.mlat
}>
<cfset ArrayAppend(dataArray,dataTemp)>
</cfif>
</cfloop>
You should consider combining the two queries into one query.
<cfquery name="qryMemberMaps" datasource="a" dbtype="ODBC">
SELECT
members.memberID, members.MemberName,
maps.mlong, maps.mlat
FROM
[members_database].dbo.members JOIN [maps_database].dbo.maps ON members.memberID = maps.member_id
</cfquery>
The current method could potentially generate thousands of queries when you only need one!
Anytime you find yourself looping over a query and calling other queries, it is a good idea to revise the original query and save hammering your database server.
For putting the data into an array of structs, Jake's answer works well.
If you are joining multiple DB's make sure that you create a non-clustered index on the column(s) that will act as the primary/foreign keys.

Session Variables, welcome messages

Why does this not work? My welcome message, it just doesn't show up:
<p>Welcome <cfoutput>#Recordset1.UserID#</cfoutput>.</p>
The session variable on the login page I created is:
<cflock timeout=999 scope="Session" type="Exclusive">
<cfset Session.IDUsers =''>
</cflock>
is this incorrect? On the index page where I'm trying to display my welcome message I have:
<cfquery name="Recordset1" datasource="cfGossip">
SELECT *
FROM users
WHERE users.IDUsers = <cfqueryparam value="#Session.IDUsers#">
</cfquery>
I'm not sure if this works, or is necessary?
If you set the userid stored in the session to be the empty string, when you query on it, you will only get users for whom the id is the empty string, which shouldn't be any of them. Therefore, the query is returning an empty set, and your page is (correctly) not displaying a user id.
How are you initially identifying the user? Are you querying against a database when they log in? Are you storing a cookie? Reading Tarot cards? For this to work, at some point, you have to store the correct userid, probably in the session. To do that, you need to first identify who the user is.
Also, if you are using CF6+, you probably do not need the cflock. It is now used to prevent race conditions, as CF is now thread-safe.
Looks like you're just starting with CF, welcome to the community.
My understanding of your code makes the structure look like the following, if I'm understanding you correctly:
<cfset session.idUsers = '' />
<cfquery datasource = "cfgossip" name = "recordset1">
SELECT * FROM USERS WHERE USERS.ID_USERS = <cfqueryparam cfsqltype = "cf_sql_integer" value = "#session.idUsers# />
</cfquery>
<cfoutput>Welcome #recordset1.userID#</cfoutput>
The reason this doesn't work is because your session.idUsers value is blank. Assuming you have a user in your database with an ID_USERS value of 1, you could change the CFSET to look like and it should return results.
Additionally, while it's great to see you using CFQUERYPARAM, I'd recommend including a CFSQLTYPE attribute in the tag whenever possible to provide an added line of defense against injection attacks. You can check out http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=Tags_p-q_18.html to see the list of available types.
Is there anywhere in your code where you set your session.IDUsers? You initialize it as a blank ''. Coldfusion does not populate it for you. The session scope is a place that will remember things for that user that you put there for a specified period of time inactivity, usually 20 minutes. So hopefully, somewhere before you run your query you have additional logic that fills that in, otherwise you are asking the database for a user named, ''.
This is just a point of style, but the following may work better for you:
<cfset Session.IDUsers =''>
<!--- Do something here to populate Session.IDUsers --->
<!--- Creates a blank query - not necessary, but can reduce errors later --->
<cfset Recordset1 = queryNew("UserID")>
<!--- Populate the query from the database --->
<cfquery name="Recordset1" datasource="cfGossip">
SELECT *
FROM users
WHERE users.IDUsers = <cfqueryparam value="#Session.IDUsers#">
</cfquery>
<!--- If the query has data, use it, otherwise show generic message --->
<cfoutput>
<cfif Recordset1.recordcount>
<p>Welcome #Recordset1.UserID#.</p>
<cfelse>
<p>Welcome new user!</p>
</cfif>
</cfoutput>
<!--- OR since we used queryNew("userID"), we can simplify without throwing an error. ---->
<cfoutput>
<p>Welcome <cfif len(Recordset1.userID)>#Recordset1.userID#.<cfelse>new user!</cfif></p>
</cfoutput>
Putting the cfoutput outside the paragraph block will make it easier if you have additional variables to insert into the text. (but will work either way)
Regardless of all that, unless you forgot to share a bit more of the code, I think the issue is that the session.IDUsers is blank and needs to be populated before the query. I hope this helps!