Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.questdb.griffin.WhereClauseParser Maven / Gradle / Ivy
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed 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.
*
******************************************************************************/
package io.questdb.griffin;
import io.questdb.cairo.ColumnType;
import io.questdb.cairo.sql.Function;
import io.questdb.cairo.sql.RecordMetadata;
import io.questdb.griffin.model.AliasTranslator;
import io.questdb.griffin.model.ExpressionNode;
import io.questdb.griffin.model.IntervalUtils;
import io.questdb.griffin.model.IntrinsicModel;
import io.questdb.std.*;
import io.questdb.std.datetime.microtime.TimestampFormatUtils;
import io.questdb.std.str.FlyweightCharSequence;
import java.util.ArrayDeque;
import static io.questdb.griffin.SqlKeywords.*;
final class WhereClauseParser implements Mutable {
private static final int INTRINSIC_OP_IN = 1;
private static final int INTRINSIC_OP_GREATER = 2;
private static final int INTRINSIC_OP_GREATER_EQ = 3;
private static final int INTRINSIC_OP_LESS = 4;
private static final int INTRINSIC_OP_LESS_EQ = 5;
private static final int INTRINSIC_OP_EQUAL = 6;
private static final int INTRINSIC_OP_NOT_EQ = 7;
private static final int INTRINSIC_OP_NOT = 8;
private static final CharSequenceIntHashMap intrinsicOps = new CharSequenceIntHashMap();
private final ArrayDeque stack = new ArrayDeque<>();
private final ObjList keyNodes = new ObjList<>();
private final ObjList keyExclNodes = new ObjList<>();
private final ObjList tempNodes = new ObjList<>();
// TODO: configure size
private final ObjectPool models = new ObjectPool<>(IntrinsicModel.FACTORY, 8);
private final CharSequenceHashSet tempKeys = new CharSequenceHashSet();
private final IntList tempPos = new IntList();
private final CharSequenceHashSet tempK = new CharSequenceHashSet();
private final IntList tempP = new IntList();
// TODO: configure size
private final ObjectPool csPool = new ObjectPool<>(FlyweightCharSequence.FACTORY, 64);
private CharSequence timestamp;
private CharSequence preferredKeyColumn;
@Override
public void clear() {
this.models.clear();
this.stack.clear();
this.keyNodes.clear();
this.keyExclNodes.clear();
this.csPool.clear();
this.tempNodes.clear();
}
private static void checkNodeValid(ExpressionNode node) throws SqlException {
if (node.lhs == null || node.rhs == null) {
throw SqlException.$(node.position, "Argument expected");
}
}
private static boolean nodesEqual(ExpressionNode left, ExpressionNode right) {
return (left.type == ExpressionNode.LITERAL || left.type == ExpressionNode.CONSTANT) &&
(right.type == ExpressionNode.LITERAL || right.type == ExpressionNode.CONSTANT) &&
Chars.equals(left.token, right.token);
}
private boolean analyzeEquals(AliasTranslator translator,
IntrinsicModel model,
ExpressionNode node,
RecordMetadata m,
FunctionParser functionParser,
SqlExecutionContext executionContext) throws SqlException {
checkNodeValid(node);
return analyzeEquals0(translator, model, node, node.lhs, node.rhs, m, functionParser, executionContext) ||
analyzeEquals0(translator, model, node, node.rhs, node.lhs, m, functionParser, executionContext);
}
private boolean analyzeEquals0(AliasTranslator translator,
IntrinsicModel model,
ExpressionNode node,
ExpressionNode a,
ExpressionNode b,
RecordMetadata m,
FunctionParser functionParser,
SqlExecutionContext executionContext) throws SqlException {
if (nodesEqual(a, b)) {
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
if (a.type == ExpressionNode.LITERAL && (b.type == ExpressionNode.CONSTANT
|| b.type == ExpressionNode.BIND_VARIABLE
|| b.type == ExpressionNode.FUNCTION
|| b.type == ExpressionNode.OPERATION)) {
if (isTimestamp(a)) {
if (b.type == ExpressionNode.CONSTANT) {
model.intersectIntervals(b.token, 1, b.token.length() - 1, b.position);
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
Function function = functionParser.parseFunction(b, m, executionContext);
checkFunctionCanBeTimestamp(m, executionContext, function);
if (function.isConstant()) {
long value = function.getTimestamp(null);
if (value == Numbers.LONG_NaN) {
// make it empty set
model.intersectEmpty();
} else {
model.intersectIntervals(value, value);
}
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
} else if (function.isRuntimeConstant()) {
model.intersectEquals(function);
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
} else {
CharSequence column = translator.translateAlias(a.token);
int index = m.getColumnIndexQuiet(column);
if (index == -1) {
throw SqlException.invalidColumn(a.position, a.token);
}
switch (m.getColumnType(index)) {
case ColumnType.SYMBOL:
case ColumnType.STRING:
case ColumnType.LONG:
case ColumnType.INT:
final boolean preferred = Chars.equalsIgnoreCaseNc(preferredKeyColumn, column);
final boolean indexed = m.isColumnIndexed(index);
if (preferred || (indexed && preferredKeyColumn == null)) {
CharSequence value = isNullKeyword(b.token) ? null : unquote(b.token);
if (Chars.equalsIgnoreCaseNc(model.keyColumn, column)) {
// compute overlap of values
// if values do overlap, keep only our value
// otherwise invalidate entire model
if (model.keyValues.contains(value)) {
// when we have "x in ('a,'b') and x = 'a')" the x='b' can never happen
// so we have to clear all other key values
if (model.keyValues.size() > 1) {
model.keyValues.clear();
model.keyValuePositions.clear();
model.keyValues.add(value);
model.keyValuePositions.add(b.position);
node.intrinsicValue = IntrinsicModel.TRUE;
}
} else {
if (model.keyExcludedValues.contains(value)) {
if (model.keyExcludedValues.size() > 1) {
int removedIndex = model.keyExcludedValues.remove(value);
if (removedIndex > -1) {
model.keyExcludedValuePositions.removeIndex(index);
}
} else {
model.keyExcludedValues.clear();
model.keyExcludedValuePositions.clear();
}
removeNodes(b, keyExclNodes);
}
node.intrinsicValue = IntrinsicModel.TRUE;
model.intrinsicValue = IntrinsicModel.FALSE;
return false;
}
} else if (model.keyColumn == null || m.getIndexValueBlockCapacity(index) > m.getIndexValueBlockCapacity(model.keyColumn)) {
model.keyColumn = column;
model.keyValues.clear();
model.keyValuePositions.clear();
model.keyExcludedValues.clear();
model.keyExcludedValuePositions.clear();
model.keyValues.add(value);
model.keyValuePositions.add(b.position);
resetNodes();
node.intrinsicValue = IntrinsicModel.TRUE;
}
keyNodes.add(node);
return true;
}
//fall through
default:
return false;
}
}
}
return false;
}
private boolean analyzeGreater(IntrinsicModel model,
ExpressionNode node,
boolean equalsTo,
FunctionParser functionParser,
RecordMetadata metadata,
SqlExecutionContext executionContext) throws SqlException {
checkNodeValid(node);
if (nodesEqual(node.lhs, node.rhs)) {
model.intrinsicValue = IntrinsicModel.FALSE;
return false;
}
if (timestamp == null) {
return false;
}
if (node.lhs.type == ExpressionNode.LITERAL && Chars.equals(node.lhs.token, timestamp)) {
return analyzeTimestampGreater(model, node, equalsTo, functionParser, metadata, executionContext, node.rhs);
} else if (node.rhs.type == ExpressionNode.LITERAL && Chars.equals(node.rhs.token, timestamp)) {
return analyzeTimestampLess(model, node, equalsTo, functionParser, metadata, executionContext, node.lhs);
}
return false;
}
private boolean analyzeTimestampGreater(IntrinsicModel model,
ExpressionNode node,
boolean equalsTo,
FunctionParser functionParser,
RecordMetadata metadata,
SqlExecutionContext executionContext,
ExpressionNode compareWithNode) throws SqlException {
long lo;
if (compareWithNode.type == ExpressionNode.CONSTANT) {
try {
lo = parseFullOrPartialDate(equalsTo, compareWithNode, true);
} catch (NumericException e) {
throw SqlException.invalidDate(compareWithNode.position);
}
model.intersectIntervals(lo, Long.MAX_VALUE);
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
} else if (compareWithNode.type == ExpressionNode.FUNCTION
|| compareWithNode.type == ExpressionNode.BIND_VARIABLE
|| compareWithNode.type == ExpressionNode.OPERATION) {
Function function = functionParser.parseFunction(compareWithNode, metadata, executionContext);
checkFunctionCanBeTimestamp(metadata, executionContext, function);
if (function.isConstant()) {
lo = function.getTimestamp(null);
if (lo == Numbers.LONG_NaN) {
// make it empty set
model.intersectEmpty();
} else {
model.intersectIntervals(lo + adjustComparison(equalsTo, true), Long.MAX_VALUE);
}
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
} else if (function.isRuntimeConstant()) {
model.intersectIntervals(function, Long.MAX_VALUE, adjustComparison(equalsTo, true));
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
}
return false;
}
private void checkFunctionCanBeTimestamp(RecordMetadata metadata, SqlExecutionContext executionContext, Function function) throws SqlException {
if (function.getType() == ColumnType.UNDEFINED) {
int timestampType = metadata.getColumnType(metadata.getTimestampIndex());
function.assignType(timestampType, executionContext.getBindVariableService());
} else if (function.getType() != ColumnType.DATE && function.getType() != ColumnType.TIMESTAMP) {
throw SqlException.invalidDate(function.getPosition());
}
}
private boolean analyzeIn(AliasTranslator translator, IntrinsicModel model, ExpressionNode node, RecordMetadata metadata) throws SqlException {
if (node.paramCount < 2) {
throw SqlException.$(node.position, "Too few arguments for 'in'");
}
ExpressionNode col = node.paramCount < 3 ? node.lhs : node.args.getLast();
if (col.type != ExpressionNode.LITERAL) {
return false;
}
CharSequence column = translator.translateAlias(col.token);
if (metadata.getColumnIndexQuiet(column) == -1) {
throw SqlException.invalidColumn(col.position, col.token);
}
return analyzeInInterval(model, col, node, false)
|| analyzeListOfValues(model, column, metadata, node)
|| analyzeInLambda(model, column, metadata, node);
}
private boolean analyzeInInterval(IntrinsicModel model, ExpressionNode col, ExpressionNode in, boolean isNegated) throws SqlException {
if (!isTimestamp(col)) {
return false;
}
if (in.paramCount > 3) {
throw SqlException.$(in.args.getQuick(0).position, "Too many args");
}
if (in.paramCount < 3) {
throw SqlException.$(in.position, "Too few args");
}
ExpressionNode lo = in.args.getQuick(1);
ExpressionNode hi = in.args.getQuick(0);
if (lo.type == ExpressionNode.CONSTANT && hi.type == ExpressionNode.CONSTANT) {
long loMillis;
long hiMillis;
try {
loMillis = TimestampFormatUtils.tryParse(lo.token, 1, lo.token.length() - 1);
} catch (NumericException ignore) {
throw SqlException.invalidDate(lo.position);
}
try {
hiMillis = TimestampFormatUtils.tryParse(hi.token, 1, hi.token.length() - 1);
} catch (NumericException ignore) {
throw SqlException.invalidDate(hi.position);
}
if (isNegated) {
model.subtractIntervals(loMillis, hiMillis);
} else {
model.intersectIntervals(loMillis, hiMillis);
}
in.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
return false;
}
private boolean analyzeInLambda(IntrinsicModel model, CharSequence columnName, RecordMetadata meta, ExpressionNode node) throws SqlException {
int columnIndex = meta.getColumnIndex(columnName);
boolean preferred = Chars.equalsIgnoreCaseNc(preferredKeyColumn, columnName);
if (preferred || (preferredKeyColumn == null && meta.isColumnIndexed(columnIndex))) {
if (preferredKeyColumn != null && !Chars.equalsIgnoreCase(columnName, preferredKeyColumn)) {
return false;
}
if (node.rhs == null || node.rhs.type != ExpressionNode.QUERY) {
return false;
}
// check if we already have indexed column and it is of worse selectivity
if (model.keyColumn != null
&& (!Chars.equalsIgnoreCase(model.keyColumn, columnName))
&& meta.getIndexValueBlockCapacity(columnIndex) <= meta.getIndexValueBlockCapacity(model.keyColumn)) {
return false;
}
if ((Chars.equalsIgnoreCaseNc(model.keyColumn, columnName) && model.keySubQuery != null) || node.paramCount > 2) {
throw SqlException.$(node.position, "Multiple lambda expressions not supported");
}
model.keyValues.clear();
model.keyValuePositions.clear();
model.keyValuePositions.add(node.position);
model.keySubQuery = node.rhs.queryModel;
// revert previously processed nodes
return revertProcessedNodes(keyNodes, model, columnName, node);
}
return false;
}
private boolean analyzeLess(IntrinsicModel model,
ExpressionNode node,
boolean equalsTo,
FunctionParser functionParser,
RecordMetadata metadata,
SqlExecutionContext executionContext) throws SqlException {
checkNodeValid(node);
if (nodesEqual(node.lhs, node.rhs)) {
model.intrinsicValue = IntrinsicModel.FALSE;
return false;
}
if (timestamp == null) {
return false;
}
if (node.lhs.type == ExpressionNode.LITERAL && Chars.equals(node.lhs.token, timestamp)) {
return analyzeTimestampLess(model, node, equalsTo, functionParser, metadata, executionContext, node.rhs);
} else if (node.rhs.type == ExpressionNode.LITERAL && Chars.equals(node.rhs.token, timestamp)) {
return analyzeTimestampGreater(model, node, equalsTo, functionParser, metadata, executionContext, node.lhs);
}
return false;
}
private boolean analyzeTimestampLess(IntrinsicModel model,
ExpressionNode node,
boolean equalsTo,
FunctionParser functionParser,
RecordMetadata metadata,
SqlExecutionContext executionContext,
ExpressionNode compareWithNode) throws SqlException {
if (compareWithNode.type == ExpressionNode.CONSTANT) {
try {
long hi = parseFullOrPartialDate(equalsTo, compareWithNode, false);
model.intersectIntervals(Long.MIN_VALUE, hi);
node.intrinsicValue = IntrinsicModel.TRUE;
} catch (NumericException e) {
throw SqlException.invalidDate(compareWithNode.position);
}
return true;
} else if (compareWithNode.type == ExpressionNode.FUNCTION
|| compareWithNode.type == ExpressionNode.BIND_VARIABLE
|| compareWithNode.type == ExpressionNode.OPERATION) {
Function function = functionParser.parseFunction(compareWithNode, metadata, executionContext);
checkFunctionCanBeTimestamp(metadata, executionContext, function);
if (function.isConstant()) {
long hi = function.getTimestamp(null);
if (hi == Numbers.LONG_NaN) {
model.intersectEmpty();
} else {
model.intersectIntervals(Long.MIN_VALUE, hi + adjustComparison(equalsTo, false));
}
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
} else if (function.isRuntimeConstant()) {
model.intersectIntervals(Long.MIN_VALUE, function, adjustComparison(equalsTo, false));
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
}
return false;
}
private static short adjustComparison(boolean equalsTo, boolean isLo) {
return equalsTo ? 0 : isLo ? (short) 1 : (short) -1;
}
private boolean analyzeListOfValues(IntrinsicModel model, CharSequence columnName, RecordMetadata meta, ExpressionNode node) {
final int columnIndex = meta.getColumnIndex(columnName);
boolean newColumn = true;
boolean preferred = Chars.equalsIgnoreCaseNc(preferredKeyColumn, columnName);
if (preferred || (preferredKeyColumn == null && meta.isColumnIndexed(columnIndex))) {
// check if we already have indexed column and it is of worse selectivity
// "preferred" is an unfortunate name, this column is from "latest by" clause, I should name it better
//
if (model.keyColumn != null
&& (newColumn = !Chars.equals(model.keyColumn, columnName))
&& meta.getIndexValueBlockCapacity(columnIndex) <= meta.getIndexValueBlockCapacity(model.keyColumn)) {
return false;
}
int i = node.paramCount - 1;
tempKeys.clear();
tempPos.clear();
// collect and analyze values of indexed field
// if any of values is not an indexed constant - bail out
if (i == 1) {
if (node.rhs == null || (node.rhs.type != ExpressionNode.CONSTANT && node.rhs.type != ExpressionNode.BIND_VARIABLE)) {
return false;
}
if (tempKeys.add(unquote(node.rhs.token))) {
tempPos.add(node.position);
}
} else {
for (i--; i > -1; i--) {
ExpressionNode c = node.args.getQuick(i);
if (c.type != ExpressionNode.CONSTANT && c.type != ExpressionNode.BIND_VARIABLE) {
return false;
}
if (isNullKeyword(c.token)) {
if (tempKeys.add(null)) {
tempPos.add(c.position);
}
} else {
if (tempKeys.add(unquote(c.token))) {
tempPos.add(c.position);
}
}
}
}
// clear values if this is new column
// and reset intrinsic values on nodes associated with old column
if (newColumn) {
model.keyValues.clear();
model.keyValuePositions.clear();
model.keyValues.addAll(tempKeys);
model.keyValuePositions.addAll(tempPos);
return revertProcessedNodes(keyNodes, model, columnName, node);
} else {
if (model.keyValues.size() == 0) {
model.keyValues.addAll(tempKeys);
model.keyValuePositions.addAll(tempPos);
}
}
if (model.keySubQuery == null) {
// calculate overlap of values
replaceAllWithOverlap(model, true);
keyNodes.add(node);
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
}
return false;
}
private boolean analyzeNotEquals(AliasTranslator translator, IntrinsicModel model, ExpressionNode node, RecordMetadata m) throws SqlException {
checkNodeValid(node);
return analyzeNotEquals0(translator, model, node, node.lhs, node.rhs, m)
|| analyzeNotEquals0(translator, model, node, node.rhs, node.lhs, m);
}
private boolean analyzeNotEquals0(AliasTranslator translator, IntrinsicModel model, ExpressionNode node, ExpressionNode a, ExpressionNode b, RecordMetadata m) throws SqlException {
if (Chars.equals(a.token, b.token)) {
model.intrinsicValue = IntrinsicModel.FALSE;
return true;
}
if (a.type == ExpressionNode.LITERAL && b.type == ExpressionNode.CONSTANT) {
if (isTimestamp(a)) {
model.subtractIntervals(b.token, 1, b.token.length() - 1, b.position);
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
} else {
CharSequence column = translator.translateAlias(a.token);
int index = m.getColumnIndexQuiet(column);
if (index == -1) {
throw SqlException.invalidColumn(a.position, a.token);
}
switch (m.getColumnType(index)) {
case ColumnType.SYMBOL:
case ColumnType.STRING:
case ColumnType.LONG:
case ColumnType.INT:
if (m.isColumnIndexed(index)) {
final boolean preferred = Chars.equalsIgnoreCaseNc(preferredKeyColumn, column);
final boolean indexed = m.isColumnIndexed(index);
if (indexed && preferredKeyColumn == null) {
CharSequence value = isNullKeyword(b.token) ? null : unquote(b.token);
if (Chars.equalsIgnoreCaseNc(model.keyColumn, column)) {
if (model.keyExcludedValues.contains(value)) {
// when we have "x not in ('a,'b') and x != 'a')" the x='b' can never happen
// so we have to clear all other key values
if (model.keyExcludedValues.size() > 1) {
model.keyExcludedValues.clear();
model.keyExcludedValuePositions.clear();
model.keyExcludedValues.add(value);
model.keyExcludedValuePositions.add(b.position);
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
} else {
if (model.keyValues.contains(value)) {
if (model.keyValues.size() > 1) {
int removedIndex = model.keyValues.remove(value);
if (removedIndex > -1) {
model.keyValuePositions.removeIndex(index);
}
model.keyValuePositions.remove(b.position);
} else {
model.keyValues.clear();
model.keyValuePositions.clear();
}
removeNodes(b, keyNodes);
}
node.intrinsicValue = IntrinsicModel.TRUE;
model.intrinsicValue = IntrinsicModel.FALSE;
return false;
}
} else if (model.keyColumn == null || m.getIndexValueBlockCapacity(index) > m.getIndexValueBlockCapacity(model.keyColumn)) {
model.keyColumn = column;
model.keyValues.clear();
model.keyValuePositions.clear();
model.keyExcludedValues.clear();
model.keyExcludedValuePositions.clear();
model.keyExcludedValues.add(value);
model.keyExcludedValuePositions.add(b.position);
resetNodes();
node.intrinsicValue = IntrinsicModel.TRUE;
}
keyExclNodes.add(node);
return true;
} else if (preferred) {
keyExclNodes.add(node);
return false;
}
}
return false;
default:
break;
}
}
}
return false;
}
private boolean analyzeNotIn(AliasTranslator translator, IntrinsicModel model, ExpressionNode notNode, RecordMetadata m) throws SqlException {
ExpressionNode node = notNode.rhs;
if (node.paramCount < 2) {
throw SqlException.$(node.position, "Too few arguments for 'in'");
}
ExpressionNode col = node.paramCount < 3 ? node.lhs : node.args.getLast();
if (col.type != ExpressionNode.LITERAL) {
throw SqlException.$(col.position, "Column name expected");
}
CharSequence column = translator.translateAlias(col.token);
if (m.getColumnIndexQuiet(column) == -1) {
throw SqlException.invalidColumn(col.position, col.token);
}
boolean ok = analyzeInInterval(model, col, node, true);
if (ok) {
notNode.intrinsicValue = IntrinsicModel.TRUE;
} else {
analyzeNotListOfValues(model, column, m, node, notNode);
}
return ok;
}
private void analyzeNotListOfValues(IntrinsicModel model, CharSequence columnName, RecordMetadata meta, ExpressionNode node, ExpressionNode notNode) {
final int columnIndex = meta.getColumnIndex(columnName);
boolean newColumn = true;
boolean preferred = Chars.equalsIgnoreCaseNc(preferredKeyColumn, columnName);
if (preferred || (preferredKeyColumn == null && meta.isColumnIndexed(columnIndex))) {
if (model.keyColumn != null
&& (newColumn = !Chars.equals(model.keyColumn, columnName))
&& meta.getIndexValueBlockCapacity(columnIndex) <= meta.getIndexValueBlockCapacity(model.keyColumn)) {
return;
}
int i = node.paramCount - 1;
tempKeys.clear();
tempPos.clear();
// collect and analyze values of indexed field
// if any of values is not an indexed constant - bail out
if (i == 1) {
if (node.rhs == null || node.rhs.type != ExpressionNode.CONSTANT) {
return;
}
if (tempKeys.add(unquote(node.rhs.token))) {
tempPos.add(node.position);
}
} else {
for (i--; i > -1; i--) {
ExpressionNode c = node.args.getQuick(i);
if (c.type != ExpressionNode.CONSTANT) {
return;
}
if (isNullKeyword(c.token)) {
if (tempKeys.add(null)) {
tempPos.add(c.position);
}
} else {
if (tempKeys.add(unquote(c.token))) {
tempPos.add(c.position);
}
}
}
}
// clear values if this is new column
// and reset intrinsic values on nodes associated with old column
if (newColumn) {
model.keyExcludedValues.clear();
model.keyExcludedValuePositions.clear();
model.keyExcludedValues.addAll(tempKeys);
model.keyExcludedValuePositions.addAll(tempPos);
revertProcessedNodes(keyExclNodes, model, columnName, notNode);
return;
} else {
if (model.keyExcludedValues.size() == 0) {
model.keyExcludedValues.addAll(tempKeys);
model.keyExcludedValuePositions.addAll(tempPos);
}
}
if (model.keySubQuery == null) {
// calculate overlap of values
replaceAllWithOverlap(model, false);
keyExclNodes.add(notNode);
notNode.intrinsicValue = IntrinsicModel.TRUE;
}
}
}
private void applyKeyExclusions(AliasTranslator translator, IntrinsicModel model) {
if (model.keyColumn != null && model.keyValues.size() > 0 && keyExclNodes.size() > 0) {
OUT:
for (int i = 0, n = keyExclNodes.size(); i < n; i++) {
ExpressionNode parent = keyExclNodes.getQuick(i);
ExpressionNode node = isNotKeyword(parent.token) ? parent.rhs : parent;
// this could either be '=' or 'in'
if (node.paramCount == 2) {
ExpressionNode col;
ExpressionNode val;
if (node.lhs.type == ExpressionNode.LITERAL) {
col = node.lhs;
val = node.rhs;
} else {
col = node.rhs;
val = node.lhs;
}
final CharSequence column = translator.translateAlias(col.token);
if (Chars.equals(column, model.keyColumn)) {
model.excludeValue(val);
parent.intrinsicValue = IntrinsicModel.TRUE;
if (model.intrinsicValue == IntrinsicModel.FALSE) {
break;
}
}
}
if (node.paramCount > 2) {
ExpressionNode col = node.args.getQuick(node.paramCount - 1);
final CharSequence column = translator.translateAlias(col.token);
if (Chars.equals(column, model.keyColumn)) {
for (int j = node.paramCount - 2; j > -1; j--) {
ExpressionNode val = node.args.getQuick(j);
model.excludeValue(val);
if (model.intrinsicValue == IntrinsicModel.FALSE) {
break OUT;
}
}
parent.intrinsicValue = IntrinsicModel.TRUE;
}
}
}
}
keyExclNodes.clear();
}
private ExpressionNode collapseIntrinsicNodes(ExpressionNode node) {
if (node == null || node.intrinsicValue == IntrinsicModel.TRUE) {
return null;
}
node.lhs = collapseIntrinsicNodes(collapseNulls0(node.lhs));
node.rhs = collapseIntrinsicNodes(collapseNulls0(node.rhs));
return collapseNulls0(node);
}
private ExpressionNode collapseNulls0(ExpressionNode node) {
if (node == null || node.intrinsicValue == IntrinsicModel.TRUE) {
return null;
}
if (node.queryModel == null && isAndKeyword(node.token)) {
if (node.lhs == null || node.lhs.intrinsicValue == IntrinsicModel.TRUE) {
return node.rhs;
}
if (node.rhs == null || node.rhs.intrinsicValue == IntrinsicModel.TRUE) {
return node.lhs;
}
}
return node;
}
IntrinsicModel extract(
AliasTranslator translator,
ExpressionNode node,
RecordMetadata m,
CharSequence preferredKeyColumn,
int timestampIndex,
FunctionParser functionParser,
RecordMetadata metadata,
SqlExecutionContext executionContext
) throws SqlException {
this.timestamp = timestampIndex < 0 ? null : m.getColumnName(timestampIndex);
this.preferredKeyColumn = preferredKeyColumn;
IntrinsicModel model = models.next();
// pre-order iterative tree traversal
// see: http://en.wikipedia.org/wiki/Tree_traversal
if (removeAndIntrinsics(translator, model, node, m, functionParser, metadata, executionContext)) {
return model;
}
ExpressionNode root = node;
while (!stack.isEmpty() || node != null) {
if (node != null) {
if (isAndKeyword(node.token)) {
if (!removeAndIntrinsics(translator, model, node.rhs, m, functionParser, metadata, executionContext)) {
stack.push(node.rhs);
}
node = removeAndIntrinsics(translator, model, node.lhs, m, functionParser, metadata, executionContext) ? null : node.lhs;
} else {
node = stack.poll();
}
} else {
node = stack.poll();
}
}
applyKeyExclusions(translator, model);
model.filter = collapseIntrinsicNodes(root);
return model;
}
private boolean isTimestamp(ExpressionNode n) {
return Chars.equalsNc(n.token, timestamp);
}
private long parseFullOrPartialDate(boolean equalsTo, ExpressionNode node, boolean isLo) throws NumericException {
long ts;
final int len = node.token.length();
if (len - 2 < 20) {
if (isLo) {
if (equalsTo) {
ts = IntervalUtils.parseFloorPartialDate(node.token, 1, len - 1);
} else {
ts = IntervalUtils.parseCCPartialDate(node.token, 1, len - 1);
}
} else {
if (equalsTo) {
ts = IntervalUtils.parseCCPartialDate(node.token, 1, len - 1) - 1;
} else {
ts = IntervalUtils.parseFloorPartialDate(node.token, 1, len - 1) - 1;
}
}
} else {
long inc = equalsTo ? 0 : isLo ? 1 : -1;
ts = TimestampFormatUtils.tryParse(node.token, 1, node.token.length() - 1) + inc;
}
return ts;
}
private boolean removeAndIntrinsics(AliasTranslator translator, IntrinsicModel model, ExpressionNode node, RecordMetadata m, FunctionParser functionParser, RecordMetadata metadata, SqlExecutionContext executionContext) throws SqlException {
switch (intrinsicOps.get(node.token)) {
case INTRINSIC_OP_IN:
return analyzeIn(translator, model, node, m);
case INTRINSIC_OP_GREATER:
return analyzeGreater(model, node, false, functionParser, metadata, executionContext);
case INTRINSIC_OP_GREATER_EQ:
return analyzeGreater(model, node, true, functionParser, metadata, executionContext);
case INTRINSIC_OP_LESS:
return analyzeLess(model, node, false, functionParser, metadata, executionContext);
case INTRINSIC_OP_LESS_EQ:
return analyzeLess(model, node, true, functionParser, metadata, executionContext);
case INTRINSIC_OP_EQUAL:
return analyzeEquals(translator, model, node, m, functionParser, executionContext);
case INTRINSIC_OP_NOT_EQ:
return analyzeNotEquals(translator, model, node, m);
case INTRINSIC_OP_NOT:
return isInKeyword(node.rhs.token) && analyzeNotIn(translator, model, node, m);
default:
return false;
}
}
private void removeNodes(ExpressionNode b, ObjList nodes) {
tempNodes.clear();
for (int i = 0, size = nodes.size(); i < size; i++) {
ExpressionNode expressionNode = nodes.get(i);
if ((expressionNode.lhs != null && Chars.equals(expressionNode.lhs.token, b.token)) || (expressionNode.rhs != null && Chars.equals(expressionNode.rhs.token, b.token))) {
expressionNode.intrinsicValue = IntrinsicModel.TRUE;
tempNodes.add(expressionNode);
}
}
for (int i = 0, size = tempNodes.size(); i < size; i++) {
nodes.remove(tempNodes.get(i));
}
}
private void replaceAllWithOverlap(IntrinsicModel model, boolean includedValues) {
CharSequenceHashSet values;
IntList positions;
if (includedValues) {
values = model.keyValues;
positions = model.keyValuePositions;
} else {
values = model.keyExcludedValues;
positions = model.keyExcludedValuePositions;
}
tempK.clear();
tempP.clear();
for (int i = 0, k = tempKeys.size(); i < k; i++) {
if (values.contains(tempKeys.get(i)) && tempK.add(tempKeys.get(i))) {
tempP.add(tempPos.get(i));
}
}
if (tempK.size() > 0) {
values.clear();
positions.clear();
values.addAll(tempK);
positions.addAll(tempP);
} else {
model.intrinsicValue = IntrinsicModel.FALSE;
}
}
private void resetNodes() {
for (int n = 0, k = keyNodes.size(); n < k; n++) {
keyNodes.getQuick(n).intrinsicValue = IntrinsicModel.UNDEFINED;
}
keyNodes.clear();
for (int n = 0, k = keyExclNodes.size(); n < k; n++) {
keyExclNodes.getQuick(n).intrinsicValue = IntrinsicModel.UNDEFINED;
}
keyExclNodes.clear();
}
private boolean revertProcessedNodes(ObjList nodes, IntrinsicModel model, CharSequence columnName, ExpressionNode node) {
for (int n = 0, k = nodes.size(); n < k; n++) {
nodes.getQuick(n).intrinsicValue = IntrinsicModel.UNDEFINED;
}
nodes.clear();
model.keyColumn = columnName;
nodes.add(node);
node.intrinsicValue = IntrinsicModel.TRUE;
return true;
}
/**
* Removes quotes and creates immutable char sequence. When value is not quoted it is returned verbatim.
*
* @param value immutable character sequence.
* @return immutable character sequence without surrounding quote marks.
*/
private CharSequence unquote(CharSequence value) {
if (Chars.isQuoted(value)) {
return csPool.next().of(value, 1, value.length() - 2);
}
return value;
}
static {
intrinsicOps.put("in", INTRINSIC_OP_IN);
intrinsicOps.put(">", INTRINSIC_OP_GREATER);
intrinsicOps.put(">=", INTRINSIC_OP_GREATER_EQ);
intrinsicOps.put("<", INTRINSIC_OP_LESS);
intrinsicOps.put("<=", INTRINSIC_OP_LESS_EQ);
intrinsicOps.put("=", INTRINSIC_OP_EQUAL);
intrinsicOps.put("!=", INTRINSIC_OP_NOT_EQ);
intrinsicOps.put("not", INTRINSIC_OP_NOT);
intrinsicOps.put("between", INTRINSIC_OP_IN);
}
}