-<!--- PRIVATE METHODS --->
-
-<cffunction name="$addDeleteClause" returntype="array" access="public" output="false">
-	<cfargument name="sql" type="array" required="true">
-	<cfargument name="softDelete" type="boolean" required="true">
-	<cfscript>
-		var loc = {};
+<cfscript>
+	/*
+	* PRIVATE METHODS
+	*/
+	
+	public array function $addDeleteClause(required array sql, required boolean softDelete) {			
 		if (variables.wheels.class.softDeletion && arguments.softDelete)
 		{
 			ArrayAppend(arguments.sql, "UPDATE #tableName()# SET #variables.wheels.class.softDeleteColumn# = ");
-			loc.param = {value=Now(), type="cf_sql_timestamp"};
-			ArrayAppend(arguments.sql, loc.param);
+			local.param = {value=Now(), type="cf_sql_timestamp"};
+			ArrayAppend(arguments.sql, local.param);
 		}
 		else
 		{
 			ArrayAppend(arguments.sql, "DELETE FROM #tableName()#");
-		}
-	</cfscript>
-	<cfreturn arguments.sql>
-</cffunction>
-
-<cffunction name="$fromClause" returntype="string" access="public" output="false">
-	<cfargument name="include" type="string" required="true">
-	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false">
-	<cfscript>
-		var loc = {};
+		}		
+		return arguments.sql;
+	}
+
+	public string function $fromClause(required string include, boolean includeSoftDeletes="false") {
 
 		// start the from statement with the SQL keyword and the table name for the current model
-		loc.rv = "FROM " & tableName();
+		local.rv = "FROM " & tableName();
 
 		// add join statements if associations have been specified through the include argument
 		if (Len(arguments.include))
 		{
 			// get info for all associations
-			loc.associations = $expandedAssociations(include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
+			local.associations = $expandedAssociations(include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
 
 			// add join statement for each include separated by space
-			loc.iEnd = ArrayLen(loc.associations);
-			for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
-			{
-				loc.rv = ListAppend(loc.rv, loc.associations[loc.i].join, " ");
-			}
-		}
-	</cfscript>
-	<cfreturn loc.rv>
-</cffunction>
-
-<cffunction name="$addKeyWhereClause" returntype="array" access="public" output="false">
-	<cfargument name="sql" type="array" required="true">
-	<cfscript>
-		var loc = {};
+			local.iEnd = ArrayLen(local.associations);
+			for (local.i=1; local.i <= local.iEnd; local.i++)
+			{
+				local.rv = ListAppend(local.rv, local.associations[local.i].join, " ");
+			}
+		}
+		return local.rv;
+	}
+	public array function $addKeyWhereClause(required array sql) {
+
+		
 		ArrayAppend(arguments.sql, " WHERE ");
-		loc.iEnd = ListLen(primaryKeys());
-		for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
-		{
-			loc.key = primaryKeys(loc.i);
-			ArrayAppend(arguments.sql, variables.wheels.class.properties[loc.key].column & " = ");
-			if (hasChanged(loc.key))
-			{
-				loc.value = changedFrom(loc.key);
+		local.iEnd = ListLen(primaryKeys());
+		for (local.i=1; local.i <= local.iEnd; local.i++)
+		{
+			local.key = primaryKeys(local.i);
+			ArrayAppend(arguments.sql, variables.wheels.class.properties[local.key].column & " = ");
+			if (hasChanged(local.key))
+			{
+				local.value = changedFrom(local.key);
 			}
 			else
 			{
-				loc.value = this[loc.key];
-			}
-			if (Len(loc.value))
-			{
-				loc.null = false;
+				local.value = this[local.key];
+			}
+			if (Len(local.value))
+			{
+				local.null = false;
 			}
 			else
 			{
-				loc.null = true;
-			}
-			loc.param = {value=loc.value, type=variables.wheels.class.properties[loc.key].type, dataType=variables.wheels.class.properties[loc.key].dataType, scale=variables.wheels.class.properties[loc.key].scale, null=loc.null};
-			ArrayAppend(arguments.sql, loc.param);
-			if (loc.i < loc.iEnd)
+				local.null = true;
+			}
+			local.param = {value=local.value, type=variables.wheels.class.properties[local.key].type, dataType=variables.wheels.class.properties[local.key].dataType, scale=variables.wheels.class.properties[local.key].scale, null=local.null};
+			ArrayAppend(arguments.sql, local.param);
+			if (local.i < local.iEnd)
 			{
 				ArrayAppend(arguments.sql, " AND ");
 			}
 		}
-	</cfscript>
-	<cfreturn arguments.sql>
-</cffunction>
-
-<cffunction name="$orderByClause" returntype="string" access="public" output="false">
-	<cfargument name="order" type="string" required="true">
-	<cfargument name="include" type="string" required="true">
-	<cfscript>
-		var loc = {};
-		loc.rv = "";
+		return arguments.sql;
+	}
+
+	public string function $orderByClause(required string order, required string include) {		
+		local.rv = "";
 		if (Len(arguments.order))
 		{
 			if (arguments.order == "random")
 			{
-				loc.rv = variables.wheels.class.adapter.$randomOrder();
+				local.rv = variables.wheels.class.adapter.$randomOrder();
 			}
 			else if (Find("(", arguments.order))
 			{
-				loc.rv = arguments.order;
+				local.rv = arguments.order;
 			}
 			else
 			{
 				// setup an array containing class info for current class and all the ones that should be included
-				loc.classes = [];
+				local.classes = [];
 				if (Len(arguments.include))
 				{
-					loc.classes = $expandedAssociations(include=arguments.include);
-				}
-				ArrayPrepend(loc.classes, variables.wheels.class);
-
-				loc.rv = "";
-				loc.iEnd = ListLen(arguments.order);
-				for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
-				{
-					loc.iItem = Trim(ListGetAt(arguments.order, loc.i));
-					if (!Find(" ASC", loc.iItem) && !Find(" DESC", loc.iItem))
-					{
-						loc.iItem &= " ASC";
-					}
-					if (Find(".", loc.iItem))
-					{
-						loc.rv = ListAppend(loc.rv, loc.iItem);
+					local.classes = $expandedAssociations(include=arguments.include);
+				}
+				ArrayPrepend(local.classes, variables.wheels.class);
+
+				local.rv = "";
+				local.iEnd = ListLen(arguments.order);
+				for (local.i=1; local.i <= local.iEnd; local.i++)
+				{
+					local.iItem = Trim(ListGetAt(arguments.order, local.i));
+					if (!Find(" ASC", local.iItem) && !Find(" DESC", local.iItem))
+					{
+						local.iItem &= " ASC";
+					}
+					if (Find(".", local.iItem))
+					{
+						local.rv = ListAppend(local.rv, local.iItem);
 					}
 					else
 					{
-						loc.property = ListLast(SpanExcluding(loc.iItem, " "), ".");
-						loc.jEnd = ArrayLen(loc.classes);
-						for (loc.j=1; loc.j <= loc.jEnd; loc.j++)
-						{
-							loc.toAdd = "";
-							loc.classData = loc.classes[loc.j];
-							if (ListFindNoCase(loc.classData.propertyList, loc.property))
-							{
-								loc.toAdd = loc.classData.tableName & "." & loc.classData.properties[loc.property].column;
-							}
-							else if (ListFindNoCase(loc.classData.calculatedPropertyList, loc.property))
-							{
-								loc.toAdd = "(" & Replace(loc.classData.calculatedProperties[loc.property].sql, ",", "[[comma]]", "all") & ")";
-							}
-							if (Len(loc.toAdd))
-							{
-								if (!ListFindNoCase(loc.classData.columnList, loc.property))
+						local.property = ListLast(SpanExcluding(local.iItem, " "), ".");
+						local.jEnd = ArrayLen(local.classes);
+						for (local.j=1; local.j <= local.jEnd; local.j++)
+						{
+							local.toAdd = "";
+							local.classData = local.classes[local.j];
+							if (ListFindNoCase(local.classData.propertyList, local.property))
+							{
+								local.toAdd = local.classData.tableName & "." & local.classData.properties[local.property].column;
+							}
+							else if (ListFindNoCase(local.classData.calculatedPropertyList, local.property))
+							{
+								local.toAdd = "(" & Replace(local.classData.calculatedProperties[local.property].sql, ",", "[[comma]]", "all") & ")";
+							}
+							if (Len(local.toAdd))
+							{
+								if (!ListFindNoCase(local.classData.columnList, local.property))
 								{
-									loc.toAdd &= " AS " & loc.property;
+									local.toAdd &= " AS " & local.property;
 								}
-								loc.toAdd &= " " & UCase(ListLast(loc.iItem, " "));
-								if (!ListFindNoCase(loc.rv, loc.toAdd))
+								local.toAdd &= " " & UCase(ListLast(local.iItem, " "));
+								if (!ListFindNoCase(local.rv, local.toAdd))
 								{
-									loc.rv = ListAppend(loc.rv, loc.toAdd);
+									local.rv = ListAppend(local.rv, local.toAdd);
 									break;
 								}
 							}
 						}
-						if (application.wheels.showErrorInformation && !Len(loc.toAdd))
-						{
-							$throw(type="Wheels.ColumnNotFound", message="Wheels looked for the column mapped to the `#loc.property#` property but couldn't find it in the database table.", extendedInfo="Verify the `order` argument and/or your property to column mappings done with the `property` method inside the model's `init` method to make sure everything is correct.");
-						}
-					}
-				}
-			}
-			loc.rv = "ORDER BY " & loc.rv;
-		}
-	</cfscript>
-	<cfreturn loc.rv>
-</cffunction>
-
-<cffunction name="$groupByClause" returntype="string" access="public" output="false">
-	<cfargument name="select" type="string" required="true">
-	<cfargument name="include" type="string" required="true">
-	<cfargument name="group" type="string" required="true">
-	<cfargument name="distinct" type="boolean" required="true">
-	<cfargument name="returnAs" type="string" required="true">
-	<cfscript>
-		var loc = {};
-		loc.rv = "";
-		loc.args = {};
-		loc.args.include = arguments.include;
-		loc.args.returnAs = arguments.returnAs;
-		loc.args.clause = "groupBy";
+						if (application.wheels.showErrorInformation && !Len(local.toAdd))
+						{
+							$throw(type="Wheels.ColumnNotFound", message="Wheels looked for the column mapped to the `#local.property#` property but couldn't find it in the database table.", extendedInfo="Verify the `order` argument and/or your property to column mappings done with the `property` method inside the model's `init` method to make sure everything is correct.");
+						}
+					}
+				}
+			}
+			local.rv = "ORDER BY " & local.rv;
+		}
+		return local.rv;
+	}
+
+	public string function $groupByClause(
+		required string select,
+		required string include,
+		required string group,
+		required boolean distinct,
+		required string returnAs
+	) { 
+		local.rv = "";
+		local.args = {};
+		local.args.include = arguments.include;
+		local.args.returnAs = arguments.returnAs;
+		local.args.clause = "groupBy";
 		if (arguments.distinct)
 		{
 			// if we want a distinct statement, we can do it grouping every field in the select
-			loc.args.list = arguments.select;
-			loc.rv = $createSQLFieldList(argumentCollection=loc.args);
+			local.args.list = arguments.select;
+			local.rv = $createSQLFieldList(argumentCollection=local.args);
 		}
 		else if (Len(arguments.group))
 		{
-			loc.args.list = arguments.group;
-			loc.rv = $createSQLFieldList(argumentCollection=loc.args);
-		}
-		if (Len(loc.rv))
-		{
-			loc.rv = "GROUP BY " & loc.rv;
-		}
-	</cfscript>
-	<cfreturn loc.rv>
-</cffunction>
-
-<cffunction name="$selectClause" returntype="string" access="public" output="false">
-	<cfargument name="select" type="string" required="true">
-	<cfargument name="include" type="string" required="true">
-	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false">
-	<cfargument name="returnAs" type="string" required="true">
-	<cfscript>
-		var loc = {};
-		loc.rv = $createSQLFieldList(clause="select", list=arguments.select, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes, returnAs=arguments.returnAs);
-		loc.rv = "SELECT " & loc.rv;
-	</cfscript>
-	<cfreturn loc.rv>
-</cffunction>
-
-<cffunction name="$createSQLFieldList" returntype="string" access="public" output="false">
-	<cfargument name="clause" type="string" required="true">
-	<cfargument name="list" type="string" required="true">
-	<cfargument name="include" type="string" required="true">
-	<cfargument name="returnAs" type="string" required="true">
-	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false">
-	<cfargument name="useExpandedColumnAliases" type="boolean" required="false" default="#application.wheels.useExpandedColumnAliases#">
-	<cfscript>
-		var loc = {};
-
+			local.args.list = arguments.group;
+			local.rv = $createSQLFieldList(argumentCollection=local.args);
+		}
+		if (Len(local.rv))
+		{
+			local.rv = "GROUP BY " & local.rv;
+		}
+		return local.rv;
+	}
+
+	public string function $selectClause(
+		required string select,
+		required string include,
+		boolean includeSoftDeletes="false",
+		required string returnAs
+	) {
+		local.rv = $createSQLFieldList(clause="select", list=arguments.select, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes, returnAs=arguments.returnAs);
+		local.rv = "SELECT " & local.rv;
+		return local.rv;
+	}
+
+	public string function $createSQLFieldList(
+		required string clause,
+		required string list,
+		required string include,
+		required string returnAs,
+		boolean includeSoftDeletes="false",
+		boolean useExpandedColumnAliases="#application.wheels.useExpandedColumnAliases#"
+	) {
 		// setup an array containing class info for current class and all the ones that should be included
-		loc.classes = [];
+		local.classes = [];
 		if (Len(arguments.include))
 		{
-			loc.classes = $expandedAssociations(include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
-		}
-		ArrayPrepend(loc.classes, variables.wheels.class);
+			local.classes = $expandedAssociations(include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
+		}
+		ArrayPrepend(local.classes, variables.wheels.class);
 
 		// if the developer passes in tablename.*, translate it into the list of fields for the developer, this is so we don't get *'s in the group by
 		if (Find(".*", arguments.list))
 		{
-			arguments.list = $expandProperties(list=arguments.list, classes=loc.classes);
+			arguments.list = $expandProperties(list=arguments.list, classes=local.classes);
 		}
 
 		// add properties to select if the developer did not specify any
 		if (!Len(arguments.list))
 		{
-			loc.iEnd = ArrayLen(loc.classes);
-			for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
-			{
-				loc.classData = loc.classes[loc.i];
-				arguments.list = ListAppend(arguments.list, loc.classData.propertyList);
-				if (StructCount(loc.classData.calculatedProperties))
-				{
-					for (loc.key in loc.classData.calculatedProperties)
-					{
-						if (loc.classData.calculatedProperties[loc.key].select)
-						{
-							arguments.list = ListAppend(arguments.list, loc.key);
+			local.iEnd = ArrayLen(local.classes);
+			for (local.i=1; local.i <= local.iEnd; local.i++)
+			{
+				local.classData = local.classes[local.i];
+				arguments.list = ListAppend(arguments.list, local.classData.propertyList);
+				if (StructCount(local.classData.calculatedProperties))
+				{
+					for (local.key in local.classData.calculatedProperties)
+					{
+						if (local.classData.calculatedProperties[local.key].select)
+						{
+							arguments.list = ListAppend(arguments.list, local.key);
 						}
 					}
 				}

		// add soft delete sql
		if (!arguments.includeSoftDeletes)
		{
			loc.addToWhere = "";
			if ($softDeletion())
			{
				loc.addToWhere = ListAppend(loc.addToWhere, tableName() & "." & $softDeleteColumn() & " IS NULL");
			}
			loc.addToWhere = Replace(loc.addToWhere, ",", " AND ", "all");
			if (Len(loc.addToWhere))
			{
				if (Len(arguments.where))
				{
					ArrayInsertAt(loc.rv, loc.wherePos, " (");
					ArrayAppend(loc.rv, ") AND (");
					ArrayAppend(loc.rv, loc.addToWhere);
					ArrayAppend(loc.rv, ")");
				}
				else
				{
					ArrayAppend(loc.rv, "WHERE ");
					ArrayAppend(loc.rv, loc.addToWhere);
				}
			}
		}
	</cfscript>
	<cfreturn loc.rv>
</cffunction>

<cffunction name="$addWhereClauseParameters" returntype="array" access="public" output="false">
	<cfargument name="sql" type="array" required="true">
	<cfargument name="where" type="string" required="true">
	<cfscript>
		var loc = {};
		if (Len(arguments.where))
		{
			loc.start = 1;
			loc.originalValues = [];
			while (!StructKeyExists(loc, "temp") || ArrayLen(loc.temp.len) > 1)
			{
				loc.temp = REFind(variables.wheels.class.RESQLWhere, arguments.where, loc.start, true);
				if (ArrayLen(loc.temp.len) > 1)
				{
					loc.start = loc.temp.pos[4] + loc.temp.len[4];
					ArrayAppend(loc.originalValues, ReplaceList(Chr(7) & Mid(arguments.where, loc.temp.pos[4], loc.temp.len[4]) & Chr(7), "#Chr(7)#(,)#Chr(7)#,#Chr(7)#','#Chr(7)#,#Chr(7)#"",""#Chr(7)#,#Chr(7)#", ",,,,,,"));
				}
			}
			loc.pos = ArrayLen(loc.originalValues);
			loc.iEnd = ArrayLen(arguments.sql);
			for (loc.i=loc.iEnd; loc.i > 0; loc.i--)
			{
				if (IsStruct(arguments.sql[loc.i]) && loc.pos > 0)
				{
					arguments.sql[loc.i].value = loc.originalValues[loc.pos];
					if (loc.originalValues[loc.pos] == "")
					{
						arguments.sql[loc.i].null = true;
					}
					loc.pos--;
				}
			}
		}
	</cfscript>
	<cfreturn arguments.sql>
</cffunction>

<cffunction name="$expandProperties" returntype="string" access="public" output="false">
	<cfargument name="list" type="string" required="true">
	<cfargument name="classes" type="array" required="true">
	<cfscript>
		var loc = {};
		loc.rv = arguments.list;
		loc.matches = REMatch("[A-Za-z1-9]+\.\*", loc.rv);
		loc.iEnd = ArrayLen(loc.matches);
		for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
		{
			loc.match = loc.matches[loc.i];
			loc.fields = "";
			loc.tableName = ListGetAt(loc.match, 1, ".");
			loc.jEnd = ArrayLen(arguments.classes);
			for (loc.j = 1; loc.j <= loc.jEnd; loc.j++)
			{
				loc.class = arguments.classes[loc.j];
				if (loc.class.tableName == loc.tableName)
				{
					for (loc.item in loc.class.properties)
					{
						loc.fields = ListAppend(loc.fields, "#loc.class.tableName#.#loc.item#");
					}
					break;
				}
			}
			if (Len(loc.fields))
			{
				loc.rv = Replace(loc.rv, loc.match, loc.fields, "all");
			}
			else if (application.wheels.showErrorInformation)
			{
				$throw(type="Wheels.ModelNotFound", message="Wheels looked for the model mapped to table name `#loc.tableName#` but couldn't find it.", extendedInfo="Verify the `select` argument and/or your model association mappings are correct.");
			}
		}
	</cfscript>
	<cfreturn loc.rv>
</cffunction>

<cffunction name="$expandedAssociations" returntype="array" access="public" output="false">
	<cfargument name="include" type="string" required="true">
	<cfargument name="includeSoftDeletes" type="boolean" default="false">
	<cfscript>
		var loc = {};
		loc.rv = [];

		// add the current class name so that the levels list start at the lowest level
		loc.levels = variables.wheels.class.modelName;

		// count the included associations
		loc.iEnd = ListLen(Replace(arguments.include, "(", ",", "all"));

		// clean up spaces in list and add a comma at the end to indicate end of string
		loc.include = Replace(arguments.include, " ", "", "all") & ",";

		// store all tables used in the query so we can alias them when needed
		loc.tables = tableName();

		loc.pos = 1;
		for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
		{
			// look for the next delimiter sequence in the string and set it (can be single delims or a chain, e.g ',' or ')),'
			loc.delimFind = ReFind("[(\(|\)|,)]+", loc.include, loc.pos, true);
			loc.delimSequence = Mid(loc.include, loc.delimFind.pos[1], loc.delimFind.len[1]);

			// set current association name and set new position to start search in the next loop
			loc.name = Mid(loc.include, loc.pos, loc.delimFind.pos[1]-loc.pos);
			loc.pos = REFindNoCase("[a-z]", loc.include, loc.delimFind.pos[1]);

			// create a reference to current class in include string and get its association info
			loc.class = model(ListLast(loc.levels));
			loc.classAssociations = loc.class.$classData().associations;

			// throw an error if the association was not found
			if (application.wheels.showErrorInformation && !StructKeyExists(loc.classAssociations, loc.name))
			{
				$throw(type="Wheels.AssociationNotFound", message="An association named `#loc.name#` could not be found on the `#ListLast(loc.levels)#` model.", extendedInfo="Setup an association in the `init` method of the `models/#capitalize(ListLast(loc.levels))#.cfc` file and name it `#loc.name#`. You can use the `belongsTo`, `hasOne` or `hasMany` method to set it up.");
			}

			// create a reference to the associated class
			loc.associatedClass = model(loc.classAssociations[loc.name].modelName);

			if (!Len(loc.classAssociations[loc.name].foreignKey))
			{
				if (loc.classAssociations[loc.name].type == "belongsTo")
				{
					loc.classAssociations[loc.name].foreignKey = loc.associatedClass.$classData().modelName & Replace(loc.associatedClass.$classData().keys, ",", ",#loc.associatedClass.$classData().modelName#", "all");
				}
				else
				{
					loc.classAssociations[loc.name].foreignKey = loc.class.$classData().modelName & Replace(loc.class.$classData().keys, ",", ",#loc.class.$classData().modelName#", "all");
				}
			}
			if (!Len(loc.classAssociations[loc.name].joinKey))
			{
				if (loc.classAssociations[loc.name].type == "belongsTo")
				{
					loc.classAssociations[loc.name].joinKey = loc.associatedClass.$classData().keys;
				}
				else
				{
					loc.classAssociations[loc.name].joinKey = loc.class.$classData().keys;
				}
			}
			loc.classAssociations[loc.name].tableName = loc.associatedClass.$classData().tableName;
			loc.classAssociations[loc.name].columnList = loc.associatedClass.$classData().columnList;
			loc.classAssociations[loc.name].properties = loc.associatedClass.$classData().properties;
			loc.classAssociations[loc.name].propertyList = loc.associatedClass.$classData().propertyList;
			loc.classAssociations[loc.name].calculatedProperties = loc.associatedClass.$classData().calculatedProperties;
			loc.classAssociations[loc.name].calculatedPropertyList = loc.associatedClass.$classData().calculatedPropertyList;

			// create the join string if it hasn't already been done
			if (!StructKeyExists(loc.classAssociations[loc.name], "join"))
			{
				loc.join = UCase(ReplaceNoCase(loc.classAssociations[loc.name].joinType, "outer", "left outer", "one")) & " JOIN " & loc.classAssociations[loc.name].tableName;

				// alias the table as the association name when joining to itself
				if (ListFindNoCase(loc.tables, loc.classAssociations[loc.name].tableName))
				{
					loc.join = variables.wheels.class.adapter.$tableAlias(loc.join,loc.classAssociations[loc.name].pluralizedName);
				}

				loc.join &= " ON ";
				loc.toAppend = "";
				loc.jEnd = ListLen(loc.classAssociations[loc.name].foreignKey);
				for (loc.j=1; loc.j <= loc.jEnd; loc.j++)
				{
					loc.key1 = ListGetAt(loc.classAssociations[loc.name].foreignKey, loc.j);
					if (loc.classAssociations[loc.name].type == "belongsTo")
					{
						loc.key2 = ListFindNoCase(loc.classAssociations[loc.name].joinKey, loc.key1);
						if (loc.key2)
						{
							loc.key2 = ListGetAt(loc.classAssociations[loc.name].joinKey, loc.key2);
						}
						else
						{
							loc.key2 = ListGetAt(loc.classAssociations[loc.name].joinKey, loc.j);
						}
						loc.first = loc.key1;
						loc.second = loc.key2;
					}
					else
					{
						loc.key2 = ListFindNoCase(loc.classAssociations[loc.name].joinKey, loc.key1);
						if (loc.key2)
						{
							loc.key2 = ListGetAt(loc.classAssociations[loc.name].joinKey, loc.key2);
						}
						else
						{
							loc.key2 = ListGetAt(loc.classAssociations[loc.name].joinKey, loc.j);
						}
						loc.first = loc.key2;
						loc.second = loc.key1;
					}

					// alias the table as the association name when joining to itself
					loc.tableName = loc.classAssociations[loc.name].tableName;
					if (ListFindNoCase(loc.tables, loc.classAssociations[loc.name].tableName))
					{
						loc.tableName = loc.classAssociations[loc.name].pluralizedName;;
					}

					loc.toAppend = ListAppend(loc.toAppend, "#loc.class.$classData().tableName#.#loc.class.$classData().properties[loc.first].column# = #loc.tableName#.#loc.associatedClass.$classData().properties[loc.second].column#");
					if (!arguments.includeSoftDeletes && loc.associatedClass.$softDeletion())
					{
						loc.toAppend = ListAppend(loc.toAppend, "#loc.associatedClass.tableName()#.#loc.associatedClass.$softDeleteColumn()# IS NULL");
					}
				}
				loc.classAssociations[loc.name].join = loc.join & Replace(loc.toAppend, ",", " AND ", "all");
			}

			// loop over each character in the delimiter sequence and move up / down the levels as appropriate
			loc.jEnd = Len(loc.delimSequence);
			for (loc.j=1; loc.j <= loc.jEnd; loc.j++)
			{
				loc.delimChar = Mid(loc.delimSequence, loc.j, 1);
				if (loc.delimChar == "(")
				{
					loc.levels = ListAppend(loc.levels, loc.classAssociations[loc.name].modelName);
				}
				else if (loc.delimChar == ")")
				{
					loc.levels = ListDeleteAt(loc.levels, ListLen(loc.levels));
				}
			}

			// add table name to the list of used ones so we know to alias it when used a second time
			loc.tables = ListAppend(loc.tables, loc.classAssociations[loc.name].tableName);

			// add info to the array that we will return
			ArrayAppend(loc.rv, loc.classAssociations[loc.name]);
		}
		</cfscript>
		<cfreturn loc.rv>
</cffunction>

<cffunction name="$keyWhereString" returntype="string" access="public" output="false">
	<cfargument name="properties" type="any" required="false" default="#primaryKeys()#">
	<cfargument name="values" type="any" required="false" default="">
	<cfargument name="keys" type="any" required="false" default="">
	<cfscript>
		var loc = {};
		loc.rv = "";
		loc.iEnd = ListLen(arguments.properties);
		for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
		{
			loc.key = Trim(ListGetAt(arguments.properties, loc.i));
			if (Len(arguments.values))
			{
				loc.value = ListGetAt(arguments.values, loc.i);
			}
			else if (Len(arguments.keys))
			{
				loc.value = this[ListGetAt(arguments.keys, loc.i)];
			}
			else
			{
				loc.value = "";
			}
			loc.toAppend = loc.key & "=" & variables.wheels.class.adapter.$quoteValue(str=loc.value, type=validationTypeForProperty(loc.key));
			loc.rv = ListAppend(loc.rv, loc.toAppend, " ");
			if (loc.i < loc.iEnd)
			{
				loc.rv = ListAppend(loc.rv, "AND", " ");
			}
		}
	</cfscript>
	<cfreturn loc.rv>
</cffunction>
		// go through the properties and map them to the database unless the developer passed in a table name or an alias in which case we assume they know what they're doing and leave the select clause as is
		if (!Find(".", arguments.list) && !Find(" AS ", arguments.list))
		{
			local.rv = "";
			local.addedProperties = "";
			local.addedPropertiesByModel = {};
			local.iEnd = ListLen(arguments.list);
			for (local.i=1; local.i <= local.iEnd; local.i++)
			{
				local.iItem = Trim(ListGetAt(arguments.list, local.i));

				// look for duplicates
				local.duplicateCount = ListValueCountNoCase(local.addedProperties, local.iItem);
				local.addedProperties = ListAppend(local.addedProperties, local.iItem);

				// loop through all classes (current and all included ones)
				local.jEnd = ArrayLen(local.classes);
				for (local.j=1; local.j <= local.jEnd; local.j++)
				{
					local.toAppend = "";
					local.classData = local.classes[local.j];

					// create a struct for this model unless it already exists
					if (!StructKeyExists(local.addedPropertiesByModel, local.classData.modelName))
					{
						local.addedPropertiesByModel[local.classData.modelName] = "";
					}

					// if we find the property in this model and it's not already added we go ahead and add it to the select clause
					if ((ListFindNoCase(local.classData.propertyList, local.iItem) || ListFindNoCase(local.classData.calculatedPropertyList, local.iItem)) && !ListFindNoCase(local.addedPropertiesByModel[local.classData.modelName], local.iItem))
					{
						// if expanded column aliases is enabled then mark all columns from included classes as duplicates in order to prepend them with their class name
						local.flagAsDuplicate = false;
						if (arguments.clause == "select")
						{
							if (local.duplicateCount)
							{
								// always flag as a duplicate when a property with this name has already been added
								local.flagAsDuplicate  = true;
							}
							else if (local.j > 1)
							{
								if (arguments.useExpandedColumnAliases)
								{
									// when on included models and using the new setting we flag every property as a duplicate so that the model name always gets prepended
									local.flagAsDuplicate  = true;
								}
								else if (!arguments.useExpandedColumnAliases && arguments.returnAs != "query")
								{
									// with the old setting we only do it when we're returning object(s) since when creating instances on none base models we need the model name prepended
									local.flagAsDuplicate  = true;
								}
							}
						}
						if (local.flagAsDuplicate)
						{
							local.toAppend &= "[[duplicate]]" & local.j;
						}
						if (ListFindNoCase(local.classData.propertyList, local.iItem))
						{
							local.toAppend &= local.classData.tableName & ".";
							if (ListFindNoCase(local.classData.columnList, local.iItem))
							{
								local.toAppend &= local.iItem;
							}
							else
							{
								local.toAppend &= local.classData.properties[local.iItem].column;
								if (arguments.clause == "select")
								{
									local.toAppend &= " AS " & local.iItem;
								}
							}
						}
						else if (ListFindNoCase(local.classData.calculatedPropertyList, local.iItem))
						{
							local.sql = Replace(local.classData.calculatedProperties[local.iItem].sql, ",", "[[comma]]", "all");
							if (arguments.clause == "select" || !REFind("^(SELECT )?(AVG|COUNT|MAX|MIN|SUM)\(.*\)", local.sql))
							{
								local.toAppend &= "(" & local.sql & ")";
								if (arguments.clause == "select")
								{
									local.toAppend &= " AS " & local.iItem;
								}
							}
						}
						local.addedPropertiesByModel[local.classData.modelName] = ListAppend(local.addedPropertiesByModel[local.classData.modelName], local.iItem);
						break;
					}
				}
				if (Len(local.toAppend))
				{
					local.rv = ListAppend(local.rv, local.toAppend);
				}
			}

			// let's replace eventual duplicates in the clause by prepending the class name
			if (Len(arguments.include) && arguments.clause == "select")
			{
				local.newSelect = "";
				local.addedProperties = "";
				local.iEnd = ListLen(local.rv);
				for (local.i=1; local.i <= local.iEnd; local.i++)
				{
					local.iItem = ListGetAt(local.rv, local.i);

					// get the property part, done by taking everytyhing from the end of the string to a . or a space (which would be found when using " AS ")
					local.property = Reverse(SpanExcluding(Reverse(local.iItem), ". "));

					// check if this one has been flagged as a duplicate, we get the number of classes to skip and also remove the flagged info from the item
					local.duplicateCount = 0;
					local.matches = REFind("^\[\[duplicate\]\](\d+)(.+)$", local.iItem, 1, true);
					if (local.matches.pos[1] > 0)
					{
						local.duplicateCount = Mid(local.iItem, local.matches.pos[2], local.matches.len[2]);
						local.iItem = Mid(local.iItem, local.matches.pos[3], local.matches.len[3]);
					}

					if (!local.duplicateCount)
					{
						// this is not a duplicate so we can just insert it as is
						local.newItem = local.iItem;
						local.newProperty = local.property;
					}
					else
					{
						// this is a duplicate so we prepend the class name and then insert it unless a property with the resulting name already exist
						local.classData = local.classes[local.duplicateCount];

						// prepend class name to the property
						local.newProperty = local.classData.modelName & local.property;

						if (Find(" AS ", local.iItem))
						{
							local.newItem = ReplaceNoCase(local.iItem, " AS " & local.property, " AS " & local.newProperty);
						}
						else
						{
							local.newItem = local.iItem & " AS " & local.newProperty;
						}
					}
					if (!ListFindNoCase(local.addedProperties, local.newProperty))
					{
						local.newSelect = ListAppend(local.newSelect, local.newItem);
						local.addedProperties = ListAppend(local.addedProperties, local.newProperty);
					}
				}
				local.rv = local.newSelect;
			}
		}
		else
		{
			local.rv = arguments.list;
			if (arguments.clause == "groupBy" && Find(" AS ", local.rv))
			{
				local.rv = REReplace(local.rv, variables.wheels.class.RESQLAs, "", "all");
			}
		}
		return local.rv;
	}

	public array function $addWhereClause(
		required array sql,
		required string where,
		required string include,
		required boolean includeSoftDeletes
	) {
		local.whereClause = $whereClause(where=arguments.where, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
		local.iEnd = ArrayLen(local.whereClause);
		for (local.i=1; local.i <= local.iEnd; local.i++)
		{
			ArrayAppend(arguments.sql, local.whereClause[local.i]);
		}
		return arguments.sql;
	}

	public array function $whereClause(
		required string where,
		string include="",
		boolean includeSoftDeletes="false"
	) {	
		local.rv = [];
		if (Len(arguments.where))
		{
			// setup an array containing class info for current class and all the ones that should be included
			local.classes = [];
			if (Len(arguments.include))
			{
				local.classes = $expandedAssociations(include=arguments.include);
			}
			ArrayPrepend(local.classes, variables.wheels.class);

			ArrayAppend(local.rv, "WHERE");
			local.wherePos = ArrayLen(local.rv) + 1;
			local.params = ArrayNew(1);
			local.where = ReplaceList(REReplace(arguments.where, variables.wheels.class.RESQLWhere, "\1?\8" , "all"), "AND,OR", "#Chr(7)#AND,#Chr(7)#OR");
			for (local.i=1; local.i <= ListLen(local.where, Chr(7)); local.i++)
			{
				local.param = {};
				local.element = Replace(ListGetAt(local.where, local.i, Chr(7)), Chr(7), "", "one");
				if (Find("(", local.element) && Find(")", local.element))
				{
					local.elementDataPart = SpanExcluding(Reverse(SpanExcluding(Reverse(local.element), "(")), ")");
				}
				else if (Find("(", local.element))
				{
					local.elementDataPart = Reverse(SpanExcluding(Reverse(local.element), "("));
				}
				else if (Find(")", local.element))
				{
					local.elementDataPart = SpanExcluding(local.element, ")");
				}
				else
				{
					local.elementDataPart = local.element;
				}
				local.elementDataPart = Trim(ReplaceList(local.elementDataPart, "AND,OR", ""));
				local.temp = REFind("^([a-zA-Z0-9-_\.]*) ?#variables.wheels.class.RESQLOperators#", local.elementDataPart, 1, true);
				if (ArrayLen(local.temp.len) > 1)
				{
					local.where = Replace(local.where, local.element, Replace(local.element, local.elementDataPart, "?", "one"));
					local.param.property = Mid(local.elementDataPart, local.temp.pos[2], local.temp.len[2]);
					local.jEnd = ArrayLen(local.classes);
					for (local.j=1; local.j <= local.jEnd; local.j++)
					{
						local.param.dataType = "char";
						local.param.type = "CF_SQL_CHAR";
						local.param.scale = 0;
						local.param.list = false;
						local.classData = local.classes[local.j];
						local.table = ListFirst(local.param.property, ".");
						local.column = ListLast(local.param.property, ".");
						if (!Find(".", local.param.property) || local.table == local.classData.tableName)
						{
							if (ListFindNoCase(local.classData.propertyList, local.column))
							{
								local.param.column = local.classData.tableName & "." & local.classData.properties[local.column].column;
								local.param.dataType = local.classData.properties[local.column].dataType;
								local.param.type = local.classData.properties[local.column].type;
								local.param.scale = local.classData.properties[local.column].scale;
								break;
							}
							else if (ListFindNoCase(local.classData.calculatedPropertyList, local.column))
							{
								local.param.column = "(" & local.classData.calculatedProperties[local.column].sql & ")";
								if (StructKeyExists(local.classData.calculatedProperties[local.column], "dataType"))
								{
									local.param.dataType = local.classData.calculatedProperties[local.column].dataType;
									local.param.type = variables.wheels.class.adapter.$getType(local.param.dataType);
								}
								break;
							}
						}
					}
					if (application.wheels.showErrorInformation && !StructKeyExists(local.param, "column"))
					{
						$throw(type="Wheels.ColumnNotFound", message="Wheels looked for the column mapped to the `#local.param.property#` property but couldn't find it in the database table.", extendedInfo="Verify the `where` argument and/or your property to column mappings done with the `property` method inside the model's `init` method to make sure everything is correct.");
					}
					local.temp = REFind("^[a-zA-Z0-9-_\.]* ?#variables.wheels.class.RESQLOperators#", local.elementDataPart, 1, true);
					local.param.operator = Trim(Mid(local.elementDataPart, local.temp.pos[2], local.temp.len[2]));
					if (Right(local.param.operator, 2) == "IN")
					{
						local.param.list = true;
					}
					ArrayAppend(local.params, local.param);
				}
			}
			local.where = ReplaceList(local.where, "#Chr(7)#AND,#Chr(7)#OR", "AND,OR");

			// add to sql array
			local.where = " " & local.where & " ";
			local.iEnd = ListLen(local.where, "?");
			for (local.i=1; local.i <= local.iEnd; local.i++)
			{
				local.item = ListGetAt(local.where, local.i, "?");
				if (Len(Trim(local.item)))
				{
					ArrayAppend(local.rv, local.item);
				}
				if (local.i < ListLen(local.where, "?"))
				{
					local.column = local.params[local.i].column;
					ArrayAppend(local.rv, local.column & " " & local.params[local.i].operator);
					local.param = {type=local.params[local.i].type, dataType=local.params[local.i].dataType, scale=local.params[local.i].scale, list=local.params[local.i].list};
					ArrayAppend(local.rv, local.param);
				}
			}
		}
