import FilterOperator from './filter-operator';
import Condition from './condition';
import moment from 'moment';
/**
* The base class of logical predicate.
*
* @module ember-flexberry-data
* @class BasePredicate
*/
export class BasePredicate {
constructor() {
}
}
/**
* The class of simple predicate for filtering attribute by value and filter operator.
*
* @class SimplePredicate
* @extends BasePredicate
*
* @param attributePath {String} The path to the attribute for filtering.
* @param operator {Query.FilterOperator|String} The filter operator.
* @param value {String|Number} The value for filtering.
* @constructor
*/
export class SimplePredicate extends BasePredicate {
constructor(attributePath, operator, value) {
super();
if (value === undefined) {
value = null;
}
this._attributePath = attributePath;
this._operator = FilterOperator.tryCreate(operator);
this._value = value;
}
/**
* The path to the attribute for filtering.
*
* @property attributePath
* @type String
* @public
*/
get attributePath() {
return this._attributePath;
}
/**
* The filter operator.
*
* @property operator
* @type Query.FilterOperator
* @public
*/
get operator() {
return this._operator;
}
/**
* The value for filtering.
*
* @property value
* @type String|Number
* @public
*/
get value() {
return this._value;
}
/**
* Converts this instance to string.
*
* @method toString
* @return {String} Text representation of the predicate.
* @public
*/
toString() {
return `(${this._attributePath} ${this._operator} ${this._value})`;
}
}
/**
* The class of date predicate for filtering attribute by value and filter operator.
*
* @class DatePredicate
* @extends BasePredicate
*
* @param attributePath {String} The path to the attribute for filtering.
* @param operator {Query.FilterOperator|String} The filter operator.
* @param value {String|Date} The value for filtering.
* @param timeless {Boolean} When true, dates will be filtered without time.
* @constructor
*/
export class DatePredicate extends BasePredicate {
constructor(attributePath, operator, value, timeless) {
super();
let momentFromValue = moment(value);
if (!momentFromValue.isValid()) {
throw new Error(`Date isn't valid or null (for null values use SimplePredicate)`);
}
this._attributePath = attributePath;
this._operator = FilterOperator.tryCreate(operator);
this._value = value;
this._timeless = timeless;
}
/**
* The path to the attribute for filtering.
*
* @property attributePath
* @type String
* @public
*/
get attributePath() {
return this._attributePath;
}
/**
* The filter operator.
*
* @property operator
* @type Query.FilterOperator
* @public
*/
get operator() {
return this._operator;
}
/**
* The value for filtering.
*
* @property value
* @type String
* @public
*/
get value() {
return this._value;
}
/**
* Flag for dates.
*
* @property timeless
* @type Boolean
* @public
*/
get timeless() {
return this._timeless;
}
/**
* Converts this instance to string.
*
* @method toString
* @return {String} Text representation of the predicate.
* @public
*/
toString() {
return this._timeless ? `(date(${this._attributePath}) ${this._operator} ${this._value})` :
`(${this._attributePath} ${this._operator} ${this._value})`;
}
}
/**
* The class of complex predicate which include multiple predicates unioned with logical condition.
*
* @class ComplexPredicate
* @extends BasePredicate
*
* @param condition {Query.Condition} Logical condition for predicates.
* @param ...predicates {Query.BasePredicate} List of predicates for combining.
* @constructor
*/
export class ComplexPredicate extends BasePredicate {
constructor(condition, ...predicates) {
super();
if (predicates === null || !(predicates instanceof Array)) {
throw new Error(`Wrong predicates`);
}
if (predicates.length < 2) {
throw new Error(`Complex predicate requires at least two nested predicates`);
}
predicates.forEach(validatePredicate);
this._condition = Condition.tryCreate(condition);
this._predicates = predicates;
}
get condition() {
return this._condition;
}
get predicates() {
return this._predicates;
}
and(...predicates) {
// Just add the predicate if conditions match.
if (this._condition === Condition.And) {
this.predicates.forEach(p => predicates.unshift(p));
return new ComplexPredicate(Condition.And, ...predicates);
}
return super.and(...predicates);
}
or(...predicates) {
// Just add the predicate if conditions match.
if (this._condition === Condition.Or) {
this.predicates.forEach(p => predicates.unshift(p));
return new ComplexPredicate(Condition.Or, ...predicates);
}
return super.or(...predicates);
}
/**
* Converts this instance to string.
*
* @method toString
* @return {String} Text representation of the predicate.
* @public
*/
toString() {
return `(${this._predicates.map(i => i.toString()).join(` ${this.condition} `)})`;
}
}
/**
* The predicate class for string attributes.
*
* @class StringPredicate
* @extends BasePredicate
*
* @param {String} attributePath The path to the attribute for predicate.
* @constructor
*/
export class StringPredicate extends BasePredicate {
constructor(attributePath) {
super();
if (!attributePath) {
throw new Error('Attribute path is required for StringPredicate constructor.');
}
this._attributePath = attributePath;
this._containsValue = null;
}
/**
* The path to the attribute for predicate.
*
* @property attributePath
* @type {String}
* @public
*/
get attributePath() {
return this._attributePath;
}
/**
* The value that has to be contained in the attribute.
*
* @property containsValue
* @type {String}
* @public
*/
get containsValue() {
return this._containsValue;
}
/**
* Sets the value that the attribute has to contain.
*
* @method contains
* @param {String} value The value that the attribute has to contain.
* @return {Query.StringPredicate} Returns this instance.
* @chainable
*/
contains(value) {
this._containsValue = value;
return this;
}
}
/**
* The predicate class for geography attributes.
*
* @class GeographyPredicate
* @extends BasePredicate
*
* @param {String} attributePath The path to the attribute for predicate.
* @constructor
*/
export class GeographyPredicate extends BasePredicate {
constructor(attributePath) {
super();
if (!attributePath) {
throw new Error('Attribute path is required for GeographyPredicate constructor.');
}
this._attributePath = attributePath;
this._intersectsValue = null;
}
/**
* The path to the attribute for predicate.
*
* @property attributePath
* @type {String}
* @public
*/
get attributePath() {
return this._attributePath;
}
/**
* The geography value that has to intersect with the attribute.
*
* @property intersectsValue
* @type {String}
* @public
*/
get intersectsValue() {
return this._intersectsValue;
}
/**
* Sets the value that the attribute has to contain.
*
* @method contains
* @param {String} geography The geography value that has to intersect with the attribute.
* @return {Query.StringPredicate} Returns this instance.
* @chainable
*/
intersects(geography) {
this._intersectsValue = geography;
return this;
}
}
/**
* The predicate class for geometry attributes.
*
* @namespace Query
* @class GeometryPredicate
* @extends BasePredicate
*
* @param {String} attributePath The path to the attribute for predicate.
* @constructor
*/
export class GeometryPredicate extends BasePredicate {
constructor(attributePath) {
super();
if (!attributePath) {
throw new Error('Attribute path is required for GeometryPredicate constructor.');
}
this._attributePath = attributePath;
this._intersectsValue = null;
}
/**
* The path to the attribute for predicate.
*
* @property attributePath
* @type {String}
* @public
*/
get attributePath() {
return this._attributePath;
}
/**
* The geometry value that has to intersect with the attribute.
*
* @property intersectsValue
* @type {String}
* @public
*/
get intersectsValue() {
return this._intersectsValue;
}
/**
* Sets the value that the attribute has to contain.
*
* @method contains
* @param {String} geometry The geometry value that has to intersect with the attribute.
* @return {Query.StringPredicate} Returns this instance.
* @chainable
*/
intersects(geometry) {
this._intersectsValue = geometry;
return this;
}
}
/**
* The predicate class for details.
*
* @class DetailPredicate
* @extends BasePredicate
*
* @param {String} detailPath The path to the detail for predicate.
* @constructor
*/
export class DetailPredicate extends BasePredicate {
constructor(detailPath) {
super();
if (!detailPath) {
throw new Error('Detail path is required for DetailPredicate constructor.');
}
this._detailPath = detailPath;
this._predicate = null;
this._all = false;
this._any = false;
}
/**
* The path to the detail for predicate.
*
* @returns {String}
*/
get detailPath() {
return this._detailPath;
}
/**
* The predicate for details.
*
* @returns {Query.BasePredicate|null}
*/
get predicate() {
return this._predicate;
}
/**
* Is need to use predicate for all details.
*
* @returns {Boolean}
*/
get isAll() {
return this._all;
}
/**
* Is need to use predicate for any detail.
*
* @returns {Boolean}
*/
get isAny() {
return this._any;
}
/**
* Adds predicate for all details.
*
* @method all
* @param ...args Predicate for all details.
* @returns {Query.DetailPredicate} Returns this instance.
* @public
*/
all(...args) {
this._predicate = createPredicate(...args);
this._all = true;
this._any = false;
return this;
}
/**
* Adds predicate for any detail.
*
* @method any
* @param ...args Predicate for any detail.
* @returns {Query.DetailPredicate} Returns this instance.
* @public
*/
any(...args) {
this._predicate = createPredicate(...args);
this._any = true;
this._all = false;
return this;
}
/**
* Converts this instance to string.
*
* @method toString
* @return {String} Text representation of the predicate.
* @public
*/
toString() {
let func = 'IncompleteDeteailPredicate';
if (this._all) {
func = 'all';
} else if (this._any) {
func = 'any';
}
return `${func}${this._predicate ? this._predicate.toString() : '<null>'}`;
}
}
/**
* The class of not predicate.
*
* @class NotPredicate
* @extends BasePredicate
*
* @param predicate {Object} Another predicate.
* @constructor
*/
export class NotPredicate extends BasePredicate {
constructor(predicate) {
if (!predicate) {
throw new Error('Inner predicate is required.');
}
super();
this._predicate = predicate;
}
/**
* Predicate getter.
*
* @property predicate
* @type String
* @public
*/
get predicate() {
return this._predicate;
}
/**
* Converts this instance to string.
*
* @method toString
* @return {String} Text representation of result predicate.
* @public
*/
toString() {
return `not ${this._predicate}`;
}
}
/**
* The predicate class that implements the isof function.
*
* Its constructor implements the following signatures:
* - `new IsOfPredicate(typeName)`
* - `new IsOfPredicate(expression, typeName)`
*
* Where:
* - `typeName` - type name to which the current instance will be assigned.
* - `expression` - an expression relative to the current instance that must point to an object for assigning a type.
*
* @namespace Query
* @class IsOfPredicate
* @extends BasePredicate
*
* @param ...args
* @constructor
*/
export class IsOfPredicate extends BasePredicate {
constructor(...args) {
super();
let expression;
let typeName = args[0];
if (args.length === 2) {
expression = args[0];
typeName = args[1];
}
if (!typeName) {
throw new Error('Type name is required.');
}
this._expression = expression;
this._typeName = typeName;
}
/**
* Expression getter.
*
* @property expression
* @type String
* @public
*/
get expression() {
return this._expression;
}
/**
* Type name getter.
*
* @property typeName
* @type String
* @public
*/
get typeName() {
return this._typeName;
}
/**
* Converts this instance to string.
*
* @method toString
* @return {String} Text representation of result predicate.
* @public
*/
toString() {
return `isof(${this._expression ? `${this._expression}, ` : ''}'${this._typeName}')`;
}
}
/**
* The class of true predicate.
*
* @namespace Query
* @class TruePredicate
* @extends BasePredicate
* @constructor
*/
export class TruePredicate extends BasePredicate {
constructor() {
super();
}
/**
* Converts this instance to string.
*
* @method toString
* @return {String} Text representation of the predicate.
* @public
*/
toString() {
return 'true';
}
}
/**
* The class of false predicate.
*
* @namespace Query
* @class FalsePredicate
* @extends BasePredicate
* @constructor
*/
export class FalsePredicate extends BasePredicate {
/**
* Converts this instance to string.
*
* @method toString
* @return {String} Text representation of the predicate.
* @public
*/
toString() {
return 'false';
}
}
/**
* Combines specified predicates using `and` logic condition.
*
* @for BasePredicate
* @method and
* @param {Query.BasePredicate} ...predicates List of predicates for combining.
* @return {Query.ComplexPredicate} Combined complex predicate with `and` logic.
* @public
*/
BasePredicate.prototype.and = function (...predicates) {
predicates.unshift(this);
return new ComplexPredicate(Condition.And, ...predicates);
};
/**
* Combines specified predicates using `or` logic condition.
*
* @for BasePredicate
* @method or
* @param {Query.BasePredicate} ...predicates List of predicates for combining.
* @return {Query.ComplexPredicate} Combined complex predicate with `or` logic.
* @public
*/
BasePredicate.prototype.or = function (...predicates) {
predicates.unshift(this);
return new ComplexPredicate(Condition.Or, ...predicates);
};
/**
* Throws error if specified argument is not a predicate.
*
* @param {Object} predicate Object for validate.
*/
function validatePredicate(predicate) {
if (!predicate || !(predicate instanceof BasePredicate)) {
throw new Error(`Wrong predicate ${predicate}`);
}
}
/**
* Creates predicate by various parameters.
*
* @method createPredicate
* @param args Arguments for the predicate.
* @return {BasePredicate}
*/
export function createPredicate(...args) {
if (args.length === 1) {
if (args[0] && args[0] instanceof BasePredicate) {
return args[0];
} else {
throw new Error(`Specified argument is not a predicate`);
}
}
if (args.length === 3) {
return new SimplePredicate(args[0], args[1], args[2]);
}
throw new Error(`Could not create predicate from arguments`);
}
/**
Convert string to predicate.
@method stringToPredicate
@param stringPredicate
@return {BasePredicate}
*/
export function stringToPredicate(stringPredicate) {
let predicate;
try {
predicate = eval('function fromString() { return ' + stringPredicate + '; } fromString;')();
} catch (e) { };
return predicate;
}