dsl-builder-test
Version:
OpenSearch Query Builder - Extract from OpenSearch Dashboards
122 lines (121 loc) • 5.24 kB
JavaScript
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Any modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { map, reduce, mapValues, get, keys, pickBy } from 'lodash';
const OPERANDS_IN_RANGE = 2;
const operators = {
gt: '>',
gte: '>=',
lte: '<=',
lt: '<',
};
const comparators = {
gt: 'boolean gt(Supplier s, def v) {return s.get() > v}',
gte: 'boolean gte(Supplier s, def v) {return s.get() >= v}',
lte: 'boolean lte(Supplier s, def v) {return s.get() <= v}',
lt: 'boolean lt(Supplier s, def v) {return s.get() < v}',
};
const dateComparators = {
gt: 'boolean gt(Supplier s, def v) {return s.get().toInstant().isAfter(Instant.parse(v))}',
gte: 'boolean gte(Supplier s, def v) {return !s.get().toInstant().isBefore(Instant.parse(v))}',
lte: 'boolean lte(Supplier s, def v) {return !s.get().toInstant().isAfter(Instant.parse(v))}',
lt: 'boolean lt(Supplier s, def v) {return s.get().toInstant().isBefore(Instant.parse(v))}',
};
const hasRangeKeys = (params) => Boolean(keys(params).find((key) => ['gte', 'gt', 'lte', 'lt', 'from', 'to'].includes(key)));
export const isRangeFilter = (filter) => filter && filter.range;
export const isScriptedRangeFilter = (filter) => {
const params = get(filter, 'script.script.params', {});
return hasRangeKeys(params);
};
export const getRangeFilterField = (filter) => {
return filter.range && Object.keys(filter.range)[0];
};
const formatValue = (field, params) => map(params, (val, key) => get(operators, key) + format(field, val)).join(' ');
const format = (field, value) => field && field.format && field.format.convert
? field.format.convert(value)
: value;
// Creates a filter where the value for the given field is in the given range
// params should be an object containing `lt`, `lte`, `gt`, and/or `gte`
export const buildRangeFilter = (field, params, indexPattern, formattedValue) => {
const filter = { meta: { index: indexPattern.id, params: {} } };
if (formattedValue) {
filter.meta.formattedValue = formattedValue;
}
params = mapValues(params, (value) => field.type === 'number' && typeof value !== 'bigint'
? isFinite(value) &&
(value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER)
? BigInt(value)
: parseFloat(value)
: value);
if ('gte' in params && 'gt' in params)
throw new Error('gte and gt are mutually exclusive');
if ('lte' in params && 'lt' in params)
throw new Error('lte and lt are mutually exclusive');
const totalInfinite = ['gt', 'lt'].reduce((acc, op) => {
const key = op in params ? op : `${op}e`;
const isInfinite = typeof params[key] !== 'bigint' &&
Math.abs(get(params, key)) === Infinity;
if (isInfinite) {
acc++;
delete params[key];
}
return acc;
}, 0);
if (totalInfinite === OPERANDS_IN_RANGE) {
filter.match_all = {};
filter.meta.field = field.name;
}
else if (field.scripted) {
filter.script = getRangeScript(field, params);
filter.script.script.params.value = formatValue(field, filter.script.script.params);
filter.meta.field = field.name;
}
else {
filter.range = {};
filter.range[field.name] = params;
}
return filter;
};
export const getRangeScript = (field, params) => {
const knownParams = pickBy(params, (val, key) => key in operators);
let script = map(knownParams, (val, key) => '(' + field.script + ')' + get(operators, key) + key).join(' && ');
// We must wrap painless scripts in a lambda in case they're more than a simple expression
if (field.lang === 'painless') {
const comp = field.type === 'date' ? dateComparators : comparators;
const currentComparators = reduce(knownParams, (acc, val, key) => acc.concat(get(comp, key)), []).join(' ');
const comparisons = map(knownParams, (val, key) => `${key}(() -> { ${field.script} }, params.${key})`).join(' && ');
script = `${currentComparators}${comparisons}`;
}
return {
script: {
source: script,
params: knownParams,
lang: field.lang,
},
};
};