123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- const parser = require('./parsers/relationExpressionParser');
- const { isObject, cloneDeep, values, union } = require('../utils/objectUtils');
- class RelationExpression {
- constructor(node = {}, recursionDepth = 0) {
- this.$name = node.$name || null;
- this.$relation = node.$relation || null;
- this.$modify = node.$modify || [];
- this.$recursive = node.$recursive || false;
- this.$allRecursive = node.$allRecursive || false;
- const childNames = getChildNames(node);
- for (let i = 0, l = childNames.length; i < l; ++i) {
- const childName = childNames[i];
- this[childName] = node[childName];
- }
- // These are non-enumerable so that the enumerable interface of this
- // class instance is the same as the result from relationExpressionParser.
- Object.defineProperties(this, {
- recursionDepth: {
- enumerable: false,
- value: recursionDepth
- },
- childNames: {
- enumerable: false,
- value: childNames
- },
- rawNode: {
- enumerable: false,
- value: node
- }
- });
- }
- // Create a relation expression from a string, a pojo or another
- // RelationExpression instance.
- static create(expr) {
- if (isObject(expr)) {
- if (expr.isObjectionRelationExpression) {
- return expr;
- } else {
- return new RelationExpression(normalizeNode(expr));
- }
- } else if (typeof expr === 'string') {
- if (expr.trim().length === 0) {
- return new RelationExpression();
- } else {
- return new RelationExpression(parser.parse(expr));
- }
- } else {
- return new RelationExpression();
- }
- }
- // Create a relation expression from a model graph.
- static fromModelGraph(graph) {
- if (!graph) {
- return new RelationExpression();
- } else {
- return new RelationExpression(modelGraphToNode(graph, newNode()));
- }
- }
- get numChildren() {
- return this.childNames.length;
- }
- get maxRecursionDepth() {
- if (typeof this.$recursive === 'number') {
- return this.$recursive;
- } else {
- return this.$recursive ? Number.MAX_SAFE_INTEGER : 0;
- }
- }
- get isAllRecursive() {
- return this.$allRecursive;
- }
- // Merges this relation expression with another. `expr` can be a string,
- // a pojo, or a RelationExpression instance.
- merge(expr) {
- return new RelationExpression(mergeNodes(this, RelationExpression.create(expr)));
- }
- // Returns true if `expr` is contained by this expression. For example
- // `a.b` is contained by `a.[b, c]`.
- isSubExpression(expr) {
- expr = RelationExpression.create(expr);
- if (this.isAllRecursive) {
- return true;
- }
- if (expr.isAllRecursive) {
- return this.isAllRecursive;
- }
- if (this.$relation !== expr.$relation) {
- return false;
- }
- const maxRecursionDepth = expr.maxRecursionDepth;
- if (maxRecursionDepth > 0) {
- return this.isAllRecursive || this.maxRecursionDepth >= maxRecursionDepth;
- }
- for (let i = 0, l = expr.childNames.length; i < l; ++i) {
- const childName = expr.childNames[i];
- const ownSubExpression = this.childExpression(childName);
- const subExpression = expr.childExpression(childName);
- if (!ownSubExpression || !ownSubExpression.isSubExpression(subExpression)) {
- return false;
- }
- }
- return true;
- }
- // Returns a RelationExpression for a child node or null if there
- // is no child with the given name `childName`.
- childExpression(childName) {
- if (
- this.isAllRecursive ||
- (childName === this.$name && this.recursionDepth < this.maxRecursionDepth - 1)
- ) {
- return new RelationExpression(this, this.recursionDepth + 1);
- }
- const child = this[childName];
- if (child) {
- return new RelationExpression(child, 0);
- } else {
- return null;
- }
- }
- // Loops throught all first level children. `allRelations` must be
- // the return value of `Model.getRelations()` where `Model` is the
- // root model of the expression.
- forEachChildExpression(allRelations, cb) {
- const maxRecursionDepth = this.maxRecursionDepth;
- if (this.isAllRecursive) {
- const relationNames = Object.keys(allRelations);
- for (let i = 0, l = relationNames.length; i < l; ++i) {
- const relationName = relationNames[i];
- const node = newNode(relationName, true);
- const relation = allRelations[relationName];
- const childExpr = new RelationExpression(node, 0);
- cb(childExpr, relation);
- }
- } else if (this.recursionDepth < maxRecursionDepth - 1) {
- const relation = allRelations[this.$name] || null;
- const childExpr = new RelationExpression(this, this.recursionDepth + 1);
- cb(childExpr, relation);
- } else if (maxRecursionDepth === 0) {
- const childNames = this.childNames;
- for (let i = 0, l = childNames.length; i < l; ++i) {
- const childName = childNames[i];
- const node = this[childName];
- const relation = allRelations[node.$relation] || null;
- const childExpr = new RelationExpression(node, 0);
- cb(childExpr, relation);
- }
- }
- }
- expressionsAtPath(path) {
- return findExpressionsAtPath(this, RelationExpression.create(path), []);
- }
- clone() {
- const node = {
- $name: this.$name,
- $relation: this.$relation,
- $modify: this.$modify.slice(),
- $recursive: this.$recursive,
- $allRecursive: this.$allRecursive
- };
- for (let i = 0, l = this.childNames.length; i < l; ++i) {
- const childName = this.childNames[i];
- node[childName] = cloneDeep(this[childName]);
- }
- return new RelationExpression(node, this.recursionDepth);
- }
- toString() {
- return toString(this);
- }
- toJSON() {
- return toJSON(this);
- }
- }
- // All enumerable properties of a node that don't start with `$`
- // are child nodes.
- function getChildNames(node) {
- const allKeys = Object.keys(node);
- const childNames = [];
- for (let i = 0, l = allKeys.length; i < l; ++i) {
- const key = allKeys[i];
- if (key[0] !== '$') {
- childNames.push(key);
- }
- }
- return childNames;
- }
- function toString(node) {
- const childNames = getChildNames(node);
- let childExpr = childNames.map(childName => node[childName]).map(toString);
- let str = node.$relation;
- if (node.$recursive) {
- if (typeof node.$recursive === 'number') {
- str += '.^' + node.$recursive;
- } else {
- str += '.^';
- }
- } else if (node.$allRecursive) {
- str += '.*';
- }
- if (childExpr.length > 1) {
- childExpr = `[${childExpr.join(', ')}]`;
- } else {
- childExpr = childExpr[0];
- }
- if (node.$modify.length) {
- str += `(${node.$modify.join(', ')})`;
- }
- if (node.$name !== node.$relation) {
- str += ` as ${node.$name}`;
- }
- if (childExpr) {
- if (str) {
- return `${str}.${childExpr}`;
- } else {
- return childExpr;
- }
- } else {
- return str;
- }
- }
- function toJSON(node, nodeName = null) {
- const json = {};
- if (node.$name && node.$name !== nodeName) {
- json.$name = node.$name;
- }
- if (node.$relation && node.$relation !== nodeName) {
- json.$relation = node.$relation;
- }
- if (!Array.isArray(node.$modify) || node.$modify.length > 0) {
- json.$modify = node.$modify.slice();
- }
- if (node.$recursive) {
- json.$recursive = node.$recursive;
- }
- if (node.$allRecursive) {
- json.$allRecursive = node.$allRecursive;
- }
- const childNames = getChildNames(node);
- for (let i = 0, l = childNames.length; i < l; ++i) {
- const childName = childNames[i];
- const childNode = node[childName];
- const childJson = toJSON(childNode, childName);
- if (Object.keys(childJson).length === 0) {
- json[childName] = true;
- } else {
- json[childName] = childJson;
- }
- }
- return json;
- }
- function modelGraphToNode(models, node) {
- if (!models) {
- return;
- }
- if (Array.isArray(models)) {
- for (let i = 0, l = models.length; i < l; ++i) {
- modelToNode(models[i], node);
- }
- } else {
- modelToNode(models, node);
- }
- return node;
- }
- // TODO: recursion check
- function modelToNode(model, node) {
- const modelClass = model.constructor;
- const relations = modelClass.getRelationArray();
- for (let r = 0, lr = relations.length; r < lr; ++r) {
- const relName = relations[r].name;
- if (model.hasOwnProperty(relName)) {
- let childNode = node[relName];
- if (!childNode) {
- childNode = newNode(relName);
- node[relName] = childNode;
- }
- modelGraphToNode(model[relName], childNode);
- }
- }
- }
- function newNode(name, allRecusive = false) {
- return {
- $name: name || null,
- $relation: name || null,
- $modify: [],
- $recursive: false,
- $allRecursive: allRecusive
- };
- }
- function normalizeNode(node, name = null) {
- const normalized = {
- $name: node.$name || name,
- $relation: node.$relation || name,
- $modify: node.$modify || [],
- $recursive: node.$recursive || false,
- $allRecursive: node.$allRecursive || false
- };
- const childNames = getChildNames(node);
- for (let i = 0, l = childNames.length; i < l; ++i) {
- const childName = childNames[i];
- const childNode = node[childName];
- if (isObject(childNode) || childNode === true) {
- normalized[childName] = normalizeNode(childNode, childName);
- }
- }
- return normalized;
- }
- function findExpressionsAtPath(target, path, results) {
- if (path.childNames.length == 0) {
- // Path leaf reached, add target node to result set.
- results.push(target);
- } else {
- for (let i = 0, l = path.childNames.length; i < l; ++i) {
- const childName = path.childNames[i];
- const pathChild = path.childExpression(childName);
- const targetChild = target.childExpression(childName);
- if (targetChild) {
- findExpressionsAtPath(targetChild, pathChild, results);
- }
- }
- }
- return results;
- }
- function mergeNodes(node1, node2) {
- const node = {
- $name: node1.$name,
- $relation: node1.$relation,
- $modify: union(node1.$modify, node2.$modify),
- $recursive: mergeRecursion(node1.$recursive, node2.$recursive),
- $allRecursive: node1.$allRecursive || node2.$allRecursive
- };
- if (!node.$recursive && !node.$allRecursive) {
- const childNames = union(getChildNames(node1), getChildNames(node2));
- for (let i = 0, l = childNames.length; i < l; ++i) {
- const childName = childNames[i];
- const child1 = node1[childName];
- const child2 = node2[childName];
- if (child1 && child2) {
- node[childName] = mergeNodes(child1, child2);
- } else {
- node[childName] = child1 || child2;
- }
- }
- }
- return node;
- }
- function mergeRecursion(rec1, rec2) {
- if (rec1 === true || rec2 === true) {
- return true;
- } else if (typeof rec1 === 'number' && typeof rec2 === 'number') {
- return Math.max(rec1, rec2);
- } else {
- return rec1 || rec2;
- }
- }
- Object.defineProperties(RelationExpression.prototype, {
- isObjectionRelationExpression: {
- enumerable: false,
- writable: false,
- value: true
- }
- });
- module.exports = RelationExpression;
|