Author: thomasm
Date: Fri Apr 28 14:23:28 2017
New Revision: 1793097

URL: http://svn.apache.org/viewvc?rev=1793097&view=rev
Log:
OAK-4637 Property index: include/exclude key pattern list

Added:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePattern.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/PropertyIndex.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/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=1793097&r1=1793096&r2=1793097&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
 Fri Apr 28 14:23:28 2017
@@ -46,6 +46,11 @@ public interface IndexConstants {
     String ENTRY_COUNT_PROPERTY_NAME = "entryCount";
 
     String KEY_COUNT_PROPERTY_NAME = "keyCount";
+    
+    /**
+     * The regular expression pattern of the values to be indexes.
+     */
+    String VALUE_PATTERN = "valuePattern";
 
     /**
      * Marks a unique property index.

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java?rev=1793097&r1=1793096&r2=1793097&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java
 Fri Apr 28 14:23:28 2017
@@ -110,7 +110,7 @@ class PropertyIndex implements QueryInde
         this.mountInfoProvider = mountInfoProvider;
     }
 
-    static Set<String> encode(PropertyValue value) {
+    static Set<String> encode(PropertyValue value, ValuePattern pattern) {
         if (value == null) {
             return null;
         }
@@ -120,6 +120,9 @@ class PropertyIndex implements QueryInde
                 if (v.length() > MAX_STRING_LENGTH) {
                     v = v.substring(0, MAX_STRING_LENGTH);
                 }
+                if (!pattern.matches(v)) {
+                    continue;
+                }
                 if (v.isEmpty()) {
                     v = EMPTY_TOKEN;
                 } else {

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=1793097&r1=1793096&r2=1793097&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
 Fri Apr 28 14:23:28 2017
@@ -74,7 +74,9 @@ class PropertyIndexEditor implements Ind
     /** Root node state */
     private final NodeState root;
 
-     private final Set<String> propertyNames;
+    private final Set<String> propertyNames;
+    
+    private final ValuePattern valuePattern;
 
     /** Type predicate, or {@code null} if there are no type restrictions */
     private final Predicate<NodeState> typePredicate;
@@ -128,6 +130,7 @@ class PropertyIndexEditor implements Ind
         } else {
             this.propertyNames = newHashSet(names.getValue(NAMES));
         }
+        this.valuePattern = new 
ValuePattern(definition.getString(IndexConstants.VALUE_PATTERN));
 
         // get declaring types, and all their subtypes
         // TODO: should we reindex when type definitions change?
@@ -155,6 +158,7 @@ class PropertyIndexEditor implements Ind
         this.definition = parent.definition;
         this.root = parent.root;
         this.propertyNames = parent.getPropertyNames();
+        this.valuePattern = parent.valuePattern;
         this.typePredicate = parent.typePredicate;
         this.keysToCheckForUniqueness = parent.keysToCheckForUniqueness;
         this.updateCallback = parent.updateCallback;
@@ -193,24 +197,24 @@ class PropertyIndexEditor implements Ind
      * @return set of encoded values, possibly initialized
      */
     private static Set<String> addValueKeys(
-            Set<String> keys, PropertyState property) {
+            Set<String> keys, PropertyState property, ValuePattern pattern) {
         if (property.getType().tag() != PropertyType.BINARY
                 && property.count() > 0) {
             if (keys == null) {
                 keys = newHashSet();
             }
-            keys.addAll(encode(PropertyValues.create(property)));
+            keys.addAll(encode(PropertyValues.create(property), pattern));
         }
         return keys;
     }
 
     private static Set<String> getMatchingKeys(
-            NodeState state, Iterable<String> propertyNames) {
+            NodeState state, Iterable<String> propertyNames, ValuePattern 
pattern) {
         Set<String> keys = null;
         for (String propertyName : propertyNames) {
             PropertyState property = state.getProperty(propertyName);
             if (property != null) {
-                keys = addValueKeys(keys, property);
+                keys = addValueKeys(keys, property, pattern);
             }
         }
         return keys;
@@ -248,8 +252,8 @@ class PropertyIndexEditor implements Ind
             if (typeChanged) {
                 // possible type change, so ignore diff results and
                 // just load all matching values from both states
-                beforeKeys = getMatchingKeys(before, getPropertyNames());
-                afterKeys = getMatchingKeys(after, getPropertyNames());
+                beforeKeys = getMatchingKeys(before, getPropertyNames(), 
valuePattern);
+                afterKeys = getMatchingKeys(after, getPropertyNames(), 
valuePattern);
             }
             if (beforeKeys != null && !typePredicate.apply(before)) {
                 // the before state doesn't match the type, so clear its values
@@ -375,7 +379,7 @@ class PropertyIndexEditor implements Ind
         String name = after.getName();
         typeChanged = typeChanged || isTypeProperty(name);
         if (getPropertyNames().contains(name)) {
-            afterKeys = addValueKeys(afterKeys, after);
+            afterKeys = addValueKeys(afterKeys, after, valuePattern);
         }
     }
 
@@ -384,8 +388,8 @@ class PropertyIndexEditor implements Ind
         String name = after.getName();
         typeChanged = typeChanged || isTypeProperty(name);
         if (getPropertyNames().contains(name)) {
-            beforeKeys = addValueKeys(beforeKeys, before);
-            afterKeys = addValueKeys(afterKeys, after);
+            beforeKeys = addValueKeys(beforeKeys, before, valuePattern);
+            afterKeys = addValueKeys(afterKeys, after, valuePattern);
         }
     }
 
@@ -394,7 +398,7 @@ class PropertyIndexEditor implements Ind
         String name = before.getName();
         typeChanged = typeChanged || isTypeProperty(name);
         if (getPropertyNames().contains(name)) {
-            beforeKeys = addValueKeys(beforeKeys, before);
+            beforeKeys = addValueKeys(beforeKeys, before, valuePattern);
         }
     }
 

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=1793097&r1=1793096&r2=1793097&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
 Fri Apr 28 14:23:28 2017
@@ -123,9 +123,10 @@ 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));
         for (IndexStoreStrategy s : getStrategies(indexMeta)) {
             iterables.add(s.query(filter, propertyName, indexMeta,
-                    encode(value)));
+                    encode(value, pattern)));
         }
         return Iterables.concat(iterables);
     }
@@ -143,9 +144,10 @@ public class PropertyIndexLookup {
             return Double.POSITIVE_INFINITY;
         }
         Set<IndexStoreStrategy> strategies = getStrategies(indexMeta);
+        ValuePattern pattern = new 
ValuePattern(indexMeta.getString(IndexConstants.VALUE_PATTERN));
         double cost = strategies.isEmpty() ? MAX_COST : COST_OVERHEAD;
         for (IndexStoreStrategy s : strategies) {
-            cost += s.count(filter, root, indexMeta, encode(value), MAX_COST);
+            cost += s.count(filter, root, indexMeta, encode(value, pattern), 
MAX_COST);
         }
         return 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=1793097&r1=1793096&r2=1793097&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
 Fri Apr 28 14:23:28 2017
@@ -84,6 +84,8 @@ public class PropertyIndexPlan {
     private final PathFilter pathFilter;
 
     private final boolean unique;
+    
+    private final ValuePattern valuePattern;
 
     PropertyIndexPlan(String name, NodeState root, NodeState definition,
                       Filter filter){
@@ -96,6 +98,7 @@ 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;
@@ -142,7 +145,28 @@ public class PropertyIndexPlan {
                         // of the child node (well, we could, for some node 
types)
                         continue;
                     }
-                    Set<String> values = getValues(restriction);
+                    Set<String> values = getValues(restriction, new 
ValuePattern(null));
+                    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;
+                            }
+                        }
+                        // we have a value pattern, for example (a|b),
+                        // but we search (also) for 'c': can't match
+                        if (!allValuesMatches) {
+                            continue;
+                        }
+                    }
                     double cost = strategies.isEmpty() ? MAX_COST : 0;
                     for (IndexStoreStrategy strategy : strategies) {
                         cost += strategy.count(filter, root, definition,
@@ -172,18 +196,18 @@ public class PropertyIndexPlan {
         this.cost = COST_OVERHEAD + bestCost;
     }
 
-    private static Set<String> getValues(PropertyRestriction restriction) {
+    private static Set<String> getValues(PropertyRestriction restriction, 
ValuePattern pattern) {
         if (restriction.firstIncluding
                 && restriction.lastIncluding
                 && restriction.first != null
                 && restriction.first.equals(restriction.last)) {
             // "[property] = $value"
-            return encode(restriction.first);
+            return encode(restriction.first, pattern);
         } else if (restriction.list != null) {
             // "[property] IN (...)
             Set<String> values = newLinkedHashSet(); // keep order for testing
             for (PropertyValue value : restriction.list) {
-                values.addAll(encode(value));
+                values.addAll(encode(value, pattern));
             }
             return values;
         } else {

Added: 
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=1793097&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePattern.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/ValuePattern.java
 Fri Apr 28 14:23:28 2017
@@ -0,0 +1,41 @@
+/*
+ * 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 java.util.regex.Pattern;
+
+/**
+ * A value pattern.
+ */
+public class ValuePattern {
+    
+    private final Pattern pattern;
+    
+    public ValuePattern(String pattern) {
+        Pattern p = pattern == null ? null : Pattern.compile(pattern);
+        this.pattern = p;
+    }
+    
+    public boolean matches(String v) {
+        return pattern == null || pattern.matcher(v).matches();
+    }
+    
+    public boolean matchesAll() {
+        return pattern == null;
+    }
+
+}

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=1793097&r1=1793096&r2=1793097&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
 Fri Apr 28 14:23:28 2017
@@ -36,11 +36,14 @@ import static org.junit.Assert.assertFal
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Set;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
+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;
@@ -454,6 +457,64 @@ public class PropertyIndexTest {
             // expected: no index for "pqr"
         }
     }
+    
+    @Test
+    public void valuePattern() 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_PATTERN, "(a.*|b)");
+        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"
+        assertTrue(pIndex.getCost(f, indexed) == Double.POSITIVE_INFINITY);
+        
+        ArrayList<PropertyValue> list = new ArrayList<PropertyValue>();
+        list.add(PropertyValues.newString("c"));
+        f.restrictPropertyAsList("foo", list);
+        // expected: no index for value c
+        assertTrue(pIndex.getCost(f, indexed) == Double.POSITIVE_INFINITY);
+
+        f = createFilter(after, NT_UNSTRUCTURED);
+        list = new ArrayList<PropertyValue>();
+        list.add(PropertyValues.newString("a"));
+        f.restrictPropertyAsList("foo", list);
+        // expected: no index for value a
+        assertTrue(pIndex.getCost(f, indexed) < Double.POSITIVE_INFINITY);
+
+    }    
 
     @Test(expected = CommitFailedException.class)
     public void testUnique() throws Exception {


Reply via email to