Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CustomPredicate #163

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Change Log

## [Unreleased]
### Added
- `CustomPredicate` for implementing custom predicates.

## [2.4.0-beta.4] - 2020-04-22
### Fixed
Expand Down
22 changes: 21 additions & 1 deletion addon/query/indexeddb-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@

import Ember from 'ember';
import FilterOperator from './filter-operator';
import { SimplePredicate, ComplexPredicate, StringPredicate, DetailPredicate, DatePredicate, GeographyPredicate, GeometryPredicate } from './predicate';
import {
SimplePredicate,
ComplexPredicate,
StringPredicate,
DetailPredicate,
DatePredicate,
GeographyPredicate,
GeometryPredicate,
CustomPredicate,
} from './predicate';
import BaseAdapter from './base-adapter';
import JSAdapter from 'ember-flexberry-data/query/js-adapter';
import Information from '../utils/information';
Expand Down Expand Up @@ -682,6 +691,17 @@ function updateWhereClause(store, table, query) {
return table.filter(jsAdapter.getAttributeFilterFunction(predicate, { booleanAsString: true }));
}

if (predicate instanceof CustomPredicate) {
const indexeddbConverter = predicate.indexeddbConverter;
if (indexeddbConverter instanceof Function) {
const jsAdapter = new JSAdapter(Ember.getOwner(store).lookup('service:moment'));
return indexeddbConverter(jsAdapter, predicate, table);
}

Ember.warn('CustomPredicate doesn\'t have a converter for indexeddb adapter');
return table;
}

throw new Error(`Unsupported predicate '${predicate}'`);
}

Expand Down
26 changes: 24 additions & 2 deletions addon/query/js-adapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import Ember from 'ember';
import BaseAdapter from './base-adapter';
import { SimplePredicate, ComplexPredicate, StringPredicate, DetailPredicate, DatePredicate, GeographyPredicate, GeometryPredicate } from './predicate';
import {
SimplePredicate,
ComplexPredicate,
StringPredicate,
DetailPredicate,
DatePredicate,
GeographyPredicate,
GeometryPredicate,
CustomPredicate,
} from './predicate';
import FilterOperator from './filter-operator';
import Condition from './condition';
import Information from '../utils/information';
Expand Down Expand Up @@ -245,7 +254,8 @@ export default class JSAdapter extends BaseAdapter {
let b4 = predicate instanceof DatePredicate;
let b5 = predicate instanceof GeographyPredicate;
let b6 = predicate instanceof GeometryPredicate;
if (b1 || b2 || b3 || b4) {
let b7 = predicate instanceof CustomPredicate;
if (b1 || b2 || b3 || b4 || b7) {
let filterFunction = this.getAttributeFilterFunction(predicate, options);
return this.getFilterFunctionAnd([filterFunction]);
}
Expand Down Expand Up @@ -419,6 +429,18 @@ export default class JSAdapter extends BaseAdapter {
}
}

if (predicate instanceof CustomPredicate) {
const jsConverter = predicate.jsConverter;
if (jsConverter instanceof Function) {
return jsConverter(this, predicate, options);
}

Ember.warn('CustomPredicate doesn\'t have a converter for js adapter');
return function (data) {
return data;
};
}

throw new Error(`Unsupported predicate '${predicate}'.`);
}

Expand Down
197 changes: 108 additions & 89 deletions addon/query/odata-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
GeometryPredicate,
NotPredicate,
IsOfPredicate,
CustomPredicate,
} from './predicate';
import FilterOperator from './filter-operator';
import Information from '../utils/information';
Expand Down Expand Up @@ -137,6 +138,111 @@ export default class ODataAdapter extends BaseAdapter {
return `${this._baseUrl}${queryMark}${queryPart}`;
}

/**
Returns OData attribute name.

@method getODataAttributeName
@param {String} modelName Model name.
@param {String} attributePath Attribute path.
@param {String} prefix Prefix for detail attributes.
@param {Boolean} noIdConversion .
@return {String} OData attribute name.
*/
getODataAttributeName(modelName, attributePath, prefix, noIdConversion) {
let attributeName = this._getODataAttributeName(modelName, attributePath, noIdConversion);
if (prefix) {
attributeName = `${prefix}/${attributeName}`;
}

return attributeName;
}

/**
Converts specified predicate into OData filter part.

@param {BasePredicate} predicate Predicate to convert.
@param {String} modelName Model name.
@param {String} prefix Prefix for detail attributes.
@param {Number} level Nesting level for recursion with comples predicates.
@return {String} OData filter part.
*/
convertPredicateToODataFilterClause(predicate, modelName, prefix, level) {
if (predicate instanceof SimplePredicate || predicate instanceof DatePredicate) {
return this._buildODataSimplePredicate(predicate, modelName, prefix);
}

if (predicate instanceof StringPredicate) {
let attribute = this.getODataAttributeName(modelName, predicate.attributePath, prefix);

return `contains(${attribute},'${String(predicate.containsValue).replace(/'/g, `''`)}')`;
}

if (predicate instanceof NotPredicate) {
return `not(${this.convertPredicateToODataFilterClause(predicate.predicate, modelName, prefix, level)})`;
}

if (predicate instanceof IsOfPredicate) {
let type = this._store.modelFor(predicate.typeName);
let typeName = Ember.get(type, 'modelName');
let namespace = Ember.get(type, 'namespace');
let expression = predicate.expression ? this._getODataAttributeName(modelName, predicate.expression, true) : '$it';
let className = classify(typeName).replace(namespace.split('.').join(''), '');

return `isof(${expression},'${namespace}.${className}')`;
}

if (predicate instanceof GeographyPredicate) {
let attribute = this.getODataAttributeName(modelName, predicate.attributePath, prefix);

return `geo.intersects(geography1=${attribute},geography2=geography'${predicate.intersectsValue}')`;
}

if (predicate instanceof GeometryPredicate) {
let attribute = this.getODataAttributeName(modelName, predicate.attributePath, prefix);

return `geom.intersects(geometry1=${attribute},geometry2=geometry'${predicate.intersectsValue}')`;
}

if (predicate instanceof DetailPredicate) {
let func = '';
if (predicate.isAll) {
func = 'all';
} else if (predicate.isAny) {
func = 'any';
} else {
throw new Error(`OData supports only 'any' or 'or' operations for details`);
}

let additionalPrefix = 'f';
let meta = this._info.getMeta(modelName, predicate.detailPath);
let detailPredicate = this.convertPredicateToODataFilterClause(predicate.predicate, meta.type, prefix + additionalPrefix, level);
let detailPath = this._getODataAttributeName(modelName, predicate.detailPath);

return `${detailPath}/${func}(${additionalPrefix}:${detailPredicate})`;
}

if (predicate instanceof ComplexPredicate) {
let separator = ` ${predicate.condition} `;
let result = predicate.predicates
.map(i => this.convertPredicateToODataFilterClause(i, modelName, prefix, level + 1)).join(separator);
let lp = level > 0 ? '(' : '';
let rp = level > 0 ? ')' : '';
return lp + result + rp;
}

if (predicate instanceof CustomPredicate) {
const odataConverter = predicate.odataConverter;
if (odataConverter instanceof Function) {
return odataConverter(this, predicate, modelName, prefix, level);
}

Ember.warn('CustomPredicate doesn\'t have a converter for odata adapter');
return '';
}

throw new Error(`Unknown predicate '${predicate}'`);
}

_buildODataSelect(query) {
return query.select.map((i) => this._getODataAttributeName(query.modelName, i, true)).join(',');
}
Expand Down Expand Up @@ -210,7 +316,7 @@ export default class ODataAdapter extends BaseAdapter {
return null;
}

return this._convertPredicateToODataFilterClause(predicate, query.modelName, '', 0);
return this.convertPredicateToODataFilterClause(predicate, query.modelName, '', 0);
}

_buildODataOrderBy(query) {
Expand All @@ -230,90 +336,6 @@ export default class ODataAdapter extends BaseAdapter {
return result;
}

/**
* Converts specified predicate into OData filter part.
*
* @param predicate {BasePredicate} Predicate to convert.
* @param {String} prefix Prefix for detail attributes.
* @param {Number} level Nesting level for recursion with comples predicates.
* @return {String} OData filter part.
*/
_convertPredicateToODataFilterClause(predicate, modelName, prefix, level) {
if (predicate instanceof SimplePredicate || predicate instanceof DatePredicate) {
return this._buildODataSimplePredicate(predicate, modelName, prefix);
}

if (predicate instanceof StringPredicate) {
let attribute = this._getODataAttributeName(modelName, predicate.attributePath);
if (prefix) {
attribute = `${prefix}/${attribute}`;
}

return `contains(${attribute},'${String(predicate.containsValue).replace(/'/g, `''`)}')`;
}

if (predicate instanceof NotPredicate) {
return `not(${this._convertPredicateToODataFilterClause(predicate.predicate, modelName, prefix, level)})`;
}

if (predicate instanceof IsOfPredicate) {
let type = this._store.modelFor(predicate.typeName);
let typeName = Ember.get(type, 'modelName');
let namespace = Ember.get(type, 'namespace');
let expression = predicate.expression ? this._getODataAttributeName(modelName, predicate.expression, true) : '$it';
let className = classify(typeName).replace(namespace.split('.').join(''), '');

return `isof(${expression},'${namespace}.${className}')`;
}

if (predicate instanceof GeographyPredicate) {
let attribute = this._getODataAttributeName(modelName, predicate.attributePath);
if (prefix) {
attribute = `${prefix}/${attribute}`;
}

return `geo.intersects(geography1=${attribute},geography2=geography'${predicate.intersectsValue}')`;
}

if (predicate instanceof GeometryPredicate) {
let attribute = this._getODataAttributeName(modelName, predicate.attributePath);
if (prefix) {
attribute = `${prefix}/${attribute}`;
}

return `geom.intersects(geometry1=${attribute},geometry2=geometry'${predicate.intersectsValue}')`;
}

if (predicate instanceof DetailPredicate) {
let func = '';
if (predicate.isAll) {
func = 'all';
} else if (predicate.isAny) {
func = 'any';
} else {
throw new Error(`OData supports only 'any' or 'or' operations for details`);
}

let additionalPrefix = 'f';
let meta = this._info.getMeta(modelName, predicate.detailPath);
let detailPredicate = this._convertPredicateToODataFilterClause(predicate.predicate, meta.type, prefix + additionalPrefix, level);
let detailPath = this._getODataAttributeName(modelName, predicate.detailPath);

return `${detailPath}/${func}(${additionalPrefix}:${detailPredicate})`;
}

if (predicate instanceof ComplexPredicate) {
let separator = ` ${predicate.condition} `;
let result = predicate.predicates
.map(i => this._convertPredicateToODataFilterClause(i, modelName, prefix, level + 1)).join(separator);
let lp = level > 0 ? '(' : '';
let rp = level > 0 ? ')' : '';
return lp + result + rp;
}

throw new Error(`Unknown predicate '${predicate}'`);
}

/**
* Converts filter operator to OData representation.
*
Expand Down Expand Up @@ -377,10 +399,7 @@ export default class ODataAdapter extends BaseAdapter {
}

_buildODataSimplePredicate(predicate, modelName, prefix) {
let attribute = this._getODataAttributeName(modelName, predicate.attributePath);
if (prefix) {
attribute = `${prefix}/${attribute}`;
}
let attribute = this.getODataAttributeName(modelName, predicate.attributePath, prefix);

if (predicate.timeless) {
attribute = `date(${attribute})`;
Expand Down
Loading