I have a simple CFC page which output data in a JSON format:
<cffunction name="test" access="remote" returnformat="plain" output="true">
<cfquery datasource="#dns#" name="rs.q" maxrows="5">
select text
from table
</cfquery>
<cfreturn serializeJSON( rs.q ) />
</cffunction>
The text outputted by the query can contain images, e.g., /webimages/1.jpg. Now for the webservice I want to search for images and replace them with http://domain.com/webimages/1.jpg.
Can that be done in the CFC file?
<cffunction name="test" access="remote" returnformat="json" output="false">
<cfquery datasource="#dns#" name="local.rs.q" maxrows="5">
select replace(text, "/webimages/", "http://domain.com/webimages/") as text
from table
</cfquery>
<cfreturn rs.q>
</cffunction>
or
<cffunction name="test" access="remote" returnformat="plain" output="false">
<cfquery datasource="#dns#" name="local.rs.q" maxrows="5">
select text
from table
</cfquery>
<cfreturn replace(serializeJSON(rs.q),
"\/webimages\/",
"http:\/\/domain.com\/webimages\/",
"all")>
</cffunction>
Related
I have this code that will passed variable to function and insert it. But I'm getting error:
<cffunction name="insertSupplierPersonnel" output="false" access="public" returnType="struct">
<cfargument name="name" type="string" required="true" />
<cfargument name="email" type="string" required="false" default="" />
<cfargument name="office_phone" type="string" required="false" default="" />
<cfargument name="mobile_phone" type="string" required="false" default="" />
<cfargument name="designation" type="string" required="false" default="" />
<cfset var res = '' />
<cfquery datasource="#session.dsn_aset#" result="res">
INSERT INTO `supplier_personnel_incharge` (
`name`,
`email`,
`office_phone`,
`mobile_phone`,
`designation`
)
VALUES
(
cfargument.name,
cfargument.email,
cfargument.office_phone,
cfargument.mobile_phone,
cfargument.designation
) ;
</cfquery>
<cfreturn res />
</cffunction>
<cfset res = insertSupplierPersonnel(name='#form.personnel_name#', email='#form.personnel_email#', office_phone='#form.personnel_office_phone#', mobile_phone='#form.personnel_mobile_phone#', designation='#form.personnel_designation#') />
<cfdump var="#res#">
I'm getting this error:
There's a problem with cfargument.name. What is the correct way to use cfargument for insert query? Thanks in advance.
First, the correct scope is arguments, not cfargument. So, change this sort of thing:
cfargument.name,
to this:
arguments.name,
Next, you have to surround your variable names with pound signs to get the value of the variable, i.e. #arguments.name#.
Next, use query parameters, i.e. <cfqueryparam value="#arguments.name#">. Among other things, they will escape special characters used in the SQL query syntax.
To summarize all of the correct answers and comments above. This would be your best practice:
Function returnType should be "query", not "struct"
If you specify a default value, CF recognizes the argument as "not required"
Use cfqueryparam on all query parameters
Optional
Use the null attribute of cfqueryparam to insert a NULL if there is no value given
You don't need a trailing semi-colon at the end of the sql statement
<!---return type is query, not struct --->
<cffunction name="insertSupplierPersonnel" output="false" access="public" returnType="query">
<cfargument name="name" type="string" required="true" />
<!--- NOTE: If you specify a default value, CF recognizes the argument as "not required" --->
<cfargument name="email" type="string" default="" />
<cfargument name="office_phone" type="string" default="" />
<cfargument name="mobile_phone" type="string" default="" />
<cfargument name="designation" type="string" default="" />
<cfquery datasource="#session.dsn_aset#" result="local.data">
INSERT INTO supplier_personnel_incharge (
name, /*Unless your database column names are case-sensitive, you don't need quotation marks around the column names*/
email,
office_phone,
mobile_phone,
designation
)
VALUES
(
<cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(arguments.name)#">,
/*insert NULL if there is no value given*/
<cfqueryparam cfsqltype="cf_sql_varchar" null="#Not Len(trim(arguments.email))#" value="#trim(arguments.email)#">,
<cfqueryparam cfsqltype="cf_sql_varchar" null="#Not Len(trim(arguments.office_phone))#" value="#trim(arguments.office_phone)#">,
<cfqueryparam cfsqltype="cf_sql_varchar" null="#Not Len(trim(arguments.mobile_phone))#" value="#trim(arguments.mobile_phone)#">,
<cfqueryparam cfsqltype="cf_sql_varchar" null="#Not Len(trim(arguments.designation))#" value="#trim(arguments.designation)#">,
) /*you don't need a trailing semi-colon*/
</cfquery>
<cfreturn local.data />
</cffunction>
<cfset local.res = insertSupplierPersonnel(name='#form.personnel_name#',
email='#form.personnel_email#',
office_phone='#form.personnel_office_phone#',
mobile_phone='#form.personnel_mobile_phone#',
designation='#form.personnel_designation#') />
<cfdump var="#local.res#">
I have a query running to gather an adjacency list and then generate an XML object of that list as a tree. Next I need to output that tree as a simple HTML.
I would like my output of XML document to be:
<ul>
<li margin="5">Title
<ul>
<li margin="10">Title</li>
</ul>
</li>
</ul>
Here is what I have coded so far:
<cfquery name="nodeTable" datasource="#database.ds#">
SELECT [mc_location].[id],[mc_location].[title], [mc_location].[parent_id] FROM [mc_location]
LEFT JOIN [mc_location_type] ON [mc_location].[id] = [mc_location_type].[location_id]
WHERE [mc_location_type].[category] = 'staff'
</cfquery>
<cffunction name="outputChildNodes" access="public" returntype="void" output="true">
<cfargument name="nodeTable" type="query" required="true" hint="I am the node query object."/>
<cfargument name="parent_id" type="numeric" required="false" default="0"/>
<cfset var local = {}/>
<cfquery name="local.childNodes" dbtype="query">
SELECT id, parent_id, title
FROM arguments.nodeTable
WHERE parent_id = <cfqueryparam value="#arguments.parent_id#" cfsqltype="cf_sql_integer" />
ORDER BY id ASC
</cfquery>
<cfloop query="local.childNodes">
<child id="#local.childNodes.id#" parent-id="#local.childNodes.parent_id#" name="#local.childNodes.title#">
<cfset outputChildNodes(arguments.nodeTable, local.childNodes.id)/>
</child>
</cfloop>
<cfreturn/>
</cffunction>
<!--- Build the node XML document recursively. --->
<cfxml variable="nodeTree">
<childern>
<!--- Output the root-level nodes. --->
<cfset outputChildNodes( nodeTable ) />
</childern>
</cfxml>
<!--- Render the XML document. --->
<cfloop index="childern" array="#nodeTree.childern#">
<cfloop index="child" array="#childern#">
<cfif isStruct(child.XmlAttributes)>
<cfdump var="#child[1].XmlAttributes#"/>
</cfif>
</cfloop>
</cfloop>
Thank you!
The problem I was experiencing had to do with not properly defining variable scopes (e.g. using "var" in cfset, using arguments.parameter, etc.), which was necessary in order to recursively call a function. Presented below is a brief solution.
<!--- query for parent child tree --->
<cfquery name="tree_nodes" datasource="#sonis.ds#">
SELECT [location].[id],[location].[title], [location].[parent_id] FROM [location]
LEFT JOIN [location_category] ON [location].[id] = [location_category].[location_id]
WHERE [location_category].[category] = 'staff'
</cfquery>
<!--- build tree from adjacency list function --->
<cffunction name="build_tree" access="public" output="true">
<cfargument var name="tree_nodes" type="query" required="true"/>
<cfargument var name="parent_id" type="numeric" required="false" default="0"/>
<cfargument var name="depth" type="numeric" required="false" default="0"/>
<cfset var local = {}/>
<cfquery name="local.child_node" dbtype="query">
SELECT id, parent_id, title
FROM arguments.tree_nodes
WHERE parent_id = <cfqueryparam value="#arguments.parent_id#" cfsqltype="cf_sql_integer" />
ORDER BY id ASC
</cfquery>
<cfset var branch = {}/>
<cfset var counter = 1/>
<cfloop query="local.child_node">
<cfset local.depth = arguments.depth/>
<cfset branch[counter++] = {
'id' = '#local.child_node.id#',
'title' = '#local.child_node.title#',
'parent_id' = '#local.child_node.parent_id#',
'depth' = local.depth,
'children' = build_tree(arguments.tree_nodes, local.child_node.id,++local.depth)
} />
</cfloop>
<cfreturn branch/>
</cffunction>
<!--- print tree as select box function --->
<cffunction name="print_tree_select" access="public" output="true">
<cfargument var name="tree" type="struct" required="true"/>
<cfargument var name="selected" type="numeric" required="false" default="0"/>
<cfargument var name="child" type="numeric" required="false" default="0"/>
<cfif child eq '0'><select name="select_tree"><option value="null"></option></cfif>
<cfloop from="1" to="#StructCount(arguments.tree)#" index="a">
<option value="#arguments.tree[a]['id']#"<cfif #arguments.selected# eq #arguments.tree[a]['id']#> selected</cfif>>
<cfif #arguments.tree[a]['depth']# GT 0>
#RepeatString('--', arguments.tree[a]['depth'])#
</cfif>
#arguments.tree[a]['title']#
</option>
<cfif StructKeyExists(arguments.tree[a], 'children') AND StructCount(arguments.tree[a]['children']) GT 0>
#print_tree_select(arguments.tree[a]['children'],arguments.selected, 1)#
</cfif>
</cfloop>
<cfif child eq '0'></select></cfif>
</cffunction>
<!--- print tree as list function --->
<cffunction name="print_tree_list" access="public" output="true">
<cfargument var name="tree" type="struct" required="true"/>
<ul style="list-style-type: circle;">
<cfloop from="1" to="#StructCount(arguments.tree)#" index="local.i">
<li>
<cfform method="post" name="edit">
#arguments.tree[local.i]['title']#
<cfinput type="hidden" name="id" value="#arguments.tree[local.i]['id']#"/>
<cfinput type="Submit" name="command" value="Edit"/>
</cfform>
<cfif StructKeyExists(arguments.tree[local.i], 'children') AND StructCount(arguments.tree[local.i]['children']) GT 0>
#print_tree_list(arguments.tree[local.i]['children'])#
</cfif>
</li>
</cfloop>
</ul>
</cffunction>
I am using ColdFusion 9, and MySQL 5.1. I am trying to align the ColdFusion encrypt/decrypt functions and mySQL AES_ENCRYPT/AES_DECRYPT so I can use them interchangeably depending on the situation. Not having much luck with that.
First I created an AES string with ColdFusion:
<cfset theKey = generateSecretKey("AES") />
<cfoutput>#theKey#</cfoutput>
Example key: 4OFWUiuqFEkGrSRFm8sLlg==
I use this key to encrypt with MySQL. Note, encrypt_test is an existing table, and fld is a varchar column.
INSERT INTO encrypt_test
SET fld = aes_encrypt('the text to encrypt', '4OFWUiuqFEkGrSRFm8sLlg==')
Next I try to decrypt with ColdFusion:
<cfset theKey = "4OFWUiuqFEkGrSRFm8sLlg=="
<cfset theAlgorithm = "AES" />
Then run a cfquery to get the data (Only 1 record in the table),
<cfquery name="testDecrypt">
SELECT fld FROM encrypt_test
</cfquery`
And finally decrypt
<cfoutput>#Decrypt(testDecrypt.fld, theKey, theAlgorithm)#</cfoutput>
This results in a Null. I suspect its a padding issue or some other mismatch, anyone have an idea what I am doing wrong, or how to make this work?
I know this thread is old, but the answer came up on a recent thread. So I am posting it for posterity. As explained in this blog entry, the reason for the difference is:
.. the MySQL algorithm just or’s the bytes of a given passphrase
against the previous bytes if the password is longer than 16 chars and
just leaves them 0 when the password is shorter than 16 chars.
So you need to perform the same manipulations on the key value, before passing it into encrypt/decrypt.
I would stick with just using CF's functions. That way you can add all kinds of layers of security processes, to include things like iterations and multiple keys, to build a custom solution with ease. THe amount of overhead it adds is not much at all for that as well.
Why don't you use ColdFusion's encrypt function instead of MySQL's?
In fact that would be one way to test where the problem might lie : try outputting both the encrypted value from your database and what CF's encrypt function would produce and see if they're identical.
Alternatively just use the aes_decrypt function in your query instead of using ColdFusion's decrypt.
Hmmm, from the docs:
Because AES is a block-level algorithm, padding is used to encode uneven length strings and so the result string length may be calculated using this formula:
16 * (trunc(string_length / 16) + 1)
If AES_DECRYPT() detects invalid data or incorrect padding, it returns NULL.
So assuming CFML doesn't do that padding, you'd have to figure out the reverse of this yourself or something.
I know it's quite an old post but here is what you should do:
Before storing into the DB:
<cfset crypt_fld = #encrypt('the text to encrypt', thekey, 'AES')#>
Then:
INSERT INTO encrypt_test
SET fld = crypt_fld
It worked for me
Use jBCrypt :: bCrypt is the strongest encryption available ... with the assistance of Mark Mandel's Fantastic JavaLoader
implementing jBCrypt is a snap in ColdFusion ...
As far as the password field it really doesn't matter what kind of database you're using ... the field could be varchar(60) or nvarchar(60) if you're dealing with locale support too...
<cfcomponent title="bcrypt (strong; recommended)" hint="I encode passwords using a popular secure password hashing algorithm called bcrypt. I am very slow, but that makes me very secure!" extends="PasswordHash"
alias="bcrypt" seq="9001" workFactor="10">
<cfset variables.loadPaths = [expandPath( "/PATHTOLIBDIR/lib/jbcrypt/jbcrypt-0.3m.jar" )]/>
<cffunction name="init" access="public" output="true" returntype="any" hint="constructor">
<cfset super.init( )/>
<!--- Allow java loader to fail silently: we can report the failure via isAvailable() --->
<cftry>
<cfset variables.oBCryptClass = createJavaClass( "org.mindrot.jbcrypt.BCrypt" )/>
<cfcatch></cfcatch>
</cftry>
<cfreturn this/>
</cffunction>
<cffunction name="isAvailable" hint="Is the hashing agorithm available in this environment?" access="public" returntype="boolean">
<cfreturn structKeyExists( variables, "oBCryptClass" )/>
</cffunction>
<cffunction name="matchesHashFormat" hint="Does the string match the format for this hash?" access="public" returntype="boolean">
<cfargument name="input" type="string" hint="String that may be a password hash" required="true"/>
<cfreturn REFind( "^\$2a\$\d+\$[\./A-Za-z0-9]+$", arguments.input )/>
</cffunction>
<cffunction name="encode" hint="Convert a clear password to its encoded value" access="public" returntype="string">
<cfargument name="password" type="string" hint="Input password" required="true"/>
<cfset var salt = variables.oBCryptClass.gensalt( JavaCast( "int", this.workFactor ) )/>
<cfreturn variables.oBCryptClass.hashpw( arguments.password, salt )/>
</cffunction>
<cffunction name="getHashWorkFactor" hint="Retrieve the work factor from a hashed string" access="public" returntype="numeric">
<cfargument name="hashedPassword" type="string" hint="Previously encoded password string" required="true"/>
<cfset var stMatch = ReFind( "^\$2a\$(\d+)\$([\./A-Za-z0-9]+)$", arguments.hashedPassword, 1, "true" )/>
<cfif stMatch.pos[1] eq 0>
<cfreturn 0>
<cfelse>
<cfreturn mid( arguments.hashedPassword, stMatch.pos[2], stMatch.len[2] )>
</cfif>
</cffunction>
<cffunction name="passwordMatch" hint="Compare a plain password against an encoded string" access="public" returntype="boolean">
<cfargument name="password" type="string" hint="Input password" required="true"/>
<cfargument name="hashedPassword" type="string" hint="Previously encoded password string" required="true"/>
<cfargument name="bCheckHashStrength" type="boolean" default="false" hint="If true, the hash strength of the hashed password must also match those generated by encode()"/>
<cfset var bMatch = variables.oBCryptClass.checkpw( arguments.password, arguments.hashedPassword )/>
<cfif bMatch and bCheckHashStrength>
<!--- Hash matched but we also need to match the bCrypt work factor --->
<cfreturn getHashWorkFactor( arguments.hashedPassword ) eq this.workFactor/>
<cfelse>
<cfreturn bMatch/>
</cfif>
</cffunction>
The PasswordHash.cfc ...
<cfcomponent hint="I am an abstract component for encoding passwords for storage and comparing passwords against previously encoded strings">
<!--- Array of Java class paths required for this component. Leave empty if no special Java libraries are needed. --->
<cfset variables.loadPaths = []/>
<cffunction name="init" access="public" output="true" returntype="any" hint="constructor">
<cfset var stMetadata = getMetadata( this )/>
<cfset var attr = ""/>
<cfloop condition="not structisempty(stMetadata)">
<!--- Get attributes --->
<cfloop collection="#stMetadata#" item="attr">
<cfif issimplevalue( stMetadata[attr] ) and not listcontains( "bindingname,extends,fullname,functions,hint,name,namespace,output,path,porttypename,serviceportname,style,type,wsdlfile", attr ) and not structkeyexists( this, attr )>
<cfset this[attr] = stMetadata[attr]/>
</cfif>
</cfloop>
<!--- Do the same for ancestors --->
<cfif structkeyexists( stMetadata, "extends" )>
<cfset stMetadata = stMetadata.extends/>
<cfelse>
<cfset stMetadata = structnew( )/>
</cfif>
</cfloop>
<cfset stMetadata = getMetadata( this )/>
<!--- If key isn't specified, use the name of the component --->
<cfif not structkeyexists( this, "alias" )>
<cfset this.alias = listlast( stMetadata.name, "." )/>
</cfif>
<!--- If title isn't specified, use the displayname --->
<cfif not structkeyexists( this, "title" )>
<cfset this.title = this.displayname/>
</cfif>
<!--- If seq isn't specified, use 9999 --->
<cfif not structkeyexists( this, "seq" )>
<cfset this.seq = 9999/>
</cfif>
<cfreturn this/>
</cffunction>
<cffunction name="isAvailable" hint="Is the hashing agorithm available in this environment?" access="public" returntype="boolean">
<cfreturn true/>
</cffunction>
<cffunction name="matchesHashFormat" hint="Does the string match the format for this hash?" access="public" returntype="boolean">
<cfargument name="input" type="string" required="true" hint="String that may be an encoding of a password"/>
<cfthrow message="The #this.alias# password encoding needs to implement the matchesHashFormat function"/>
<cfreturn ""/>
</cffunction>
<cffunction name="encode" hint="Convert a clear password to its encoded value" access="public" returntype="string">
<cfargument name="password" type="string" required="true" hint="Input password"/>
<cfthrow message="The #this.alias# password encoding needs to implement the encode function"/>
<cfreturn ""/>
</cffunction>
<cffunction name="passwordMatch" hint="Compare a plain password against an encoded string" access="public" returntype="boolean">
<cfargument name="password" type="string" required="true" hint="Input password"/>
<cfargument name="hashedPassword" type="string" required="true" hint="Previously encoded password string"/>
<cfargument name="bCheckHashStrength" type="string" default="false" hint="If true, the hash strength of the hashed password must also match those generated by encode()"/>
<cfthrow message="The #this.alias# password encoding needs to implement the passwordMatch function"/>
<cfreturn false/>
</cffunction>
<!--- Private Java library helper functions --->
<cffunction access="private" name="getJavaLoader" returntype="any" output="false">
<!--- Lazy-loading the JavaLoader makes it easier for plugins/projects to add custom crypto libraries --->
<cfif not structKeyExists( variables, "loader" )>
<cfset variables.loader = createObject( "component", "PATH.TO.JavaLoader" ).init( variables.loadPaths )/>
</cfif>
<cfreturn variables.loader/>
</cffunction>
<cffunction access="private" name="createJavaClass" returntype="any" output="false" hint="Return a java class from the crypto libraries">
<cfargument name="className" type="string" required="true"/>
<cfreturn getJavaLoader( ).create( arguments.className )/>
</cffunction>
... yada yada ... more code ...
I'm trying to create pagination for search results using MySQL and ColdFusion. My intention is to only retrieve the queries that can be displayed on a single page, thus making the process efficient. I tried using two queries in my function, but I could not return two variables to the cfinvoke.
The following code does not paginate, but it displays the result search results using a CFC:
<!---DEFINE DEFAULT STATE--->
<cfparam name="variables.searchResponse" default="">
<cfparam name="URL.titleName" default="">
<cfparam name="URL.genreID" default="">
<cfparam name="URL.platformID" default="">
<!---TitleName can only be blank if one or both genre and platform are selected--->
<cfif StructKeyExists(URL, "searchQuery") AND (Len(Trim(URL.titleName)) LTE 2 AND Len(URL.genreID) IS 0 AND Len(URL.platformID) IS 0)>
<cfset variables.searchResponse = "invalidString">
<cfelseif StructKeyExists(URL, "searchQuery")>
<cfinvoke component="gz.cfcomp.test" method="searchGames" returnvariable="resultData" argumentcollection="#URL#">
<cfset variables.searchResponse = "hasResult">
</cfif>
<cfif searchResponse EQ "hasResult" AND resultData.RecordCount EQ 0>
<cfset variables.searchResponse = "noResult">
</cfif>
Using this logic, I can display what I need to display on the page:
<cfif searchResponse EQ "invalidString">
<cfoutput>Invalid search</cfoutput>
</cfif>
<cfif searchResponse EQ "noResult">
<cfoutput>No results found</cfoutput>
</cfif>
<cfif searchResponse EQ "hasResult">
<cfoutput>Display Results</cfoutput>
</cfif>
If I were executing the queries on the same page, it would be easy to follow the many tutorials out there. But the queries are executing in a function. Displaying the data is easy, but paginating it has become a nightmare for me. Here is my function:
<cffunction name="searchGames" access="public" output="false">
<cfargument name="titleName" required="no" type="string">
<cfargument name="genreID" required="no" type="string">
<cfargument name="platformID" required="no" type="string">
<!--- DEFINE LOCAL VARIABLES--->
<cfset var resultData = "">
<!---GET DATA--->
<cfquery name="resultData" datasource="myDSN">
SELECT *
<!---JOINS FOR GENRE/PLATFORM GO HERE--->
WHERE
<!---CONDITIONS GO HERE--->
</cfquery>
<!---RETURN VARIABLE--->
<cfreturn resultData>
</cffunction>
To paginate, I thought about modifying my function to the following (a new query using a count statement):
<!--- DEFINE LOCAL VARIABLES--->
<cfset var resultCount = "">
<!---GET DATA--->
<cfquery name="resultCount" datasource="myDSN">
SELECT COUNT(gameID) AS rowsFound FROM GAMES
<!---JOINS FOR GENRE/PLATFORM GO HERE--->
WHERE
<!---CONDITIONS GO HERE--->
</cfquery>
<!---RETURN VARIABLE--->
<cfreturn resultCount>
Then I figured if there is a result to return, I would execute a nested query and create the pagination variables:
<cfif resultCount.rowsFound GTE 0>
<cfparam name="pageNumber" default="1">
<cfset var recordsPerPage = 5>
<cfset var numberOfPages = Int(resultCount.RecordCount / recordsPerPage)>
<cfset var recordsToSkip = pageNumber * recordsPerPage - recordsPerPage>
<!---DEFINE LOCAL VARIABLE--->
<cfset var resultData = "">
<cfquery name="resultData" datasource="myDSN">
<!---GET DATA AND SEND IT BACK USING LIMIT WITH #recordsToSkip# and #RecordsPerPage#--->
</cfquery>
<!---RETURN VARIABLE--->
<cfreturn resultData>
</cffunction>
I figured I would return two variables: resultCount and resultData. I would use #resultCount# to build my pagination, and #resultData# to display the output. The problem is I can't return two variables in the same cfinvoke tag. Any ideas of how to approach the the right way? I'm totally lost as to the logic I need to follow.
EDIT: I'm using the following code to paginate now (the only problem is now I have to repass all the search filters back into the URL because using #CGI.SCRIPT_NAME# clears them):
<cfif searchResponse EQ "hasResult">
<!---BASICALLY, IF resultCount.rowsFound is not 0, execute this method--->
<cfinvoke component="gz.cfcomp.test" method="getResult" returnvariable="resultData" argumentcollection="#URL#">
<cfif URL.currentPage IS 1>
--
<cfelse>
Prev Page
</cfif>
<cfif URL.currentPage * recordsPerPage LT resultCount.rowsFound>
Next Page
<cfelse>
--
</cfif>
</cfif>
If your results is not huge, you can stay with the same SQL that returns everything and use
<cfoutput query="data" startrow="#url.start#" maxrows="#recordsPerPage#">
when you display it, see: http://www.coldfusionjedi.com/index.cfm/2006/4/24/ColdFusion-and-Pagination. No Query of Query is needed.
To answer your question
The problem is I can't return two
variables in the same cfinvoke tag.
WHY do you want to return two variables in the same cfinvoke? Instead, write 2 functions: countResult() and getResultData(page, RecordsPerPage)
<cffunction name="countResult" output="false" returntype="numeric">
<cfset var resultCount = "">
<cfquery name="resultCount" datasource="myDSN">
SELECT COUNT(gameID) AS rowsFound FROM GAMES
<!---JOINS FOR GENRE/PLATFORM GO HERE--->
WHERE
<!---CONDITIONS GO HERE--->
</cfquery>
<cfreturn resultCount.rowsFound>
</cffunction>
For getResultData(page, RecordsPerPage) using true paging in DB level:
If you want to do true pagnation in DB level, use LIMIT and OFFSET in MySQL.
<cffunction name="getResultData" output="false" returntype="Query">
<cfargument name="page" type="numeric" default="1">
<cfargument name="recordsPerPage" type="numeric" default="5">
<cfset var resultData = "">
<cfset var offset = (page-1) * RecordsPerPage>
<cfquery name="resultData" datasource="myDSN">
SELECT * LIMIT #recordsPerPage# OFFSET #offset#
<!---JOINS FOR GENRE/PLATFORM GO HERE--->
WHERE
<!---CONDITIONS GO HERE--->
</cfquery>
<cfreturn resultData>
</cffunction>
To figure out how many pages there are:
totalNumOfPages = ceiling(countResult() / recordsPerPage);
Any other question?
Rather than have two functions and two database calls, I've done it like this before (not in MySQL however):
<cffunction name="getResultData" output="false" returntype="Query">
<cfargument name="page" type="numeric" default="1">
<cfargument name="recordsPerPage" type="numeric" default="5">
<cfset var resultData = "">
<cfset var offset = (page-1) * RecordsPerPage>
<cfquery name="resultData" datasource="myDSN">
SELECT *,
(
SELECT COUNT(gameID) AS rowsFound
FROM
<!---JOINS FOR GENRE/PLATFORM GO HERE--->
WHERE
<!---CONDITIONS GO HERE--->
) AS rowsFound
LIMIT #recordsPerPage# OFFSET #offset#
<!---JOINS FOR GENRE/PLATFORM GO HERE--->
WHERE
<!---CONDITIONS GO HERE--->
</cfquery>
<cfreturn resultData>
</cffunction>
It adds a column to the returned recordset called 'rowsFound'.
Not very normalized, but not a big deal. Might be worth it to minimize the DB hits.
I think it's referred to as 'subquery as a scalar operand':
http://dev.mysql.com/doc/refman/5.1/en/scalar-subqueries.html
Tony
I have a Function in CFC file, which will be called from .cfm file like below
<cffunction name="cftest" access="public" returntype="query" output="true" hint="Function returns Records">
<cfquery name="qryTest" datasource="DBTest">
select * from emp_tab;
</cfquery>
<cfreturn selectRecordsResultSet />
</cffunction>
How can I handle DB Exception using cftry? as this is returning Query, Is it possible to catch the DB Exception and pass the Details to the other page from where it is called?
Thanks
Here is an example of my usual implementation of this:
<cffunction name="getCurrentRecordsCount" access="public" output="false" returntype="any" hint="Get total history records count">
<cfargument name="filters" type="struct" required="false" default="#StructNew()#" hint="Filtering rules">
<cfset var qGetRecordCount = "" />
<cftry>
<cfquery datasource="#variables.dsn#" name="qGetRecordCount">
SELECT COUNT(*) AS cnt FROM ....
</cfquery>
<cfreturn qGetRecordCount.cnt />
<cfcatch type="any">
<cfreturn error(cfcatch.message, cfcatch.detail) />
</cfcatch>
</cftry>
</cffunction>
If you want to handle only database errors, change type to the database.
Errors handling and reporting performed using these three methods:
<cffunction name="error" access="private" output="false" returntype="boolean" hint="Set error status and message">
<cfargument name="message" type="string" required="true" hint="Error message text" />
<cfargument name="detail" type="string" required="false" default="" hint="Error detail text" />
<cfset variables.fError = true />
<cfset variables.fErrorText = arguments.message />
<cfif Len(arguments.detail)>
<cfset variables.fErrorText = variables.fErrorText & " [" & arguments.detail & "]" />
</cfif>
<cfreturn false />
</cffunction>
<cffunction name="gotError" access="public" output="false" returntype="boolean" hint="Return latest error flag state">
<cfreturn variables.fError />
</cffunction>
<cffunction name="getError" access="public" output="false" returntype="string" hint="Return latest error text and reset the flag">
<cfset var txt = variables.fErrorText />
<cfset variables.fError = false />
<cfset variables.fErrorText = "" />
<cfreturn txt />
</cffunction>
Please note that for methods with returntype="void" I use cfset instead of cfreturn:
<cfset error(cfcatch.message, cfcatch.detail) />
So in code I can do the following (cfscript):
// calculate filtered records count
totalLogCount = request.loggingService.getCurrentRecordsCount(filters);
// check if error was thrown
if (request.loggingService.gotError()) {
// report the error details somehow
WriteOutput(request.loggingService.getError());
}