changing the query mode through ColdFusion to allow IN instead of equals - coldfusion

I am trying to change some content of the query through ColdFusion. Well I cannot try in query itself because I am getting query from an external source
I have the following query
(q.company_na = 'a, b, c, d')
and I am trying to convert it to
(q.company_na in ('a, b, c, d'))
I am using a normal replace but that is not gonna solve the issue. I have seen my try is like this
<cfset data = Replace(data,'q.company_na = ','q.company_na IN ','ALL')>
but I am missing the opening and closing braces around the IN. How can I approach this?

Another benefit of query parmaters
where q.company_na in (
<cfqueryparam cfsqltype="cf_sql_char" value="a,b,c,d" list="yes">
)

Playing a riff off Dan's answer, you can add parameters to the query object.
Find all the equality operators in the query.
For each equality operator found,
Store the right-hand side into a temporary variable.
Replace the equality operator and the right-hand side with IN (?).
Add a parameter to the query object (query.addParam(...), see query object documentation) using the value you stored in the temporary variable.
Make sure to remove white-space from around the list delimiters.
Make sure to set the list property of the parameter to true.
Set the sql property of the query object with the modified SQL.

Could you try something like this?
<cfset str = "(q.company_na = 'a, b, c, d')" />
<cfset str = REReplace(str, "(,)\s*", "'\1'", "all") />
<cfset str = REReplace(str, "= ('[^)]*)", "IN (\1)") />
Output:
(q.company_na IN ('a','b','c','d'))
If you want your output to be
(q.company_na in ('a, b, c, d'))
(which I can't imagine you do, since that is semantically the same as using = in this instance), then simply omit the second line above and do this:
<cfset str = "(q.company_na = 'a, b, c, d')" />
<cfset str = REReplace(str, "= ('[^)]*)", "IN (\1)") />

Related

How to get associative arrays notation to assign simple values

I created sample code using the Microsoft SQL Server Northwind demo database. If you don't have access to this demo database here is a simple (MS-SQL) script to create the table and a row of data for this question.
CREATE TABLE [dbo].[Products](
[ProductID] [int] IDENTITY(1,1) NOT NULL,
[ProductName] [nvarchar](40) NOT NULL,
[SupplierID] [int] NULL,
[CategoryID] [int] NULL,
[QuantityPerUnit] [nvarchar](20) NULL,
[UnitPrice] [money] NULL CONSTRAINT [DF_Products_UnitPrice] DEFAULT (0),
[UnitsInStock] [smallint] NULL CONSTRAINT [DF_Products_UnitsInStock] DEFAULT (0),
[UnitsOnOrder] [smallint] NULL CONSTRAINT [DF_Products_UnitsOnOrder] DEFAULT (0),
[ReorderLevel] [smallint] NULL CONSTRAINT [DF_Products_ReorderLevel] DEFAULT (0),
[Discontinued] [bit] NOT NULL CONSTRAINT [DF_Products_Discontinued] DEFAULT (0),
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
[ProductID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Products] ON
GO
INSERT [dbo].[Products] ([ProductID], [ProductName], [SupplierID], [CategoryID], [QuantityPerUnit], [UnitPrice], [UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued]) VALUES (1, N'Chai', 1, 1, N'10 boxes x 20 bags', 18.0000, 39, 0, 10, 0)
GO
SET IDENTITY_INSERT [dbo].[Products] OFF
GO
Here is the ColdFusion code:
<cfset variables.useTempVar = false>
<cfquery datasource="Northwind2014" name="qryNWProducts">
SELECT TOP 1 * from Products;
</cfquery>
<cfdump var="#qryNWProducts#" label="qryNWProducts">
<cfset variables['stProduct'] = {}>
<cfloop index="vcColName" list="#qryNWProducts.columnlist#">
<cfif variables.useTempVar>
<cfset variables['temp'] = qryNWProducts[vcColName]>
<cfset variables['stProduct'][vcColName] = variables.temp>
<cfelse>
<cfset variables['stProduct'][vcColName] = qryNWProducts[vcColName]>
</cfif>
</cfloop>
<cfdump var="#variables['stProduct']#" label="variables['stProduct']">
<cfloop collection="#variables['stProduct']#" item="key"><cfoutput>
variables['stProduct']['#key#'] JVM datatype = #getMetadata(variables['stProduct'][key]).getName()#<br>
</cfoutput></cfloop>
<br>
This always works:<br>
<cfset variables['aPhrase'] = "I ordered " & variables.stProduct.ProductName & " for " & DollarFormat(variables.stProduct.UnitPrice) & ".">
<cfoutput>#variables['aPhrase']#<br></cfoutput>
<br>
With "variables.useTempVar = false", the next line will throw a "Complex object types cannot be converted to simple values. " error.<br>
<cfset variables['aPhrase'] = "I ordered " & variables['stProduct']['ProductName'] & " for " & DollarFormat(variables['stProduct']['UnitPrice']) & ".">
<cfoutput>#variables['aPhrase']#<br></cfoutput>
The code above has a boolean variable named "variables.useTempVar" at the top that can be flipped to see the error that I'm getting.
It looks like the direct assignment (when variables.useTempVar = false) from the query to the structure causes the structure values to be of JVM type "coldfusion.sql.QueryColumn".
Another note: if this line of code:
<cfset variables['stProduct'][vcColName] = variables.temp>
is changed to:
<cfset variables['stProduct'][vcColName] = variables['temp']>
The JVM datatype will be "coldfusion.sql.QueryColumn".
When the dot notation temp variable is used to assign the query field (when variables.useTempVar = true); the JVM datatypes are simple types that matches up pretty well with the database column types (java.lang.Integer, java.math.BigDecimal, java.lang.String, etc.).
I've also experiemented with statements like this and that provided some odd results:
<cfset variables['stProduct'][vcColName] = qryNWProducts[vcColName].toString()>
Here's the question. Is this the best way to transfer the simple values from a query to a structure? It seems odd to be forced to use a temp variable and dot notation to make this work.
Comment: I've always thought that dot notation and associative array notation were equivalent. This code example appears to contradict that opinion.
#Leigh is correct in that you need to supply the row number when using associative array notation with a query object. So you'd reference row 1 like: qryNWProducts[vcColName][1]
As for your question
Is this the best way to transfer the simple values from a query to a structure?
Are you sure you need a struct? Your question doesn't really specify the use case, so it is entirely possible that you would be better off using the query object as-is.
If you do need it to be a struct (and since you are using ColdFusion 11) might I suggest you take a look at serializeJSON/deSerializeJSON to convert this to a struct. The serializeJSON has a new attribute that will properly serialize a query object into an "AJAX friendly" JSON array of structs. You can then deSerialize the JSON into a CF array, like so:
NWProducts = deSerializeJSON( serializeJSON( qryNWProducts, 'struct' ) )[1];
Which would return a struct representation of the first row in that query object.
Although it's not obvious from the Adobe docs for serializeJSON, the second parameter can be one of: true|false|struct|row|column which will change how the resulting data is formatted.
Here's a runnable example of using the above technique showcasing each serializeQueryAs option.
It's also a better practice to start moving that kind of code into cfscript. queryExecute is quite easy to use and makes script based queries very easy to develop. See the How To Create a Query in cfscript tutorial at trycf.com for more on how to develop script based queries.
Final note, and this is a bit off topic but it is a generally accepted best practice to not use Hungarian Notation when naming variables.
#Abram's covered the mains answer, but just to pick up one tangential point you raise.
Dot notation and associative array notation are generally equivalent in CFML. However in the case of queries, there is a slight variation. Dot notation: query.columnName is treated as shorthand for query.columnName[currentRow] (where currentRow defaults to 1).
Associative array notation with queries does not have this "syntactic sugar", so query["columnName"] refers to the entire column, as the syntax actually indicates.
There are no functions I am aware of in CFML that take a query column as an argument, however the CFML engine will convert the column to an array if it's used in an array function. This is quite handy sometimes.

Why is ListAppend non destructive, while ArrayAppend and StructInsert are both destructive?

I just spent almost an hour trying to figure out an issue with having a list that would always return an empty string. I was using ListAppend just like one uses ArrayAppend or StructInsert, but apparently ListAppend works differently. What, if any, is the reasoning behind having ListAppend work differently from everything else?
<cfset ListAppend(list, item)>
list = ''
<cfset ArrayAppend(array, item)>
array[1] = item
<cfset StructInsert(struct, 'key', item)>
struct.key = item
Possibly because a list is just a big String. Unlike arrays and structures, Strings are immutable, meaning they cannot be changed. To "append" a new value, you need to create an entirely new String. Arrays and structures are mutable. So you can modify them "in place".
Lists in ColdFusion are just Strings and strings in ColdFusion (and Java) are immutable. They cannot be changed. So ListAppend() must return the a new string with the value instead of modifying the existing string.
<cfset newList = listAppend(oldList, "New Value") />

ColdFusion-9 "includeEmptyValues"

This is my code:
returnStruct.myList = myList;
returnStruct.first = trim(ListGetAt(myList,3));
returnStruct.last = trim(ListGetAt(myList,13));
returnStruct.address = trim(ListGetAt(myList,15));
returnStruct.city = trim(ListGetAt(myList,2));
returnStruct.state = trim(ListGetAt(myList,9));
Everything is working fine until myList hits empty values and then everything crashes.
I found a command "includeEmptyValues" that I can set to 'yes' but I am not familiar with it and the documentation of ColdFusion 9 isn't the best I've come across.
http://cfquickdocs.com/cf9/#listgetat
Previous versions of ColdFusion (and CF9 by default) counted consecutive delimiters as a single delimiter. So a list that looked like this:
<cfset myList="a,b,,c,,d" />
was considered to have four elements.
Recently added is the "includeEmptyValues" attribute.
listGetAt(list, position [, delimiters, includeEmptyValues ])
So while
<cfset myVar=listGetAt(myList,6) />
will throw an error
<cfset myVar=listGetAt(myList,6,",","true") />
will successfully set myVar to d.
might want to use listToArray(), and ArrayIsDefined(). Play with includeEmptyFields attr and see which behavior you prefer. True = Convert empty elements in a list to empty array entries

Use string function to select all text after specific character

How would I use use a string function to select everything in a variable after the last "/"
http://domain.com/g34/abctest.html
So in this case I would like to select "abctest.html"
Running ColdFusion 8.
Any suggestions?
Um, a bit strange to give very similar answer within few days, but ListLast looks as most compact and straightforward approach:
<cfset filename = ListLast("http://domain.com/g34/abctest.html","/") />
And yes, IMO you should start with this page before asking such questions on SO.
Joe I'd use the listToArray function, passing the "/" as the delimiter, get the length of the array and get the value in the last slot. See sample below
<cfset str = "http://domain.com/g34/abctest.html"/>
<cfset arr = ListToArray(str,"/","false")/>
<cfset val = arr[ArrayLen(arr)]/>
<cfoutput>#str# : #val#</cfoutput>
produces
http://domain.com/g34/abctest.html : abctest.html

How do I get the contents of a string minus the extension in ColdFusion?

For example, I want just the "filename" of a file in a field. Say I have myimage.jpg I only want to display "myimage" How do I get just that?
Use the List functions to your advantage.
<cfset FileName = ListDeleteAt(FileFullName, ListLen(FileFullName, "."), ".")>
Be aware that this only works for file names that actually have a file extension (that is defined as the thing after the last dot). To make it safer, the following is better:
<cfset ExtensionIndex = ListLen(FileFullName, ".")>
<cfif ExtensionIndex gt 1>
<cfset FileExt = ListGetAt(ExtensionIndex , ".")>
<cfset FileName = ListDeleteAt(FileFullName, ExtensionIndex, ".")>
<cfelse>
<cfset FileExt = "">
<cfset FileName = FileFullName>
</cfif>
To complicate things a bit further: There may be files that start with a dot. There may be file names that contain many adjacent dots. List functions return wrong results for them, as they ignore empty list elements. There may also be files that have dots, but no extension. These can only be handled if you provide an extension white list: ListFindNoCase(FileExt, "doc,xls,ppt,jpg"). If you want to account for all of this, you probably need to resign to a reguar expression:
<cfset FileExtRe = "(?:\.(?:doc|xls|ppt|jpg))?$">
<cfset FileName = REReplaceNoCase(FileFullName, FileExtRe, "")>
To split file name from path, ColdFusion provides distinct functions that also handle platform differences: GetFileFromPath() and GetDirectoryFromPath()
Tomalak's answer is good, but this can get tricky. Given a file named "mydoc.ver1.doc" (a valid Windows file name) which is the filename and which is the extension? What if there is a filepath?
You can still leverage the list functions to your advantage, however, even in these scenarios.
You can easily parse out the file from the path with
fullFileName=listLast(fieldname,"\/")
If you assume the filename is everything before the dot, then
theFileName=listFirst(fullFileName,".")
will work.
If you want to ensure that you get everything but what's after the last period, then a little trickery is needed, but not much. There is not a listAllButLast() function (although such a thing might exist on CFLIB.org) but there are two ways I can think of to get what you're after.
fileName=reverse(listRest(reverse(fullFileName),"."))
or
fileName=listDeleteAt(fullFileName,listLen(fullFileName,"."),".")
As with Tomalak's suggestion, however, this will break down on a filename that lacks an extension. Wrapping this in a <cfif listLen(fullFileName,".") GT 1> will account for that.
The current accepted solution will not work for a file that does not contain an extension.
You can solve this by using a regular expression to strip the extension only if it exists:
<cfset FileName = rereplace( FullFileName , '\.[^.]+$' , '' ) />
This might still not be perfect - you might have a file that has a . but it isn't considered an extension - you can solve this either by using a list of known extensions to strip, or by limiting how long an extension you will accept (e.g. upto 5):
<cfset FileName = rereplace( FullFileName , '\.(jpg|png|gif|bmp)$' , '' ) />
<cfset FileName = rereplace( FullFileName , '\.[^.]{1,5}$' , '' ) />
So you first need to find the position of the last fullstop (there could be more than one fullstop in the full filename). I don't think Coldfusion has a find function that works backwards, so reverse the string first:
<cfset Position = Find(".", Reverse(FullFileName))>
If that returns zero then you don't have a fullstop in the file name, so handle appropriately. Else ...
<cfset Filename = Left(FullFileName, Len(FullFileName) - Position>
As of ColdFusion 9+ (perhaps earlier, but I can't verify), the Apache Commons library was included. Within that is org.apache.commons.io.FilenameUtils. You can utilize methods within the cut down on the amount of operations needed in CF to get the same (or similar) results.
filepath = "some/dir/archive.tar.gz";
oUtils = createObject("java", "org.apache.commons.io.FilenameUtils");
writeDump(oUtils.getFullPath(filepath)); // "some/dir/"
writeDump(oUtils.getName(filepath)); // "archive.tar.gz"
writeDump(oUtils.getBaseName(filepath)); // "archive.tar"
writeDump(oUtils.getExtension(filepath)); // "gz"
writeDump(oUtils.getPath(filepath)); // "some/dir/"
writeDump(oUtils.getPathNoEndSeparator(filepath)); // "some/dir"