UNPKG

@themost/data

Version:

MOST Web Framework Codename Blueshift - Data module

1,012 lines (988 loc) 48.5 kB
// MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved /*eslint no-var: "off"*/ // noinspection ES6ConvertVarToLetConst var _ = require('lodash'); var {QueryUtils} = require('@themost/query'); var {QueryEntity} = require('@themost/query'); var {QueryField} = require('@themost/query'); var {DataError} = require('@themost/common') var {hasOwnProperty} = require('./has-own-property'); var {isObjectDeep} = require('./is-object'); /** * @param {Array<QueryField|*>} fields * @param {string} name * @return {*} */ function containsField(fields, name) { return fields.find(function(field) { if (field instanceof QueryField) { return field.as() === name || field.getName() === name; } else { var f = Object.assign(new QueryField(), field); return f.as() === name || f.getName() === name; } }); } class DataMappingExtender { constructor(mapping) { this.mapping = mapping; } /** * @param {DataQueryable} queryable * @returns */ for(queryable) { this.queryable = queryable; return this; } getChildModel() { if (this.queryable == null) { return; } if (this._childModel != null) { return this._childModel; } this._childModel = this.queryable.model.context.model(this.mapping.childModel); return this._childModel; } getParentModel() { if (this.queryable == null) { return; } if (this._parentModel != null) { return this._parentModel; } this._parentModel = this.queryable.model.context.model(this.mapping.parentModel); return this._parentModel; } /** * @param {*} items * @returns {Promise|*} */ getParents(items) { var thisArg = this; var isSilent = false; var thisQueryable = this.queryable; var mapping = this.mapping; return new Promise(function(resolve, reject) { if (_.isNil(items)) { return resolve(); } var arr = _.isArray(items) ? items : [items]; if (arr.length === 0) { return resolve(); } if (_.isNil(thisQueryable)) { return reject('The underlying data queryable cannot be empty at this context.'); } if ((mapping.childModel !== thisQueryable.model.name) || (mapping.associationType!=='junction')) { return resolve(); } //get array of key values (for childs) var values = arr.filter(function(x) { return (typeof x[mapping.childField]!=='undefined') && (x[mapping.childField]!=null); }) .map(function(x) { return x[mapping.childField] }); //query junction model var HasParentJunction = require('./has-parent-junction').HasParentJunction; var junction = new HasParentJunction(thisQueryable.model.convert({ }), mapping); isSilent = !!thisQueryable.$silent; junction.getBaseModel().where(mapping.associationValueField).in(values).flatten().silent(isSilent).all(function(err, junctions) { if (err) { return reject(err); } //get array of parent key values values = _.intersection(junctions.map(function(x) { return x[mapping.associationObjectField] })); //get parent model var parentModel = thisArg.getParentModel(); //query parent with parent key values parentModel.filter(mapping.options, function(err, q) { if (err) { return reject(err); } q.prepare(); //Important Backward compatibility issue (<1.8.0) //Description: if $levels parameter is not defined then set the default value to 0. if (typeof q.$levels === 'undefined') { q.$levels = 0; } q.where(mapping.parentField).in(values); //inherit silent mode if (thisQueryable.$silent) { q.silent(); } if (q.query.hasFields() === false) { q.select(); } var parentField = thisArg.getParentModel().getAttribute(mapping.parentField); var keyField = parentField.property || parentField.name; // if query is a select statement if (q.query.$fixed == null) { // check if foreign key field exists in query var selectEntity = q.model.viewAdapter; if (Object.prototype.hasOwnProperty.call(q.query.$select, selectEntity)) { /** * @type {Array} */ var select = Object.getOwnPropertyDescriptor(q.query.$select, selectEntity).value; // find foreign key key var find = containsField(select, keyField); // if foreign key field does not exist if (find == null) { // clone query var q1 = q.clone().select(keyField); // and select foreign key field var select1 = Object.getOwnPropertyDescriptor(q1.query.$select, selectEntity).value; if (Array.isArray(select1)) { // append field to select fields select.push.apply(select, select1); } } } } //and finally query parent q.getItems().then(function(parents){ //if result contains only one item if (arr.length === 1) { arr[0][mapping.refersTo] = parents; return resolve(); } //otherwise loop result array arr.forEach(function(x) { //get child (key value) var childValue = x[mapping.childField]; //get parent(s) var p = junctions.filter(function(y) { return (y[mapping.associationValueField]===childValue); }).map(function(r) { return r[mapping.associationObjectField]; }); //filter data and set property value (a filtered array of parent objects) x[mapping.refersTo] = parents.filter(function(z) { return p.indexOf(z[mapping.parentField])>=0; }); }); return resolve(); }).catch(function(err) { return reject(err); }); }); }); }); } /** * @param {*} items * @returns {Promise|*} */ getChildren(items) { var thisArg = this; var isSilent = false; var thisQueryable = this.queryable; var mapping = this.mapping; return new Promise(function(resolve, reject) { if (_.isNil(items)) { return resolve(); } var arr = _.isArray(items) ? items : [items]; if (arr.length === 0) { return resolve(); } if (_.isNil(thisQueryable)) { return reject('The underlying data queryable cannot be empty at this context.'); } if ((mapping.parentModel !== thisQueryable.model.name) || (mapping.associationType!=='junction')) { return resolve(); } var values = arr.filter(function(x) { return (typeof x[mapping.parentField]!=='undefined') && (x[mapping.parentField]!=null); }).map(function(x) { return x[mapping.parentField]; }); if (_.isNil(mapping.childModel)) { var DataObjectTag = require('./data-object-tag').DataObjectTag; junction = new DataObjectTag(thisQueryable.model.convert({ }), mapping); var objectField = junction.getObjectField(); var valueField = junction.getValueField(); isSilent = !!thisQueryable.$silent; return junction.getBaseModel().where(objectField).in(values).flatten().silent(isSilent).select(objectField, valueField).all().then(function(items) { arr.forEach(function(x) { x[mapping.refersTo] = items.filter(function(y) { return y[objectField]===x[mapping.parentField]; }).map(function (y) { return y[valueField]; }); }); return resolve(); }).catch(function (err) { return reject(err); }); } //create a dummy object var DataObjectJunction = require('./data-object-junction').DataObjectJunction; var junction = new DataObjectJunction(thisQueryable.model.convert({ }), mapping); //query junction model isSilent = !!thisQueryable.$silent; return junction.getBaseModel().where(mapping.associationObjectField).in(values).silent(isSilent).flatten().getItems().then(function(junctions) { //get array of child key values var values = junctions.map(function(x) { return x[mapping.associationValueField] }); //get child model var childModel = thisArg.getChildModel(); childModel.filter(mapping.options, function(err, q) { if (err) { return reject(err); } q.prepare(); //Important Backward compatibility issue (<1.8.0) //Description: if $levels parameter is not defined then set the default value to 0. if (typeof q.$levels === 'undefined') { q.$levels = 0; } if (q.query.hasFields() === false) { q.select(); } //inherit silent mode if (thisQueryable.$silent) { q.silent(); } var childField = thisArg.getChildModel().getAttribute(mapping.childField); var keyField = childField.property || childField.name; // if query is a select statement if (q.query.$fixed == null) { // check if foreign key field exists in query var selectEntity = q.model.viewAdapter; if (Object.prototype.hasOwnProperty.call(q.query.$select, selectEntity)) { /** * @type {Array} */ var select = Object.getOwnPropertyDescriptor(q.query.$select, selectEntity).value; // find foreign key key var find = containsField(select, keyField); // if foreign key field does not exist if (find == null) { // clone query var q1 = q.clone().select(keyField); // and select foreign key field var select1 = Object.getOwnPropertyDescriptor(q1.query.$select, selectEntity).value; if (Array.isArray(select1)) { // append field to select fields select.push.apply(select, select1); } } } } //append where statement for this operation if (values.length===1) { q.where(mapping.childField).equal(values[0]); } else { q.where(mapping.childField).in(values); } //and finally query childs var refersTo = thisQueryable.model.getAttribute(mapping.refersTo); q.getItems().then(function(childs) { //if result contains only one item if (arr.length === 1) { if (refersTo && (refersTo.multiplicity === 'ZeroOrOne' || refersTo.multiplicity === 'One')) { arr[0][mapping.refersTo] = childs[0] != null ? childs[0] : null; return resolve(); } arr[0][mapping.refersTo] = childs; return resolve(); } //otherwise loop result array arr.forEach(function(x) { //get parent (key value) var parentValue = x[mapping.parentField]; //get parent(s) var p = junctions.filter(function(y) { return (y[mapping.associationObjectField]===parentValue); }).map(function(r) { return r[mapping.associationValueField]; }); //filter data and set property value (a filtered array of parent objects) if (refersTo && (refersTo.multiplicity === 'ZeroOrOne' || refersTo.multiplicity === 'One')) { // get only one child x[mapping.refersTo] = childs.find(function(z) { return p.indexOf(z[mapping.childField])>=0; }); } else { x[mapping.refersTo] = childs.filter(function(z) { return p.indexOf(z[mapping.childField])>=0; }); } }); return resolve(); }).catch(function(err) { return reject(err); }); }); }).catch(function (err) { return reject(err); }); }); } /** * @param {*} items * @returns {Promise|*} */ getAssociatedParents(items) { var thisArg = this; var thisQueryable = this.queryable; var mapping = this.mapping; return new Promise(function(resolve, reject) { if (_.isNil(items)) { return resolve(); } var arr = _.isArray(items) ? items : [items]; if (arr.length === 0) { return resolve(); } if (_.isNil(thisQueryable)) { return reject('The underlying data queryable cannot be empty at this context.'); } if ((mapping.childModel !== thisQueryable.model.name) || (mapping.associationType!=='association')) { return resolve(); } thisArg.getParentModel().migrate(function(err) { if (err) { return reject(err); } var childField = thisQueryable.model.field(mapping.childField); var keyField = childField.property || childField.name; if (_.isNil(childField)) { return reject('The specified field cannot be found on child model'); } var childFieldType = thisQueryable.model.context.model(childField.type); var values = _.intersection(_.map(_.filter(arr, function(x) { return hasOwnProperty(x, keyField) && x[keyField] != null; }), function (x) { return x[keyField]; })).map(function(x) { if (isObjectDeep(x)) { if (childFieldType) { return x[childFieldType.primaryKey]; } throw new Error('The child item is an object but its type cannot determined.'); } return x; }).filter(function(x) { return x != null; }); if (values.length===0) { return resolve(); } thisArg.getParentModel().filter(mapping.options, function(err, q) { if (err) { return reject(err); } q.prepare(); //Important Backward compatibility issue (<1.8.0) //Description: if $levels parameter is not defined then set the default value to 0. if (typeof q.$levels === 'undefined') { q.$levels = 0; } //inherit silent mode if (thisQueryable.$silent) { q.silent(); } //append where statement for this operation q.where(mapping.parentField).in(values); //set silent (?) if (childField && childField.nested === true) { q.silent(); } q.getAllItems().then(function(parents) { var iterator = function(x) { var key = x[keyField]; var property = childField.property || childField.name; if (property == null) { throw new DataError( 'E_NULL', 'Child attribute cannot be empty', null, mapping.childModel, mapping.childField); } var find = parents.find(function(parent) { return parent[mapping.parentField] === key; }); // clone parent or set null x[property] = find != null ? _.cloneDeep(find) : null; }; if (Array.isArray(arr)) { arr.forEach(iterator); } return resolve(); }).catch(function(err) { return reject(err); }); }); }); }); } /** * @param {*} items * @returns {Promise|*} */ getAssociatedChildren(items) { var thisArg = this; var thisQueryable = this.queryable; var mapping = this.mapping; return new Promise(function(resolve, reject) { if (_.isNil(items)) { return resolve(); } var arr = _.isArray(items) ? items : [items]; if (arr.length === 0) { return resolve(); } if (_.isNil(thisQueryable)) { return reject('The underlying data queryable cannot be empty at this context.'); } if ((mapping.parentModel !== thisQueryable.model.name) || (mapping.associationType!=='association')) { return resolve(); } thisArg.getChildModel().migrate(function(err) { if (err) { return reject(err); } var parentField = thisQueryable.model.field(mapping.parentField); if (_.isNil(parentField)) { return reject('The specified field cannot be found on parent model'); } var keyField = parentField.property || parentField.name; var parentFieldType = thisQueryable.model.context.model(parentField.type); var values = _.intersection(_.map(_.filter(arr, function(x) { return hasOwnProperty(x, keyField); }), function (x) { return x[keyField];})).map(function(x) { if (isObjectDeep(x)) { if (parentFieldType) { return x[parentFieldType.primaryKey]; } throw new Error('The parent item is an object but its type cannot determined.'); } return x; }).filter(function(x) { return x != null; }); if (values.length===0) { return resolve(); } //search for view named summary thisArg.getChildModel().filter(mapping.options, function(err, q) { if (err) { return reject(err); } var childField = thisArg.getChildModel().field(mapping.childField); if (_.isNil(childField)) { return reject('The specified field cannot be found on child model'); } var foreignKeyField = childField.property || childField.name; //Important Backward compatibility issue (<1.8.0) //Description: if $levels parameter is not defined then set the default value to 0. if (typeof q.$levels === 'undefined') { q.$levels = 0; } q.prepare(); if (values.length===1) { q.where(mapping.childField).equal(values[0]); } else { q.where(mapping.childField).in(values); } if (q.query.hasFields() === false) { q.select(); } //inherit silent mode if (thisQueryable.$silent) { q.silent(); } // if query is a select statement if (q.query.$fixed == null) { // check if foreign key field exists in query var selectEntity = q.model.viewAdapter; if (Object.prototype.hasOwnProperty.call(q.query.$select, selectEntity)) { /** * @type {Array} */ var select = Object.getOwnPropertyDescriptor(q.query.$select, selectEntity).value; // find foreign key key var find = containsField(select, foreignKeyField); // if foreign key field does not exist if (find == null) { // clone query var q1 = q.clone().select(foreignKeyField); // and select foreign key field var select1 = Object.getOwnPropertyDescriptor(q1.query.$select, selectEntity).value; if (Array.isArray(select1)) { // append field to select fields select.push.apply(select, select1); } if (Array.isArray(q.query.$group)) { find = containsField(q.query.$group, foreignKeyField); if (find == null) { q.query.$group.push.apply(q.query.$group, select1); } } } } } //final execute query return q.getItems().then(function(childs) { // get referrer field of parent model var refersTo = thisArg.getParentModel().getAttribute(mapping.refersTo); _.forEach(arr, function(x) { var items = _.filter(childs, function(y) { if (!_.isNil(y[foreignKeyField]) && hasOwnProperty(y[foreignKeyField], keyField)) { return y[foreignKeyField][keyField] === x[keyField]; } return y[foreignKeyField] === x[keyField]; }); // if parent field multiplicity attribute defines an one-to-one association if (refersTo && (refersTo.multiplicity === 'ZeroOrOne' || refersTo.multiplicity === 'One')) { if (items[0] != null) { // todo raise error if there are more than one items // get only the first item x[mapping.refersTo] = items[0]; } else { // or nothing x[mapping.refersTo] = null; } } else { // otherwise get all items x[mapping.refersTo] = items; } }); return resolve(); }).catch(function(err) { return reject(err); }); }); }); }); } } class DataMappingOptimizedExtender extends DataMappingExtender { constructor(mapping) { super(mapping); } /** * @param {*} items * @returns {Promise|*} */ getParents(items) { var thisArg = this; var thisQueryable = this.queryable; var mapping = this.mapping; return new Promise(function(resolve, reject) { if (items == null) { return resolve(); } var arr = Array.isArray(items) ? items : [ items ]; if (arr.length === 0) { return resolve(); } if (_.isNil(thisQueryable)) { return reject('The underlying data queryable cannot be empty at this context.'); } if ((mapping.childModel !== thisQueryable.model.name) || (mapping.associationType!=='junction')) { return resolve(); } var HasParentJunction = require('./has-parent-junction').HasParentJunction; var junction = new HasParentJunction(thisQueryable.model.convert({ }), mapping); return junction.migrate(function(err) { if (err) { return reject(err); } var parentModel = thisArg.getParentModel(); parentModel.filter(mapping.options, function(err, q) { if (err) { return reject(err); } if (!q.query.hasFields()) { q.select(); } //get junction sub-query var junctionQuery = QueryUtils.query(junction.getBaseModel().name).select([mapping.associationObjectField, mapping.associationValueField]) .join(thisQueryable.query.as('j0')) .with(QueryUtils.where(new QueryEntity(junction.getBaseModel().name).select(mapping.associationValueField)) .equal(new QueryEntity('j0').select(mapping.childField))); //append join statement with sub-query q.query.join(junctionQuery.as('g0')) .with(QueryUtils.where(new QueryEntity(parentModel.viewAdapter).select(mapping.parentField)) .equal(new QueryEntity('g0').select(mapping.associationObjectField))); if (!q.query.hasFields()) { q.select(); } //inherit silent mode if (thisQueryable.$silent) { q.silent(); } // append child key field if (hasOwnProperty(q.query.$select, q.model.viewAdapter)) { const select = Object.getOwnPropertyDescriptor(q.query.$select, q.model.viewAdapter).value; select.push(QueryField.select(mapping.associationValueField).from('g0').as('__ref')); } else { throw new Error('Query expression is invalid. Expected a collection of selected attributes'); } //append child key field return q.getItems().then(function (parents) { _.forEach(arr, function(item) { var values = parents.filter(function(parent) { if (parent.__ref === item[mapping.childField]) { return true; } return false; }); var cloned = _.cloneDeep(values); cloned.forEach(function(item1) { delete item1.__ref; }); item[mapping.refersTo] = cloned; }); return resolve(); }).catch(function (err) { return reject(err); }); }); }); }); } /** * @param {*} items * @returns {Promise|*} */ getChildren(items) { var thisArg = this; var thisQueryable = this.queryable; var mapping = this.mapping; return new Promise(function(resolve, reject) { if (items == null) { return resolve(); } var arr = Array.isArray(items) ? items : [ items ]; if (arr.length === 0) { return resolve(); } if (thisQueryable == null) { return reject('The underlying data queryable cannot be empty at this context.'); } if ((mapping.parentModel !== thisQueryable.model.name) || (mapping.associationType!=='junction')) { return resolve(); } var junction; if (mapping.childModel == null) { var DataObjectTag = require('./data-object-tag').DataObjectTag; junction = new DataObjectTag(thisQueryable.model.convert({ }), mapping); return junction.migrate(function(err) { if (err) { return reject(err); } //get junction sub-query var q = junction.getBaseModel() .select(mapping.associationObjectField, mapping.associationValueField); q.query.join(thisQueryable.query.as('j0')) .with(QueryUtils.where(new QueryEntity(junction.getBaseModel().name).select(mapping.associationObjectField)) .equal(new QueryEntity('j0').select(mapping.parentField))); if (thisQueryable.$silent) { q.silent(); } return q.getItems().then(function (children) { arr.forEach(function(item) { var values = children.filter(function(child) { if (child[mapping.associationObjectField] === item[mapping.parentField]) { return true; } return false; }); var cloned = _.cloneDeep(values); item[mapping.refersTo] = cloned.map(function(item) { return item[mapping.associationValueField]; }); }); return resolve(); }).catch(function (err) { return reject(err); }); }); } var DataObjectJunction = require('./data-object-junction').DataObjectJunction; junction = new DataObjectJunction(thisQueryable.model.convert({ }), mapping); return junction.migrate(function(err) { if (err) { return reject(err); } var childModel = thisArg.getChildModel(); childModel.filter(mapping.options, function(err, q) { if (err) { return reject(err); } if (!q.query.hasFields()) { q.select(); } //get junction sub-query var junctionQuery = QueryUtils.query(junction.getBaseModel().name).select([mapping.associationObjectField, mapping.associationValueField]) .join(thisQueryable.query.as('j0')) .with(QueryUtils.where(new QueryEntity(junction.getBaseModel().name).select(mapping.associationObjectField)) .equal(new QueryEntity('j0').select(mapping.parentField))); //append join statement with sub-query q.query.join(junctionQuery.as('g0')) .with(QueryUtils.where(new QueryEntity(childModel.viewAdapter).select(mapping.childField)) .equal(new QueryEntity('g0').select(mapping.associationValueField))); //inherit silent mode if (thisQueryable.$silent) { q.silent(); } // append item reference if (hasOwnProperty(q.query.$select, q.model.viewAdapter)) { const select = Object.getOwnPropertyDescriptor(q.query.$select, q.model.viewAdapter).value; select.push(QueryField.select(mapping.associationObjectField).from('g0').as('__ref')); } else { throw new Error('Query expression is invalid. Expected a collection of selected attributes'); } return q.getItems().then(function (children) { arr.forEach(function(item) { var values = children.filter(function(child) { // important:: use type equal operator if (child.__ref === item[mapping.parentField]) { return true; } return false; }); var cloned = _.cloneDeep(values); cloned.forEach(function(item1) { delete item1.__ref; }); item[mapping.refersTo] = cloned; }); return resolve(); }).catch(function (err) { return reject(err); }); }); }); }); } /** * @param {*} items * @returns {Promise|*} */ getAssociatedParents(items) { var thisArg = this; var thisQueryable = this.queryable; var mapping = this.mapping; return new Promise(function(resolve, reject) { if (items == null) { return resolve(); } var arr = Array.isArray(items) ? items : [items]; if (arr.length === 0) { return resolve(); } if (thisQueryable == null) { return reject('The underlying data queryable cannot be empty at this context.'); } if ((mapping.childModel !== thisQueryable.model.name) || (mapping.associationType!=='association')) { return resolve(); } thisArg.getParentModel().migrate(function(err) { if (err) { return reject(err); } thisArg.getParentModel().filter(mapping.options, function(err, q) { if (err) { return reject(err); } //Important Backward compatibility issue (<1.8.0) //Description: if $levels parameter is not defined then set the default value to 0. if (q.$levels == null) { q.$levels = 0; } if (q.query.$select == null) { q.select(); } var childField = thisQueryable.model.field(mapping.childField); var keyField = childField.property || childField.name; // if query is a select statement if (thisQueryable.query.$fixed == null) { // check if foreign key field exists in query var selectEntity = thisQueryable.model.viewAdapter; if (Object.prototype.hasOwnProperty.call(thisQueryable.query.$select, selectEntity)) { /** * @type {Array} */ var select = Object.getOwnPropertyDescriptor(thisQueryable.query.$select, selectEntity).value; // find foreign key key var find = containsField(select, keyField); // if foreign key field does not exist if (find == null) { // clone query var q1 = thisQueryable.clone().select(keyField); // and select foreign key field var select1 = Object.getOwnPropertyDescriptor(q1.query.$select, selectEntity).value; if (Array.isArray(select1)) { // append field to select fields select.push.apply(select, select1); } } } } q.query .join(thisQueryable.query.as('j0')) .with(QueryUtils.where(new QueryEntity(thisArg.getParentModel().viewAdapter).select(mapping.parentField)) .equal(new QueryEntity('j0').select(mapping.childField))); //inherit silent mode if (thisQueryable.$silent) { q.silent(); } var refersTo = thisArg.getChildModel().getAttribute(mapping.childField); if (refersTo.nested === true) { q.silent(); } // validate nested attribute q.getAllItems().then(function(results) { var iterator = function(item) { var find = results.find(function(result) { return result[mapping.parentField] == item[keyField]; }); item[keyField] = _.cloneDeep(find); }; arr.forEach(iterator); return resolve(); }).catch(function (err) { return reject(err); }); }); }); }); } /** * @param {*} items * @returns {Promise|*} */ getAssociatedChildren(items) { var thisArg = this; var thisQueryable = this.queryable; var mapping = this.mapping; return new Promise(function(resolve, reject) { if (items == null) { return resolve(); } var arr = Array.isArray(items) ? items : [items]; if (arr.length === 0) { return resolve(); } if (thisQueryable == null) { return reject('The underlying data queryable cannot be empty at this context.'); } if ((mapping.parentModel !== thisQueryable.model.name) || (mapping.associationType!=='association')) { return resolve(); } thisArg.getChildModel().migrate(function(err) { if (err) { return reject(err); } var parentField = thisArg.getParentModel().field(mapping.parentField); if (parentField == null) { return reject('The specified field cannot be found on parent model'); } var keyField = parentField.property || parentField.name; var parentFieldType = thisQueryable.model.context.model(parentField.type); var values = _.intersection(_.map(_.filter(arr, function(x) { return hasOwnProperty(x, keyField); }), function (x) { return x[keyField]; })).map(function(x) { if (isObjectDeep(x)) { if (parentFieldType) { return x[parentFieldType.primaryKey]; } throw new Error('The parent item is an object but its type cannot determined.'); } return x; }).filter(function(x) { return x != null; }); if (values.length===0) { return resolve(); } //search for view named summary thisArg.getChildModel().filter(mapping.options, function(err, q) { if (err) { return reject(err); } var childField = thisArg.getChildModel().field(mapping.childField); if (childField == null) { return reject('The specified field cannot be found on child model'); } var foreignKeyField = childField.property || childField.name; //Important Backward compatibility issue (<1.8.0) //Description: if $levels parameter is not defined then set the default value to 0. if (q.$levels == null) { q.$levels = 0; } if (q.query.hasFields() === false) { q.select(); } //inherit silent mode if (thisQueryable.$silent) { q.silent(); } // if query is a select statement if (q.query.$fixed == null) { // check if foreign key field exists in query var selectEntity = q.model.viewAdapter; if (Object.prototype.hasOwnProperty.call(q.query.$select, selectEntity)) { /** * @type {Array} */ var select = Object.getOwnPropertyDescriptor(q.query.$select, selectEntity).value; // find foreign key key var find = containsField(select, foreignKeyField); // if foreign key field does not exist if (find == null) { // clone query var q1 = q.clone().select(foreignKeyField); // and select foreign key field var select1 = Object.getOwnPropertyDescriptor(q1.query.$select, selectEntity).value; if (Array.isArray(select1)) { // append field to select fields select.push.apply(select, select1); } if (Array.isArray(q.query.$group)) { find = containsField(q.query.$group, foreignKeyField); if (find == null) { q.query.$group.push.apply(q.query.$group, select1); } } } } } // join parents q.query.join(thisQueryable.query.as('j0')) .with(QueryUtils.where(new QueryEntity(thisArg.getChildModel().viewAdapter).select(mapping.childField)) .equal(new QueryEntity('j0').select(mapping.parentField))); q.prepare(); // final execute query return q.getItems().then(function(results) { arr.forEach(function(item) { var children = results.filter(function(result) { if (item[keyField] == null) { return false; } var value = result[foreignKeyField]; if (value != null && hasOwnProperty(value, keyField) === true) { // important:: use equal value operator (==) return value[keyField] == item[keyField]; } return value == item[keyField]; }); var refersTo = thisArg.getParentModel().getAttribute(mapping.refersTo); // if parent field multiplicity attribute defines an one-to-one association if (refersTo && (refersTo.multiplicity === 'ZeroOrOne' || refersTo.multiplicity === 'One')) { if (children[0] != null) { item[mapping.refersTo] = _.cloneDeep(children[0]); } else { item[mapping.refersTo] = null; } } else { item[mapping.refersTo] = _.cloneDeep(children); } }); return resolve(); }).catch(function(err) { return reject(err); }); }); }); }); } } module.exports = { DataMappingExtender, DataMappingOptimizedExtender };