plywood
Version:
A query planner and executor
336 lines (335 loc) • 17.5 kB
JavaScript
import { NamedArray } from 'immutable-class';
import { Set } from '../../datatypes';
import { AbsoluteExpression, AddExpression, AndExpression, CastExpression, ChainableExpression, ChainableUnaryExpression, ConcatExpression, ContainsExpression, DivideExpression, ExtractExpression, FallbackExpression, IndexOfExpression, IpMatchExpression, IpSearchExpression, IpStringifyExpression, IsExpression, LengthExpression, LiteralExpression, LogExpression, LookupExpression, MatchExpression, MultiplyExpression, MvContainsExpression, MvOverlapExpression, NotExpression, NumberBucketExpression, OrExpression, OverlapExpression, PowerExpression, RefExpression, SubstrExpression, SubtractExpression, ThenExpression, TimeBucketExpression, TimeFloorExpression, TimePartExpression, TimeShiftExpression, TransformCaseExpression, } from '../../expressions';
import { continuousFloorExpression } from '../../helper';
var DruidExpressionBuilder = (function () {
function DruidExpressionBuilder(options) {
this.rawAttributes = options.rawAttributes;
this.timeAttribute = options.timeAttribute;
}
DruidExpressionBuilder.escape = function (str) {
return str.replace(DruidExpressionBuilder.UNSAFE_CHAR, function (s) {
return '\\u' + ('000' + s.charCodeAt(0).toString(16)).substr(-4);
});
};
DruidExpressionBuilder.escapeVariable = function (name) {
return "\"".concat(DruidExpressionBuilder.escape(name), "\"");
};
DruidExpressionBuilder.escapeLiteral = function (x) {
if (x == null)
return 'null';
if (x.toISOString) {
return String(x.valueOf());
}
else if (typeof x === 'number') {
return String(x);
}
else {
return "'".concat(DruidExpressionBuilder.escape(String(x)), "'");
}
};
DruidExpressionBuilder.escapeLike = function (str) {
return str.replace(/([%_~])/g, '~$1');
};
DruidExpressionBuilder.expressionTypeToOutputType = function (type) {
switch (type) {
case 'TIME':
case 'TIME_RANGE':
return 'LONG';
case 'NUMBER':
case 'NUMBER_RANGE':
return 'DOUBLE';
default:
return 'STRING';
}
};
DruidExpressionBuilder.prototype.expressionToDruidExpression = function (expression) {
var _this = this;
if (expression instanceof LiteralExpression) {
var literalValue = expression.getLiteralValue();
if (literalValue === null) {
return "null";
}
else {
switch (typeof literalValue) {
case 'string':
return DruidExpressionBuilder.escapeLiteral(literalValue);
case 'number':
return String(literalValue);
case 'boolean':
return String(Number(literalValue));
case 'object':
if (literalValue instanceof Date) {
return DruidExpressionBuilder.escapeLiteral(literalValue);
}
else
return "no_such_type";
default:
return "no_such_type";
}
}
}
else if (expression instanceof RefExpression) {
if (expression.name === this.timeAttribute) {
return '__time';
}
else {
var exStr = DruidExpressionBuilder.escapeVariable(expression.name);
var info = this.getAttributesInfo(expression.name);
if (info) {
if (info.nativeType === 'STRING') {
if (info.type === 'TIME') {
exStr = this.castToType(exStr, info.nativeType, info.type);
}
}
}
return exStr;
}
}
else if (expression instanceof ChainableExpression) {
var myOperand = expression.operand;
var ex1_1 = this.expressionToDruidExpression(myOperand);
if (expression instanceof CastExpression) {
return this.castToType(ex1_1, expression.operand.type, expression.outputType);
}
else if (expression instanceof SubstrExpression) {
return "substring(".concat(ex1_1, ",").concat(expression.position, ",").concat(expression.len, ")");
}
else if (expression instanceof ExtractExpression) {
return "regexp_extract(".concat(ex1_1, ",").concat(DruidExpressionBuilder.escapeLiteral(expression.regexp), ",1)");
}
else if (expression instanceof MatchExpression) {
return "(regexp_extract(".concat(ex1_1, ",").concat(DruidExpressionBuilder.escapeLiteral(expression.regexp), ")!='')");
}
else if (expression instanceof ContainsExpression) {
var needle = expression.expression;
if (needle instanceof LiteralExpression) {
var needleValue = DruidExpressionBuilder.escape(DruidExpressionBuilder.escapeLike(needle.value));
if (expression.compare === ContainsExpression.IGNORE_CASE) {
return "like(lower(".concat(ex1_1, "),'%").concat(needleValue.toLowerCase(), "%','~')");
}
else {
return "like(".concat(ex1_1, ",'%").concat(needleValue, "%','~')");
}
}
else {
throw new Error("can not plan ".concat(expression, " into Druid"));
}
}
else if (expression instanceof LengthExpression) {
return "strlen(".concat(ex1_1, ")");
}
else if (expression instanceof NotExpression) {
return "!".concat(ex1_1);
}
else if (expression instanceof AbsoluteExpression) {
return "abs(".concat(ex1_1, ")");
}
else if (expression instanceof NumberBucketExpression) {
return continuousFloorExpression(ex1_1, 'floor', expression.size, expression.offset);
}
else if (expression instanceof TimePartExpression) {
var format = DruidExpressionBuilder.TIME_PART_TO_FORMAT[expression.part];
if (!format)
throw new Error("can not convert ".concat(expression.part, " to Druid expression format"));
return "timestamp_extract(".concat(ex1_1, ",'").concat(format, "',").concat(DruidExpressionBuilder.escapeLiteral(expression.timezone.toString()), ")");
}
else if (expression instanceof TimeFloorExpression ||
expression instanceof TimeBucketExpression) {
return "timestamp_floor(".concat(ex1_1, ",'").concat(expression.duration, "',null,").concat(DruidExpressionBuilder.escapeLiteral(expression.timezone.toString()), ")");
}
else if (expression instanceof TimeShiftExpression) {
return "timestamp_shift(".concat(ex1_1, ",'").concat(expression.duration, "',").concat(expression.step, ",").concat(DruidExpressionBuilder.escapeLiteral(expression.timezone.toString()), ")");
}
else if (expression instanceof LookupExpression) {
return "lookup(".concat(ex1_1, ",").concat(DruidExpressionBuilder.escapeLiteral(expression.lookupFn), ")");
}
else if (expression instanceof TransformCaseExpression) {
if (expression.transformType === TransformCaseExpression.UPPER_CASE) {
return "upper(".concat(ex1_1, ")");
}
else {
return "lower(".concat(ex1_1, ")");
}
}
else if (expression instanceof MvContainsExpression) {
return "array_contains(".concat(ex1_1, ", [").concat(expression.mvArray
.map(DruidExpressionBuilder.escapeLiteral)
.join(','), "])");
}
else if (expression instanceof MvOverlapExpression) {
return "array_overlap(".concat(ex1_1, ", [").concat(expression.mvArray
.map(DruidExpressionBuilder.escapeLiteral)
.join(','), "])");
}
else if (expression instanceof IpMatchExpression) {
return expression.ipSearchType === 'ipPrefix'
? "ip_match(".concat(DruidExpressionBuilder.escapeLiteral(expression.ipToSearch.toString()), ", ").concat(ex1_1, ")")
: "ip_match(".concat(ex1_1, ", ").concat(DruidExpressionBuilder.escapeLiteral(expression.ipToSearch.toString()), ")");
}
else if (expression instanceof IpSearchExpression) {
return expression.ipSearchType === 'ipPrefix'
? "ip_search(".concat(DruidExpressionBuilder.escapeLiteral(expression.ipToSearch.toString()), ", ").concat(ex1_1, ")")
: "ip_search(".concat(ex1_1, ", ").concat(DruidExpressionBuilder.escapeLiteral(expression.ipToSearch.toString()), ")");
}
else if (expression instanceof IpStringifyExpression) {
return "ip_stringify(".concat(ex1_1, ")");
}
else if (expression instanceof ChainableUnaryExpression) {
var myExpression = expression.expression;
if (expression instanceof ConcatExpression) {
return ('concat(' +
expression
.getExpressionList()
.map(function (ex) { return _this.expressionToDruidExpression(ex); })
.join(',') +
')');
}
var ex2 = this.expressionToDruidExpression(myExpression);
if (expression instanceof AddExpression) {
return "(".concat(ex1_1, "+").concat(ex2, ")");
}
else if (expression instanceof SubtractExpression) {
return "(".concat(ex1_1, "-").concat(ex2, ")");
}
else if (expression instanceof MultiplyExpression) {
return "(".concat(ex1_1, "*").concat(ex2, ")");
}
else if (expression instanceof DivideExpression) {
if (myExpression instanceof LiteralExpression) {
return "(cast(".concat(ex1_1, ",'DOUBLE')/").concat(ex2, ")");
}
else {
return "if(".concat(ex2, "!=0,(cast(").concat(ex1_1, ",'DOUBLE')/").concat(ex2, "),null)");
}
}
else if (expression instanceof PowerExpression) {
return "pow(".concat(ex1_1, ",").concat(ex2, ")");
}
else if (expression instanceof LogExpression) {
var myLiteral = myExpression.getLiteralValue();
if (myLiteral === Math.E)
return "log(".concat(ex1_1, ")");
if (myLiteral === 10)
return "log10(".concat(ex1_1, ")");
return "log(".concat(ex1_1, ")/log(").concat(ex2, ")");
}
else if (expression instanceof ThenExpression) {
return "if(".concat(ex1_1, ",").concat(ex2, ",null)");
}
else if (expression instanceof FallbackExpression) {
return "nvl(".concat(ex1_1, ",").concat(ex2, ")");
}
else if (expression instanceof AndExpression) {
return "(".concat(ex1_1, "&&").concat(ex2, ")");
}
else if (expression instanceof OrExpression) {
return "(".concat(ex1_1, "||").concat(ex2, ")");
}
else if (expression instanceof IsExpression) {
var myLiteral = myExpression.getLiteralValue();
if (myLiteral instanceof Set) {
return ('(' +
myLiteral.elements
.map(function (e) {
return "".concat(ex1_1, "==").concat(DruidExpressionBuilder.escapeLiteral(e));
})
.join('||') +
')');
}
else {
return "(".concat(ex1_1, "==").concat(ex2, ")");
}
}
else if (expression instanceof OverlapExpression) {
var myExpressionType = myExpression.type;
switch (myExpressionType) {
case 'NUMBER_RANGE':
case 'TIME_RANGE':
if (myExpression instanceof LiteralExpression) {
var range = myExpression.value;
return this.overlapExpression(ex1_1, DruidExpressionBuilder.escapeLiteral(range.start), DruidExpressionBuilder.escapeLiteral(range.end), range.bounds);
}
throw new Error("can not convert ".concat(expression, " to Druid expression"));
case 'STRING_RANGE':
if (myExpression instanceof LiteralExpression) {
var stringRange = myExpression.value;
return this.overlapExpression(ex1_1, DruidExpressionBuilder.escapeLiteral(stringRange.start), DruidExpressionBuilder.escapeLiteral(stringRange.end), stringRange.bounds);
}
throw new Error("can not convert ".concat(expression, " to Druid expression"));
case 'SET/NUMBER_RANGE':
case 'SET/TIME_RANGE':
if (myExpression instanceof LiteralExpression) {
var setOfRange = myExpression.value;
return setOfRange.elements
.map(function (range) {
return _this.overlapExpression(ex1_1, DruidExpressionBuilder.escapeLiteral(range.start), DruidExpressionBuilder.escapeLiteral(range.end), range.bounds);
})
.join('||');
}
throw new Error("can not convert ".concat(expression, " to Druid expression"));
default:
throw new Error("can not convert ".concat(expression, " to Druid expression"));
}
}
else if (expression instanceof IndexOfExpression) {
return "strpos(".concat(ex1_1, ",").concat(ex2, ")");
}
}
}
throw new Error("can not convert ".concat(expression, " to Druid expression"));
};
DruidExpressionBuilder.prototype.castToType = function (operand, sourceType, destType) {
switch (destType) {
case 'TIME':
if (sourceType === 'NUMBER') {
return "cast(".concat(operand, ",'LONG')");
}
else {
return "timestamp(".concat(operand, ")");
}
case 'STRING':
return "cast(".concat(operand, ",'STRING')");
case 'NUMBER':
return "cast(".concat(operand, ",'DOUBLE')");
default:
throw new Error("cast to ".concat(destType, " not implemented yet"));
}
};
DruidExpressionBuilder.prototype.overlapExpression = function (operand, start, end, bounds) {
if (start === end && bounds === '[]')
return "(".concat(operand, "==").concat(start, ")");
var startExpression = null;
if (start !== 'null') {
startExpression = start + (bounds[0] === '[' ? '<=' : '<') + operand;
}
var endExpression = null;
if (end !== 'null') {
endExpression = operand + (bounds[1] === ']' ? '<=' : '<') + end;
}
if (startExpression) {
return endExpression ? "(".concat(startExpression, " && ").concat(endExpression, ")") : startExpression;
}
else {
return endExpression ? endExpression : 'true';
}
};
DruidExpressionBuilder.prototype.getAttributesInfo = function (attributeName) {
return NamedArray.get(this.rawAttributes, attributeName);
};
DruidExpressionBuilder.TIME_PART_TO_FORMAT = {
SECOND_OF_MINUTE: 'SECOND',
MINUTE_OF_HOUR: 'MINUTE',
HOUR_OF_DAY: 'HOUR',
DAY_OF_WEEK: 'DOW',
DAY_OF_MONTH: 'DAY',
DAY_OF_YEAR: 'DOY',
WEEK_OF_YEAR: 'WEEK',
MONTH_OF_YEAR: 'MONTH',
QUARTER: 'QUARTER',
YEAR: 'YEAR',
};
DruidExpressionBuilder.UNSAFE_CHAR = /[^a-z0-9 ,._\-;:(){}\[\]<>!@#$%^&*`~?]/gi;
return DruidExpressionBuilder;
}());
export { DruidExpressionBuilder };