Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

New master - Microsoft Access Adapter #5

Closed
wants to merge 7 commits into from

3 participants

@fordray

Hi Tony & Chris,
I have just put up the new Microsoft Access Adapter into a fork.

I just noticed a file in the wheels/test directory that will require some code to recognize the new Access adapter.

Regards
Ray.

@rip747
Collaborator

excellent! before we can apply this, we'll need to run it against the test suite to make sure everything passes. i'll see if i can fork from you and help out

@rip747
Collaborator

ray,

there seems to be quite a number of problem with using msaccess. first, cfdbinfo doesn't work with it unless you use a jdbc driver such as the "unicode" driver.

also for some reason cfdbinfo will return an empty string when using DDL and creating the table with a char() type. the column is marked as a "Text" type in access, but cfdbinfo return an empty string. this could be a problem with the jdbc driver itself and ACF8.

running the model test suite i get 3 FAILS and 10 ERRORS. these failures and errors MUST be ironed out before we can consider pulling these changes in. i setup a branch in my fork that you can pull the changes i made in order to get the test suite running:

https://github.com/rip747/cfwheels/tree/microsoft-access-adapter

@fordray
@perdjurner
Owner

This is not something that can easily be turned into a plugin so if possible I think we should add it as a new adapter.

I know this is really old but if you're still around and using this, could you submit a pull request on the "master" branch instead and we'll have a look at what tests are failing at this point?

Thank you.

@perdjurner perdjurner closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 26, 2011
  1. @fordray

    New Microsoft Access adapter.

    fordray authored
Commits on Mar 28, 2011
  1. @fordray
  2. @fordray

    latest now uses loc vars

    fordray authored
Commits on Apr 2, 2011
  1. @fordray

    Now only 1 error left in test:

    fordray authored
    SELF JOIN WITH OTHER ASSOCIATIONS
    wheels/tests/model/associations
  2. @fordray

    Only one error remaining:

    fordray authored
    SELF JOIN WITH OTHER ASSOCIATIONS
  3. @fordray
Commits on Apr 20, 2011
  1. @fordray
This page is out of date. Refresh to see the latest.
View
4 wheels/Connection.cfc
@@ -41,8 +41,10 @@
loc.adapterName = "PostgreSQL";
else if (loc.info.driver_name Contains "H2")
loc.adapterName = "H2";
+ else if (loc.info.driver_name Contains "Jet")
+ loc.adapterName = "MicrosoftAccess";
else
- $throw(type="Wheels.DatabaseNotSupported", message="#loc.info.database_productname# is not supported by Wheels.", extendedInfo="Use Microsoft SQL Server, MySQL, Oracle or PostgreSQL.");
+ $throw(type="Wheels.DatabaseNotSupported", message="#loc.info.database_productname# is not supported by Wheels.", extendedInfo="Use Microsoft SQL Server, MySQL, Oracle, PostgreSQL, H2, or Microsoft Access.");
loc.returnValue = CreateObject("component", "model.adapters.#loc.adapterName#").init(argumentCollection=variables.instance.connection);
</cfscript>
<cfreturn loc.returnValue>
View
2  wheels/global/internal.cfm
@@ -837,7 +837,7 @@ Should now call bar() instead and marking foo() as deprecated
var loc = {};
application.wheels.plugins = {};
application.wheels.incompatiblePlugins = "";
- application.wheels.mixableComponents = "application,dispatch,controller,model,cache,base,connection,microsoftsqlserver,mysql,oracle,postgresql,h2";
+ application.wheels.mixableComponents = "application,dispatch,controller,model,cache,base,connection,microsoftsqlserver,mysql,oracle,postgresql,h2,microsoftaccess";
application.wheels.mixins = {};
application.wheels.dependantPlugins = "";
loc.pluginFolder = GetDirectoryFromPath(GetBaseTemplatePath()) & "plugins";
View
336 wheels/model/adapters/MicrosoftAccess.cfc
@@ -0,0 +1,336 @@
+<cfcomponent extends="Base" output="false">
+
+ <cffunction name="$generatedKey" returntype="string" access="public" output="false">
+ <cfreturn "identitycol">
+ </cffunction>
+
+
+ <cffunction name="$randomOrder" returntype="string" access="public" output="false">
+ <cfreturn "NEWID()">
+ </cffunction>
+
+ <!--- Phord change $getType --->
+ <cffunction name="$getType" returntype="string" access="public" output="false">
+ <!--- Access data type: Memo to LongText to cf_sql_clob . --->
+ <!--- Using cf_sql_clob truncates a MSAccess Memo field to 65536 chars if submitted from a textarea. --->
+ <!--- Using cf_sql_longvarchar throws a CF error if the MSAccess Memo field is more than 65536 chars. --->
+ <!--- Unfortunately COLUMN_SIZE returns 0 for a MS Access Memo field. It should return 65536. --->
+ <!--- Therefore cf_sql_clob works best for Memo fields --->
+ <!--- Currency might be better as cf_sql_decimal, however MSAccess stores Currency as double --->
+ <!--- MSAccess Yes/No fields become cf_sql_bit. A default value (true or false) needs to be assigned to a Yes/No field in MSAccess or the table can't be accessed & throws a CF error' --->
+ <cfargument name="type" type="string" required="true">
+<!--- <cfset loc = {}>
+ <cfloop index='i' delimiters='#chr(10)#' list='
+ VarChar cf_sql_varchar
+ LongText cf_sql_clob
+ Byte cf_sql_tinyint
+ Decimal cf_sql_decimal
+ Short cf_sql_smallint
+ Long cf_sql_integer
+ Single cf_sql_real
+ Double cf_sql_double
+ DateTime cf_sql_timestamp
+ Currency cf_sql_double
+ Bit cf_sql_bit
+ '>
+ <cfif arguments.type IS trim(listfirst(trim(i),' '))>
+ <cfset loc.returnValue = trim(listlast(trim(i),' '))>
+ <cfbreak>
+ </cfif>
+ </cfloop> --->
+
+ <cfscript>
+ var loc = {};
+ switch(arguments.type)
+ {
+ case "": case "varchar": case "char": case "nvarchar": case "nchar": {loc.returnValue = "cf_sql_varchar"; break;}
+ case "longtext": {loc.returnValue = "cf_sql_clob"; break;}
+ case "byte": {loc.returnValue = "cf_sql_tinyint"; break;}
+ case "decimal": {loc.returnValue = "cf_sql_decimal"; break;}
+ case "short": {loc.returnValue = "cf_sql_smallint"; break;}
+ case "long": {loc.returnValue = "cf_sql_integer"; break;}
+ case "single": {loc.returnValue = "cf_sql_real"; break;}
+ case "double": case "currency": {loc.returnValue = "cf_sql_double"; break;}
+ case "bit": {loc.returnValue = "cf_sql_bit"; break;}
+ case "longbinary": {loc.returnValue = "cf_sql_blob"; break;}
+ case "datetime": {loc.returnValue = "cf_sql_timestamp"; break;}
+ }
+ </cfscript>
+
+
+ <cfreturn loc.returnValue>
+
+ </cffunction>
+
+ <!--- Phord change $query --->
+ <cffunction name="$query" returntype="struct" access="public" output="false">
+ <cfargument name="sql" type="array" required="true">
+ <cfargument name="limit" type="numeric" required="false" default=0>
+ <cfargument name="offset" type="numeric" required="false" default=0>
+ <cfargument name="parameterize" type="boolean" required="true">
+ <cfargument name="$primaryKey" type="string" required="false" default="">
+
+ <cfscript>
+ var loc = {};
+ </cfscript>
+
+ <!--- List of MS Access Jet reserved words. Only put [] around fields if these field names are used --->
+ <!--- http://support.microsoft.com/kb/321266 --->
+ <!--- Test now passes: model.crud.create NON AUTO INCREMENTING PRIMARY KEY SHOULD NOT BE CHANGED. For some reason shopid cannot be in brackets. --->
+ <!--- It seems that an error may occur if a MSJET reserved word is used as a fieldname and set as a Primary Key. --->
+ <cfset loc.MSAccessReservedWords ="password:ADD:ALL:Alphanumeric:ALTER:AND:ANY:Application:AS:ASC:Assistant:AUTOINCREMENT:Avg:BETWEEN:BINARY:BIT:BOOLEAN:BY:BYTE:CHAR:CHARACTER:COLUMN:CompactDatabase:CONSTRAINT:Container:Count:COUNTER:CREATE:CreateDatabase:CreateField:CreateGroup:CreateIndex:CreateObject:CreateProperty:CreateRelation:CreateTableDef:CreateUser:CreateWorkspace:CURRENCY:CurrentUser:DATABASE:DATE:DATETIME:DELETE:DESC:Description:DISALLOW:DISTINCT:DISTINCTROW:Document:DOUBLE:DROP:Echo:Else:End:Eqv:Error:EXISTS:Exit:FALSE:Field:Fields:FillCache:FLOAT:FLOAT4:FLOAT8:FOREIGN:Form:Forms:FROM:Full:FUNCTION:GENERAL:GetObject:GetOption:GotoPage:GROUP:GROUP BY:GUID:HAVING:Idle:IEEEDOUBLE:IEEESINGLE:If:IGNORE:Imp:IN:INDEX:Index:Indexes:INNER:INSERT:InsertText:INT:INTEGER:INTEGER1:INTEGER2:INTEGER4:INTO:IS:JOIN:KEY:LastModified:LEFT:Level:Like:LOGICAL:LOGICAL1:LONG:LONGBINARY:LONGTEXT:Macro:Match:Max:Min:Mod:MEMO:Module:MONEY:Move:NAME:NewPassword:NO:Not:NULL:NUMBER:NUMERIC:Object:OLEOBJECT:OFF:ON:OpenRecordset:OPTION:OR:ORDER:Orientation:Outer:OWNERACCESS:Parameter:PARAMETERS:Partial:PERCENT:PIVOT:PRIMARY:PROCEDURE:Property:Queries:Query:Quit:REAL:Recalc:Recordset:REFERENCES:Refresh:RefreshLink:RegisterDatabase:Relation:Repaint:RepairDatabase:Report:Reports:Requery:RIGHT:SCREEN:SECTION:SELECT):SET:SetFocus:SetOption:SHORT:SINGLE:SMALLINT:SOME:SQL:StDev:StDevP:STRING:Sum:TABLE:TableDef:TableDefs:TableID:TEXT:TIME:TIMESTAMP:TOP:TRANSFORM:TRUE:Type:UNION:UNIQUE:UPDATE:User:VALUE:VALUES:Var:VarP:VARBINARY:VARCHAR:WHERE:WITH:Workspace:Xor:Year:YES:YESNO">
+
+
+ <!--- MS ACCESS needs square brackets around reserved word field names in SQL INSERT statements. Allows reserved words like password for field names --->
+ <cfif IsSimpleValue(arguments.sql[1]) AND (Left(arguments.sql[1], 11) IS "INSERT INTO")>
+ <cfloop index="loc.i" from ="2" to="#arrayLen(arguments.sql)#" step="2">
+ <cfif arguments.sql[loc.i - 1] eq ")">
+ <cfbreak >
+ </cfif>
+ <cfset loc.FieldName = trim(arguments.sql[loc.i])>
+ <cfif ListFindNoCase(loc.MSAccessReservedWords,loc.FieldName,":")>
+ <cfset arguments.sql[loc.i] = "[" & loc.FieldName & "]">
+ </cfif>
+ </cfloop>
+ </cfif>
+
+
+ <!--- MS ACCESS needs square brackets [] around reserved word field names in SQL UPDATE statements. Allows reserved words like password for field names --->
+ <cfif IsSimpleValue(arguments.sql[1]) AND Left(arguments.sql[1], 6) IS "UPDATE">
+ <cfloop index="loc.i" from ="2" to="#arrayLen(arguments.sql)#">
+ <cfif IsSimpleValue(arguments.sql[loc.i]) AND right(trim(arguments.sql[loc.i]), 1) EQ "=">
+ <cfset loc.FieldName = trim(replace(arguments.sql[loc.i], " =",""))>
+ <cfif ListFindNoCase(loc.MSAccessReservedWords,loc.FieldName,":")>
+ <cfset arguments.sql[loc.i] = "[" & loc.FieldName & "]" & " =">
+ </cfif>
+ </cfif>
+ </cfloop>
+ </cfif>
+
+
+ <!--- MSACCESS needs round brackets () around the TABLE JOIN CLAUSES --->
+ <!--- This must happen before the SQL changes that come after this. --->
+ <cfif (arraylen(arguments.sql) GTE 2) AND IsSimpleValue(arguments.sql[2]) AND Left(arguments.sql[2], 4) IS "FROM" AND arguments.sql[2] contains " JOIN ">
+ <cfset loc.aryJoins = REMatchNoCase(" INNER JOIN| LEFT OUTER JOIN",arguments.sql[2])>
+ <cfloop array="#loc.aryJoins#" index="loc.i">
+ <cfset arguments.sql[2] = replaceNoCase(arguments.sql[2], loc.i, ')' & trim(loc.i))>
+ <cfset arguments.sql[2] = replaceNoCase(arguments.sql[2], 'FROM' , 'FROM (' )>
+ </cfloop>
+ </cfif>
+
+
+ <!--- MS ACCESS doesn't support limit and offset in sql. Code copied from MicrosoftSQLServer.cfc --->
+ <cfscript>
+ if (arguments.limit + arguments.offset gt 0)
+ {
+ loc.containsGroup = false;
+ loc.afterWhere = "";
+
+ if (IsSimpleValue(arguments.sql[ArrayLen(arguments.sql) - 1]) and FindNoCase("GROUP BY", arguments.sql[ArrayLen(arguments.sql) - 1]))
+ loc.containsGroup = true;
+ if (arguments.sql[ArrayLen(arguments.sql)] Contains ",")
+ {
+ // fix for pagination issue when ordering multiple columns with same name
+ loc.order = arguments.sql[ArrayLen(arguments.sql)];
+ loc.newOrder = "";
+ loc.doneColumns = "";
+ loc.done = 0;
+ loc.iEnd = ListLen(loc.order);
+ for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
+ {
+ loc.item = ListGetAt(loc.order, loc.i);
+ loc.column = SpanExcluding(Reverse(SpanExcluding(Reverse(loc.item), ".")), " ");
+ if (ListFind(loc.doneColumns, loc.column))
+ {
+ loc.done++;
+ loc.item = loc.item & " AS tmp" & loc.done;
+ }
+ loc.doneColumns = ListAppend(loc.doneColumns, loc.column);
+ loc.newOrder = ListAppend(loc.newOrder, loc.item);
+ }
+ arguments.sql[ArrayLen(arguments.sql)] = loc.newOrder;
+ }
+
+ // select clause always comes first in the array, the order by clause last, remove the leading keywords leaving only the columns and set to the ones used in the inner most sub query
+ loc.thirdSelect = ReplaceNoCase(ReplaceNoCase(arguments.sql[1], "SELECT DISTINCT ", ""), "SELECT ", "");
+ loc.thirdOrder = ReplaceNoCase(arguments.sql[ArrayLen(arguments.sql)], "ORDER BY ", "");
+ if (loc.containsGroup)
+ loc.thirdGroup = ReplaceNoCase(arguments.sql[ArrayLen(arguments.sql) - 1], "GROUP BY ", "");
+
+ // the first select is the outer most in the query and need to contain columns without table names and using aliases when they exist
+ loc.firstSelect = $columnAlias(list=$tableName(list=loc.thirdSelect, action="remove"), action="keep");
+
+ // we need to add columns from the inner order clause to the select clauses in the inner two queries
+ loc.iEnd = ListLen(loc.thirdOrder);
+ for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
+ {
+ loc.iItem = ReReplace(ReReplace(ListGetAt(loc.thirdOrder, loc.i), " ASC\b", ""), " DESC\b", "");
+ if (!ListFindNoCase(loc.thirdSelect, loc.iItem))
+ loc.thirdSelect = ListAppend(loc.thirdSelect, loc.iItem);
+ if (loc.containsGroup) {
+ loc.iItem = REReplace(loc.iItem, "[[:space:]]AS[[:space:]][A-Za-z1-9]+", "", "all");
+ if (!ListFindNoCase(loc.thirdGroup, loc.iItem))
+ loc.thirdGroup = ListAppend(loc.thirdGroup, loc.iItem);
+ }
+ }
+
+ // the second select also needs to contain columns without table names and using aliases when they exist (but now including the columns added above)
+ loc.secondSelect = $columnAlias(list=$tableName(list=loc.thirdSelect, action="remove"), action="keep");
+
+ // first order also needs the table names removed, the column aliases can be kept since they are removed before running the query anyway
+ loc.firstOrder = $tableName(list=loc.thirdOrder, action="remove");
+
+ // second order clause is the same as the first but with the ordering reversed
+ loc.secondOrder = Replace(ReReplace(ReReplace(loc.firstOrder, " DESC\b", chr(7), "all"), " ASC\b", " DESC", "all"), chr(7), " ASC", "all");
+
+ // fix column aliases from order by clauses
+ loc.thirdOrder = $columnAlias(list=loc.thirdOrder, action="remove");
+ loc.secondOrder = $columnAlias(list=loc.secondOrder, action="keep");
+ loc.firstOrder = $columnAlias(list=loc.firstOrder, action="keep");
+
+ // build new sql string and replace the old one with it
+ loc.beforeWhere = "SELECT " & loc.firstSelect & " FROM (SELECT TOP " & arguments.limit & " " & loc.secondSelect & " FROM (SELECT ";
+ if (ListRest(arguments.sql[2], " ") Contains " ")
+ loc.beforeWhere = loc.beforeWhere & "DISTINCT ";
+ loc.beforeWhere = loc.beforeWhere & "TOP " & arguments.limit+arguments.offset & " " & loc.thirdSelect & " " & arguments.sql[2];
+ if (loc.containsGroup)
+ loc.afterWhere = "GROUP BY " & loc.thirdGroup & " ";
+ loc.afterWhere = "ORDER BY " & loc.thirdOrder & ") AS tmp1 ORDER BY " & loc.secondOrder & ") AS tmp2 ORDER BY " & loc.firstOrder;
+ ArrayDeleteAt(arguments.sql, 1);
+ ArrayDeleteAt(arguments.sql, 1);
+ ArrayDeleteAt(arguments.sql, ArrayLen(arguments.sql));
+ if (loc.containsGroup)
+ ArrayDeleteAt(arguments.sql, ArrayLen(arguments.sql));
+ ArrayPrepend(arguments.sql, loc.beforeWhere);
+ ArrayAppend(arguments.sql, loc.afterWhere);
+
+ }
+ else
+ {
+ arguments.sql = $removeColumnAliasesInOrderClause(arguments.sql);
+ }
+ // MS ACCESS doesn't support limit and offset in sql
+ StructDelete(arguments, "limit", false);
+ StructDelete(arguments, "offset", false);
+ // loc.returnValue = $performQuery(argumentCollection=arguments);
+ </cfscript>.
+
+ <!--- MSACCESS cant handle DISTINCT inside of aggregate funstions e.g. sum, count, avg, etc.. --->
+ <!--- However MSACCESS does understand DISTINCT after SELECT --->
+ <!--- This uses a sub table after the FROM & includes the WHERE when needed --->
+ <cfif IsSimpleValue(arguments.sql[1]) AND arguments.sql[1] contains "AS wheelsqueryresult" AND arguments.sql[1] contains "(DISTINCT">
+ <cfset loc.originalSQL = arguments.sql[1] & " " & arguments.sql[2] >
+ <cfset arguments.sql[1] = replaceNoCase(arguments.sql[1], "(DISTINCT ","(")>
+ <cfset loc.FieldName = listFirst(arguments.sql[1],")")>
+ <cfset loc.FieldName = trim(listLast(loc.FieldName,"("))>
+ <cfset loc.TableName = trim(replaceNoCase(arguments.sql[2],"FROM ",""))>
+ <cfset arguments.sql[2] = "FROM (SELECT DISTINCT #loc.FieldName# FROM #loc.TableName# ">
+ <cfset ArrayAppend(arguments.sql,")")>
+ </cfif>
+
+
+
+
+
+
+ <!--- Perform the query --->
+ <cfscript>
+ loc.returnValue = $performQuery(argumentCollection=arguments);
+ </cfscript>
+
+
+ <!--- Who wants to see crappy MSAccess SQL workarounds. Reverts the result SQL to the it is for other databases. --->
+ <cfif isDefined("loc.originalSQL")>
+ <cfset loc.returnValue.Result.SQL = loc.originalSQL>
+ </cfif>
+
+
+
+ <!--- DUMP to be removed --->
+
+<!--- <cfsavecontent variable="bbb">
+ <cftry>
+ <hr>
+ arguments
+ <br />
+ <cfdump var="#arguments#">
+
+ noOfJoins
+ <br />
+ <cfdump var="#loc.aryJoins#">
+ <br />
+
+ returnValue
+ <br />
+ <cfdump var="#loc.returnValue#">
+
+ <cfcatch></cfcatch>
+ </cftry>
+ </cfsavecontent>
+ <cffile action="append" addnewline="true" file="c:\log.html" output="#bbb#" > --->
+
+
+
+ <cfreturn loc.returnValue>
+ </cffunction>
+
+ <!--- Phord change $identitySelect --->
+ <cffunction name="$identitySelect" returntype="any" access="public" output="false">
+ <cfargument name="queryAttributes" type="struct" required="true">
+ <cfargument name="result" type="struct" required="true">
+ <cfargument name="primaryKey" type="string" required="true">
+ <cfset var loc = {}>
+ <cfset var query = {}>
+ <cfset loc.sql = Trim(arguments.result.sql)>
+ <cfif Left(loc.sql, 11) IS "INSERT INTO" AND NOT StructKeyExists(arguments.result, $generatedKey())>
+ <cfset loc.startPar = Find("(", loc.sql) + 1>
+ <cfset loc.endPar = Find(")", loc.sql)>
+ <cfset loc.columnList = ReplaceList(Mid(loc.sql, loc.startPar, (loc.endPar-loc.startPar)), "#Chr(10)#,#Chr(13)#, ", ",,")>
+ <cfif NOT ListFindNoCase(loc.columnList, ListFirst(arguments.primaryKey))>
+ <cfset loc.returnValue = {}>
+ <cfset loc.tbl = SpanExcluding(Right(loc.sql, Len(loc.sql)-12), " ")>
+ <cfquery attributeCollection="#arguments.queryAttributes#">
+ SELECT TOP 1 #arguments.primaryKey#
+ AS lastId
+ FROM #loc.tbl#
+ ORDER BY #arguments.primaryKey# DESC
+ </cfquery>
+ <cfset loc.returnValue[$generatedKey()] = query.name.lastId>
+ <cfreturn loc.returnValue>
+ </cfif>
+ </cfif>
+ </cffunction>
+
+
+ <!--- Phord replaces $getColumnInfo from base.cfc to deal with MSACCESS, to allow adding new records with the bit datatype--->
+ <cffunction name="$getColumnInfo" returntype="query" access="public" output="false">
+ <cfargument name="table" type="string" required="true">
+ <cfargument name="datasource" type="string" required="true">
+ <cfargument name="username" type="string" required="true">
+ <cfargument name="password" type="string" required="true">
+ <cfscript>
+ var loc = {};
+ </cfscript>
+
+ <cfset arguments.type = "columns">
+
+ <!--- Make a copy of the query to work with --->
+ <cfset loc.returnValue = $dbinfo(argumentCollection=arguments)>
+ <!--- Loop through the columns & make corrections --->
+ <cfloop query="loc.returnValue">
+ <!--- Replace COLUMN_DEFAULT_VALUE [empty string] with 0 for the MSACCESS Yes/No datatype. <cfdbinfo> returns an [empty string] if MSACCESS does not have a default set for Yes/No , which prevents CFWheels from adding new records --->
+ <cfif COLUMN_DEFAULT_VALUE IS "" And TYPE_NAME IS 'Bit'>
+ <cfset QuerySetCell(loc.returnValue, "COLUMN_DEFAULT_VALUE", 0, loc.returnValue.currentRow)>
+ </cfif>
+ <!--- Correct the COLUMN_SIZE to 65535 for the MSACCESS Memo datatype. <cfdbinfo> returns a 0 COLUMN_SIZE for the MSACCESS Memo datatypes --->
+ <cfif TYPE_NAME IS "LongText">
+ <cfset QuerySetCell(loc.returnValue, "COLUMN_SIZE", 65535, loc.returnValue.currentRow)>
+ </cfif>
+ </cfloop>
+ <!--- Returned the corrected query to CFWheels --->
+ <cfreturn loc.returnValue>
+ </cffunction>
+
+ <cfinclude template="../../plugins/injection.cfm">
+
+</cfcomponent>
Something went wrong with that request. Please try again.