From 7d3fc9996e667080059efd5d8875eda2db9ab155 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Wed, 10 Apr 2019 13:57:58 +0200 Subject: [PATCH] Fix usage of indexes with CONTAINSTEXT in SQL Resolves: #8693 --- .../core/sql/executor/FetchFromIndexStep.java | 23 +++++- .../sql/executor/OSelectExecutionPlanner.java | 76 +++++++++++++++++++ .../sql/parser/OContainsTextCondition.java | 16 ++++ 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/executor/FetchFromIndexStep.java b/core/src/main/java/com/orientechnologies/orient/core/sql/executor/FetchFromIndexStep.java index 2017a3d42a7..b67bdb401e8 100755 --- a/core/src/main/java/com/orientechnologies/orient/core/sql/executor/FetchFromIndexStep.java +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/executor/FetchFromIndexStep.java @@ -396,6 +396,8 @@ private void init(OCollection fromKey, boolean fromKeyIncluded, OCollection toKe } else if (additionalRangeCondition == null && allEqualities((OAndBlock) condition)) { cursor = index.iterateEntries(toIndexKey(indexDef, secondValue), isOrderAsc()); + } else if (isFullTextIndex(index)) { + cursor = index.iterateEntries(toIndexKey(indexDef, secondValue), isOrderAsc()); } else { throw new UnsupportedOperationException("Cannot evaluate " + this.condition + " on index " + index); } @@ -408,6 +410,10 @@ private void init(OCollection fromKey, boolean fromKeyIncluded, OCollection toKe } } + private boolean isFullTextIndex(OIndex index) { + return index.getType().equalsIgnoreCase("FULLTEXT") && !index.getAlgorithm().equalsIgnoreCase("LUCENE"); + } + private OIndexCursor getCursorForNullKey() { Object result = index.get(null); @@ -432,7 +438,7 @@ public OIdentifiable next() { @Override public Map.Entry nextEntry() { - if(!iter.hasNext()){ + if (!iter.hasNext()) { return null; } Object val = iter.next(); @@ -727,6 +733,13 @@ private OCollection indexKeyFrom(OAndBlock keyCondition, OBinaryCondition additi throw new UnsupportedOperationException("Cannot execute index query with " + exp); } + }else if (exp instanceof OContainsTextCondition) { + if (((OContainsTextCondition) exp).getRight() != null) { + result.add(((OContainsTextCondition) exp).getRight()); + } else { + throw new UnsupportedOperationException("Cannot execute index query with " + exp); + } + } else { throw new UnsupportedOperationException("Cannot execute index query with " + exp); } @@ -767,6 +780,8 @@ private OCollection indexKeyTo(OAndBlock keyCondition, OBinaryCondition addition throw new UnsupportedOperationException("Cannot execute index query with " + exp); } + } else if (exp instanceof OContainsTextCondition) { + result.add(((OContainsTextCondition) exp).getRight()); } else { throw new UnsupportedOperationException("Cannot execute index query with " + exp); } @@ -796,6 +811,8 @@ private boolean indexKeyFromIncluded(OAndBlock keyCondition, OBinaryCondition ad } else { return false; } + } else if (exp instanceof OContainsTextCondition){ + return true; } else { throw new UnsupportedOperationException("Cannot execute index query with " + exp); } @@ -844,7 +861,9 @@ private boolean indexKeyToIncluded(OAndBlock keyCondition, OBinaryCondition addi } else { return false; } - } else { + } else if(exp instanceof OContainsTextCondition){ + return true; + }else { throw new UnsupportedOperationException("Cannot execute index query with " + exp); } } diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/executor/OSelectExecutionPlanner.java b/core/src/main/java/com/orientechnologies/orient/core/sql/executor/OSelectExecutionPlanner.java index 6f167d20362..8e3b452e87f 100755 --- a/core/src/main/java/com/orientechnologies/orient/core/sql/executor/OSelectExecutionPlanner.java +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/executor/OSelectExecutionPlanner.java @@ -2157,6 +2157,15 @@ private IndexSearchDescriptor findBestIndexFor(OCommandContext ctx, Set buildIndexSearchDescriptor(ctx, index, block, clazz)).filter(Objects::nonNull) .filter(x -> x.keyCondition != null).filter(x -> x.keyCondition.getSubBlocks().size() > 0).collect(Collectors.toList()); + List fullTextIndexDescriptors = indexes.stream() + .filter(idx->idx.getType().equalsIgnoreCase("FULLTEXT")) + .filter(idx->!idx.getAlgorithm().equalsIgnoreCase("LUCENE")) + .map(idx -> buildIndexSearchDescriptorForFulltext(ctx, idx, block, clazz)).filter(Objects::nonNull) + .filter(x -> x.keyCondition != null).filter(x -> x.keyCondition.getSubBlocks().size() > 0).collect(Collectors.toList()); + + descriptors.addAll(fullTextIndexDescriptors); + + //remove the redundant descriptors (eg. if I have one on [a] and one on [a, b], the first one is redundant, just discard it) descriptors = removePrefixIndexes(descriptors); @@ -2418,6 +2427,73 @@ && isMap(clazz, indexField) && isIndexByValue(index, indexField)) { return null; } + + /** + * given a full text index and a flat AND block, returns a descriptor on how to process it with an index (index, index key and additional + * filters to apply after index fetch + * + * @param ctx + * @param index + * @param block + * @param clazz + * + * @return + */ + private IndexSearchDescriptor buildIndexSearchDescriptorForFulltext(OCommandContext ctx, OIndex index, OAndBlock block, OClass clazz) { + List indexFields = index.getDefinition().getFields(); + OBinaryCondition keyCondition = new OBinaryCondition(-1); + OIdentifier key = new OIdentifier("key"); + keyCondition.setLeft(new OExpression(key)); + boolean found = false; + + OAndBlock blockCopy = block.copy(); + Iterator blockIterator; + + OAndBlock indexKeyValue = new OAndBlock(-1); + IndexSearchDescriptor result = new IndexSearchDescriptor(); + result.idx = index; + result.keyCondition = indexKeyValue; + for (String indexField : indexFields) { + blockIterator = blockCopy.getSubBlocks().iterator(); + boolean breakHere = false; + boolean indexFieldFound = false; + while (blockIterator.hasNext()) { + OBooleanExpression singleExp = blockIterator.next(); + if (singleExp instanceof OContainsTextCondition) { + OExpression left = ((OContainsTextCondition) singleExp).getLeft(); + if (left.isBaseIdentifier()) { + String fieldName = left.getDefaultAlias().getStringValue(); + if (indexField.equals(fieldName)) { + found = true; + indexFieldFound = true; + OContainsTextCondition condition = new OContainsTextCondition(-1); + condition.setLeft(left); + condition.setRight(((OContainsTextCondition) singleExp).getRight().copy()); + indexKeyValue.getSubBlocks().add(condition); + blockIterator.remove(); + break; + } + } + } + } + if (breakHere || !indexFieldFound) { + break; + } + } + + if (result.keyCondition.getSubBlocks().size() < index.getDefinition().getFields().size() && !index + .supportsOrderedIterations()) { + //hash indexes do not support partial key match + return null; + } + + if (found) { + result.remainingCondition = blockCopy; + return result; + } + return null; + } + private boolean isIndexByKey(OIndex index, String field) { OIndexDefinition def = index.getDefinition(); for (String o : def.getFieldsToIndex()) { diff --git a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsTextCondition.java b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsTextCondition.java index 568887e45d6..4a59575db55 100644 --- a/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsTextCondition.java +++ b/core/src/main/java/com/orientechnologies/orient/core/sql/parser/OContainsTextCondition.java @@ -174,5 +174,21 @@ public boolean isCacheable() { } return true; } + + public void setLeft(OExpression left) { + this.left = left; + } + + public void setRight(OExpression right) { + this.right = right; + } + + public OExpression getLeft() { + return left; + } + + public OExpression getRight() { + return right; + } } /* JavaCC - OriginalChecksum=b588492ba2cbd0f932055f1f64bbbecd (do not edit this line) */