RelationExpression.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. const parser = require('./parsers/relationExpressionParser');
  2. const { isObject, cloneDeep, values, union } = require('../utils/objectUtils');
  3. class RelationExpression {
  4. constructor(node = {}, recursionDepth = 0) {
  5. this.$name = node.$name || null;
  6. this.$relation = node.$relation || null;
  7. this.$modify = node.$modify || [];
  8. this.$recursive = node.$recursive || false;
  9. this.$allRecursive = node.$allRecursive || false;
  10. const childNames = getChildNames(node);
  11. for (let i = 0, l = childNames.length; i < l; ++i) {
  12. const childName = childNames[i];
  13. this[childName] = node[childName];
  14. }
  15. // These are non-enumerable so that the enumerable interface of this
  16. // class instance is the same as the result from relationExpressionParser.
  17. Object.defineProperties(this, {
  18. recursionDepth: {
  19. enumerable: false,
  20. value: recursionDepth
  21. },
  22. childNames: {
  23. enumerable: false,
  24. value: childNames
  25. },
  26. rawNode: {
  27. enumerable: false,
  28. value: node
  29. }
  30. });
  31. }
  32. // Create a relation expression from a string, a pojo or another
  33. // RelationExpression instance.
  34. static create(expr) {
  35. if (isObject(expr)) {
  36. if (expr.isObjectionRelationExpression) {
  37. return expr;
  38. } else {
  39. return new RelationExpression(normalizeNode(expr));
  40. }
  41. } else if (typeof expr === 'string') {
  42. if (expr.trim().length === 0) {
  43. return new RelationExpression();
  44. } else {
  45. return new RelationExpression(parser.parse(expr));
  46. }
  47. } else {
  48. return new RelationExpression();
  49. }
  50. }
  51. // Create a relation expression from a model graph.
  52. static fromModelGraph(graph) {
  53. if (!graph) {
  54. return new RelationExpression();
  55. } else {
  56. return new RelationExpression(modelGraphToNode(graph, newNode()));
  57. }
  58. }
  59. get numChildren() {
  60. return this.childNames.length;
  61. }
  62. get maxRecursionDepth() {
  63. if (typeof this.$recursive === 'number') {
  64. return this.$recursive;
  65. } else {
  66. return this.$recursive ? Number.MAX_SAFE_INTEGER : 0;
  67. }
  68. }
  69. get isAllRecursive() {
  70. return this.$allRecursive;
  71. }
  72. // Merges this relation expression with another. `expr` can be a string,
  73. // a pojo, or a RelationExpression instance.
  74. merge(expr) {
  75. return new RelationExpression(mergeNodes(this, RelationExpression.create(expr)));
  76. }
  77. // Returns true if `expr` is contained by this expression. For example
  78. // `a.b` is contained by `a.[b, c]`.
  79. isSubExpression(expr) {
  80. expr = RelationExpression.create(expr);
  81. if (this.isAllRecursive) {
  82. return true;
  83. }
  84. if (expr.isAllRecursive) {
  85. return this.isAllRecursive;
  86. }
  87. if (this.$relation !== expr.$relation) {
  88. return false;
  89. }
  90. const maxRecursionDepth = expr.maxRecursionDepth;
  91. if (maxRecursionDepth > 0) {
  92. return this.isAllRecursive || this.maxRecursionDepth >= maxRecursionDepth;
  93. }
  94. for (let i = 0, l = expr.childNames.length; i < l; ++i) {
  95. const childName = expr.childNames[i];
  96. const ownSubExpression = this.childExpression(childName);
  97. const subExpression = expr.childExpression(childName);
  98. if (!ownSubExpression || !ownSubExpression.isSubExpression(subExpression)) {
  99. return false;
  100. }
  101. }
  102. return true;
  103. }
  104. // Returns a RelationExpression for a child node or null if there
  105. // is no child with the given name `childName`.
  106. childExpression(childName) {
  107. if (
  108. this.isAllRecursive ||
  109. (childName === this.$name && this.recursionDepth < this.maxRecursionDepth - 1)
  110. ) {
  111. return new RelationExpression(this, this.recursionDepth + 1);
  112. }
  113. const child = this[childName];
  114. if (child) {
  115. return new RelationExpression(child, 0);
  116. } else {
  117. return null;
  118. }
  119. }
  120. // Loops throught all first level children. `allRelations` must be
  121. // the return value of `Model.getRelations()` where `Model` is the
  122. // root model of the expression.
  123. forEachChildExpression(allRelations, cb) {
  124. const maxRecursionDepth = this.maxRecursionDepth;
  125. if (this.isAllRecursive) {
  126. const relationNames = Object.keys(allRelations);
  127. for (let i = 0, l = relationNames.length; i < l; ++i) {
  128. const relationName = relationNames[i];
  129. const node = newNode(relationName, true);
  130. const relation = allRelations[relationName];
  131. const childExpr = new RelationExpression(node, 0);
  132. cb(childExpr, relation);
  133. }
  134. } else if (this.recursionDepth < maxRecursionDepth - 1) {
  135. const relation = allRelations[this.$name] || null;
  136. const childExpr = new RelationExpression(this, this.recursionDepth + 1);
  137. cb(childExpr, relation);
  138. } else if (maxRecursionDepth === 0) {
  139. const childNames = this.childNames;
  140. for (let i = 0, l = childNames.length; i < l; ++i) {
  141. const childName = childNames[i];
  142. const node = this[childName];
  143. const relation = allRelations[node.$relation] || null;
  144. const childExpr = new RelationExpression(node, 0);
  145. cb(childExpr, relation);
  146. }
  147. }
  148. }
  149. expressionsAtPath(path) {
  150. return findExpressionsAtPath(this, RelationExpression.create(path), []);
  151. }
  152. clone() {
  153. const node = {
  154. $name: this.$name,
  155. $relation: this.$relation,
  156. $modify: this.$modify.slice(),
  157. $recursive: this.$recursive,
  158. $allRecursive: this.$allRecursive
  159. };
  160. for (let i = 0, l = this.childNames.length; i < l; ++i) {
  161. const childName = this.childNames[i];
  162. node[childName] = cloneDeep(this[childName]);
  163. }
  164. return new RelationExpression(node, this.recursionDepth);
  165. }
  166. toString() {
  167. return toString(this);
  168. }
  169. toJSON() {
  170. return toJSON(this);
  171. }
  172. }
  173. // All enumerable properties of a node that don't start with `$`
  174. // are child nodes.
  175. function getChildNames(node) {
  176. const allKeys = Object.keys(node);
  177. const childNames = [];
  178. for (let i = 0, l = allKeys.length; i < l; ++i) {
  179. const key = allKeys[i];
  180. if (key[0] !== '$') {
  181. childNames.push(key);
  182. }
  183. }
  184. return childNames;
  185. }
  186. function toString(node) {
  187. const childNames = getChildNames(node);
  188. let childExpr = childNames.map(childName => node[childName]).map(toString);
  189. let str = node.$relation;
  190. if (node.$recursive) {
  191. if (typeof node.$recursive === 'number') {
  192. str += '.^' + node.$recursive;
  193. } else {
  194. str += '.^';
  195. }
  196. } else if (node.$allRecursive) {
  197. str += '.*';
  198. }
  199. if (childExpr.length > 1) {
  200. childExpr = `[${childExpr.join(', ')}]`;
  201. } else {
  202. childExpr = childExpr[0];
  203. }
  204. if (node.$modify.length) {
  205. str += `(${node.$modify.join(', ')})`;
  206. }
  207. if (node.$name !== node.$relation) {
  208. str += ` as ${node.$name}`;
  209. }
  210. if (childExpr) {
  211. if (str) {
  212. return `${str}.${childExpr}`;
  213. } else {
  214. return childExpr;
  215. }
  216. } else {
  217. return str;
  218. }
  219. }
  220. function toJSON(node, nodeName = null) {
  221. const json = {};
  222. if (node.$name && node.$name !== nodeName) {
  223. json.$name = node.$name;
  224. }
  225. if (node.$relation && node.$relation !== nodeName) {
  226. json.$relation = node.$relation;
  227. }
  228. if (!Array.isArray(node.$modify) || node.$modify.length > 0) {
  229. json.$modify = node.$modify.slice();
  230. }
  231. if (node.$recursive) {
  232. json.$recursive = node.$recursive;
  233. }
  234. if (node.$allRecursive) {
  235. json.$allRecursive = node.$allRecursive;
  236. }
  237. const childNames = getChildNames(node);
  238. for (let i = 0, l = childNames.length; i < l; ++i) {
  239. const childName = childNames[i];
  240. const childNode = node[childName];
  241. const childJson = toJSON(childNode, childName);
  242. if (Object.keys(childJson).length === 0) {
  243. json[childName] = true;
  244. } else {
  245. json[childName] = childJson;
  246. }
  247. }
  248. return json;
  249. }
  250. function modelGraphToNode(models, node) {
  251. if (!models) {
  252. return;
  253. }
  254. if (Array.isArray(models)) {
  255. for (let i = 0, l = models.length; i < l; ++i) {
  256. modelToNode(models[i], node);
  257. }
  258. } else {
  259. modelToNode(models, node);
  260. }
  261. return node;
  262. }
  263. // TODO: recursion check
  264. function modelToNode(model, node) {
  265. const modelClass = model.constructor;
  266. const relations = modelClass.getRelationArray();
  267. for (let r = 0, lr = relations.length; r < lr; ++r) {
  268. const relName = relations[r].name;
  269. if (model.hasOwnProperty(relName)) {
  270. let childNode = node[relName];
  271. if (!childNode) {
  272. childNode = newNode(relName);
  273. node[relName] = childNode;
  274. }
  275. modelGraphToNode(model[relName], childNode);
  276. }
  277. }
  278. }
  279. function newNode(name, allRecusive = false) {
  280. return {
  281. $name: name || null,
  282. $relation: name || null,
  283. $modify: [],
  284. $recursive: false,
  285. $allRecursive: allRecusive
  286. };
  287. }
  288. function normalizeNode(node, name = null) {
  289. const normalized = {
  290. $name: node.$name || name,
  291. $relation: node.$relation || name,
  292. $modify: node.$modify || [],
  293. $recursive: node.$recursive || false,
  294. $allRecursive: node.$allRecursive || false
  295. };
  296. const childNames = getChildNames(node);
  297. for (let i = 0, l = childNames.length; i < l; ++i) {
  298. const childName = childNames[i];
  299. const childNode = node[childName];
  300. if (isObject(childNode) || childNode === true) {
  301. normalized[childName] = normalizeNode(childNode, childName);
  302. }
  303. }
  304. return normalized;
  305. }
  306. function findExpressionsAtPath(target, path, results) {
  307. if (path.childNames.length == 0) {
  308. // Path leaf reached, add target node to result set.
  309. results.push(target);
  310. } else {
  311. for (let i = 0, l = path.childNames.length; i < l; ++i) {
  312. const childName = path.childNames[i];
  313. const pathChild = path.childExpression(childName);
  314. const targetChild = target.childExpression(childName);
  315. if (targetChild) {
  316. findExpressionsAtPath(targetChild, pathChild, results);
  317. }
  318. }
  319. }
  320. return results;
  321. }
  322. function mergeNodes(node1, node2) {
  323. const node = {
  324. $name: node1.$name,
  325. $relation: node1.$relation,
  326. $modify: union(node1.$modify, node2.$modify),
  327. $recursive: mergeRecursion(node1.$recursive, node2.$recursive),
  328. $allRecursive: node1.$allRecursive || node2.$allRecursive
  329. };
  330. if (!node.$recursive && !node.$allRecursive) {
  331. const childNames = union(getChildNames(node1), getChildNames(node2));
  332. for (let i = 0, l = childNames.length; i < l; ++i) {
  333. const childName = childNames[i];
  334. const child1 = node1[childName];
  335. const child2 = node2[childName];
  336. if (child1 && child2) {
  337. node[childName] = mergeNodes(child1, child2);
  338. } else {
  339. node[childName] = child1 || child2;
  340. }
  341. }
  342. }
  343. return node;
  344. }
  345. function mergeRecursion(rec1, rec2) {
  346. if (rec1 === true || rec2 === true) {
  347. return true;
  348. } else if (typeof rec1 === 'number' && typeof rec2 === 'number') {
  349. return Math.max(rec1, rec2);
  350. } else {
  351. return rec1 || rec2;
  352. }
  353. }
  354. Object.defineProperties(RelationExpression.prototype, {
  355. isObjectionRelationExpression: {
  356. enumerable: false,
  357. writable: false,
  358. value: true
  359. }
  360. });
  361. module.exports = RelationExpression;