Source: soql-builder.js

  1. /**
  2. * @file Create and build SOQL string from configuration
  3. * @author Shinichi Tomita <shinichi.tomita@gmail.com>
  4. */
  5. 'use strict';
  6. var _ = require('lodash/core'),
  7. SfDate = require("./date");
  8. /**
  9. * Create SOQL
  10. * @private
  11. */
  12. function createSOQL(query) {
  13. var soql = [
  14. "SELECT ",
  15. createFieldsClause(query.fields, query.includes),
  16. " FROM ",
  17. query.table
  18. ].join("");
  19. var cond = createConditionClause(query.conditions);
  20. if (cond) {
  21. soql += " WHERE " + cond;
  22. }
  23. var orderby = createOrderByClause(query.sort);
  24. if (orderby) {
  25. soql += " ORDER BY " + orderby;
  26. }
  27. if (query.limit) {
  28. soql += " LIMIT " + query.limit;
  29. }
  30. if (query.offset) {
  31. soql += " OFFSET " + query.offset;
  32. }
  33. return soql;
  34. }
  35. /** @private **/
  36. function createFieldsClause(fields, childQueries) {
  37. childQueries = _.map(_.values(childQueries || {}), function(cquery) {
  38. return '(' + createSOQL(cquery) + ')';
  39. });
  40. return (fields || [ "Id" ]).concat(childQueries).join(', ');
  41. }
  42. /** @private **/
  43. function createConditionClause(conditions, operator, depth) {
  44. if (_.isString(conditions)) {
  45. return conditions;
  46. }
  47. conditions = conditions || [];
  48. operator = operator || "AND";
  49. depth = depth || 0;
  50. if (!isArray(conditions)) { // if passed in hash object
  51. conditions = _.keys(conditions).map(function(key) {
  52. return {
  53. key: key,
  54. value: conditions[key]
  55. };
  56. });
  57. } else {
  58. conditions = conditions.map(function(cond) {
  59. var conds = [];
  60. for (var key in cond) {
  61. conds.push({
  62. key: key,
  63. value: cond[key]
  64. });
  65. }
  66. return conds.length>1 ? conds : conds[0];
  67. });
  68. }
  69. conditions = conditions.map(function(cond) {
  70. var d = depth+1, op;
  71. switch (cond.key) {
  72. case "$or" :
  73. case "$and" :
  74. case "$not" :
  75. if (operator !== "NOT" && conditions.length === 1) {
  76. d = depth; // not change tree depth
  77. }
  78. op = cond.key === "$or" ? "OR" :
  79. cond.key === "$and" ? "AND" :
  80. "NOT";
  81. return createConditionClause(cond.value, op, d);
  82. default:
  83. return createFieldExpression(cond.key, cond.value);
  84. }
  85. }).filter(function(expr) { return expr; });
  86. var paren;
  87. if (operator === 'NOT') {
  88. paren = depth > 0;
  89. return (paren ? "(" : "") + "NOT " + conditions[0] + (paren ? ")" : "");
  90. } else {
  91. paren = depth > 0 && conditions.length > 1;
  92. return (paren ? "(" : "") + conditions.join(" "+operator+" ") + (paren ? ")" : "");
  93. }
  94. }
  95. var opMap = {
  96. "=" : "=",
  97. "$eq" : "=",
  98. "!=" : "!=",
  99. "$ne" : "!=",
  100. ">" : ">",
  101. "$gt" : ">",
  102. "<" : "<",
  103. "$lt" : "<",
  104. ">=" : ">=",
  105. "$gte" : ">=",
  106. "<=" : "<=",
  107. "$lte" : "<=",
  108. "$like" : "LIKE",
  109. "$nlike" : "NOT LIKE",
  110. "$in" : "IN",
  111. "$nin" : "NOT IN",
  112. "$exists" : "EXISTS"
  113. };
  114. /** @private **/
  115. function createFieldExpression(field, value) {
  116. var op = "$eq";
  117. // Assume the `$in` operator if value is an array and none was supplied.
  118. if (_.isArray(value)) { op = "$in"; }
  119. // Otherwise, if an object was passed then process the supplied ops.
  120. else if (_.isObject(value)) {
  121. var _value;
  122. for (var k in value) {
  123. if (k[0] === "$") {
  124. op = k;
  125. value = value[k];
  126. break;
  127. }
  128. }
  129. }
  130. var sfop = opMap[op];
  131. if (!sfop || _.isUndefined(value)) { return null; }
  132. var valueExpr = createValueExpression(value);
  133. if (_.isUndefined(valueExpr)) { return null; }
  134. switch (sfop) {
  135. case "NOT LIKE":
  136. return "(" + [ "NOT", field, 'LIKE', valueExpr ].join(" ") + ")";
  137. case "EXISTS":
  138. return [ field, value ? "!=" : "=", "null" ].join(" ");
  139. default:
  140. return [ field, sfop, valueExpr ].join(" ");
  141. }
  142. }
  143. /** @private **/
  144. function createValueExpression(value) {
  145. if (isArray(value)) {
  146. return value.length > 0 ?
  147. "(" + value.map(createValueExpression).join(", ") + ")" :
  148. undefined;
  149. }
  150. if (value instanceof SfDate) {
  151. return value.toString();
  152. }
  153. if (_.isString(value)) {
  154. return "'" + escapeSOQLString(value) + "'";
  155. }
  156. if (_.isNumber(value)) {
  157. return (value).toString();
  158. }
  159. if (_.isNull(value)) {
  160. return "null";
  161. }
  162. return value;
  163. }
  164. /** @private **/
  165. function escapeSOQLString(str) {
  166. return String(str || '').replace(/'/g, "\\'");
  167. }
  168. /** @private **/
  169. function isArray(a) {
  170. return _.isObject(a) && _.isFunction(a.pop);
  171. }
  172. /** @private **/
  173. function createOrderByClause(sort) {
  174. sort = sort || [];
  175. if (_.isString(sort)) {
  176. if (/,|\s+(asc|desc)\s*$/.test(sort)) {
  177. // must be specified in pure "order by" clause. Return raw config.
  178. return sort;
  179. }
  180. // sort order in mongoose-style expression.
  181. // e.g. "FieldA -FieldB" => "ORDER BY FieldA ASC, FieldB DESC"
  182. sort = sort.split(/\s+/).map(function(field) {
  183. var dir = "ASC"; // ascending
  184. var flag = field[0];
  185. if (flag === '-') {
  186. dir = "DESC";
  187. field = field.substring(1);
  188. } else if (flag === '+') {
  189. field = field.substring(1);
  190. }
  191. return [ field, dir ];
  192. });
  193. } else if (!isArray(sort)) {
  194. sort = _.keys(sort).map(function(field) {
  195. var dir = sort[field];
  196. return [ field, dir ];
  197. });
  198. }
  199. return sort.map(function(s) {
  200. var field = s[0], dir = s[1];
  201. switch (String(dir)) {
  202. case "DESC":
  203. case "desc":
  204. case "descending":
  205. case "-":
  206. case "-1":
  207. dir = "DESC";
  208. break;
  209. default:
  210. dir = "ASC";
  211. }
  212. return field + " " + dir;
  213. }).join(", ");
  214. }
  215. exports.createSOQL = createSOQL;