plywood
Version:
A query planner and executor
412 lines (411 loc) • 17.2 kB
JavaScript
import { isDate } from 'chronoshift';
import { NamedArray } from 'immutable-class';
import { NumberRange, Range, Set, TimeRange } from '../../datatypes';
import { AndExpression, ContainsExpression, Expression, IpMatchExpression, IpSearchExpression, IpStringifyExpression, IsExpression, LiteralExpression, MatchExpression, MvContainsExpression, MvOverlapExpression, NotExpression, OrExpression, OverlapExpression, r, RefExpression, } from '../../expressions';
import { DruidExpressionBuilder } from './druidExpressionBuilder';
import { DruidExtractionFnBuilder } from './druidExtractionFnBuilder';
var DruidFilterBuilder = (function () {
function DruidFilterBuilder(options) {
this.rawAttributes = options.rawAttributes;
this.timeAttribute = options.timeAttribute;
this.allowEternity = options.allowEternity;
this.customTransforms = options.customTransforms;
}
DruidFilterBuilder.prototype.filterToDruid = function (filter) {
var _this = this;
if (!filter.canHaveType('BOOLEAN'))
throw new Error("can not filter on ".concat(filter.type));
if (filter.equals(Expression.FALSE)) {
return {
intervals: [],
filter: null,
};
}
else {
var _a = filter.extractFromAnd(function (ex) {
return ((ex instanceof IsExpression || ex instanceof OverlapExpression) &&
_this.isTimeRef(ex.operand) &&
ex.expression instanceof LiteralExpression);
}), extract = _a.extract, rest = _a.rest;
return {
intervals: this.timeFilterToIntervals(extract),
filter: this.timelessFilterToFilter(rest),
};
}
};
DruidFilterBuilder.prototype.timeFilterToIntervals = function (filter) {
if (!filter.canHaveType('BOOLEAN'))
throw new Error("can not filter on ".concat(filter.type));
if (filter instanceof LiteralExpression) {
if (!filter.value)
return [];
if (!this.allowEternity)
throw new Error('must filter on time unless the allowEternity flag is set');
return DruidFilterBuilder.TRUE_INTERVAL;
}
else if (filter instanceof IsExpression) {
var lhs = filter.operand, rhs = filter.expression;
if (lhs instanceof RefExpression && rhs instanceof LiteralExpression) {
return this.valueToIntervals(rhs.value);
}
else {
throw new Error("can not convert ".concat(filter, " to Druid interval"));
}
}
else if (filter instanceof OverlapExpression) {
var lhs = filter.operand, rhs = filter.expression;
if (lhs instanceof RefExpression && rhs instanceof LiteralExpression) {
return this.valueToIntervals(rhs.value);
}
else {
throw new Error("can not convert ".concat(filter, " to Druid intervals"));
}
}
else {
throw new Error("can not convert ".concat(filter, " to Druid intervals"));
}
};
DruidFilterBuilder.prototype.timelessFilterToFilter = function (filter) {
var _this = this;
if (!filter.canHaveType('BOOLEAN'))
throw new Error("can not filter on ".concat(filter.type));
if (filter instanceof RefExpression) {
filter = filter.is(true);
}
if (filter instanceof LiteralExpression) {
if (filter.value === true) {
return null;
}
else {
return { type: 'false' };
}
}
else if (filter instanceof NotExpression) {
return {
type: 'not',
field: this.timelessFilterToFilter(filter.operand),
};
}
else if (filter instanceof AndExpression) {
return {
type: 'and',
fields: filter.getExpressionList().map(function (p) { return _this.timelessFilterToFilter(p); }),
};
}
else if (filter instanceof OrExpression) {
return {
type: 'or',
fields: filter.getExpressionList().map(function (p) { return _this.timelessFilterToFilter(p); }),
};
}
else if (filter instanceof IsExpression) {
var lhs = filter.operand, rhs = filter.expression;
if (rhs instanceof LiteralExpression) {
if (Set.isSetType(rhs.type)) {
return this.makeInFilter(lhs, rhs.value);
}
else {
return this.makeSelectorFilter(lhs, rhs.value);
}
}
else {
throw new Error("can not convert ".concat(filter, " to Druid filter"));
}
}
else if (filter instanceof OverlapExpression) {
var lhs_1 = filter.operand, rhs = filter.expression;
if (rhs instanceof LiteralExpression) {
var rhsType = rhs.type;
if (rhsType === 'SET/STRING' || rhsType === 'SET/NUMBER' || rhsType === 'SET/NULL') {
return this.makeInFilter(lhs_1, rhs.value);
}
else if (Set.unwrapSetType(rhsType) === 'TIME_RANGE' && this.isTimeRef(lhs_1)) {
return this.makeIntervalFilter(lhs_1, rhs.value);
}
else if (rhsType === 'NUMBER_RANGE' ||
rhsType === 'TIME_RANGE' ||
rhsType === 'STRING_RANGE') {
return this.makeBoundFilter(lhs_1, rhs.value);
}
else if (rhsType === 'SET/NUMBER_RANGE' ||
rhsType === 'SET/TIME_RANGE' ||
rhsType === 'SET/STRING_RANGE') {
return {
type: 'or',
fields: rhs.value.elements.map(function (range) {
return _this.makeBoundFilter(lhs_1, range);
}),
};
}
else {
throw new Error("not supported OVERLAP rhs type ".concat(rhsType));
}
}
else {
throw new Error("can not convert ".concat(filter, " to Druid filter"));
}
}
else if (filter instanceof MatchExpression) {
return this.makeRegexFilter(filter.operand, filter.regexp);
}
else if (filter instanceof ContainsExpression) {
var lhs = filter.operand, rhs = filter.expression, compare = filter.compare;
return this.makeContainsFilter(lhs, rhs, compare);
}
else if (filter instanceof MvContainsExpression) {
var operand_1 = filter.operand, mvArray = filter.mvArray;
return {
type: 'and',
fields: mvArray.map(function (elem) { return _this.makeSelectorFilter(operand_1, elem); }),
};
}
else if (filter instanceof MvOverlapExpression) {
return this.makeInFilter(filter.operand, Set.fromJS(filter.mvArray));
}
else if (filter instanceof IpMatchExpression) {
return this.makeExpressionFilter(filter.operand.ipMatch(filter.ipToSearch.toString(), filter.ipSearchType));
}
else if (filter instanceof IpSearchExpression) {
return this.makeExpressionFilter(filter.operand.ipSearch(filter.ipToSearch.toString(), filter.ipSearchType));
}
else if (filter instanceof IpStringifyExpression) {
return this.makeExpressionFilter(filter.operand.ipStringify());
}
throw new Error("could not convert filter ".concat(filter, " to Druid filter"));
};
DruidFilterBuilder.prototype.valueToIntervals = function (value) {
if (isDate(value)) {
return TimeRange.intervalFromDate(value);
}
else if (value instanceof TimeRange) {
return value.toInterval();
}
else if (value instanceof Set) {
return value.elements.map(function (v) {
if (isDate(v)) {
return TimeRange.intervalFromDate(v);
}
else if (v instanceof TimeRange) {
return v.toInterval();
}
else {
throw new Error("can not convert set value ".concat(JSON.stringify(v), " to Druid interval"));
}
});
}
else {
throw new Error("can not convert ".concat(JSON.stringify(value), " to Druid intervals"));
}
};
DruidFilterBuilder.prototype.makeSelectorFilter = function (ex, value) {
var attributeInfo = this.getSingleReferenceAttributeInfo(ex);
if (!attributeInfo) {
return this.makeExpressionFilter(ex.is(r(value)));
}
if (attributeInfo.unsplitable) {
throw new Error("can not convert ".concat(ex, " = ").concat(value, " to filter because it references an un-filterable metric '").concat(attributeInfo.name, "' which is most likely rolled up."));
}
var extractionFn;
try {
extractionFn = new DruidExtractionFnBuilder(this).expressionToExtractionFn(ex);
}
catch (_a) {
return this.makeExpressionFilter(ex.is(r(value)));
}
if (value instanceof Range)
value = value.start;
var druidFilter = {
type: 'selector',
dimension: this.getDimensionNameForAttributeInfo(attributeInfo),
value: value,
};
if (extractionFn)
druidFilter.extractionFn = extractionFn;
return druidFilter;
};
DruidFilterBuilder.prototype.makeInFilter = function (ex, valueSet) {
var _this = this;
var elements = valueSet.elements;
var attributeInfo = this.getSingleReferenceAttributeInfo(ex);
if (!attributeInfo) {
var fields = elements.map(function (value) {
return _this.makeSelectorFilter(ex, value);
});
return { type: 'or', fields: fields };
}
var extractionFn;
try {
extractionFn = new DruidExtractionFnBuilder(this).expressionToExtractionFn(ex);
}
catch (_a) {
return this.makeExpressionFilter(ex.is(r(valueSet)));
}
var inFilter = {
type: 'in',
dimension: this.getDimensionNameForAttributeInfo(attributeInfo),
values: elements,
};
if (extractionFn)
inFilter.extractionFn = extractionFn;
return inFilter;
};
DruidFilterBuilder.prototype.makeBoundFilter = function (ex, range) {
var r0 = range.start;
var r1 = range.end;
var bounds = range.bounds;
var attributeInfo = this.getSingleReferenceAttributeInfo(ex);
if (!attributeInfo) {
return this.makeExpressionFilter(ex.overlap(range));
}
var extractionFn;
try {
extractionFn = new DruidExtractionFnBuilder(this).expressionToExtractionFn(ex);
}
catch (_a) {
return this.makeExpressionFilter(ex.overlap(range));
}
var boundFilter = {
type: 'bound',
dimension: this.getDimensionNameForAttributeInfo(attributeInfo),
};
if (extractionFn)
boundFilter.extractionFn = extractionFn;
if (range instanceof NumberRange || attributeInfo.nativeType === 'LONG') {
boundFilter.ordering = 'numeric';
}
function dataToBound(d) {
if (attributeInfo.nativeType === 'LONG') {
return d.valueOf();
}
else {
return d.toISOString();
}
}
if (r0 != null) {
boundFilter.lower = isDate(r0) ? dataToBound(r0) : r0;
if (bounds[0] === '(')
boundFilter.lowerStrict = true;
}
if (r1 != null) {
boundFilter.upper = isDate(r1) ? dataToBound(r1) : r1;
if (bounds[1] === ')')
boundFilter.upperStrict = true;
}
return boundFilter;
};
DruidFilterBuilder.prototype.makeIntervalFilter = function (ex, range) {
var attributeInfo = this.getSingleReferenceAttributeInfo(ex);
if (!attributeInfo) {
return this.makeExpressionFilter(ex.overlap(range));
}
var extractionFn;
try {
extractionFn = new DruidExtractionFnBuilder(this).expressionToExtractionFn(ex);
}
catch (_a) {
return this.makeExpressionFilter(ex.overlap(range));
}
var interval = this.valueToIntervals(range);
var intervalFilter = {
type: 'interval',
dimension: this.getDimensionNameForAttributeInfo(attributeInfo),
intervals: Array.isArray(interval) ? interval : [interval],
};
if (extractionFn)
intervalFilter.extractionFn = extractionFn;
return intervalFilter;
};
DruidFilterBuilder.prototype.makeRegexFilter = function (ex, regex) {
var attributeInfo = this.getSingleReferenceAttributeInfo(ex);
if (!attributeInfo) {
return this.makeExpressionFilter(ex.match(regex));
}
var extractionFn;
try {
extractionFn = new DruidExtractionFnBuilder(this).expressionToExtractionFn(ex);
}
catch (_a) {
return this.makeExpressionFilter(ex.match(regex));
}
var regexFilter = {
type: 'regex',
dimension: this.getDimensionNameForAttributeInfo(attributeInfo),
pattern: regex,
};
if (extractionFn)
regexFilter.extractionFn = extractionFn;
return regexFilter;
};
DruidFilterBuilder.prototype.makeContainsFilter = function (lhs, rhs, compare) {
if (rhs instanceof LiteralExpression) {
var attributeInfo = this.getSingleReferenceAttributeInfo(lhs);
if (!attributeInfo) {
return this.makeExpressionFilter(lhs.contains(rhs, compare));
}
if (lhs instanceof RefExpression && attributeInfo.termsDelegate) {
return {
type: 'fullText',
textColumn: this.getDimensionNameForAttributeInfo(attributeInfo),
termsColumn: attributeInfo.termsDelegate,
query: rhs.value,
matchAll: true,
usePrefixForLastTerm: true,
};
}
var extractionFn = void 0;
try {
extractionFn = new DruidExtractionFnBuilder(this).expressionToExtractionFn(lhs);
}
catch (_a) {
return this.makeExpressionFilter(lhs.contains(rhs, compare));
}
var searchFilter = {
type: 'search',
dimension: this.getDimensionNameForAttributeInfo(attributeInfo),
query: {
type: 'contains',
value: rhs.value,
caseSensitive: compare === ContainsExpression.NORMAL,
},
};
if (extractionFn)
searchFilter.extractionFn = extractionFn;
return searchFilter;
}
else {
return this.makeExpressionFilter(lhs.contains(rhs, compare));
}
};
DruidFilterBuilder.prototype.makeExpressionFilter = function (filter) {
var druidExpression = new DruidExpressionBuilder(this).expressionToDruidExpression(filter);
if (druidExpression === null) {
throw new Error("could not convert ".concat(filter, " to Druid expression for filter"));
}
return {
type: 'expression',
expression: druidExpression,
};
};
DruidFilterBuilder.prototype.getSingleReferenceAttributeInfo = function (ex) {
var freeReferences = ex.getFreeReferences();
if (freeReferences.length !== 1)
return null;
var referenceName = freeReferences[0];
return this.getAttributesInfo(referenceName);
};
DruidFilterBuilder.prototype.getDimensionNameForAttributeInfo = function (attributeInfo) {
return attributeInfo.name === this.timeAttribute
? DruidFilterBuilder.TIME_ATTRIBUTE
: attributeInfo.name;
};
DruidFilterBuilder.prototype.getAttributesInfo = function (attributeName) {
return NamedArray.get(this.rawAttributes, attributeName);
};
DruidFilterBuilder.prototype.isTimeRef = function (ex) {
return ex instanceof RefExpression && ex.name === this.timeAttribute;
};
DruidFilterBuilder.TIME_ATTRIBUTE = '__time';
DruidFilterBuilder.TRUE_INTERVAL = '1000/3000';
return DruidFilterBuilder;
}());
export { DruidFilterBuilder };