anchor.diff:  	<cfproperty name="dbversion" type="string">
   	<cfproperty name="writeTransactionLog" type="boolean">
   	<cfset _resetCriteria() />
  -	<cffunction name="init" access="public" returntype="DAO" output="false" displayname="DAO Constructor" hint="I initialize DAO.">
  -		<cfargument name="dsn" type="string" required="true" hint="Data Source Name" />
  -		<cfargument name="dbtype" type="string" required="false" hint="Database Type" default="" />
  -		<cfargument name="user" type="string" required="false" default="" hint="Data Source User Name" />
  -		<cfargument name="password" type="string" required="false" default="" hint="Data Source Password" />
  -		<cfargument name="writeTransactionLog" type="boolean" required="false" hint="Write transactions to log (for replication)?" default="false" />
  -		<cfargument name="transactionLogFile" type="string" required="false" hint="Location to write the transaction log" default="#expandPath('/')#sql_transaction_log.sql" />
  -		<cfargument name="useCFQueryParams" type="boolean" required="false" hint="Determines if execute queries will use cfqueryparam" default="true" />
  -
  -		<cfscript>
  +	<cfscript>
  +
  +		/**
  +		* I initialize DAO
  +		* @dsn Data Source Name
  +		* @dbtype Database Type
  +		* @user Data Source User Name
  +		* @password Data Source Password
  +		* @writeTransactionLog Write transactions to log (for replication)
  +		* @transactionLogFile Location to write the transaction log
  +		* @useCFQueryParams Determines if execute queries will use cfqueryparam
  +		**/
  +		public DAO function init( required string dsn, string dbtype = "", string user = "", string password ="", boolean writeTransactionLog = false, string transactionLogFile = "#expandPath('/')#sql_transaction_log.sql", boolean useCFQueryParams = true ){
   
   			//This is the datasource name for the system
   			variables.dsn = arguments.dsn;
  
positive.code: 
  			variables.tabledefs = {};
  
  		</cfscript>
  
  		<cfreturn this />
  	</cffunction>
  
  	<cffunction name="getConnecterType">
  
  		<cfswitch expression="#variables.dbVersion.database_productname#">
  			<cfcase value="Microsoft SQL Server">
  				<cfreturn "mssql"/>
  			</cfcase>
  			<cfcase value="MySQL">
  				<cfreturn "mysql"/>
  			</cfcase>
  		</cfswitch>
  
  	</cffunction>
  
  	<cffunction name="getLastID" hint="I return the ID of the last inserted record." returntype="any" output="true">
  
  		<cfset var ret = this.conn.getLastID()/>
  
  		<cfreturn ret />
  	</cffunction>
  
  	<cffunction name="read" hint="I read from the database. I take either a tablename or sql statement as a parameter." returntype="any" output="false">
  		<cfargument name="sql" required="false" type="string" default="" hint="Either a tablename or full SQL statement.">
  		<cfargument name="name" required="false" type="string" hint="Name of Query (required for cachedwithin)" default="ret_#listFirst(createUUID(),'-')#_#getTickCount()#">
  		<cfargument name="QoQ" required="false" type="struct" hint="Struct containing query object for QoQ" default="#{}#">
  		<cfargument name="cachedwithin" required="false" type="any" hint="createTimeSpan() to cache this query" default="">
  		<cfargument name="table" required="false" type="string" default="" hint="Table name to select from, use only if not using SQL">
  		<cfargument name="columns" required="false" type="string" default="" hint="List of valid column names for select statement, use only if not using SQL">
  		<cfargument name="where" required="false" type="string" hint="Where clause. Only used if sql is a tablename" default="">
  		<cfargument name="limit" required="false" type="any" hint="Limit records returned.  Only used if sql is a tablename" default="">
  		<cfargument name="offset" required="false" type="any" hint="Offset queried recordset.  Only used if sql is a tablename" default="">
  		<cfargument name="orderby" required="false" type="string" hint="Order By columns.  Only used if sql is a tablename" default="">
  
  
  		<cfset var tmpSQL = "" />
  		<cfset var tempCFSQLType = "" />
  		<cfset var tempValue = "" />
  		<cfset var tmpName = "" />
  		<cfset var idx = "" />
  		<cfset var LOCAL = {} />
  
  		<cfif !len( trim( arguments.sql ) ) && !len( trim( arguments.table ) )>
  			<cfthrow message="You must pass in either a table name or sql statement." />
  		</cfif>
  
  		<cfif listlen( arguments.sql, ' ') EQ 1 && !len( trim( arguments.table ) )>
  			<cfset arguments.table = arguments.sql/>
  		</cfif>
  
  		<!--- <cftry> --->
  			<cfif len( trim( arguments.sql ) ) || len( trim( arguments.table ) )>
  				<cftimer label="Query: #arguments.name#" type="debug">
  				<cfif !structKeyExists( arguments.QoQ, 'query' ) >
  						<cfif listlen(arguments.sql, ' ') GT 1>
  							<cfif len(trim(arguments.cachedwithin))>
  									<!---
  										We need to parse the sql
  										statement to find $queryParam()$ calls.  We do this by
  										passing the sql to parseQueryParams, which replaces the
  										$queryParam()$ function call with a pseudo cfqueryparam that
  										we can digest here to build the query.  The returned pseudo
  										cfqueryparam tag is structured as follows:
  
  										<cfqueryparam
  													cfsqltype="sql data type"  <--- this is converted
  																					to cfsqltype using
  																					getCFSQLType
  													value="actual value" />
  										EXAMPLE: $queryParam(value='abc',cfsqltype='varchar')$
  										This can also be done prior to sending the SQL statement to this
  										function by calling the queryParam() function directly.
  										EXAMPLE: #dao.queryParam(value='abc',cfsqltype='varchar')#
  										This direct method is recommended.
  									 --->
  									<!--- First thing to do is parse the queryparams from the sql statement (if any exist) --->
  									<!--- <cfset tmpSQL = parseQueryParams(arguments.sql)/> --->
  
  
  									<!--- Now we build the query --->
  									<cfquery name="LOCAL.#arguments.name#" datasource="#variables.dsn#" result="results_#arguments.name#">
  										<!---
  											Parse out the queryParam calls inside the where statement
  											This has to be done this way because you cannot use
  											cfqueryparam tags outside of a cfquery.
  											@TODO: refactor to use the query.cfc
  										--->
  										<cfset tmpSQL = parameterizeSQL( arguments.sql )/>
  										<cfloop from="1" to="#arrayLen( tmpSQL.statements )#" index="idx">
  											<cfset SqlPart = tmpSQL.statements[idx].before />
  											#preserveSingleQuotes( SqlPart )#
  											<cfif structKeyExists( tmpSQL.statements[idx], 'cfsqltype' )>
  												<cfqueryparam
  													cfsqltype="#tmpSQL.statements[idx].cfSQLType#"
  													value="#tmpSQL.statements[idx].value#"
  													list="#tmpSQL.statements[idx].isList#">
  											</cfif>
  										</cfloop>
  										<!--- /Parse out the queryParam calls inside the where statement --->
  									</cfquery>
  
  							<cfelse>
  
  									<!--- Now we build the query --->
  									<cfquery name="LOCAL.#arguments.name#" datasource="#variables.dsn#" result="results_#arguments.name#">
  										<!---
  											Parse out the queryParam calls inside the where statement
  											This has to be done this way because you cannot use
  											cfqueryparam tags outside of a cfquery.
  											@TODO: refactor to use the query.cfc
  										--->
  										<cfset tmpSQL = parameterizeSQL( arguments.sql )/>
  										<cfloop from="1" to="#arrayLen( tmpSQL.statements )#" index="idx">
  											<cfset SqlPart = tmpSQL.statements[idx].before />
  											#preserveSingleQuotes( SqlPart )#
  											<cfif structKeyExists( tmpSQL.statements[idx], 'cfsqltype' )>
  												<cfqueryparam
  													cfsqltype="#tmpSQL.statements[idx].cfSQLType#"
  													value="#tmpSQL.statements[idx].value#"
  													list="#tmpSQL.statements[idx].isList#">
  											</cfif>
  										</cfloop>
  										<!--- /Parse out the queryParam calls inside the where statement --->
  									</cfquery>
  
  							</cfif>
  						<cfelse>
  							<!--- abstract --->
  							<cfset LOCAL[arguments.name] = this.conn.select(
  																table = arguments.table,
  																columns = arguments.columns,
  																name = arguments.name,
  																where = arguments.where,
  																orderby = arguments.orderby,
  																limit = arguments.limit,
  																offset = arguments.offset,
  																cachedwithin = arguments.cachedwithin
  															)/>
  						</cfif>
  
  				<cfelse>
  					<cfset setVariable( arguments.qoq.name, arguments.qoq.query)>
  					<cfquery name="LOCAL.#arguments.name#" dbtype="query">
  						#PreserveSingleQuotes( sql )#
  					</cfquery>
  				</cfif>
  				</cftimer>
  			</cfif>
  			<!--- <cfcatch type="any">
  				<cfthrow errorcode="880" type="custom.error" detail="Unexpected Error" message="There was an unexpected error reading from the database.  Please contact your administrator. #cfcatch.message# #chr(10)# #arguments.sql#">
  			</cfcatch>
  		</cftry> --->
  
  		<cfreturn LOCAL[arguments.name]  />
  	</cffunction>
  
  
  	<cffunction name="readFromQuery" hint="I read from another query (query of query). I take a sql statement as a parameter." returntype="query" output="false">
  		<cfargument name="sql" required="true" type="string" hint="Full SQL statement.">
  
  		<cfset var ret = "" />
  
  		<cfif len(trim(arguments.sql))>
  			<cfquery name="ret" dbtype="query">
  				#PreserveSingleQuotes(sql)#
  			</cfquery>
  		</cfif>
  
  		<cfreturn ret />
  	</cffunction>
  
  	<cffunction name="write" hint="I insert data into the database.  I take a tabledef object containing the tablename and column values. I return the new record's Primary Key value." returntype="any" output="false">
  		<cfargument name="tabledef" required="true" type="tabledef" hint="TableDef object containing data.">
  
  			<cfset var ret = 0/>
  			<cfset ret = this.conn.write( arguments.tabledef )/>
  
  		<cfreturn ret />
  	</cffunction>
  
  	<cffunction name="insert" hint="I insert data into a table in the database." returntype="any" access="public" output="false">
  		<cfargument name="table" type="string" required="true" hint="Name of table to insert data into.">
  		<cfargument name="data" required="true" type="struct" hint="Struct of name value pairs containing data.  Name must match column name.  This could be a form scope">
  		<cfargument name="dryRun" type="boolean" required="false" default="false" hint="for debugging, will dump the data used to insert instead of actually inserting.">
  		<cfargument name="onFinish" type="any" required="false" default="" hint="Will execute when finished inserting.  Can be used for audit logging, notifications, post update processing, etc...">
  
  		<cfset var LOCAL = {}/>
  
  		<cfif !structKeyExists(variables.tabledefs,arguments.table)>
  			<cfset variables.tabledefs[arguments.table] = createObject("component","tabledef").init(tablename=arguments.table,dsn=getDSN())/>
  		</cfif>
  		<cfset LOCAL.table = duplicate(variables.tabledefs[arguments.table])/>
  
  		<cfset LOCAL.columns = LOCAL.table.getColumns()/>
  
  		<cfloop list="#LOCAL.columns#" index="LOCAL.column">
  			<cfparam name="arguments.data.#LOCAL.column#" default="#LOCAL.table.getColumnDefaultValue(LOCAL.column)#">
  		</cfloop>
  
  		<cfscript>
  			LOCAL.row = LOCAL.table.addRow();
  			for (LOCAL.i=1;LOCAL.i LTE listLen(LOCAL.columns);LOCAL.i++){
  				LOCAL.col = listGetAt(LOCAL.columns,LOCAL.i);
  				if (structKeyExists(arguments.data,LOCAL.col)){
  					if(LOCAL.col eq LOCAL.table.getPrimaryKeyColumn() && LOCAL.table.getTableMeta().columns[LOCAL.col].type != 4 && !len(trim(arguments.data[LOCAL.col]))){
  						LOCAL.table.setColumn(column=LOCAL.col,value=createUUID(),row=LOCAL.row);
  					}else{
  						LOCAL.table.setColumn(column=LOCAL.col,value=arguments.data[LOCAL.col],row=LOCAL.row);
  					}
  				}
  			}
  
  			/// insert it
  			if (not arguments.dryrun){
  				LOCAL.newRecord = this.conn.write(LOCAL.table);
  			}
  		</cfscript>
  
  		<cfif arguments.dryrun>
  			<cfset dryRun = { "Data" = arguments.data,"Table Instance" = LOCAL.table, "Table Definition" = LOCAL.table.getTableMeta(), "Records to be Inserted" = LOCAL.table.getRows()}/>
  			<cfreturn dryRun />
  		</cfif>
  
  
  		<cfif isCustomFunction( onFinish )>
  			<cfset LOCAL.callbackData = { "data" = LOCAL.table.getRows(), "ID" = newRecord }/>
  			<cfset onFinish( LOCAL.callbackData )/>
  		</cfif>
  
  		<cfreturn LOCAL.newRecord />
  
  	</cffunction>
  
  	<cffunction name="bulkInsert" hint="I insert data into the database.  I take a tabledef object containing the tablename and column values. I return the number of records inserted." returntype="any" output="false">
  		<cfargument name="tabledef" required="true" type="tabledef" hint="TableDef object containing data.">
  
  		<cfset var qry = arguments.tabledef.getRows()/>
  
  		<cfset this.conn.write(arguments.tabledef)/>
  
  
  		<cfreturn qry.recordcount />
  	</cffunction>
  
  	<cffunction name="update" hint="I update data in a table in the database." returntype="any" access="public" output="false">
  		<cfargument name="table" type="string" required="true" hint="Name of table to update data from.">
  		<cfargument name="data" required="true" type="struct" hint="Struct of name value pairs containing data.  Name must match column name.  This could be a form scope">
  		<cfargument name="IDField" required="false" type="string" default="ID" hint="The name of the Primary Key column in the table.">
  		<cfargument name="ID" required="false" type="string" default="" hint="The value of the Primary Key column in the table.">
  		<cfargument name="dryRun" type="boolean" required="false" default="false" hint="for debugging, will dump the data used to insert instead of actually inserting.">
  		<cfargument name="onFinish" type="any" required="false" default="" hint="Will execute when finished updating.  Can be used for audit logging, notifications, post update processing, etc...">
  
  		<cfset var LOCAL = {}/>
  
  		<cfset LOCAL.isDirty = false/>
  
  		<!--- Check for the tabledef object for this table, if it doesn't already exist, create it --->
  		<cfif !structKeyExists(variables.tabledefs,arguments.table)>
  			<cfset variables.tabledefs[arguments.table] = createObject("component","tabledef").init(tablename=arguments.table,dsn=getDSN())/>
  		</cfif>
  		<cfset LOCAL.table = duplicate(variables.tabledefs[arguments.table])/>
  
  		<cfset LOCAL.columns = LOCAL.table.getColumns()/>
  
  		<cfset LOCAL.currentData = this.read("
  			SELECT #this.getSafeColumnNames(LOCAL.columns)#
  			FROM #arguments.table#
  			WHERE #LOCAL.table.getPrimaryKeyColumn()# = #this.queryParam(value=arguments.data[LOCAL.table.getPrimaryKeyColumn()],cfsqltype=local.table.instance.tablemeta.columns[LOCAL.table.getPrimaryKeyColumn()].type eq 4 ? 'int' : 'varchar')#
  		")>
  
  		<cfloop list="#LOCAL.columns#" index="LOCAL.column">
  			<cfif len(trim(LOCAL.currentData[LOCAL.column][1]))>
  				<!---
  					If the form field for this column was not passed in,
  					but the column has a value in the DB, let's use that
  				--->
  				<cfparam name="arguments.data.#LOCAL.column#" default="#LOCAL.currentData[LOCAL.column][1]#">
  			<cfelseif len(trim(LOCAL.table.getColumnDefaultValue(LOCAL.column))) and LOCAL.table.getColumnDefaultValue(LOCAL.column) NEQ '0000-00-00 00:00:00' and LOCAL.table.getColumnDefaultValue(LOCAL.column) NEQ 'NULL'>
  				<!---
  					If the form field for this column was not passed in
  					and the column doesn't have a value, pass the default value
  				--->
  				<cfparam name="arguments.data.#LOCAL.column#" default="#LOCAL.table.getColumnDefaultValue(LOCAL.column)#">
  			</cfif>
  
  			<cfif structKeyExists(arguments.data,LOCAL.column)
  				&& compare(LOCAL.currentData[LOCAL.column][1].toString(), arguments.data[LOCAL.column].toString())>
  				<!---
  						This will cause dao.update to only update the columns that have changed.
  						This will not only make the update slightly faster, but it will cut down
  						the transaction log size for offline replication.
  				--->
  				<cfset LOCAL.table.setColumnIsDirty(
  						column = LOCAL.column,
  						isDirty = true
  				)/>
  				<cfset LOCAL.isDirty = true/>
  			</cfif>
  
  		</cfloop>
  
  		<!---
  			This will loop through each column and create a table object (see tabledef.cfc)
  			with the form values.
  			NOTE: The Primary Key field will be updated.  The form.ID variable will be used for
  			this value so either make sure it is the ID for the table, or pass the attribute "ID"
  			with the ID value to be used.
  		 --->
  		<cfscript>
  			LOCAL.row = LOCAL.table.addRow();
  			LOCAL.pk = LOCAL.table.getPrimaryKeyColumn();
  			for (LOCAL.i=1;LOCAL.i LTE listLen(LOCAL.columns);LOCAL.i++){
  				LOCAL.col = listGetAt(LOCAL.columns,LOCAL.i);
  				if (len(trim(arguments.ID)) and LOCAL.col is LOCAL.pk){
  					LOCAL.table.setColumn(column=LOCAL.col,value=arguments.id,row=LOCAL.row);
  				}else if (structKeyExists(arguments.data,LOCAL.col)){
  					LOCAL.table.setColumn(column=LOCAL.col,value=arguments.data[LOCAL.col],row=LOCAL.row);
  				}
  
  			}
  			//update it
  			if (not arguments.dryrun){
  				this.updateTable(LOCAL.table);
  			}
  		</cfscript>
  		<cfif arguments.dryrun>
  			<cfset dryRun = { "Data" = arguments.data, "Table Definition" = LOCAL.table.getTableMeta(), "Records to Update" = LOCAL.table.getRows()}/>
  			<cfreturn dryRun />
  		</cfif>
  
  		<cfif isCustomFunction( onFinish )>
  			<cfset LOCAL.callbackData = { "data" = LOCAL.table.getRows(), "id" = LOCAL.table.getRows()[IDField] }/>
  			<cfset onFinish( LOCAL.callbackData )/>
  		</cfif>
  
  		<cfreturn val(arguments.ID) />
  	</cffunction>
  
  	<cffunction name="updateTable" hint="I update data in the database.  I take a tabledef object containing the tablename and column values. I return the record's Primary Key value." returntype="numeric" output="false">
  		<cfargument name="tabledef" required="true" type="tabledef" hint="TableDef object containing data.">
  		<cfargument name="columns" required="false" type="string" hint="Optional list columns to be updated." default="">
  		<cfargument name="IDField" required="false" type="string" hint="Optional ID field." default="">
  
  		<cfset var ret = "" />
  
  		<cfif !len(trim(arguments.IDField))>
  			<cfset arguments.IDField = arguments.tabledef.getPrimaryKeyColumn()/>
  		</cfif>
  		<cfset ret = this.conn.update(tabledef=arguments.tabledef,columns=arguments.columns,IDField=arguments.IDField)>
  
  
  		<cfreturn ret />
  	</cffunction>
  
  	<cffunction name="bulkUpdate" hint="I update data in the database.  I take a tabledef object containing the tablename and column values. I return the number of records updated." returntype="numeric" output="false">
  		<cfargument name="tabledef" required="true" type="tabledef" hint="TableDef object containing data.">
  
  		<cfset var qry = arguments.tabledef.getRows() />
  		<cfset var curRow = 0 />
  		<cfset var cols = arguments.tabledef.getNonAutoIncrementColumns() />
  		<cfset var pk = arguments.tabledef.getPrimaryKeyColumn() />
  		<cfset var ins = "" />
  		<cfset var col = "" />
  
  		<cfoutput query="qry">
  			<cfset curRow = 0>
   			<cftransaction>
  				<cfquery name="ins" datasource="#variables.dsn#">
  					update #arguments.tabledef.getTableName()#
  					set
  						<cfloop list="#arguments.tabledef.getNonAutoIncrementColumns()#" index="col">
  							<cfset curRow = curRow +1>
  							#getSafeColumnName(col)# = <cfqueryparam value="#evaluate(col)#" cfsqltype="#arguments.tabledef.getCFSQLType(col)#"><cfif curRow lt listLen(arguments.tabledef.getNonAutoIncrementColumns())>,</cfif>
  						</cfloop>
  					where #pk# = #evaluate(pk)#
  				</cfquery>
  			</cftransaction>
  		</cfoutput>
  		<cfreturn qry.recordcount />
  	</cffunction>
  
  	<cffunction name="delete" hint="I delete data in the database.  I take the table name and either the ID of the record to be deleted or a * to indicate delete all." returntype="boolean" output="false">
  		<cfargument name="table" required="true" type="string" hint="Table to delete from.">
  		<cfargument name="recordID" required="true" type="string" hint="Record ID of record to be deleted. Use * to delete all.">
  		<cfargument name="IDField" required="false" type="string" hint="ID Field of record to be deleted. Default value = table's Primary Key." default="">
  		<cfargument name="onFinish" type="any" required="false" default="" hint="Will execute when finished deleting.  Can be used for audit logging, notifications, post update processing, etc...">
  
  		<cfset var ret = true>
   		<cftry>
  			<cftransaction>
  
  				<cfif arguments.RecordID is "*">
  					<cfset ret = this.conn.deleteall(tablename=arguments.table)>
  				<cfelse>
  					<cfset ret = this.conn.delete(tablename=arguments.table,recordid=arguments.recordid,idField=arguments.idfield)>
  				</cfif>
  
  			</cftransaction>
  			<cfcatch type="any">
  				<cfset ret = false>
  			</cfcatch>
  		</cftry>
  
  		<cfif isCustomFunction( onFinish )>
  			<cfset LOCAL.callbackData = { "ID" = arguments.recordID }/>
  			<cfset onFinish( LOCAL.callbackData )/>
  		</cfif>
  
  		<cfreturn ret />
  
  	</cffunction>
  
  
  	<cffunction name="markDeleted" hint="I mark the record as deleted.  I take the table name and either the ID of the record to be deleted or a * to indicate delete all." returntype="boolean" output="false">
  		<cfargument name="table" required="true" type="string" hint="Table to delete from.">
  		<cfargument name="recordID" required="true" type="string" hint="Record ID of record to be deleted. Use * to delete all.">
  		<cfargument name="IDField" required="false" type="string" hint="ID Field of record to be deleted. Default value = table's Primary Key." default="ID">
  		<cfargument name="userID" required="false" type="numeric" hint="User ID of user performing delete." default="ID">
  
  		<cfset var ret = true>
   		<cftry>
  
  			<cfif arguments.RecordID is "*">
  				<cfset ret = this.conn.execute("
  					UPDATE #arguments.table#
  					SET status = #this.getDeleteStatusCode()#,
  					modified_datetime = #createODBCDateTime(now())#,
  					modified_by_users_ID = #queryParam(value=arguments.userID,cfsqltype='integer')#
  					")>
  				<!--- can't do a delete comment to the entire table. --->
  			<cfelse>
  				<cfset ret = this.conn.execute("
  					UPDATE #arguments.table#
  					SET status = #this.getDeleteStatusCode()#,
  					modified_datetime = #createODBCDateTime(now())#,
  					modified_by_users_ID = #queryParam(value=arguments.userID,cfsqltype='integer')#
  					WHERE  #arguments.IDfield# = #queryParam(value=arguments.recordID,cfsqltype='integer')#
  					")>
  				<cfset ret = this.conn.execute("
  					INSERT INTO deleted_records (`table`, `record_ID`,`deleted_by_users_ID`,`deleted_datetime`,`delete_comment`)
  						VALUES (#queryParam(value=arguments.table,cfsqltype='varchar')#,#queryParam(value=arguments.recordID,cfsqltype='integer')#,#queryParam(value=arguments.userID,cfsqltype='integer')#,#createODBCDateTime(now())#,#queryParam(value=arguments.deleteComment,cfsqltype='varchar')#)
  				")>
  			</cfif>
  
  			<cfcatch type="any">
  				<cfthrow errorcode="805" type="custom.error" detail="Unexpected Error" message="There was an unexpected error updating the database.  Please contact your administrator.">
  				<cfset ret = false>
  			</cfcatch>
  		</cftry>
  
  		<cfreturn ret />
  
  	</cffunction>
  
  	<cffunction name="logTransaction" returntype="void" output="false">
  		<cfargument name="sql" required="true" type="string">
  		<cfargument name="lastID" required="false" type="string">
  
  		<!--- Duck out if we were told not to write the transaction log --->
  		<cfif !getWriteTransactionLog()>
  			<cfreturn />
  		</cfif>
  
  		<cfset var LOCAL = structNew()/>
  
  		<!--- Transaction logging: --->
  		<!---
  			For push style replication we need to capture each data
  			altering statement.  In the case of inserts, we need
  			to also include the ID that was created when we
  			locally inserted the record.  This will be inserted
  			on the server, and when the server repliates the data
  			back to the client, they will be skipped because they
  			already exist (per my.ini config setting:
  			"slave-skip-errors = 1062" which skips duplicate
  			record errors when replicating.)
  		 --->
  		<cfif reFindNoCase('INSERT(.*?)INTO',arguments.sql)>
  			<!--- Now let's inspect the sql to determine the table name --->
  			<cfset LOCAL.tableName = reReplaceNoCase(arguments.sql,'(.*?)INSERT(.*?)INTO (.*?)\((.*)','\3','one')/>
  			<!---
  				With the table name we can now find out what field is the primary key
  				field.  This is sort of a limitation of this routine, as it only supports
  				one primary key insertion.  This should be fine though as MySQL only allows
  				one auto incrementing field, which means that if we had more than one
  				primary key we would be creating it on the client side anyway and we'd
  				already know it and it would already be in the sql statement, thus no
  				need to do anything with it.
  			--->
  
  			<cfset LOCAL.PKInfo = getPrimaryKey(LOCAL.tableName)/>
  			<cfset LOCAL.primaryKey = LOCAL.PKInfo.field/>
  			<cfset LOCAL.primaryKeyType = LOCAL.PKInfo.type/>
  
  			<cfif len(trim(LOCAL.primaryKey)) AND NOT reFindNoCase('\b#LOCAL.primaryKey#\b',arguments.sql)>
  				<cfset LOCAL.tmpSQL = reReplaceNoCase(arguments.sql,'INSERT(.*?)INTO (.*?)\(','REPLACE INTO \2(`' & LOCAL.primaryKey & '`, ','one')/>
  				<cfset LOCAL.tmpSQL = reReplaceNoCase(LOCAL.tmpSQL,'VALUES(.*?)\(',"VALUES (" & queryParam(value=arguments.lastID, cfsqltype='int',list='false',null='false') & ", ",'one')/><!--- #LOCAL.primaryKeyType# --->
  				<cfset arguments.sql = LOCAL.tmpSQL />
  			</cfif>
  		</cfif>
  
  		<cfif not directoryExists(getDirectoryFromPath(expandPath(transactionLogFile)))>
  			<cfdirectory action="create" directory="#getDirectoryFromPath(expandPath(transactionLogFile))#" mode="775">
  		</cfif>
  		<cfif not fileExists(transactionLogFile)>
  			<cffile action="write" file="#transactionLogFile#" output="" mode="664" addnewline="false" charset="utf-8">
  		</cfif>
  		<!--- Count lines in file to get a sequence number --->
  		<cfscript>
  		    LOCAL.file = createObject("java","java.io.File").init(javacast("string",transactionLogFile));
  		    LOCAL.fileReader = createObject("java","java.io.FileReader").init(LOCAL.file);
  			LOCAL.reader = createObject("java","java.io.LineNumberReader").init(LOCAL.fileReader);
  
negative.code: 			LOCAL.lines = LOCAL.reader.getLineNumber();
  			LOCAL.fileReader.close();
  			LOCAL.reader.close();
  
  			// Strip out line feeds, tabs and multiple spaces
  			LOCAL.content = "#trim(reReplaceNoCase(arguments.sql,chr(9),' ','all'))#";
  			LOCAL.content = reReplaceNoCase(LOCAL.content,chr(10),' ','all');
  			LOCAL.content = reReplaceNoCase(LOCAL.content,'[[:space:]]{2,2}',' ','all');
  			LOCAL.content = "#lines+1##chr(444)##now()##chr(444)##this.getDSN()##chr(444)##createUUID()##chr(444)##trim(LOCAL.content)##chr(10)#";
  
  			// Append transaction to file.
  			fileWriteLine( newFile, '#encrypt(LOCAL.content,'0E69C1BB-BABA-4D48-A7C9D60D020485B0')##chr(555)#' );
  			fileClose( newFile );
  		}
  
  		// Database Generic Functions
  
  		/**
  		* I prepare a parameter value for SQL execution when not using cfqueryparam.  Basically I try to do the same thing as cfqueryparam.
  		**/
  		public function prepareNonQueryParamValue( required string value, required string cfsqltype, required boolean null ){
  			var LOCAL = {};
  
  			if( arguments.cfSQLType is "cf_sql_timestamp" or arguments.cfSQLType is "cf_sql_date" ){
  				if( arguments.value is "0000-00-00 00:00" ){
  					LOCAL.ret = "'#arguments.value#'";
  				}else if( isDate(arguments.value) ){
  					LOCAL.ret = createODBCDateTime(arguments.value);
  				}else{
  					LOCAL.ret = "'0000-00-00 00:00'";
  				}
  			}else if( arguments.cfSQLType is "cf_sql_integer" ){
  				LOCAL.ret = val( arguments.value );
  			}else if( arguments.cfSQLType is "cf_sql_boolean" ){
  				LOCAL.ret = val( arguments.value );
  			}else if( isSimpleValue( arguments.value ) ){
  				LOCAL.ret = "'#arguments.value#'";
  			}
  
  			return LOCAL.ret;
  
  		}
  
  		/**
  		* I prepare a parameter value for SQL execution when not using cfqueryparam.  Basically I try to do the same thing as cfqueryparam.
  		**/
  		public function getNonQueryParamFormattedValue( required string value, required string cfsqltype, required string list, required boolean null ){
  			var ret = "";
  
  			if( arguments.list ){
  				for( var idx in listToArray( arguments.value ) ){
  					ret = listAppend( ret,
  						prepareNonQueryParamValue(
  							value = listGetAt( arguments.value, idx ),
  							cfsqltype = arguments.cfsqltype,
  							null = arguments.null
  						) );
  				}
  
  			}else{
  				ret = prepareNonQueryParamValue(
  						value = arguments.value,
  						cfsqltype = arguments.cfsqltype,
  						null = arguments.null
  					);
  			}
  
  			return ret;
  
  		}
  
  		/**
  		* I determine the CFSQL type for the passd value and return the proper type as a string to be used in cfqueryparam.
  		*/
  		public function getCFSQLType( required string type ){
  
  			var int_types = "int,integer,numeric,number,cf_sql_integer";
  			var string_types = "varchar,char,text,memo,nchar,nvarchar,ntext,cf_sql_varchar";
  			var date_types = "datetime,date,cf_sql_date";
  			var decimal_types = "decimal,cf_sql_decimal";
  			var money_types = "money,cf_sql_money";
  			var timestamp_types = "timestamp,cf_sql_timestamp";
  			var double_types = "double,cf_sql_double";
  			var bit_types = "bit";
  			// Default return = varchar
  			var ret = "cf_sql_varchar";
  
  			if( listFindNoCase( int_types, arguments.type ) ){
  				ret = "cf_sql_integer";
  			}else if( listFindNoCase( string_types, arguments.type ) ){
  				ret = "cf_sql_varchar";
  			}else if( listFindNoCase( date_types, arguments.type ) ){
  				ret = "cf_sql_date";
  			}else if( listFindNoCase( decimal_types, arguments.type ) ){
  				ret = "cf_sql_decimal";
  			}else if( listFindNoCase( money_types, arguments.type ) ){
  				ret = "cf_sql_money";
  			}else if( listFindNoCase( double_types, arguments.type ) ){
  				ret = "cf_sql_double";
  			}else if( listFindNoCase( timestamp_types, arguments.type ) ){
  				ret = "cf_sql_timestamp";
  			}else if( listFindNoCase( bit_types, arguments.type ) ){
  				ret = "cf_sql_bit";
  			}
  
  			return ret;
  		}
  
  		/**
  		* I return the structure of the passed table.
  		**/
  		public query function define( required string table ) {
  			var def = this.conn.define( arguments.table );
  
  			return def;
  		}
  
  		public void function setUseCFQueryParams( required boolean useCFQueryParams ){
  			this.useCFQueryParams = arguments.useCFQueryParams;
  			this.conn.useCFQueryParams = arguments.useCFQueryParams;
  		}
  
  		public boolean function getUseCFQueryParams(){
  			return this.conn.getUseCFQueryParams();
  		}
  
  		/**
  		* I return a list of tables for the current database
  		**/
      	public query function getTables(){
  			return this.conn.getTables();
  		}
  
  		/**
  		* I return a list of columns for the passed table
  		**/
  		public function getColumns( required string table ){
  			var def = new tabledef( tablename = arguments.table, dsn = variables.dsn );
  			var cols = def.getColumns();
  
  			if( !len( trim( cols ) ) ){
  				cols = "*";
  			}
  
  			return cols;
  		}
  	/**
  	* I get the primary key for the given table. To do this I envoke the getPrimaryKey from the conneted database type.
  	**/
  		public function getPrimaryKey( required string table ){
  			return this.conn.getPrimaryKey( arguments.table );
  		}
  		/**
  		* I take a list of columns and return it as a safe columns list with each column wrapped within the DB specific escape characters.
  		**/
  		public function getSafeColumnNames( required string cols ){
  			return this.conn.getSafeColumnNames( arguments.cols );;
  		}
  
  		/**
  		*I take a single column name and return it as a safe columns list with each column wrapped within the DB specific escape characters.
  		**/
  		public function getSafeColumnName( required string col ){
  			return this.conn.getSafeColumnName( arguments.col );
  		}
  
  		/**
  		* I return the opening escape character for a column name.
  		**/
  		public function getSafeIdentifierStartChar(){
  			return this.conn.getSafeIdentifierStartChar();
  		}
  
  		/**
  		* I return the closing escape character for a column name.
  		**/
  		public function getSafeIdentifierEndChar(){
  			return this.conn.getSafeIdentifierEndChar();
  		}
  
  		public function addTableDef( required tabledef tabledef ){
  			variables.tabledefs[ arguments.tabledef.instance.name ] = arguments.tabledef;
  		}
  
  		/**
  		* I create the values to build the cfqueryparam tag.
  		* @value The value to be queryparam'd
  		* @cfsqltype This can be a standard RDBS datatype or a cf_sql_type (see getCFSQLType())
  		* @list Whether or not to param as a list (i.e. for passing a param'd list to IN() statements )
  		* @null Whether the value is null or not
  		**/
  		public function queryParam( required string value, string cfsqltype = "cf_sql_varchar", boolean list = false, boolean null = false ){
  
  			var returnString = {};
  			var returnStruct = {};
  
  			returnStruct = queryParamStruct( value = trim( arguments.value ), cfsqltype = arguments.cfsqltype, list = arguments.list, null = arguments.null );
  	 		returnString = '#chr(998)#list=#chr(777)##returnStruct.list##chr(777)# null=#chr(777)##returnStruct.null##chr(777)# cfsqltype=#chr(777)##returnStruct.cfsqltype##chr(777)# value=#chr(777)##returnStruct.value##chr(777)##chr(999)#';
  			return returnString;
  		}
  		/**
  		* I create the struct used to build the cfqueryparam tag.
  		* @value The value to be queryparam'd
  		* @cfsqltype This can be a standard RDBS datatype or a cf_sql_type (see getCFSQLType())
  		* @list Whether or not to param as a list (i.e. for passing a param'd list to IN() statements )
  		* @null Whether the value is null or not
  		**/
  		public function queryParamStruct( required string value, string cfsqltype = "cf_sql_varchar", boolean list = false, boolean null = false ){
  			var returnStruct = {};
  
  			returnStruct.cfsqltype = reReplaceNoCase( getCFSQLType( arguments.cfsqltype ), '\$queryparam', 'INVALID', 'all' );
  			// strip out any queryparam calls in the value, this will prevent the ability to submit malicious code through the SQL string
  			returnStruct.value = reReplaceNoCase( arguments.value, '\$queryparam', 'INVALID', 'all');
  			returnStruct.list = reReplaceNoCase( arguments.list, '\$queryparam', 'INVALID', 'all');
  			returnStruct.null = reReplaceNoCase( arguments.null, '\$queryparam', 'INVALID', 'all');
  
  			return returnStruct;
  		}
  
  
  
  	</cfscript>
  
  
  
  	<cffunction name="parameterizeSQL" output="true" access="public" returntype="struct" hint="I build a struct containing all of the where clause of the SQL statement, parameterized when possible.  The returned struct will contain an array of each parameterized clause containing the data necessary to build a <cfqueryparam> tag.">
  		<cfargument name="sql" type="string" required="true" hint="SQL statement (or partial SQL statement) which contains tokenized queryParam calls">
  