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

CQL: Advanced Comparison operarors #339

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
52 changes: 50 additions & 2 deletions src/components/QueryableInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,38 @@
<b-badge variant="dark" class="ml-2">{{ op.label }}</b-badge>
</b-dropdown-item-button>
</b-dropdown>

<multiselect
v-if="operator.SYMBOL === 'in'"
multiple taggable id="in-op"
:value="value.value" :options="queryable.isSelection ? queryableOptions : value.value"
@input="updateValue" @tag="addInElement"
>
<template #noOptions>{{ $t('search.noOptions') }}</template>
</multiselect>

<div v-else-if="operator.SYMBOL === 'between'" class="value">
<b-form-input
:number="queryable.isNumeric"
:type="queryable.isNumeric ? 'number' : 'text'"
size="sm"
:value="value.value[0]"
@input="updateBetweenValue(0, $event)"
v-bind="validation"
/>
<span class="sep">-</span>
<b-form-input
:number="queryable.isNumeric"
:type="queryable.isNumeric ? 'number' : 'text'"
size="sm"
:value="value.value[1]"
@input="updateBetweenValue(1, $event)"
v-bind="validation"
/>
</div>

<date-picker
v-if="queryable.isTemporal"
v-else-if="queryable.isTemporal"
type="date"
class="value"
:lang="datepickerLang"
Expand Down Expand Up @@ -76,6 +105,7 @@ import { BBadge, BDropdown, BDropdownItemButton, BFormCheckbox, BFormInput, BFor
import DatePickerMixin from './DatePickerMixin';
import Utils from '../utils';
import CqlValue from '../models/cql2/value';
import { CqlBetween, CqlIn } from '../models/cql2/operators/advanced';

export default {
name: 'QueryableInput',
Expand All @@ -87,7 +117,8 @@ export default {
BFormInput,
BFormSelect,
BIconXCircleFill,
Description: () => import('./Description.vue')
Description: () => import('./Description.vue'),
Multiselect: () => import('vue-multiselect')
},
mixins: [
DatePickerMixin
Expand Down Expand Up @@ -177,7 +208,24 @@ export default {
this.$emit('update:value', CqlValue.create(val));
},
updateOperator(op) {
if (op === CqlIn) {
this.updateValue([]);
}
else if (op === CqlBetween) {
this.updateValue([this.queryable.defaultValue, this.queryable.defaultValue]);
}
this.$emit('update:operator', op);
},
addInElement(element) {
this.updateValue(this.value.value.concat([element]));
},
setInElements(elements) {
this.updateValue(elements);
},
updateBetweenValue(ix, value) {
let values = this.value.value.slice(0);
values[ix] = value;
this.updateValue(values);
}
}
};
Expand Down
4 changes: 4 additions & 0 deletions src/locales/en/texts.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@
"addFilter": "Add filter",
"addItemIds": "Press enter to add an Item ID",
"additionalFilters": "Additional filters",
"between": "between",
"betweenOperatorDescription": "Tests whether a numeric field value lies within the given range. The given range is inclusive.",
"buttons": {
"filter": "Filter",
"reset": "Reset"
Expand All @@ -200,6 +202,8 @@
"freeTextDescription": "Search for the given text in fields such as the title and the description.",
"greaterThan": "greater than",
"greaterThanEqual": "greater than or equal to",
"in": "included in",
"inOperatorDescription": "Checks whether the field value is any of the given values.",
"itemIds": "Item IDs",
"itemsPerPage": "Items per page",
"itemsPerPageDescription": "Number of items requested per page, max. {maxItems} items.",
Expand Down
54 changes: 54 additions & 0 deletions src/models/cql2/operators/advanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,57 @@ export class CqlLike extends CqlComparisonOperator {
}

}

export class CqlBetween extends CqlComparisonOperator {

static SYMBOL = "between";

constructor(pred = null, obj = []) {
super(CqlBetween.SYMBOL, pred, obj);
}

static get label() {
return "≥ … ≤";
}

static get longLabel() {
return i18n.t('search.between');
}

static get description() {
return i18n.t('search.betweenOperatorDescription');
}

toText() {
return `${this.args[0].toText()} BETWEEN ${this.args[1].toText()} and ${this.args[2].toText()}`;
}

}

// Currently only implemented for strings and numbers, not for booleans and time instances
export class CqlIn extends CqlComparisonOperator {

static SYMBOL = "in";

constructor(pred = null, obj = []) {
super(CqlIn.SYMBOL, pred, obj);
}

static get label() {
return "∈";
}

static get longLabel() {
return i18n.t('search.in');
}

static get description() {
return i18n.t('search.inOperatorDescription');
}

toText() {
let values = this.args.slice(1).map(arg => arg.toText()).join(`,`);
return `${this.args[0].toText()} IN (${values})`;
}

}
9 changes: 7 additions & 2 deletions src/models/cql2/queryable.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { formatKey } from "@radiantearth/stac-fields/helper";
import i18n from '../../i18n.js';
import { CqlEqual, CqlGreaterThan, CqlGreaterThanEqual, CqlLessThan, CqlLessThanEqual, CqlNotEqual } from "./operators/comparison";
import { CqlLike } from "./operators/advanced";
import { CqlBetween, CqlIn, CqlLike } from "./operators/advanced";

export default class Queryable {

Expand Down Expand Up @@ -103,8 +103,13 @@ export default class Queryable {
ops.push(CqlGreaterThan);
ops.push(CqlGreaterThanEqual);
}
else if (this.isText && cql.advancedComparison) {
if (this.isNumeric && cql.advancedComparison) {
ops.push(CqlBetween);
ops.push(CqlIn);
}
if (this.isText && cql.advancedComparison) {
ops.push(CqlLike);
ops.push(CqlIn);
}
return ops;
}
Expand Down
24 changes: 23 additions & 1 deletion src/models/cql2/value.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export default class CqlValue {
}

static create(value) {
if (value instanceof Date) {
if (Array.isArray(value)) {
return new CqlArray(value);
}
else if (value instanceof Date) {
return new CqlTimestamp(value);
}
else if (typeof value === 'string') {
Expand Down Expand Up @@ -63,3 +66,22 @@ export class CqlString extends CqlValue {
}

}

export class CqlArray extends CqlValue {

constructor(value) {
super(value);
}

toJSON() {
return this.value;
}

toText() {
return this.value.map(elem => {
let cql = elem instanceof CqlValue ? elem : CqlValue.create(elem);
return cql.toText();
});
}

}