You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by th...@apache.org on 2017/06/14 10:12:33 UTC
svn commit: r1798661 - in /jackrabbit/oak/trunk/oak-core/src:
main/java/org/apache/jackrabbit/oak/plugins/index/
main/java/org/apache/jackrabbit/oak/plugins/index/property/
test/java/org/apache/jackrabbit/oak/plugins/index/property/
Author: thomasm
Date: Wed Jun 14 10:12:32 2017
New Revision: 1798661
URL: http://svn.apache.org/viewvc?rev=1798661&view=rev
Log:
OAK-4637 Property index: include/exclude key pattern list
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePatternTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePattern.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java?rev=1798661&r1=1798660&r2=1798661&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java Wed Jun 14 10:12:32 2017
@@ -53,6 +53,17 @@ public interface IndexConstants {
String VALUE_PATTERN = "valuePattern";
/**
+ * A list of prefixes to be excluded from the index.
+ */
+ String VALUE_EXCLUDED_PREFIXES = "valueExcludedPrefixes";
+
+ /**
+ * A list of prefixes to be included from the index.
+ * Include has higher priority than exclude.
+ */
+ String VALUE_INCLUDED_PREFIXES = "valueIncludedPrefixes";
+
+ /**
* Marks a unique property index.
*/
String UNIQUE_PROPERTY_NAME = "unique";
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java?rev=1798661&r1=1798660&r2=1798661&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java Wed Jun 14 10:12:32 2017
@@ -130,7 +130,7 @@ class PropertyIndexEditor implements Ind
} else {
this.propertyNames = newHashSet(names.getValue(NAMES));
}
- this.valuePattern = new ValuePattern(definition.getString(IndexConstants.VALUE_PATTERN));
+ this.valuePattern = new ValuePattern(definition);
// get declaring types, and all their subtypes
// TODO: should we reindex when type definitions change?
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java?rev=1798661&r1=1798660&r2=1798661&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java Wed Jun 14 10:12:32 2017
@@ -123,7 +123,7 @@ public class PropertyIndexLookup {
throw new IllegalArgumentException("No index for " + propertyName);
}
List<Iterable<String>> iterables = Lists.newArrayList();
- ValuePattern pattern = new ValuePattern(indexMeta.getString(IndexConstants.VALUE_PATTERN));
+ ValuePattern pattern = new ValuePattern(indexMeta);
for (IndexStoreStrategy s : getStrategies(indexMeta)) {
iterables.add(s.query(filter, propertyName, indexMeta,
encode(value, pattern)));
@@ -144,7 +144,7 @@ public class PropertyIndexLookup {
return Double.POSITIVE_INFINITY;
}
Set<IndexStoreStrategy> strategies = getStrategies(indexMeta);
- ValuePattern pattern = new ValuePattern(indexMeta.getString(IndexConstants.VALUE_PATTERN));
+ ValuePattern pattern = new ValuePattern(indexMeta);
double cost = strategies.isEmpty() ? MAX_COST : COST_OVERHEAD;
for (IndexStoreStrategy s : strategies) {
cost += s.count(filter, root, indexMeta, encode(value, pattern), MAX_COST);
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java?rev=1798661&r1=1798660&r2=1798661&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java Wed Jun 14 10:12:32 2017
@@ -26,10 +26,12 @@ import static org.apache.jackrabbit.oak.
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.PROPERTY_NAMES;
import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.encode;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
import org.apache.jackrabbit.oak.plugins.index.PathFilter;
@@ -85,8 +87,6 @@ public class PropertyIndexPlan {
private final boolean unique;
- private final ValuePattern valuePattern;
-
PropertyIndexPlan(String name, NodeState root, NodeState definition,
Filter filter){
this(name, root, definition, filter, Mounts.defaultMountInfoProvider());
@@ -98,7 +98,6 @@ public class PropertyIndexPlan {
this.unique = definition.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME);
this.definition = definition;
this.properties = newHashSet(definition.getNames(PROPERTY_NAMES));
- this.valuePattern = new ValuePattern(definition.getString(IndexConstants.VALUE_PATTERN));
pathFilter = PathFilter.from(definition.builder());
this.strategies = getStrategies(definition, mountInfoProvider);
this.filter = filter;
@@ -109,6 +108,8 @@ public class PropertyIndexPlan {
this.matchesNodeTypes =
matchesAllTypes || any(types, in(filter.getSupertypes()));
+ ValuePattern valuePattern = new ValuePattern(definition);
+
double bestCost = Double.POSITIVE_INFINITY;
Set<String> bestValues = emptySet();
int bestDepth = 1;
@@ -145,25 +146,21 @@ public class PropertyIndexPlan {
// of the child node (well, we could, for some node types)
continue;
}
- Set<String> values = getValues(restriction, new ValuePattern(null));
+ Set<String> values = getValues(restriction, new ValuePattern());
if (valuePattern.matchesAll()) {
// matches all values: not a problem
} else if (values == null) {
// "is not null" condition, but we have a value pattern
// that doesn't match everything
- // so we can't use that index
- continue;
- } else {
- boolean allValuesMatches = true;
- for (String v : values) {
- if (!valuePattern.matches(v)) {
- allValuesMatches = false;
- break;
- }
+ String prefix = getLongestPrefix(filter, property);
+ if (!valuePattern.matchesPrefix(prefix)) {
+ // region match which is not fully in the pattern
+ continue;
}
+ } else {
// we have a value pattern, for example (a|b),
// but we search (also) for 'c': can't match
- if (!allValuesMatches) {
+ if (!valuePattern.matchesAll(values)) {
continue;
}
}
@@ -196,6 +193,50 @@ public class PropertyIndexPlan {
this.cost = COST_OVERHEAD + bestCost;
}
+ /**
+ * Get the longest prefix of restrictions on a property.
+ *
+ * @param filter the filter with all restrictions
+ * @param property the property
+ * @return the longest prefix, or null if none
+ */
+ public static String getLongestPrefix(Filter filter, String property) {
+ boolean first = false, last = false;
+ List<String> list = new ArrayList<>();
+ for(PropertyRestriction p : filter.getPropertyRestrictions(property)) {
+ if (p.isLike) {
+ continue;
+ }
+ if (p.first != null) {
+ if (p.first.isArray()) {
+ return null;
+ }
+ list.add(p.first.getValue(Type.STRING));
+ first = true;
+ }
+ if (p.last != null) {
+ if (p.last.isArray()) {
+ return null;
+ }
+ list.add(p.last.getValue(Type.STRING));
+ last = true;
+ }
+ }
+ if (!first || !last) {
+ return null;
+ }
+ String prefix = list.get(0);
+ for (String s : list) {
+ while (!s.startsWith(prefix)) {
+ prefix = prefix.substring(0, prefix.length() - 1);
+ if (prefix.isEmpty()) {
+ return null;
+ }
+ }
+ }
+ return prefix;
+ }
+
private static Set<String> getValues(PropertyRestriction restriction, ValuePattern pattern) {
if (restriction.firstIncluding
&& restriction.lastIncluding
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePattern.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePattern.java?rev=1798661&r1=1798660&r2=1798661&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePattern.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePattern.java Wed Jun 14 10:12:32 2017
@@ -16,26 +16,142 @@
*/
package org.apache.jackrabbit.oak.plugins.index.property;
+import java.util.Collections;
+import java.util.Set;
import java.util.regex.Pattern;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
/**
* A value pattern.
*/
public class ValuePattern {
private final Pattern pattern;
+ private final Iterable<String> includePrefixes;
+ private final Iterable<String> excludePrefixes;
+
+ public ValuePattern(NodeBuilder node) {
+ this(node.getString(IndexConstants.VALUE_PATTERN),
+ getStrings(node, IndexConstants.VALUE_INCLUDED_PREFIXES),
+ getStrings(node, IndexConstants.VALUE_EXCLUDED_PREFIXES));
+ }
+
+ public ValuePattern(NodeState node) {
+ this(node.getString(IndexConstants.VALUE_PATTERN),
+ getStrings(node, IndexConstants.VALUE_INCLUDED_PREFIXES),
+ getStrings(node, IndexConstants.VALUE_EXCLUDED_PREFIXES));
+ }
- public ValuePattern(String pattern) {
+ public ValuePattern() {
+ this(null, null, null);
+ }
+
+ public ValuePattern(String pattern,
+ Iterable<String> includePrefixes, Iterable<String> excludePrefixes) {
Pattern p = pattern == null ? null : Pattern.compile(pattern);
+ this.includePrefixes = includePrefixes;
+ this.excludePrefixes = excludePrefixes;
this.pattern = p;
}
public boolean matches(String v) {
+ if (v == null) {
+ return true;
+ }
+ if (includePrefixes != null) {
+ for(String inc : includePrefixes) {
+ if (v.startsWith(inc)) {
+ return true;
+ }
+ }
+ }
+ if (excludePrefixes != null) {
+ for(String exc : excludePrefixes) {
+ if (v.startsWith(exc) || exc.startsWith(v)) {
+ return false;
+ }
+ }
+ }
+ if (includePrefixes != null && pattern == null) {
+ // we have include prefixes and no pattern
+ return false;
+ }
return pattern == null || pattern.matcher(v).matches();
}
public boolean matchesAll() {
- return pattern == null;
+ return includePrefixes == null && excludePrefixes == null && pattern == null;
+ }
+
+ public static Iterable<String> getStrings(NodeBuilder node, String propertyName) {
+ if (!node.hasProperty(propertyName)) {
+ return null;
+ }
+ PropertyState s = node.getProperty(propertyName);
+ if (s.isArray()) {
+ return node.getProperty(propertyName).getValue(Type.STRINGS);
+ }
+ return Collections.singleton(node.getString(propertyName));
+ }
+
+ public static Iterable<String> getStrings(NodeState node, String propertyName) {
+ if (!node.hasProperty(propertyName)) {
+ return null;
+ }
+ PropertyState s = node.getProperty(propertyName);
+ if (s.isArray()) {
+ return node.getStrings(propertyName);
+ }
+ return Collections.singleton(node.getString(propertyName));
+ }
+
+ public boolean matchesAll(Set<String> values) {
+ if (matchesAll() || values == null) {
+ return true;
+ }
+ for (String v : values) {
+ if (!matches(v)) {
+ return false;
+ }
+ }
+ return true;
}
+ public boolean matchesPrefix(String prefix) {
+ if (pattern != null) {
+ // with a regular expression pattern, we don't know
+ return false;
+ }
+ if (includePrefixes == null && excludePrefixes == null) {
+ // no includes and excludes
+ return true;
+ }
+ if (prefix == null || prefix.isEmpty()) {
+ // we just have "> x" or "< y":
+ // comparison is not supported
+ return false;
+ }
+ if (includePrefixes != null) {
+ for(String inc : includePrefixes) {
+ if (prefix.startsWith(inc)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if (excludePrefixes != null) {
+ for(String exc : excludePrefixes) {
+ if (prefix.startsWith(exc) || exc.startsWith(prefix)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
}
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java?rev=1798661&r1=1798660&r2=1798661&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java Wed Jun 14 10:12:32 2017
@@ -41,12 +41,14 @@ import java.util.Arrays;
import java.util.Set;
import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.PropertyValue;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider;
import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy;
import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
import org.apache.jackrabbit.oak.plugins.memory.PropertyValues;
import org.apache.jackrabbit.oak.query.NodeStateNodeTypeInfoProvider;
import org.apache.jackrabbit.oak.query.ast.NodeTypeInfo;
@@ -515,6 +517,96 @@ public class PropertyIndexTest {
assertTrue(pIndex.getCost(f, indexed) < Double.POSITIVE_INFINITY);
}
+
+ @Test
+ public void valuePatternExclude() throws Exception {
+ NodeState root = EMPTY_NODE;
+ // Add index definitions
+ NodeBuilder builder = root.builder();
+ NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME);
+ NodeBuilder indexDef = createIndexDefinition(
+ index, "fooIndex", true, false,
+ ImmutableSet.of("foo"), null);
+ indexDef.setProperty(IndexConstants.VALUE_EXCLUDED_PREFIXES, "test");
+ valuePatternExclude0(builder);
+ }
+
+ @Test
+ public void valuePatternExclude2() throws Exception {
+ NodeState root = EMPTY_NODE;
+ // Add index definitions
+ NodeBuilder builder = root.builder();
+ NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME);
+ NodeBuilder indexDef = createIndexDefinition(
+ index, "fooIndex", true, false,
+ ImmutableSet.of("foo"), null);
+ PropertyState ps = PropertyStates.createProperty(
+ IndexConstants.VALUE_EXCLUDED_PREFIXES,
+ Arrays.asList("test"),
+ Type.STRINGS);
+ indexDef.setProperty(ps);
+ valuePatternExclude0(builder);
+ }
+
+ private void valuePatternExclude0(NodeBuilder builder) throws CommitFailedException {
+ NodeState before = builder.getNodeState();
+
+ // Add some content and process it through the property index hook
+ builder.child("a")
+ .setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, Type.NAME)
+ .setProperty("foo", "a");
+ builder.child("a1")
+ .setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, Type.NAME)
+ .setProperty("foo", "a1");
+ builder.child("b")
+ .setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, Type.NAME)
+ .setProperty("foo", "b");
+ builder.child("c")
+ .setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, Type.NAME)
+ .setProperty("foo", "c");
+ NodeState after = builder.getNodeState();
+
+ // Add an index
+ NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY);
+
+ FilterImpl f = createFilter(after, NT_UNSTRUCTURED);
+
+ // Query the index
+ PropertyIndexLookup lookup = new PropertyIndexLookup(indexed);
+ PropertyIndex pIndex = new PropertyIndex(Mounts.defaultMountInfoProvider());
+ assertEquals(ImmutableSet.of("a"), find(lookup, "foo", "a", f));
+ assertEquals(ImmutableSet.of("a1"), find(lookup, "foo", "a1", f));
+ assertEquals(ImmutableSet.of("b"), find(lookup, "foo", "b", f));
+
+ // expected: no index for "is not null", "= 'test'", "like 't%'"
+ assertTrue(pIndex.getCost(f, indexed) == Double.POSITIVE_INFINITY);
+ f.restrictProperty("foo", Operator.EQUAL, PropertyValues.newString("test"));
+ assertTrue(pIndex.getCost(f, indexed) == Double.POSITIVE_INFINITY);
+ f = createFilter(after, NT_UNSTRUCTURED);
+ f.restrictProperty("foo", Operator.LIKE, PropertyValues.newString("t%"));
+ assertTrue(pIndex.getCost(f, indexed) == Double.POSITIVE_INFINITY);
+ f = createFilter(after, NT_UNSTRUCTURED);
+
+
+ // expected: index for "like 'a%'"
+ f.restrictProperty("foo", Operator.GREATER_OR_EQUAL, PropertyValues.newString("a"));
+ f.restrictProperty("foo", Operator.LESS_OR_EQUAL, PropertyValues.newString("a0"));
+ assertTrue(pIndex.getCost(f, indexed) < Double.POSITIVE_INFINITY);
+ f = createFilter(after, NT_UNSTRUCTURED);
+
+ // expected: index for value c
+ ArrayList<PropertyValue> list = new ArrayList<PropertyValue>();
+ list.add(PropertyValues.newString("c"));
+ f.restrictPropertyAsList("foo", list);
+ assertTrue(pIndex.getCost(f, indexed) < Double.POSITIVE_INFINITY);
+
+ // expected: index for value a
+ f = createFilter(after, NT_UNSTRUCTURED);
+ list = new ArrayList<PropertyValue>();
+ list.add(PropertyValues.newString("a"));
+ f.restrictPropertyAsList("foo", list);
+ assertTrue(pIndex.getCost(f, indexed) < Double.POSITIVE_INFINITY);
+ }
@Test(expected = CommitFailedException.class)
public void testUnique() throws Exception {
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePatternTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePatternTest.java?rev=1798661&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePatternTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePatternTest.java Wed Jun 14 10:12:32 2017
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+package org.apache.jackrabbit.oak.plugins.index.property;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyValues;
+import org.apache.jackrabbit.oak.query.ast.Operator;
+import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Test;
+
+import com.google.common.collect.Lists;
+
+public class ValuePatternTest {
+
+ @Test
+ public void getStringsBuilder() {
+ NodeBuilder b = new MemoryNodeBuilder(EmptyNodeState.EMPTY_NODE);
+ assertNull(ValuePattern.getStrings(b, "x"));
+
+ b.setProperty("x", "");
+ assertEquals("[]", ValuePattern.getStrings(b, "x").toString());
+
+ b.setProperty("x", "test");
+ assertEquals("[test]", ValuePattern.getStrings(b, "x").toString());
+
+ PropertyState ps = PropertyStates.createProperty(
+ "x",
+ Arrays.asList("hello"),
+ Type.STRINGS);
+ b.setProperty(ps);
+ assertEquals("[hello]", ValuePattern.getStrings(b, "x").toString());
+
+ ps = PropertyStates.createProperty(
+ "x",
+ Arrays.asList(),
+ Type.STRINGS);
+ b.setProperty(ps);
+ assertEquals("[]", ValuePattern.getStrings(b, "x").toString());
+
+ ps = PropertyStates.createProperty(
+ "x",
+ Arrays.asList("a", "b"),
+ Type.STRINGS);
+ b.setProperty(ps);
+ assertEquals("[a, b]", ValuePattern.getStrings(b, "x").toString());
+ }
+
+ @Test
+ public void getStringsState() {
+ NodeBuilder b = new MemoryNodeBuilder(EmptyNodeState.EMPTY_NODE);
+ assertNull(ValuePattern.getStrings(b.getNodeState(), "x"));
+
+ b.setProperty("x", "");
+ assertEquals("[]", ValuePattern.getStrings(b.getNodeState(), "x").toString());
+
+ b.setProperty("x", "test");
+ assertEquals("[test]", ValuePattern.getStrings(b.getNodeState(), "x").toString());
+
+ PropertyState ps = PropertyStates.createProperty(
+ "x",
+ Arrays.asList("hello"),
+ Type.STRINGS);
+ b.setProperty(ps);
+ assertEquals("[hello]", ValuePattern.getStrings(b.getNodeState(), "x").toString());
+
+ ps = PropertyStates.createProperty(
+ "x",
+ Arrays.asList(),
+ Type.STRINGS);
+ b.setProperty(ps);
+ assertEquals("[]", ValuePattern.getStrings(b.getNodeState(), "x").toString());
+
+ ps = PropertyStates.createProperty(
+ "x",
+ Arrays.asList("a", "b"),
+ Type.STRINGS);
+ b.setProperty(ps);
+ assertEquals("[a, b]", ValuePattern.getStrings(b.getNodeState(), "x").toString());
+ }
+
+ @Test
+ public void empty() {
+ ValuePattern vp = new ValuePattern();
+ assertTrue(vp.matches(null));
+ assertTrue(vp.matches("x"));
+ assertTrue(vp.matchesAll());
+ assertTrue(vp.matchesAll(null));
+ assertTrue(vp.matchesAll(Collections.emptySet()));
+ assertTrue(vp.matchesAll(Collections.singleton("x")));
+ assertTrue(vp.matchesAll(new HashSet<String>(Arrays.asList("x", "y"))));
+ assertTrue(vp.matchesPrefix(null));
+ assertTrue(vp.matchesPrefix(""));
+ assertTrue(vp.matchesPrefix("x"));
+ }
+
+ @Test
+ public void regex() {
+ ValuePattern vp = new ValuePattern("x.*", null, null);
+ assertTrue(vp.matches(null));
+ assertTrue(vp.matches("x"));
+ assertFalse(vp.matches("y"));
+ assertFalse(vp.matchesAll());
+ assertTrue(vp.matchesAll(null));
+ assertTrue(vp.matchesAll(Collections.emptySet()));
+ assertTrue(vp.matchesAll(Collections.singleton("x")));
+ assertFalse(vp.matchesAll(Collections.singleton("y")));
+ assertTrue(vp.matchesAll(new HashSet<String>(Arrays.asList("x1", "x2"))));
+ assertFalse(vp.matchesAll(new HashSet<String>(Arrays.asList("x1", "y2"))));
+ assertFalse(vp.matchesPrefix(null));
+ assertFalse(vp.matchesPrefix(""));
+ // unkown, as we don't do regular expression analysis
+ assertFalse(vp.matchesPrefix("x"));
+ }
+
+ @Test
+ public void included() {
+ ValuePattern vp = new ValuePattern(null, Lists.newArrayList("abc", "bcd"), null);
+ assertTrue(vp.matches(null));
+ assertTrue(vp.matches("abc1"));
+ assertTrue(vp.matches("bcd"));
+ assertFalse(vp.matches("y"));
+ assertFalse(vp.matchesAll());
+ assertTrue(vp.matchesAll(null));
+ assertTrue(vp.matchesAll(Collections.emptySet()));
+ assertTrue(vp.matchesAll(Collections.singleton("abc0")));
+ assertFalse(vp.matchesAll(Collections.singleton("c")));
+ assertTrue(vp.matchesAll(new HashSet<String>(Arrays.asList("abc1", "bcd1"))));
+ assertFalse(vp.matchesAll(new HashSet<String>(Arrays.asList("abc1", "c2"))));
+ assertFalse(vp.matchesPrefix(null));
+ assertFalse(vp.matchesPrefix(""));
+ assertFalse(vp.matchesPrefix("hello"));
+ assertTrue(vp.matchesPrefix("abcdef"));
+ assertFalse(vp.matchesPrefix("a"));
+ }
+
+ @Test
+ public void excluded() {
+ ValuePattern vp = new ValuePattern(null, null, Lists.newArrayList("abc", "bcd"));
+ assertTrue(vp.matches(null));
+ assertFalse(vp.matches("abc1"));
+ assertFalse(vp.matches("bcd"));
+ assertTrue(vp.matches("y"));
+ assertFalse(vp.matchesAll());
+ assertTrue(vp.matchesAll(null));
+ assertTrue(vp.matchesAll(Collections.emptySet()));
+ assertFalse(vp.matchesAll(Collections.singleton("abc0")));
+ assertTrue(vp.matchesAll(Collections.singleton("c")));
+ assertFalse(vp.matchesAll(new HashSet<String>(Arrays.asList("abc1", "bcd1"))));
+ assertFalse(vp.matchesAll(new HashSet<String>(Arrays.asList("abc1", "c2"))));
+ assertTrue(vp.matchesAll(new HashSet<String>(Arrays.asList("c2", "d2"))));
+ assertFalse(vp.matchesPrefix(null));
+ assertTrue(vp.matchesPrefix("hello"));
+ assertFalse(vp.matchesPrefix("abcdef"));
+ assertFalse(vp.matchesPrefix("a"));
+ }
+
+ @Test
+ public void longestPrefix() {
+ FilterImpl filter;
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.EQUAL,
+ PropertyValues.newString("hello"));
+ assertEquals("hello", PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.GREATER_OR_EQUAL,
+ PropertyValues.newString("hello welt"));
+ filter.restrictProperty("x", Operator.LESS_OR_EQUAL,
+ PropertyValues.newString("hello world"));
+ assertEquals("hello w", PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.GREATER_THAN,
+ PropertyValues.newString("hello welt"));
+ filter.restrictProperty("x", Operator.LESS_THAN,
+ PropertyValues.newString("hello world"));
+ assertEquals("hello w", PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.GREATER_THAN,
+ PropertyValues.newString("hello welt"));
+ filter.restrictProperty("x", Operator.GREATER_THAN,
+ PropertyValues.newString("hello welt!"));
+ filter.restrictProperty("x", Operator.LESS_THAN,
+ PropertyValues.newString("hello world"));
+ filter.restrictProperty("x", Operator.EQUAL,
+ PropertyValues.newString("hell"));
+ assertEquals("hell", PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.GREATER_THAN,
+ PropertyValues.newString("abc"));
+ filter.restrictProperty("x", Operator.LESS_THAN,
+ PropertyValues.newString("bde"));
+ assertNull(PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.GREATER_THAN,
+ PropertyValues.newString("abc"));
+ filter.restrictProperty("x", Operator.GREATER_THAN,
+ PropertyValues.newString("bcd"));
+ filter.restrictProperty("x", Operator.LESS_THAN,
+ PropertyValues.newString("dce"));
+ assertNull(PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.LIKE,
+ PropertyValues.newString("hello%"));
+ assertNull(PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.GREATER_OR_EQUAL,
+ PropertyValues.newString("hello"));
+ assertNull(PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.LESS_THAN,
+ PropertyValues.newString("hello"));
+ assertNull(PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.NOT_EQUAL, null);
+ assertNull(PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.GREATER_THAN,
+ PropertyValues.newString(Arrays.asList("a0", "a1")));
+ filter.restrictProperty("x", Operator.LESS_THAN,
+ PropertyValues.newString("a2"));
+ assertNull(PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ filter = new FilterImpl(null, null, null);
+ filter.restrictProperty("x", Operator.GREATER_THAN,
+ PropertyValues.newString("a0"));
+ filter.restrictProperty("x", Operator.LESS_THAN,
+ PropertyValues.newString(Arrays.asList("a3", "a4")));
+ assertNull(PropertyIndexPlan.getLongestPrefix(filter, "x"));
+
+ }
+
+
+}