Skip to content

Commit

Permalink
add data value resolver (#177)
Browse files Browse the repository at this point in the history
* add data value resolver

* prepare getting attribute for a many-to-many association

* resolve array of values

* 2.16.0
  • Loading branch information
kbarbounakis authored Nov 18, 2024
1 parent b7b677e commit 2bf9d23
Show file tree
Hide file tree
Showing 9 changed files with 357 additions and 13 deletions.
6 changes: 6 additions & 0 deletions data-attribute-resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,12 @@ DataAttributeResolver.prototype.resolveJunctionAttributeJoin = function(attr) {
//create new join
var parentAlias = field.name + '_' + parentModel.name;
entity = new QueryEntity(parentModel.viewAdapter).as(parentAlias);
Object.defineProperty(entity, 'model', {
configurable: true,
enumerable: false,
writable: true,
value: parentModel.name
});
expr = QueryUtils.query().where(QueryField.select(mapping.associationObjectField).from(field.name))
.equal(QueryField.select(mapping.parentField).from(parentAlias));
//append join
Expand Down
70 changes: 61 additions & 9 deletions data-queryable.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var {hasOwnProperty} = require('./has-own-property');
var { DataAttributeResolver } = require('./data-attribute-resolver');
var { DataExpandResolver } = require('./data-expand-resolver');
var {instanceOf} = require('./instance-of');

var { DataValueResolver } = require('./data-value-resolver');
/**
* @param {DataQueryable} target
*/
Expand Down Expand Up @@ -235,6 +235,17 @@ DataQueryable.prototype.where = function(attr) {
this.query.where(DataAttributeResolver.prototype.resolveNestedAttribute.call(this, attr));
return this;
}
// check if attribute defines a many-to-many association
var mapping = this.model.inferMapping(attr);
if (mapping && mapping.associationType === 'junction') {
// append mapping id e.g. groups -> groups/id or members -> members/id etc
let attrId = attr + '/' + mapping.parentField;
if (mapping.parentModel === this.model.name) {
attrId = attr + '/' + mapping.childField;
}
this.query.where(DataAttributeResolver.prototype.resolveNestedAttribute.call(this, attrId));
return this;
}
this.query.where(this.fieldOf(attr));
return this;
};
Expand Down Expand Up @@ -352,6 +363,17 @@ DataQueryable.prototype.and = function(attr) {
this.query.and(DataAttributeResolver.prototype.resolveNestedAttribute.call(this, attr));
return this;
}
// check if attribute defines a many-to-many association
var mapping = this.model.inferMapping(attr);
if (mapping && mapping.associationType === 'junction') {
// append mapping id e.g. groups -> groups/id or members -> members/id etc
let attrId = attr + '/' + mapping.parentField;
if (mapping.parentModel === this.model.name) {
attrId = attr + '/' + mapping.childField;
}
this.query.where(DataAttributeResolver.prototype.resolveNestedAttribute.call(this, attrId));
return this;
}
this.query.and(this.fieldOf(attr));
return this;
};
Expand All @@ -374,6 +396,17 @@ DataQueryable.prototype.or = function(attr) {
this.query.or(DataAttributeResolver.prototype.resolveNestedAttribute.call(this, attr));
return this;
}
// check if attribute defines a many-to-many association
var mapping = this.model.inferMapping(attr);
if (mapping && mapping.associationType === 'junction') {
// append mapping id e.g. groups -> groups/id or members -> members/id etc
let attrId = attr + '/' + mapping.parentField;
if (mapping.parentModel === this.model.name) {
attrId = attr + '/' + mapping.childField;
}
this.query.where(DataAttributeResolver.prototype.resolveNestedAttribute.call(this, attrId));
return this;
}
this.query.or(this.fieldOf(attr));
return this;
};
Expand Down Expand Up @@ -417,8 +450,16 @@ function resolveValue(obj) {
});
*/
DataQueryable.prototype.equal = function(obj) {

this.query.equal(resolveValue.bind(this)(obj));
// check if the given object is an array
if (Array.isArray(obj)) {
var resolver = new DataValueResolver(this);
// and resolve each value separately
this.query.equal(obj.map(function(value) {
return resolver.resolve(value);
}));
return this;
}
this.query.equal(new DataValueResolver(this).resolve(obj));
return this;
};

Expand Down Expand Up @@ -456,7 +497,17 @@ DataQueryable.prototype.is = function(obj) {
});
*/
DataQueryable.prototype.notEqual = function(obj) {
this.query.notEqual(resolveValue.bind(this)(obj));
// check if the given object is an array
if (Array.isArray(obj)) {
var resolver = new DataValueResolver(this);
// and resolve each value separately
this.query.notEqual(obj.map(function(value) {
return resolver.resolve(value);
}));
return this;
}
// otherwise resolve the value
this.query.notEqual(new DataValueResolver(this).resolve(obj));
return this;
};
// noinspection JSUnusedGlobalSymbols
Expand Down Expand Up @@ -486,7 +537,7 @@ DataQueryable.prototype.notEqual = function(obj) {
89 Nvidia GeForce GTX 650 Ti Boost 1625.49 2015-11-21 17:29:21.000+02:00
*/
DataQueryable.prototype.greaterThan = function(obj) {
this.query.greaterThan(resolveValue.bind(this)(obj));
this.query.greaterThan(new DataValueResolver(this).resolve(obj));
return this;
};

Expand All @@ -507,7 +558,7 @@ DataQueryable.prototype.greaterThan = function(obj) {
});
*/
DataQueryable.prototype.greaterOrEqual = function(obj) {
this.query.greaterOrEqual(resolveValue.bind(this)(obj));
this.query.greaterOrEqual(new DataValueResolver(this).resolve(obj));
return this;
};

Expand Down Expand Up @@ -544,7 +595,7 @@ DataQueryable.prototype.bit = function(value, result) {
* @returns {DataQueryable}
*/
DataQueryable.prototype.lowerThan = function(obj) {
this.query.lowerThan(resolveValue.bind(this)(obj));
this.query.lowerThan(new DataValueResolver(this).resolve(obj));
return this;
};

Expand All @@ -565,7 +616,7 @@ DataQueryable.prototype.lowerThan = function(obj) {
});
*/
DataQueryable.prototype.lowerOrEqual = function(obj) {
this.query.lowerOrEqual(resolveValue.bind(this)(obj));
this.query.lowerOrEqual(new DataValueResolver(this).resolve(obj));
return this;
};
// noinspection JSUnusedGlobalSymbols
Expand Down Expand Up @@ -745,7 +796,8 @@ DataQueryable.prototype.notContains = function(value) {
440 Bose SoundLink Bluetooth Mobile Speaker II HS5288 155.27
*/
DataQueryable.prototype.between = function(value1, value2) {
this.query.between(resolveValue.bind(this)(value1), resolveValue.bind(this)(value2));
const resolver = new DataValueResolver(this);
this.query.between(resolver.resolve(value1), resolver.resolve(value2));
return this;
};

Expand Down
6 changes: 6 additions & 0 deletions data-value-resolver.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {QueryExpression} from '@themost/query';

export declare class DataValueResolver {
constructor(target: any );
resolve(value: any): any;
}
150 changes: 150 additions & 0 deletions data-value-resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
const {DataAttributeResolver} = require('./data-attribute-resolver');
const {isObjectDeep} = require('./is-object');
const {sprintf} = require('sprintf-js');

/**
* @class DataValueResolver
* @param {import('./index').DataQueryable} target
* @constructor
*/
function DataValueResolver(target) {
Object.defineProperty(this, 'target', { get: function() {
return target;
}, configurable:false, enumerable:false});
}

DataValueResolver.prototype.resolve = function(value) {
/**
* @type {DataQueryable}
*/
var target = this.target;
if (typeof value === 'string' && /^\$it\//.test(value)) {
var attr = value.replace(/^\$it\//,'');
if (DataAttributeResolver.prototype.testNestedAttribute(attr)) {
return DataAttributeResolver.prototype.resolveNestedAttribute.call(target, attr);
}
else {
attr = DataAttributeResolver.prototype.testAttribute(attr);
if (attr) {
return target.fieldOf(attr.name);
}
}
}
if (isObjectDeep(value)) {
// try to get in-process left operand
// noinspection JSUnresolvedReference
var left = target.query.privates && target.query.privates.property;
if (typeof left === 'string' && /\./.test(left)) {
var members = left.split('.');
if (Array.isArray(members)) {
// try to find member mapping
/**
* @type {import('./data-model').DataModel}
*/
var model = target.model;
var mapping;
var attribute;
var index = 0;
var context = target.model.context;
// if the first segment contains the view adapter name
if (members[0] === target.model.viewAdapter) {
// move next
index++;
} else if (target.query.$expand != null) {
// try to find if the first segment is contained in the collection of joined entities
var joins = Array.isArray(target.query.$expand) ? target.query.$expand : [ target.query.$expand ];
if (joins.length) {
var found = joins.find(function(x) {
return x.$entity && x.$entity.$as === members[0];
});
if (found) {
var mapping1 = model.inferMapping(found.$entity.$as);
if (mapping1 && mapping1.associationType === 'junction') {
// get next segment of members
var nextMember = members[index + 1];
if (nextMember === mapping1.associationObjectField) {
// the next segment is the association object field
// e.g. groups/group
model = context.model(mapping1.parentModel);
members[index + 1] = mapping1.parentField;
} else if (nextMember === mapping1.associationValueField) {
// the next segment is the association value field
// e.g. groups/user
model = context.model(mapping1.childModel);
members[index + 1] = mapping1.childField;
} else if (model.name === mapping1.parentModel) {
model = context.model(mapping1.childModel);
} else {
model = context.model(mapping1.parentModel);
}
} else if (found.$entity.model != null) {
model = context.model(found.$entity.model);
} else {
throw new Error(sprintf('Expected a valid mapping for property "%s"', found.$entity.$as));
}
index++;
}
}
}

var mapValue = function(x) {
if (Object.hasOwnProperty.call(x, name)) {
return x[name];
}
throw new Error(sprintf('Invalid value for property "%s"', members[members.length - 1]));
}

while (index < members.length) {
mapping = model.inferMapping(members[index]);
if (mapping) {
if (mapping.associationType === 'association' && mapping.childModel === model.name) {
model = context.model(mapping.parentModel);
if (model) {
attribute = model.getAttribute(mapping.parentField);
}
} else if (mapping.associationType === 'association' && mapping.parentModel === model.name) {
model = context.model(mapping.childModel);
if (model) {
attribute = model.getAttribute(mapping.childField);
}
} else if (mapping.associationType === 'junction' && mapping.childModel === model.name) {
model = context.model(mapping.parentModel);
if (model) {
attribute = model.getAttribute(mapping.parentField);
}
} else if (mapping.associationType === 'junction' && mapping.parentModel === model.name) {
model = context.model(mapping.childModel);
if (model) {
attribute = model.getAttribute(mapping.childField);
}
}
} else {
// if mapping is not found, and we are in the last segment
// try to find if this last segment is a field of the current model
if (index === members.length - 1) {
attribute = model.getAttribute(members[index]);
break;
}
attribute = null;
model = null;
break;
}
index++;
}
if (attribute) {
var name = attribute.property || attribute.name;
if (Array.isArray(value)) {
return value.map(function(x) {
return mapValue(x);
});
} else {
return mapValue(value);
}
}
}
}
}
return value;
}

module.exports = { DataValueResolver };
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export * from './data-listeners';
export * from './data-associations';
export * from './data-application';
export * from './UnattendedMode';
export * from './data-value-resolver';
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ var { DataApplication } = require('./data-application');

var { executeInUnattendedMode, executeInUnattendedModeAsync, enableUnattendedExecution, disableUnattendedExecution } = require('./UnattendedMode');

var { DataValueResolver } = require('./data-value-resolver');

module.exports = {
TypeParser,
PrivilegeType,
Expand Down Expand Up @@ -171,6 +173,7 @@ module.exports = {
executeInUnattendedMode,
executeInUnattendedModeAsync,
enableUnattendedExecution,
disableUnattendedExecution
disableUnattendedExecution,
DataValueResolver
};

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@themost/data",
"version": "2.15.1",
"version": "2.16.0",
"description": "MOST Web Framework Codename Blueshift - Data module",
"main": "index.js",
"scripts": {
Expand Down
Loading

0 comments on commit 2bf9d23

Please sign in to comment.