PageRenderTime 44ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/wheels/model/sql.cfm

http://cfwheels.googlecode.com/
ColdFusion | 744 lines | 707 code | 37 blank | 0 comment | 158 complexity | 8cb2403daa19ba81182660cf5e9b94bb MD5 | raw file
Possible License(s): Apache-2.0, CPL-1.0
  1. <cffunction name="$addDeleteClause" returntype="array" access="public" output="false">
  2. <cfargument name="sql" type="array" required="true">
  3. <cfargument name="softDelete" type="boolean" required="true">
  4. <cfscript>
  5. var loc = {};
  6. if (variables.wheels.class.softDeletion && arguments.softDelete)
  7. {
  8. ArrayAppend(arguments.sql, "UPDATE #tableName()# SET #variables.wheels.class.softDeleteColumn# = ");
  9. loc.param = {value=Now(), type="cf_sql_timestamp"};
  10. ArrayAppend(arguments.sql, loc.param);
  11. }
  12. else
  13. {
  14. ArrayAppend(arguments.sql, "DELETE FROM #tableName()#");
  15. }
  16. </cfscript>
  17. <cfreturn arguments.sql>
  18. </cffunction>
  19. <cffunction name="$fromClause" returntype="string" access="public" output="false">
  20. <cfargument name="include" type="string" required="true">
  21. <cfscript>
  22. var loc = {};
  23. // start the from statement with the SQL keyword and the table name for the current model
  24. loc.returnValue = "FROM " & tableName();
  25. // add join statements if associations have been specified through the include argument
  26. if (Len(arguments.include))
  27. {
  28. // get info for all associations
  29. loc.associations = $expandedAssociations(include=arguments.include);
  30. // add join statement for each include separated by space
  31. loc.iEnd = ArrayLen(loc.associations);
  32. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  33. loc.returnValue = ListAppend(loc.returnValue, loc.associations[loc.i].join, " ");
  34. }
  35. </cfscript>
  36. <cfreturn loc.returnValue>
  37. </cffunction>
  38. <cffunction name="$addKeyWhereClause" returntype="array" access="public" output="false">
  39. <cfargument name="sql" type="array" required="true">
  40. <cfscript>
  41. var loc = {};
  42. ArrayAppend(arguments.sql, " WHERE ");
  43. loc.iEnd = ListLen(primaryKeys());
  44. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  45. {
  46. loc.key = primaryKeys(loc.i);
  47. ArrayAppend(arguments.sql, "#variables.wheels.class.properties[loc.key].column# = ");
  48. if (hasChanged(loc.key))
  49. loc.value = changedFrom(loc.key);
  50. else
  51. loc.value = this[loc.key];
  52. if (Len(loc.value))
  53. loc.null = false;
  54. else
  55. loc.null = true;
  56. 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};
  57. ArrayAppend(arguments.sql, loc.param);
  58. if (loc.i < loc.iEnd)
  59. ArrayAppend(arguments.sql, " AND ");
  60. }
  61. </cfscript>
  62. <cfreturn arguments.sql>
  63. </cffunction>
  64. <cffunction name="$orderByClause" returntype="string" access="public" output="false">
  65. <cfargument name="order" type="string" required="true">
  66. <cfargument name="include" type="string" required="true">
  67. <cfscript>
  68. var loc = {};
  69. loc.returnValue = "";
  70. if (Len(arguments.order))
  71. {
  72. if (arguments.order == "random")
  73. {
  74. loc.returnValue = variables.wheels.class.adapter.$randomOrder();
  75. }
  76. else if (arguments.order Contains "(")
  77. {
  78. loc.returnValue = arguments.order;
  79. }
  80. else
  81. {
  82. // setup an array containing class info for current class and all the ones that should be included
  83. loc.classes = [];
  84. if (Len(arguments.include))
  85. loc.classes = $expandedAssociations(include=arguments.include);
  86. ArrayPrepend(loc.classes, variables.wheels.class);
  87. loc.returnValue = "";
  88. loc.iEnd = ListLen(arguments.order);
  89. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  90. {
  91. loc.iItem = Trim(ListGetAt(arguments.order, loc.i));
  92. if (!FindNoCase(" ASC", loc.iItem) && !FindNoCase(" DESC", loc.iItem))
  93. loc.iItem = loc.iItem & " ASC";
  94. if (loc.iItem Contains ".")
  95. {
  96. loc.returnValue = ListAppend(loc.returnValue, loc.iItem);
  97. }
  98. else
  99. {
  100. loc.property = ListLast(SpanExcluding(loc.iItem, " "), ".");
  101. loc.jEnd = ArrayLen(loc.classes);
  102. for (loc.j=1; loc.j <= loc.jEnd; loc.j++)
  103. {
  104. loc.toAdd = "";
  105. loc.classData = loc.classes[loc.j];
  106. // we need the name of the table on the first go, and the alias otherwise
  107. loc.tableName = loc.classData.tableName;
  108. if (loc.j != 1)
  109. loc.tableName = loc.classData.alias;
  110. if (ListFindNoCase(loc.classData.propertyList, loc.property))
  111. loc.toAdd = loc.tableName & "." & loc.classData.properties[loc.property].column;
  112. else if (ListFindNoCase(loc.classData.calculatedPropertyList, loc.property))
  113. loc.toAdd = Replace(loc.classData.calculatedProperties[loc.property].sql, ",", "[[comma]]", "all");
  114. if (Len(loc.toAdd))
  115. {
  116. if (!ListFindNoCase(loc.classData.columnList, loc.property))
  117. loc.toAdd = loc.toAdd & " AS " & loc.property;
  118. loc.toAdd = loc.toAdd & " " & UCase(ListLast(loc.iItem, " "));
  119. if (!ListFindNoCase(loc.returnValue, loc.toAdd))
  120. {
  121. loc.returnValue = ListAppend(loc.returnValue, loc.toAdd);
  122. break;
  123. }
  124. }
  125. }
  126. if (application.wheels.showErrorInformation && !Len(loc.toAdd))
  127. $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.");
  128. }
  129. }
  130. }
  131. loc.returnValue = "ORDER BY " & loc.returnValue;
  132. }
  133. </cfscript>
  134. <cfreturn loc.returnValue>
  135. </cffunction>
  136. <cffunction name="$groupByClause" returntype="string" access="public" output="false">
  137. <cfargument name="select" type="string" required="true">
  138. <cfargument name="include" type="string" required="true">
  139. <cfargument name="group" type="string" required="true">
  140. <cfargument name="distinct" type="boolean" required="true">
  141. <cfargument name="returnAs" type="string" required="true">
  142. <cfscript>
  143. var returnValue = "";
  144. // if we want a distinct statement, we can do it grouping every field in the select
  145. if (arguments.distinct)
  146. returnValue = $createSQLFieldList(list=arguments.select, include=arguments.include, returnAs=arguments.returnAs, renameFields=false, addCalculatedProperties=false);
  147. else if (Len(arguments.group))
  148. returnValue = $createSQLFieldList(list=arguments.group, include=arguments.include, returnAs=arguments.returnAs, renameFields=false, addCalculatedProperties=false);
  149. if (Len(returnValue))
  150. returnValue = "GROUP BY " & returnValue;
  151. </cfscript>
  152. <cfreturn returnValue>
  153. </cffunction>
  154. <cffunction name="$selectClause" returntype="string" access="public" output="false">
  155. <cfargument name="select" type="string" required="true">
  156. <cfargument name="include" type="string" required="true">
  157. <cfargument name="returnAs" type="string" required="true">
  158. <cfscript>
  159. var returnValue = "";
  160. returnValue = $createSQLFieldList(list=arguments.select, include=arguments.include, returnAs=arguments.returnAs);
  161. returnValue = "SELECT " & returnValue;
  162. </cfscript>
  163. <cfreturn returnValue>
  164. </cffunction>
  165. <cffunction name="$createSQLFieldList" returntype="string" access="public" output="false">
  166. <cfargument name="list" type="string" required="true">
  167. <cfargument name="include" type="string" required="true">
  168. <cfargument name="returnAs" type="string" required="true">
  169. <cfargument name="renameFields" type="boolean" required="false" default="true">
  170. <cfargument name="addCalculatedProperties" type="boolean" required="false" default="true">
  171. <cfargument name="useExpandedColumnAliases" type="boolean" required="false" default="#application.wheels.useExpandedColumnAliases#">
  172. <cfscript>
  173. var loc = {};
  174. // setup an array containing class info for current class and all the ones that should be included
  175. loc.classes = [];
  176. if (Len(arguments.include))
  177. loc.classes = $expandedAssociations(include=arguments.include);
  178. ArrayPrepend(loc.classes, variables.wheels.class);
  179. // if the develop passes in tablename.*, translate it into the list of fields for the developer
  180. // this is so we don't get *'s in the group by
  181. if (Find(".*", arguments.list))
  182. arguments.list = $expandProperties(list=arguments.list, classes=loc.classes);
  183. // add properties to select if the developer did not specify any
  184. if (!Len(arguments.list))
  185. {
  186. loc.iEnd = ArrayLen(loc.classes);
  187. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  188. {
  189. loc.classData = loc.classes[loc.i];
  190. arguments.list = ListAppend(arguments.list, loc.classData.propertyList);
  191. if (Len(loc.classData.calculatedPropertyList))
  192. arguments.list = ListAppend(arguments.list, loc.classData.calculatedPropertyList);
  193. }
  194. }
  195. // 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
  196. if (arguments.list Does Not Contain "." AND arguments.list Does Not Contain " AS ")
  197. {
  198. loc.list = "";
  199. loc.addedProperties = "";
  200. loc.addedPropertiesByModel = {};
  201. loc.iEnd = ListLen(arguments.list);
  202. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  203. {
  204. loc.iItem = Trim(ListGetAt(arguments.list, loc.i));
  205. // look for duplicates
  206. loc.duplicateCount = ListValueCountNoCase(loc.addedProperties, loc.iItem);
  207. loc.addedProperties = ListAppend(loc.addedProperties, loc.iItem);
  208. // loop through all classes (current and all included ones)
  209. loc.jEnd = ArrayLen(loc.classes);
  210. for (loc.j=1; loc.j <= loc.jEnd; loc.j++)
  211. {
  212. loc.toAppend = "";
  213. loc.classData = loc.classes[loc.j];
  214. // we need the name of the table on the first go, and the alias otherwise
  215. loc.tableName = loc.classData.tableName;
  216. if (loc.j != 1)
  217. loc.tableName = loc.classData.alias;
  218. // create a struct for this model unless it already exists
  219. if (!StructKeyExists(loc.addedPropertiesByModel, loc.classData.alias))
  220. loc.addedPropertiesByModel[loc.classData.alias] = "";
  221. // if we find the property in this model and it's not already added we go ahead and add it to the select clause
  222. if ((ListFindNoCase(loc.classData.propertyList, loc.iItem) || ListFindNoCase(loc.classData.calculatedPropertyList, loc.iItem)) && !ListFindNoCase(loc.addedPropertiesByModel[loc.classData.alias], loc.iItem))
  223. {
  224. // if expanded column aliases is enabled then mark all columns from included classes as duplicates in order to prepend them with their class name
  225. loc.flagAsDuplicate = false;
  226. if (arguments.renameFields)
  227. {
  228. if (loc.duplicateCount)
  229. {
  230. // always flag as a duplicate when a property with this name has already been added
  231. loc.flagAsDuplicate = true;
  232. }
  233. else if (loc.j gt 1)
  234. {
  235. if (arguments.useExpandedColumnAliases)
  236. {
  237. // when on included models and using the new setting we flag every property as a duplicate so that the model name always gets prepended
  238. loc.flagAsDuplicate = true;
  239. }
  240. else if (!arguments.useExpandedColumnAliases && arguments.returnAs != "query")
  241. {
  242. // 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
  243. loc.flagAsDuplicate = true;
  244. }
  245. }
  246. }
  247. if (loc.flagAsDuplicate )
  248. loc.toAppend = loc.toAppend & "[[duplicate]]" & loc.j;
  249. if (ListFindNoCase(loc.classData.propertyList, loc.iItem))
  250. {
  251. loc.toAppend = loc.toAppend & loc.tableName & ".";
  252. if (ListFindNoCase(loc.classData.columnList, loc.iItem))
  253. {
  254. loc.toAppend = loc.toAppend & loc.iItem;
  255. }
  256. else
  257. {
  258. loc.toAppend = loc.toAppend & loc.classData.properties[loc.iItem].column;
  259. if (arguments.renameFields)
  260. loc.toAppend = loc.toAppend & " AS " & loc.iItem;
  261. }
  262. }
  263. else if (ListFindNoCase(loc.classData.calculatedPropertyList, loc.iItem) && arguments.addCalculatedProperties)
  264. {
  265. loc.toAppend = loc.toAppend & "(" & Replace(loc.classData.calculatedProperties[loc.iItem].sql, ",", "[[comma]]", "all") & ") AS " & loc.iItem;
  266. }
  267. loc.addedPropertiesByModel[loc.classData.alias] = ListAppend(loc.addedPropertiesByModel[loc.classData.alias], loc.iItem);
  268. break;
  269. }
  270. }
  271. if (Len(loc.toAppend))
  272. loc.list = ListAppend(loc.list, loc.toAppend);
  273. else if (application.wheels.showErrorInformation && (not arguments.addCalculatedProperties && not ListFindNoCase(loc.classData.calculatedPropertyList, loc.iItem)))
  274. $throw(type="Wheels.ColumnNotFound", message="Wheels looked for the column mapped to the `#loc.iItem#` property but couldn't find it in the database table.", extendedInfo="Verify the `select` 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.");
  275. }
  276. // let's replace eventual duplicates in the clause by prepending the class name
  277. if (Len(arguments.include) && arguments.renameFields)
  278. {
  279. loc.newSelect = "";
  280. loc.addedProperties = "";
  281. loc.iEnd = ListLen(loc.list);
  282. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  283. {
  284. loc.iItem = ListGetAt(loc.list, loc.i);
  285. // 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 ")
  286. loc.property = Reverse(SpanExcluding(Reverse(loc.iItem), ". "));
  287. // 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
  288. loc.duplicateCount = 0;
  289. loc.matches = REFind("^\[\[duplicate\]\](\d+)(.+)$", loc.iItem, 1, true);
  290. if (loc.matches.pos[1] gt 0)
  291. {
  292. loc.duplicateCount = Mid(loc.iItem, loc.matches.pos[2], loc.matches.len[2]);
  293. loc.iItem = Mid(loc.iItem, loc.matches.pos[3], loc.matches.len[3]);
  294. }
  295. if (!loc.duplicateCount)
  296. {
  297. // this is not a duplicate so we can just insert it as is
  298. loc.newItem = loc.iItem;
  299. loc.newProperty = loc.property;
  300. }
  301. else
  302. {
  303. // this is a duplicate so we prepend the class name and then insert it unless a property with the resulting name already exist
  304. loc.classData = loc.classes[loc.duplicateCount];
  305. // prepend class name to the property
  306. loc.newProperty = loc.classData.modelName & loc.property;
  307. if (loc.iItem Contains " AS ")
  308. loc.newItem = ReplaceNoCase(loc.iItem, " AS " & loc.property, " AS " & loc.newProperty);
  309. else
  310. loc.newItem = loc.iItem & " AS " & loc.newProperty;
  311. }
  312. if (!ListFindNoCase(loc.addedProperties, loc.newProperty))
  313. {
  314. loc.newSelect = ListAppend(loc.newSelect, loc.newItem);
  315. loc.addedProperties = ListAppend(loc.addedProperties, loc.newProperty);
  316. }
  317. }
  318. loc.list = loc.newSelect;
  319. }
  320. }
  321. else
  322. {
  323. loc.list = arguments.list;
  324. if (!arguments.renameFields && Find(" AS ", loc.list))
  325. loc.list = REReplace(loc.list, variables.wheels.class.RESQLAs, "", "all");
  326. }
  327. </cfscript>
  328. <cfreturn loc.list />
  329. </cffunction>
  330. <cffunction name="$addWhereClause" returntype="array" access="public" output="false">
  331. <cfargument name="sql" type="array" required="true">
  332. <cfargument name="where" type="string" required="true">
  333. <cfargument name="include" type="string" required="true">
  334. <cfargument name="includeSoftDeletes" type="boolean" required="true">
  335. <cfscript>
  336. var loc = {};
  337. loc.whereClause = $whereClause(where=arguments.where, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
  338. loc.iEnd = ArrayLen(loc.whereClause);
  339. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  340. ArrayAppend(arguments.sql, loc.whereClause[loc.i]);
  341. </cfscript>
  342. <cfreturn arguments.sql>
  343. </cffunction>
  344. <cffunction name="$whereClause" returntype="array" access="public" output="false">
  345. <cfargument name="where" type="string" required="true">
  346. <cfargument name="include" type="string" required="false" default="">
  347. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false">
  348. <cfscript>
  349. var loc = {};
  350. loc.returnValue = [];
  351. if (Len(arguments.where))
  352. {
  353. // setup an array containing class info for current class and all the ones that should be included
  354. loc.classes = [];
  355. if (Len(arguments.include))
  356. loc.classes = $expandedAssociations(include=arguments.include);
  357. ArrayPrepend(loc.classes, variables.wheels.class);
  358. ArrayAppend(loc.returnValue, "WHERE");
  359. loc.wherePos = ArrayLen(loc.returnValue) + 1;
  360. loc.params = ArrayNew(1);
  361. loc.where = ReplaceList(REReplace(arguments.where, variables.wheels.class.RESQLWhere, "\1?\8" , "all"), "AND,OR", "#chr(7)#AND,#chr(7)#OR");
  362. for (loc.i=1; loc.i <= ListLen(loc.where, Chr(7)); loc.i++)
  363. {
  364. loc.param = {};
  365. loc.element = Replace(ListGetAt(loc.where, loc.i, Chr(7)), Chr(7), "", "one");
  366. if (Find("(", loc.element) && Find(")", loc.element))
  367. loc.elementDataPart = SpanExcluding(Reverse(SpanExcluding(Reverse(loc.element), "(")), ")");
  368. else if (Find("(", loc.element))
  369. loc.elementDataPart = Reverse(SpanExcluding(Reverse(loc.element), "("));
  370. else if (Find(")", loc.element))
  371. loc.elementDataPart = SpanExcluding(loc.element, ")");
  372. else
  373. loc.elementDataPart = loc.element;
  374. loc.elementDataPart = Trim(ReplaceList(loc.elementDataPart, "AND,OR", ""));
  375. loc.temp = REFind("^([a-zA-Z0-9-_\.]*) ?#variables.wheels.class.RESQLOperators#", loc.elementDataPart, 1, true);
  376. if (ArrayLen(loc.temp.len) gt 1)
  377. {
  378. loc.where = Replace(loc.where, loc.element, Replace(loc.element, loc.elementDataPart, "?", "one"));
  379. loc.param.property = Mid(loc.elementDataPart, loc.temp.pos[2], loc.temp.len[2]);
  380. loc.jEnd = ArrayLen(loc.classes);
  381. for (loc.j=1; loc.j <= loc.jEnd; loc.j++)
  382. {
  383. // defaults for cfqueryparam, will be overridden and set appropriately when a column mapping is found below
  384. loc.param.type = "CF_SQL_CHAR";
  385. loc.param.dataType = "char";
  386. loc.param.scale = 0;
  387. loc.param.list = false;
  388. loc.classData = loc.classes[loc.j];
  389. // we need the name of the table on the first go, and the alias otherwise
  390. loc.tableName = loc.classData.tableName;
  391. if (loc.j != 1)
  392. loc.tableName = loc.classData.alias;
  393. if (loc.param.property Does Not Contain "." || ListFirst(loc.param.property, ".") == loc.classData.tableName)
  394. {
  395. if (ListFindNoCase(loc.classData.propertyList, ListLast(loc.param.property, ".")))
  396. {
  397. loc.param.type = loc.classData.properties[ListLast(loc.param.property, ".")].type;
  398. loc.param.dataType = loc.classData.properties[ListLast(loc.param.property, ".")].dataType;
  399. loc.param.scale = loc.classData.properties[ListLast(loc.param.property, ".")].scale;
  400. loc.param.column = loc.tableName & "." & loc.classData.properties[ListLast(loc.param.property, ".")].column;
  401. break;
  402. }
  403. else if (ListFindNoCase(loc.classData.calculatedPropertyList, ListLast(loc.param.property, ".")))
  404. {
  405. loc.param.column = loc.classData.calculatedProperties[ListLast(loc.param.property, ".")].sql;
  406. break;
  407. }
  408. }
  409. }
  410. if (application.wheels.showErrorInformation && !StructKeyExists(loc.param, "column"))
  411. $throw(type="Wheels.ColumnNotFound", message="Wheels looked for the column mapped to the `#loc.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.");
  412. loc.temp = REFind("^[a-zA-Z0-9-_\.]* ?#variables.wheels.class.RESQLOperators#", loc.elementDataPart, 1, true);
  413. loc.param.operator = Trim(Mid(loc.elementDataPart, loc.temp.pos[2], loc.temp.len[2]));
  414. if (Right(loc.param.operator, 2) == "IN")
  415. loc.param.list = true;
  416. ArrayAppend(loc.params, loc.param);
  417. }
  418. }
  419. loc.where = ReplaceList(loc.where, "#Chr(7)#AND,#Chr(7)#OR", "AND,OR");
  420. // add to sql array
  421. loc.where = " #loc.where# ";
  422. loc.iEnd = ListLen(loc.where, "?");
  423. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  424. {
  425. loc.item = ListGetAt(loc.where, loc.i, "?");
  426. if (Len(Trim(loc.item)))
  427. ArrayAppend(loc.returnValue, loc.item);
  428. if (loc.i < ListLen(loc.where, "?"))
  429. {
  430. loc.column = loc.params[loc.i].column;
  431. ArrayAppend(loc.returnValue, "#loc.column# #loc.params[loc.i].operator#");
  432. loc.param = {type=loc.params[loc.i].type, dataType=loc.params[loc.i].dataType, scale=loc.params[loc.i].scale, list=loc.params[loc.i].list};
  433. ArrayAppend(loc.returnValue, loc.param);
  434. }
  435. }
  436. }
  437. if (!arguments.includeSoftDeletes)
  438. {
  439. /// add soft delete sql
  440. loc.classes = [];
  441. if (Len(arguments.include))
  442. loc.classes = $expandedAssociations(include=arguments.include);
  443. ArrayPrepend(loc.classes, variables.wheels.class);
  444. loc.addToWhere = "";
  445. for (loc.i=1; loc.i <= ArrayLen(loc.classes); loc.i++)
  446. {
  447. // we need the name of the table on the first go, and the alias otherwise
  448. loc.classData = loc.classes[loc.i];
  449. loc.tableName = loc.classData.tableName;
  450. if (loc.i != 1)
  451. loc.tableName = loc.classData.alias;
  452. loc.model = model(loc.classData.modelName);
  453. if (loc.model.$softDeletion())
  454. loc.addToWhere = ListAppend(loc.addToWhere, loc.model.$aliasName() & "." & loc.model.$softDeleteColumn() & " IS NULL");
  455. }
  456. loc.addToWhere = Replace(loc.addToWhere, ",", " AND ", "all");
  457. if (Len(loc.addToWhere))
  458. {
  459. if (Len(arguments.where))
  460. {
  461. ArrayInsertAt(loc.returnValue, loc.wherePos, " (");
  462. ArrayAppend(loc.returnValue, ") AND (");
  463. ArrayAppend(loc.returnValue, loc.addToWhere);
  464. ArrayAppend(loc.returnValue, ")");
  465. }
  466. else
  467. {
  468. ArrayAppend(loc.returnValue, "WHERE ");
  469. ArrayAppend(loc.returnValue, loc.addToWhere);
  470. }
  471. }
  472. }
  473. </cfscript>
  474. <cfreturn loc.returnValue>
  475. </cffunction>
  476. <cffunction name="$addWhereClauseParameters" returntype="array" access="public" output="false">
  477. <cfargument name="sql" type="array" required="true">
  478. <cfargument name="where" type="string" required="true">
  479. <cfscript>
  480. var loc = {};
  481. if (Len(arguments.where))
  482. {
  483. loc.start = 1;
  484. loc.originalValues = [];
  485. while (!StructKeyExists(loc, "temp") || ArrayLen(loc.temp.len) gt 1)
  486. {
  487. loc.temp = REFind(variables.wheels.class.RESQLWhere, arguments.where, loc.start, true);
  488. if (ArrayLen(loc.temp.len) gt 1)
  489. {
  490. loc.start = loc.temp.pos[4] + loc.temp.len[4];
  491. 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)#", ",,,,,,"));
  492. }
  493. }
  494. loc.pos = ArrayLen(loc.originalValues);
  495. loc.iEnd = ArrayLen(arguments.sql);
  496. for (loc.i=loc.iEnd; loc.i gt 0; loc.i--)
  497. {
  498. if (IsStruct(arguments.sql[loc.i]) && loc.pos gt 0)
  499. {
  500. arguments.sql[loc.i].value = loc.originalValues[loc.pos];
  501. if (loc.originalValues[loc.pos] == "")
  502. arguments.sql[loc.i].null = true;
  503. loc.pos--;
  504. }
  505. }
  506. }
  507. </cfscript>
  508. <cfreturn arguments.sql>
  509. </cffunction>
  510. <cffunction name="$expandProperties" returntype="string" access="public" output="false">
  511. <cfargument name="list" type="string" required="true">
  512. <cfargument name="classes" type="array" required="true">
  513. <cfscript>
  514. var loc = {};
  515. loc.matches = REMatch("[A-Za-z1-9]+\.\*", arguments.list);
  516. loc.iEnd = ArrayLen(loc.matches);
  517. for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
  518. {
  519. loc.match = loc.matches[loc.i];
  520. loc.fields = "";
  521. loc.tableName = ListGetAt(loc.match, 1, ".");
  522. loc.jEnd = ArrayLen(arguments.classes);
  523. for (loc.j = 1; loc.j lte loc.jEnd; loc.j++)
  524. {
  525. loc.class = arguments.classes[loc.j];
  526. if (loc.class.tableName == loc.tableName)
  527. {
  528. for (loc.item in loc.class.properties)
  529. loc.fields = ListAppend(loc.fields, "#loc.class.tableName#.#loc.item#");
  530. break;
  531. }
  532. }
  533. if (Len(loc.fields))
  534. arguments.list = Replace(arguments.list, loc.match, loc.fields, "all");
  535. else if (application.wheels.showErrorInformation)
  536. $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.");
  537. }
  538. </cfscript>
  539. <cfreturn arguments.list />
  540. </cffunction>
  541. <cffunction name="$expandedAssociations" returntype="array" access="public" output="false">
  542. <cfargument name="include" type="string" required="true">
  543. <cfscript>
  544. var loc = {};
  545. loc.returnValue = [];
  546. // add the current class name so that the levels list start at the lowest level
  547. loc.levels = variables.wheels.class.modelName;
  548. // count the included associations
  549. loc.iEnd = ListLen(Replace(arguments.include, "(", ",", "all"));
  550. // clean up spaces in list and add a comma at the end to indicate end of string
  551. loc.include = Replace(arguments.include, " ", "", "all") & ",";
  552. loc.pos = 1;
  553. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  554. {
  555. // look for the next delimiter sequence in the string and set it (can be single delims or a chain, e.g ',' or ')),'
  556. loc.delimFind = ReFind("[(\(|\)|,)]+", loc.include, loc.pos, true);
  557. loc.delimSequence = Mid(loc.include, loc.delimFind.pos[1], loc.delimFind.len[1]);
  558. // set current association name and set new position to start search in the next loop
  559. loc.previousName = "";
  560. if (StructKeyExists(loc, "name"))
  561. loc.previousName = loc.name;
  562. loc.name = Mid(loc.include, loc.pos, loc.delimFind.pos[1]-loc.pos);
  563. loc.pos = REFindNoCase("[a-z]", loc.include, loc.delimFind.pos[1]);
  564. // create a reference to current class in include string and get its association info
  565. loc.className = ListLast(loc.levels);
  566. loc.class = model(loc.className);
  567. loc.classAssociations = loc.class.$classData().associations;
  568. // throw an error if the association was not found
  569. if (application.wheels.showErrorInformation && !StructKeyExists(loc.classAssociations, loc.name))
  570. $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.");
  571. // create a reference to the associated class
  572. loc.associatedClass = model(loc.classAssociations[loc.name].modelName);
  573. // create the join string if it hasn't already been done
  574. // (no need to lock this code since when multiple requests process it they will end up setting the same value (no intermediate value is ever set on the join variable in the application scoped model object)
  575. if (!StructKeyExists(loc.classAssociations[loc.name], "join"))
  576. {
  577. // figure out the foreignKey for this association
  578. if (!Len(loc.classAssociations[loc.name].foreignKey))
  579. {
  580. if (loc.classAssociations[loc.name].type == "belongsTo")
  581. {
  582. loc.classAssociations[loc.name].foreignKey = loc.associatedClass.$classData().modelName & Replace(loc.associatedClass.$classData().keys, ",", ",#loc.associatedClass.$classData().modelName#", "all");
  583. }
  584. else
  585. {
  586. loc.classAssociations[loc.name].foreignKey = loc.class.$classData().modelName & Replace(loc.class.$classData().keys, ",", ",#loc.class.$classData().modelName#", "all");
  587. }
  588. }
  589. // figure out the joinKey for this association
  590. if (!Len(loc.classAssociations[loc.name].joinKey))
  591. {
  592. if (loc.classAssociations[loc.name].type == "belongsTo")
  593. {
  594. loc.classAssociations[loc.name].joinKey = loc.associatedClass.$classData().keys;
  595. }
  596. else
  597. {
  598. loc.classAssociations[loc.name].joinKey = loc.class.$classData().keys;
  599. }
  600. }
  601. // set our alias to the tableName if we do not have one
  602. loc.classAssociations[loc.name].alias = loc.associatedClass.$classData().tableName;
  603. loc.classAssociations[loc.name].tableName = loc.associatedClass.$classData().tableName;
  604. loc.classAssociations[loc.name].columnList = loc.associatedClass.$classData().columnList;
  605. loc.classAssociations[loc.name].properties = loc.associatedClass.$classData().properties;
  606. loc.classAssociations[loc.name].propertyList = loc.associatedClass.$classData().propertyList;
  607. loc.classAssociations[loc.name].calculatedProperties = loc.associatedClass.$classData().calculatedProperties;
  608. loc.classAssociations[loc.name].calculatedPropertyList = loc.associatedClass.$classData().calculatedPropertyList;
  609. // check to see if we have a self join and make the joining table name unique
  610. if (loc.class.$classData().tableName == loc.associatedClass.$classData().tableName)
  611. {
  612. loc.associatedClass.$alias(associationName=loc.name);
  613. loc.classAssociations[loc.name].alias = loc.associatedClass.$aliasName(associationName=loc.name);
  614. }
  615. loc.joinType = ReplaceNoCase(loc.classAssociations[loc.name].joinType, "outer", "left outer", "one");
  616. //loc.join = UCase(loc.joinType) & " JOIN #loc.classAssociations[loc.name].tableName# AS #loc.classAssociations[loc.name].alias# ON ";
  617. loc.join = UCase(loc.joinType) & " JOIN " & variables.wheels.class.adapter.$tableAliasForJoin(loc.classAssociations[loc.name].tableName, loc.classAssociations[loc.name].alias) & " ON ";
  618. loc.toAppend = "";
  619. loc.jEnd = ListLen(loc.classAssociations[loc.name].foreignKey);
  620. for (loc.j=1; loc.j <= loc.jEnd; loc.j++)
  621. {
  622. loc.key1 = ListGetAt(loc.classAssociations[loc.name].foreignKey, loc.j);
  623. if (loc.classAssociations[loc.name].type == "belongsTo")
  624. {
  625. loc.key2 = ListFindNoCase(loc.classAssociations[loc.name].joinKey, loc.key1);
  626. if (loc.key2)
  627. loc.key2 = ListGetAt(loc.classAssociations[loc.name].joinKey, loc.key2);
  628. else
  629. loc.key2 = ListGetAt(loc.classAssociations[loc.name].joinKey, loc.j);
  630. loc.first = loc.key1;
  631. loc.second = loc.key2;
  632. }
  633. else
  634. {
  635. loc.key2 = ListFindNoCase(loc.classAssociations[loc.name].joinKey, loc.key1);
  636. if (loc.key2)
  637. loc.key2 = ListGetAt(loc.classAssociations[loc.name].joinKey, loc.key2);
  638. else
  639. loc.key2 = ListGetAt(loc.classAssociations[loc.name].joinKey, loc.j);
  640. loc.first = loc.key2;
  641. loc.second = loc.key1;
  642. }
  643. loc.toAppend = ListAppend(loc.toAppend, "#loc.class.$aliasName(associationname=loc.previousName)#.#loc.class.$classData().properties[loc.first].column# = #loc.classAssociations[loc.name].alias#.#loc.associatedClass.$classData().properties[loc.second].column#");
  644. }
  645. loc.classAssociations[loc.name].join = loc.join & Replace(loc.toAppend, ",", " AND ", "all");
  646. }
  647. // loop over each character in the delimiter sequence and move up/down the levels as appropriate
  648. for (loc.x=1; loc.x lte Len(loc.delimSequence); loc.x++)
  649. {
  650. loc.delimChar = Mid(loc.delimSequence, loc.x, 1);
  651. if (loc.delimChar == "(")
  652. loc.levels = ListAppend(loc.levels, loc.classAssociations[loc.name].modelName);
  653. else if (loc.delimChar == ")")
  654. loc.levels = ListDeleteAt(loc.levels, ListLen(loc.levels));
  655. }
  656. // add info to the array that we will return
  657. ArrayAppend(loc.returnValue, loc.classAssociations[loc.name]);
  658. }
  659. </cfscript>
  660. <cfreturn loc.returnValue>
  661. </cffunction>
  662. <cffunction name="$keyWhereString" returntype="string" access="public" output="false">
  663. <cfargument name="properties" type="any" required="false" default="#primaryKeys()#">
  664. <cfargument name="values" type="any" required="false" default="">
  665. <cfargument name="keys" type="any" required="false" default="">
  666. <cfscript>
  667. var loc = {};
  668. loc.returnValue = "";
  669. loc.iEnd = ListLen(arguments.properties);
  670. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  671. {
  672. loc.key = Trim(ListGetAt(arguments.properties, loc.i));
  673. if (Len(arguments.values))
  674. loc.value = Trim(ListGetAt(arguments.values, loc.i));
  675. else if (Len(arguments.keys))
  676. loc.value = this[ListGetAt(arguments.keys, loc.i)];
  677. else
  678. loc.value = "";
  679. loc.toAppend = loc.key & "=";
  680. if (!IsNumeric(loc.value))
  681. loc.toAppend = loc.toAppend & "'";
  682. loc.toAppend = loc.toAppend & loc.value;
  683. if (!IsNumeric(loc.value))
  684. loc.toAppend = loc.toAppend & "'";
  685. loc.returnValue = ListAppend(loc.returnValue, loc.toAppend, " ");
  686. if (loc.i < loc.iEnd)
  687. loc.returnValue = ListAppend(loc.returnValue, "AND", " ");
  688. }
  689. </cfscript>
  690. <cfreturn loc.returnValue>
  691. </cffunction>