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"));
+        
+    }
+    
+    
+}