CRUD stands for Create, Retrieve, Update and Delete.
I've talked with other developers where I work about this....
"Script injection attack" is mentioned in ColdFusion Developer's Journal:
http://coldfusion.sys-con.com/read/45571.htm
Basically it's this:
A web page's address could be something like this:
http://www.myDomain.com/Question.cfm?ID=3
Which would then be used in ColdFusion like this:
SELECT * FROM Question WHERE QuestionID = #
URL.ID#
This is a normal thing to do, and a lot of Applications use this type of
addressing.
The problem is that a malicious hacker could append a valid SQL command onto
the end of the web address.
http://www.myDomain.com/Question.cfm?ID=3;DELETE * FROM CUST
This would have the effect of issuing the following two (2) SQL commands:
SELECT * FROM Question WHERE QuestionID = 3
DELETE * FROM CUST
'nough said.
To combat this, there are several things that can be done
A) Use method="post" instead of method="get".
B) Use the cfqueryparam tag (check to make sure the URL parameter is
numeric).
C) Allow only stored procedures.
The problem with (A) is it's not fool-proof against a determined attacker.
The problem with (B) is that SQL Server is still relying upon someone else
to ensure its own security. If I were SQL Server,
I would want to provide my own security, and not have to rely upon the
application to validate parameters. And if SQL Server
is to validate parameters, it has to use a stored procedure.
The CFDJ article says to turn everything off except stored procedures.
So instead of:
<cfquery name="Question" datasource="#Application.DSN#">
SELECT * FROM Question
</cfquery>
we now have to say:
<cfstoredproc procedure="QuestionSelect" datasource="#Application.DSN#"
debug="yes">
<cfprocresult name="Question">
</cfstoredproc>
So how to create a generic CRUD form that doesn't use cfquery?
Here are 6 SQL Server stored procedures A) Select, B) Update, C) Insert, D)
Delete.
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
CREATE PROC dbo.SQLServerSelect(
@TableName VARCHAR(255),
@GUID UniqueIdentifier,
@SQLSelect VARCHAR(255) OUTPUT
) AS
SET NOCOUNT ON
DECLARE
@Error INT
SET @SQLSelect = 'SELECT * FROM ' + @TableName
PRINT @GUID
IF @GUID IS NOT NULL
SET @SQLSelect = @SQLSelect + ' WHERE ' + @TableName + 'ID = ' +
QUOTENAME(@GUID,CHAR(39))
INSERT INTO dbo.Log (LogID, SPROC, LogDescription)
VALUES(NEWID(),'Select',@SQLSelect)
EXEC(@SQLSelect)
SET @Error = @@Error
IF @Error <> 0 GOTO ProcErr
RETURN 0
ProcErr:
BEGIN
RAISERROR('There was an error trying to SELECT', 16, 1)
RETURN -100
END
CREATE PROC dbo.SQLServerUpdate(
@TableName VARCHAR(128),
@GUID UniqueIdentifier,
@Log_TimeStmp Integer,
@SQLstr VARCHAR(8000) OUTPUT,
@RowCount Integer OUTPUT
) AS
SET NOCOUNT ON
DECLARE
@SQLSelect VARCHAR(320),
@Error Integer
SET @SQLstr = 'UPDATE ' + @TableName + ' SET ' + @SQLstr
+ ', Log_TimeStmp=Log_TimeStmp+1'
+ ' WHERE ' + @TableName + 'ID=' + QUOTENAME(@GUID,CHAR(39))
+ ' AND Log_TimeStmp=' + CAST(@Log_TimeStmp AS VARCHAR(10))
INSERT INTO dbo.Log (LogID, SPROC, LogDescription)
VALUES(NEWID(),'Update',@SQLStr)
BEGIN TRANSACTION
EXEC(@SQLstr)
SELECT @Error = @@ERROR, @RowCount = @@ROWCOUNT
IF @Error <> 0 GOTO ProcErr
COMMIT TRANSACTION
SET @SQLSelect = 'SELECT * FROM ' + @TableName
+ ' WHERE ' + @TableName + 'ID = ' + QUOTENAME(@GUID,CHAR(39))
-- SELECT @Log_TimeStmp=Log_TimeStmp doesn't seem to work
EXEC(@SQLSelect)
RETURN(0)
ProcErr:
BEGIN
ROLLBACK TRANSACTION
SET @RowCount = 0
RAISERROR('There was an error trying to UPDATE', 16, 1)
RETURN -100
END
CREATE PROC dbo.SQLServerInsert(
@TableName VARCHAR(255),
@FieldsInserted VARCHAR(8000),
@ValuesInserted VARCHAR(8000),
@ID UniqueIdentifier OUTPUT,
@RowCount Integer OUTPUT
) AS
SET NOCOUNT ON
DECLARE
@SQLInsert VARCHAR(8000),
@SQLSelect VARCHAR(320),
@GUID CHAR(36),
@Error INT
SET @GUID = NEWID()
SET @SQLInsert = 'INSERT INTO ' + @TableName + ' (' + @TableName + 'ID, ' +
@FieldsInserted + ') VALUES (' +
QUOTENAME(@GUID,CHAR(39)) + ', ' + @ValuesInserted + ')'
INSERT INTO dbo.Log (LogID, SPROC, LogDescription)
VALUES(NEWID(),'Insert',@SQLInsert)
BEGIN TRANSACTION
EXEC(@SQLInsert)
SELECT @Error = @@ERROR, @RowCount = @@ROWCOUNT
IF @Error <> 0 GOTO ProcErr
SET @SQLSelect = 'SELECT * FROM ' + @TableName + ' WHERE ' + @TableName +
'ID = ' + QUOTENAME(@GUID,CHAR(39))
EXEC(@SQLSelect)
SET @Error = @@Error
IF @Error <> 0 GOTO ProcErr
COMMIT TRANSACTION
SET @ID = @GUID
RETURN 0
ProcErr:
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error trying to INSERT', 16, 1)
RETURN -100
END
CREATE PROC dbo.SQLServerDelete(
@TableName VARCHAR(255),
@GUID UniqueIdentifier,
@SQLDelete VARCHAR(255) OUTPUT
) AS
SET NOCOUNT ON
DECLARE
@Error INT
SET @SQLDelete = 'DELETE FROM ' + @TableName + ' WHERE ' + @TableName + 'ID
= ' + QUOTENAME(@GUID,CHAR(39))
INSERT INTO dbo.Log (LogID, SPROC, LogDescription)
VALUES(NEWID(),'SQLServerDelete',@SQLDelete)
EXEC(@SQLDelete)
SET @Error = @@Error
IF @Error <> 0 GOTO ProcErr
RETURN 0
ProcErr:
BEGIN
RAISERROR('There was an error trying to DELETE', 16, 1)
RETURN -100
END
-- I think the following one could be done directly in ColdFusion, but
haven't taken the time to do it.
CREATE PROC dbo.getPrimaryKey(
@TableName VARCHAR(255)
)
AS
SET NOCOUNT ON
EXEC sp_pkeys @TableName
RETURN 0
-- And this one could probably be refactored into dbo.SQLServerSELECT:
CREATE PROC dbo.get(
@FieldName VARCHAR(255),
@TableName VARCHAR(255),
@SortOrder VARCHAR(255),
@Condition VARCHAR(255),
@SQLstr VARCHAR(8000) OUTPUT
) AS
SET NOCOUNT ON
DECLARE
@Error INT
SET @SQLStr = 'SELECT ' + @FieldName + ' FROM ' + @TableName
IF @Condition <> ''
SET @SQLStr = @SQLStr + ' WHERE ' + @Condition
IF @SortOrder <> ''
SET @SQLStr = @SQLStr + ' ORDER BY ' + @SortOrder
-- INSERT INTO dbo.Log (LogID, SPROC, LogDescription)
VALUES(NEWID(),'Get',@SQLStr)
EXEC(@SQLStr)
SET @Error = @@Error
IF @Error <> 0 GOTO ProcErr
RETURN 0
ProcErr:
BEGIN
RAISERROR('There was an error trying to GET data', 16, 1)
RETURN -100
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
These procedures are not ready for prime time. They make several
assumptions because of the way I program.
The major assumption is that every table has a GUID primary key whose
fieldname is the tablename followed by "ID".
OK, so how are they used?
Here are the ColdFusion CUSTOM TAGS that go with the SQL Server stored
procedures:
retrieve.cfm:
<cfset myName = "cf_retrieve">
<cfif NOT IsDefined("Attributes.DataSource")>
Usage: <#myName# <b>DataSource="myDataSource"</b>,
tableName="myTable"[, debug="yes"]><br />
<cfabort>
</cfif>
<cfif NOT IsDefined("Attributes.tableName")>
Usage: <#myName# DataSource="myDataSource",
tableName="<b>myTable</b>"[, debug="yes"]><br />
<cfabort>
</cfif>
<!--- Get the Primary Key --->
<cfstoredproc procedure="getPrimaryKey" datasource="#Attributes.DataSource#"
debug="yes" returncode="yes">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@TableName" value="#Attributes.tableName#">
<cfprocresult name="RS1">
</cfstoredproc>
<cfset GUID = Evaluate("caller.FORM.#RS1.Column_Name#")>
<!--- <cfstoredproc procedure="dbo.sp_pkeys" datasource="Master">
<cfprocparam type="IN" dbvarname="@table_name"
value="#Attributes.tableName#" cfsqltype="CF_SQL_VARCHAR">
<cfprocresult name="RS2">
</cfstoredproc>
<cfset GUID = Evaluate("caller.FORM.#RS2.Column_Name#")> --->
<cfif IsDefined("Attributes.Debug")>
<cfif Attributes.Debug EQ "yes">
<cfset DebugInfo1 = StructNew()>
<cfset DebugInfo1.PrimaryKey = RS1.Column_Name>
<cfset DebugInfo1.GUID = GUID>
<cfdump var="#DebugInfo1#" label="#myName# before the stored
procedure">
</cfif>
</cfif>
<!--- This SELECT procedure will work with any table --->
<cftry>
<cfstoredproc procedure="SQLServerSelect"
datasource="#Attributes.DataSource#" debug="yes" returncode="yes">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@TableName" value="#Attributes.tableName#">
<cfprocparam type="in" cfsqltype="cf_sql_char" maxlength="36"
dbvarname="@GUID" value="#GUID#">
<cfprocparam type="out" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@SQLSelect" variable=SQLSelect>
<cfprocresult name="SQLServerSelect">
</cfstoredproc>
<cfcatch type="database">
<cfinvoke component="Components.Gendbs" method="TrimErrorMessage"
errormessage="#cfcatch.detail#"
returnvariable="CALLER.Variables.HeaderMessage" />
<cfexit>
</cfcatch>
</cftry>
<cfif IsDefined("Attributes.Debug")>
<cfif Attributes.Debug EQ "yes">
<cfset DebugInfo2 = StructNew()>
<cfset DebugInfo2.statusCode = cfstoredproc.statusCode>
<cfset DebugInfo2.SQLSelect = SQLSelect>
<cfset DebugInfo2.RecordsSelected =
SQLServerSelect.RecordCount>
<cfset DebugInfo2.ColumnsSelected =
SQLServerSelect.ColumnList>
<cfdump var="#DebugInfo2#" label="#myName# after the stored
procedure">
</cfif>
</cfif>
<!--- Now populate the FORM fields from the database --->
<cfobject name="BitColumnsObj" component="Components.Gendbs">
<cfset BitColumnsQry = BitColumnsObj.Select(
FieldName="SYSCOLUMNS.Name",
TableName="SYSCOLUMNS JOIN SYSOBJECTS ON
SYSCOLUMNS.ID =
SYSOBJECTS.ID",
Condition="
SYSOBJECTS.NAME = '#Attributes.tableName#' AND SYSCOLUMNS.xtype
IN (104)",
SortOrder="ColID",
DataSource="#Attributes.DataSource#")>
<cfset BitColumns = VALUELIST(BitColumnsQry.Name)>
<cfloop index="FieldName" list=#SQLServerSelect.ColumnList#>
<cfset X = Evaluate("SQLServerSelect.#FieldName#")>
<cfif LISTFINDNOCASE(BitColumns,FieldName)>
<cfif X EQ 0>
<cfset X = false>
<cfelse>
<cfset X = true>
</cfif>
</cfif>
<cfset "caller.FORM.#FieldName#" = X>
<cfset "caller.SAVE.#FieldName#" = X>
</cfloop>
update.cfm:
<cfset myName = "cf_update">
<cfif NOT IsDefined("Attributes.DataSource")>
Usage: <#myName# <b>DataSource="myDataSource"</b>,
tableName="myTable"[, debug="yes"]><br />
<cfabort>
</cfif>
<cfif NOT IsDefined("Attributes.tableName")>
Usage: <#myName# DataSource="myDataSource",
tableName="<b>myTable</b>"[, debug="yes"]><br />
<cfabort>
</cfif>
<!--- Get the Primary Key Value --->
<cfstoredproc procedure="getPrimaryKey" datasource="#Attributes.DataSource#"
debug="yes" returncode="yes">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@TableName" value="#Attributes.tableName#">
<cfprocresult name="RS1">
</cfstoredproc>
<cfset GUID = Evaluate("caller.FORM.#RS1.Column_Name#")>
<!--- <cfstoredproc procedure="dbo.sp_pkeys" datasource="Master">
<cfprocparam type="IN" dbvarname="@table_name"
value="#Attributes.tableName#" cfsqltype="CF_SQL_VARCHAR">
<cfprocresult name="RS2">
</cfstoredproc>
<cfset GUID = Evaluate("caller.FORM.#RS2.Column_Name#")> --->
<cfobject name="ListofColumnsObj" component="Components.Gendbs">
<cfset ListofColumnsQry = ListofColumnsObj.Select(FieldName="TOP 1
*",TableName="#Attributes.tableName#",Condition="",SortOrder="",DataSource="
#Attributes.DataSource#")>
<cfset ListofColumns = ListofColumnsQry.ColumnList>
<cfset X = LISTFINDNOCASE(ListofColumns,Attributes.tableName & "ID")>
<cfif X>
<cfset ListofColumns = LISTDELETEAT(ListofColumns,X)>
</cfif>
<cfset X = LISTFINDNOCASE(ListofColumns,"Log_TimeStmp")>
<cfif X>
<cfset ListofColumns = LISTDELETEAT(ListofColumns,X)>
<cfelse>
<cfset CALLER.Variables.HeaderMessage = "No Log_TimeStmp found in "
& Attributes.tableName>
<cfexit>
</cfif>
<cfobject name="StringColumnsObj" component="Components.Gendbs">
<cfset StringColumnsQry = StringColumnsObj.Select(
FieldName="SYSCOLUMNS.Name",
TableName="SYSCOLUMNS JOIN SYSOBJECTS ON
SYSCOLUMNS.ID =
SYSOBJECTS.ID",
Condition="
SYSOBJECTS.NAME = '#Attributes.tableName#' AND SYSCOLUMNS.xtype
IN (35,36,99,167,175,231,239)",
SortOrder="ColID",
DataSource="#Attributes.DataSource#")>
<cfset StringColumns = VALUELIST(StringColumnsQry.Name)>
<cfobject name="BitColumnsObj" component="Components.Gendbs">
<cfset BitColumnsQry = BitColumnsObj.Select(
FieldName="SYSCOLUMNS.Name",
TableName="SYSCOLUMNS JOIN SYSOBJECTS ON
SYSCOLUMNS.ID =
SYSOBJECTS.ID",
Condition="
SYSOBJECTS.NAME = '#Attributes.tableName#' AND SYSCOLUMNS.xtype
IN (104)",
SortOrder="ColID",
DataSource="#Attributes.DataSource#")>
<cfset BitColumns = VALUELIST(BitColumnsQry.Name)>
<!--- Build a list such as: A=1, Description='Chopped Liver', C=3 --->
<cfset FieldList = "">
<cfloop index="FieldName" list="#CALLER.FORM.FieldNames#">
<cfif LISTFINDNOCASE(ListofColumns,FieldName)>
<cfset X = Evaluate("Caller.FORM.#FieldName#")>
<cfif LISTFINDNOCASE(StringColumns,FieldName)>
<cfset X = "'" & X & "'">
<cfelseif LISTFINDNOCASE(BitColumns,FieldName) AND (X EQ false)>
<cfset X = 0>
<cfelseif LISTFINDNOCASE(BitColumns,FieldName) AND (X EQ true)>
<cfset X = 1>
</cfif>
<cfif Len(X) GT 0>
<!--- What if they're nulling a field? --->
<cfset FieldList = FieldList & FieldName & "=" & X & ', '>
</cfif>
</cfif>
</cfloop>
<cfset FieldList = Left(FieldList,Len(FieldList)-2)>
<cfset RowCount = 0>
<cfset Log_TimeStmp = CALLER.FORM.Log_TimeStmp>
<cfif IsDefined("Attributes.Debug")>
<cfif Attributes.Debug EQ "yes">
<cfoutput>Update #Attributes.tableName# SET
Log_TimeStmp=Log_TimeStmp+1,<br>
#FieldList#<br>
WHERE #Attributes.tableName#ID='#GUID#'
AND Log_TimeStmp = #Log_TimeStmp#
</cfoutput>
</cfif>
</cfif>
<!--- This UDPATE procedure will now work with any table, any list of fields
--->
<cfset caller.FORM.RowCount = 0>
<cftry>
<cfparam name="TableName" default="">
<cfparam name="GUID" default="">
<cfparam name="Log_TimeStmp" default="">
<cfstoredproc procedure="dbo.SQLServerUpdate"
datasource="#Attributes.DataSource#" debug="yes" returncode="YES">
<cfprocparam type="IN" dbvarname="@TableName"
value="#Attributes.tableName#" cfsqltype="CF_SQL_VARCHAR">
<cfprocparam type="IN" dbvarname="@GUID" value="#GUID#"
cfsqltype="CF_SQL_CHAR" maxlength="36">
<cfprocparam type="IN" dbvarname="@Log_TimeStmp"
value="#Log_TimeStmp#" cfsqltype="CF_SQL_INTEGER">
<cfprocparam type="INOUT" dbvarname="@SQLstr" value="#FieldList#"
variable="SQLstr" cfsqltype="CF_SQL_VARCHAR">
<cfprocparam type="OUT" dbvarname="@RowCount" variable="RowCount"
cfsqltype="CF_SQL_INTEGER">
<cfprocresult name="SQLSelect">
</cfstoredproc>
<cfcatch type="database">
<cfinvoke component="Components.Gendbs" method="TrimErrorMessage"
errormessage="#cfcatch.detail#"
returnvariable="CALLER.Variables.HeaderMessage" />
<cfexit>
</cfcatch>
<cfcatch type="any">
<cfrethrow>
</cfcatch>
</cftry>
<cfif RowCount EQ 0>
<cfset CALLER.Variables.HeaderMessage = "Could not update.">
<cfloop index="I" from="1" to="#ListLen(ListofColumns)#">
<cfset FieldName = ListGetAt(ListofColumns,I)>
<cfif ListFind(CALLER.FORM.FieldNames,FieldName)>
<cfset X = Evaluate("Caller.FORM.#FieldName#")>
<cfset Y = Evaluate("SQLSelect.#FieldName#")>
<cfif X NEQ Y>
<cfset CALLER.Variables.HeaderMessage =
"Could not update - " & FieldName & " was changed to '" & Y & "' by someone
else.">
<cfbreak>
</cfif>
</cfif>
</cfloop>
</cfif>
<cfset caller.FORM.RowCount = RowCount>
<cfset caller.FORM.Log_TimeStmp = SQLSelect.Log_TimeStmp>
<!--- <cfif IsDefined("Attributes.Debug")>
<cfif Attributes.Debug EQ "yes">
<cfset DebugInfo2 = StructNew()>
<cfset DebugInfo2.StatusCode = cfstoredproc.statusCode>
<cfdump var="#DebugInfo2#" label="#myName# after the stored
procedure">
</cfif>
</cfif> --->
addnew.cfm:
<cfset myName = "cf_addnew">
<cfif NOT IsDefined("Attributes.DataSource")>
Usage: <#myName# <b>DataSource="myDataSource"</b>,
tableName="myTable"[, debug="yes"]><br />
<cfabort>
</cfif>
<cfif NOT IsDefined("Attributes.tableName")>
Usage: <#myName# DataSource="myDataSource",
tableName="<b>myTable</b>"[, debug="yes"]><br />
<cfabort>
</cfif>
<cfobject name="ListofColumnsObj" component="Components.Gendbs">
<cfset ListofColumnsQry = ListofColumnsObj.Select(FieldName="TOP 1
*",TableName="#Attributes.tableName#",Condition="",SortOrder="",DataSource="
#Attributes.DataSource#")>
<cfset ListofColumns = ListofColumnsQry.ColumnList>
<cfset X = LISTFINDNOCASE(ListofColumns,Attributes.tableName & "ID")>
<cfif X>
<cfset ListofColumns = LISTDELETEAT(ListofColumns,X)>
</cfif>
<cfobject name="StringColumnsObj" component="Components.Gendbs">
<cfset StringColumnsQry = StringColumnsObj.Select(
FieldName="SYSCOLUMNS.Name",
TableName="SYSCOLUMNS JOIN SYSOBJECTS ON
SYSCOLUMNS.ID =
SYSOBJECTS.ID",
Condition="
SYSOBJECTS.NAME = '#Attributes.tableName#' AND SYSCOLUMNS.xtype
IN (35,36,99,167,175,231,239)",
SortOrder="ColID",
DataSource="#Attributes.DataSource#")>
<cfset StringColumns = VALUELIST(StringColumnsQry.Name)>
<cfobject name="BitColumnsObj" component="Components.Gendbs">
<cfset BitColumnsQry = BitColumnsObj.Select(
FieldName="SYSCOLUMNS.Name",
TableName="SYSCOLUMNS JOIN SYSOBJECTS ON
SYSCOLUMNS.ID =
SYSOBJECTS.ID",
Condition="
SYSOBJECTS.NAME = '#Attributes.tableName#' AND SYSCOLUMNS.xtype
IN (104)",
SortOrder="ColID",
DataSource="#Attributes.DataSource#")>
<cfset BitColumns = VALUELIST(BitColumnsQry.Name)>
<cfset FieldsInserted = "">
<cfset ValuesInserted = "">
<cfloop index="FieldName" list="#CALLER.FORM.FieldNames#">
<cfif LISTFINDNOCASE(ListofColumns,FieldName)>
<cfset X = Evaluate("Caller.FORM.#FieldName#")>
<cfif Len(X) GT 0>
<cfif LISTFINDNOCASE(StringColumns,FieldName)>
<cfset X = "'" & X & "'">
<cfelseif LISTFINDNOCASE(BitColumns,FieldName) AND
(X EQ false)>
<cfset X = 0>
<cfelseif LISTFINDNOCASE(BitColumns,FieldName) AND
(X EQ true)>
<cfset X = 1>
</cfif>
<cfset FieldsInserted = FieldsInserted & FieldName & ", ">
<cfset ValuesInserted = ValuesInserted & X & ", ">
</cfif>
</cfif>
</cfloop>
<cfset FieldsInserted = Left(FieldsInserted,Len(FieldsInserted)-2)>
<cfset ValuesInserted = Left(ValuesInserted,Len(ValuesInserted)-2)>
<cfif IsDefined("Attributes.Debug")>
<cfif Attributes.Debug EQ "yes">
<cfoutput>INSERT INTO #Attributes.tableName#
(#Attributes.tableName#ID,<br>
#FieldsInserted#<br>
) VALUES (NEWID(),<br>
#ValuesInserted#)
</cfoutput>
</cfif>
</cfif>
<!--- This INSERT procedure will now work with any table, any list of fields
--->
<cftry>
<cfstoredproc procedure="SQLServerInsert"
datasource="#Attributes.DataSource#" debug="yes" returncode="yes">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@TableName" value="#Attributes.tableName#">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" maxlength="8000"
dbvarname="@FieldsInserted" value="#FieldsInserted#">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" maxlength="8000"
dbvarname="@ValuesInserted" value="#ValuesInserted#">
<cfprocparam type="out" cfsqltype="cf_sql_char" maxlength="36"
dbvarname="@ID" variable="ID">
<cfprocparam type="out" cfsqltype="cf_sql_integer"
dbvarname="@RowCount" variable="RowCount">
<!--- <cfprocparam type="out" cfsqltype="cf_sql_varchar"
maxlength="255" dbvarname="@SQLInsert" variable=SQLInsert>
<cfprocparam type="out" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@SQLSelect" variable=SQLSelect> --->
<cfprocresult name="SQLServerSelect">
</cfstoredproc>
<cfcatch type="database">
<cfinvoke component="Components.Gendbs" method="TrimErrorMessage"
errormessage="#cfcatch.detail#"
returnvariable="CALLER.Variables.HeaderMessage" />
<cfexit>
</cfcatch>
<cfcatch type="any">
<cfrethrow>
</cfcatch>
</cftry>
<!--- <cfif IsDefined("Attributes.Debug")>
<cfif Attributes.Debug EQ "yes">
<cfset DebugInfo2 = StructNew()>
<!--- <cfset DebugInfo2.SQLInsert = SQLInsert> --->
<cfset DebugInfo2.statusCode = cfstoredproc.statusCode>
<cfset DebugInfo2.RowCount = RowCount>
<!--- <cfset DebugInfo2.SQLSelect = SQLSelect> --->
<cfset DebugInfo2.RecordsSelected =
SQLServerSelect.RecordCount>
<cfset DebugInfo2.ColumnsSelected =
SQLServerSelect.ColumnList>
<cfdump var="#DebugInfo2#" label="#myName# after the stored
procedure">
</cfif>
</cfif> --->
<!--- Now populate the FORM fields with any defaults from the database --->
<cfset "caller.FORM.#Attributes.tableName#ID" = ID>
<cfloop index="FieldName" list=#SQLServerSelect.ColumnList#>
<cfset "caller.FORM.#FieldName#" =
Evaluate("SQLServerSelect.#FieldName#")>
</cfloop>
delete.cfm:
<cfset myName = "cf_delete">
<cfif NOT IsDefined("Attributes.DataSource")>
Usage: <#myName# <b>DataSource="myDataSource"</b>,
tableName="myTable"[, debug="yes"]><br />
<cfabort>
</cfif>
<cfif NOT IsDefined("Attributes.tableName")>
Usage: <#myName# DataSource="myDataSource",
tableName="<b>myTable</b>"[, debug="yes"]><br />
<cfabort>
</cfif>
<!--- Get the Primary Key --->
<cfstoredproc procedure="getPrimaryKey" datasource="#Attributes.DataSource#"
debug="yes" returncode="yes">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@TableName" value="#Attributes.tableName#">
<cfprocresult name="RS1">
</cfstoredproc>
<cfset GUID = Evaluate("caller.FORM.#RS1.Column_Name#")>
<cfif IsDefined("Attributes.Debug")>
<cfif Attributes.Debug EQ "yes">
<cfset DebugInfo1 = StructNew()>
<cfset DebugInfo1.PrimaryKey = RS1.Column_Name>
<cfset DebugInfo1.GUID = GUID>
<cfdump var="#DebugInfo1#" label="#myName# before the stored
procedure">
</cfif>
</cfif>
<!--- This SELECT procedure will work with any table --->
<cftry>
<cfstoredproc procedure="SQLServerDelete"
datasource="#Attributes.DataSource#" debug="yes" returncode="yes">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@TableName" value="#Attributes.tableName#">
<cfprocparam type="in" cfsqltype="cf_sql_char" maxlength="36"
dbvarname="@GUID" value="#GUID#">
<cfprocparam type="out" cfsqltype="cf_sql_varchar" maxlength="255"
dbvarname="@SQLDelete" variable=SQLDelete>
</cfstoredproc>
<cfcatch type="database">
<cfinvoke component="Components.Gendbs" method="TrimErrorMessage"
errormessage="#cfcatch.detail#"
returnvariable="CALLER.Variables.HeaderMessage" />
<cfexit>
</cfcatch>
</cftry>
<cfif IsDefined("Attributes.Debug")>
<cfif Attributes.Debug EQ "yes">
<cfset DebugInfo2 = StructNew()>
<cfset DebugInfo2.statusCode = cfstoredproc.statusCode>
<cfset DebugInfo2.SQLDelete = SQLDelete>
<cfdump var="#DebugInfo2#" label="#myName# after the stored
procedure">
</cfif>
</cfif>
<cfset caller.sHeaderMessage = "Record Deleted">
<cfset "caller.FORM.#RS1.Column_Name#" = "">
And finally, a component!
Gendbs.cfc:
<cfcomponent>
<!---
gendbs - Generic database commands
gendbs - Generate database commands
You decide --->
<cffunction name="TrimErrorMessage" access="public" returntype="string">
<cfargument name="errorMessage" type="string" required="yes">
<cfset var result = arguments.errorMessage>
<cfset result = REPLACE(result,'[Macromedia]','','ALL')>
<cfset result = REPLACE(result,'[SQLServer JDBC Driver]','','ALL')>
<cfset result = REPLACE(result,'[SQLServer]','','ALL')>
<!---
<cfset result = REPLACE(result,'UPDATE statement conflicted with
COLUMN CHECK constraint','Invalid','ALL')>
<cfset result = REPLACE(result,'. The conflict occurred in
database','','ALL')> --->
<cfreturn result>
</cffunction>
<cffunction name="Select" output="true">
<cfargument name="FieldName" type="string" default="*">
<cfargument name="TableName" type="string" required="yes">
<cfargument name="Condition" type="string" default="">
<cfargument name="SortOrder" type="string" default="">
<cfargument name="DataSource" type="string" required="yes"> <!---
This routine is too generic to allow a default DataSource --->
<cftry>
<cfstoredproc procedure="dbo.get" datasource="#DataSource#"
debug="yes">
<cfprocparam type="in" cfsqltype="cf_sql_varchar"
maxlength="255" dbvarname="@FieldName" value="#FieldName#">
<cfprocparam type="in" cfsqltype="cf_sql_varchar"
maxlength="255" dbvarname="@TableName" value="#TableName#">
<cfprocparam type="in" cfsqltype="cf_sql_varchar"
maxlength="255" dbvarname="@SortOrder" value="#SortOrder#">
<cfprocparam type="in" cfsqltype="cf_sql_varchar"
maxlength="255" dbvarname="@Condition" value="#Condition#">
<cfprocparam type="out" cfsqltype="cf_sql_varchar"
maxlength="8000" dbvarname="@SQLStr" variable=SQLStr>
<cfprocresult name="getRecordSet">
</cfstoredproc>
<cfcatch type="database">
<cfinvoke component="Components.Gendbs"
method="TrimErrorMessage" errormessage="#cfcatch.detail#"
returnvariable="CALLER.Variables.HeaderMessage" />
<cfexit>
</cfcatch>
<cfcatch type="any">
<cfrethrow>
</cfcatch>
</cftry>
<!--- <cfoutput>Database: #SQLStr#</cfoutput> --->
<cfreturn getRecordset>
</cffunction>
</cfcomponent>
That's a lot of code and I still haven't wrapped it all up yet.
I'll wrap it up in the next post.