jackrabbit-oak-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From thom...@apache.org
Subject svn commit: r1329667 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/mk/index/ main/java/org/apache/jackrabbit/oak/api/ main/java/org/apache/jackrabbit/oak/core/ main/java/org/apache/jackrabbit/oak/query/ main/java/org/apache/j...
Date Tue, 24 Apr 2012 12:17:28 GMT
Author: thomasm
Date: Tue Apr 24 12:17:27 2012
New Revision: 1329667

URL: http://svn.apache.org/viewvc?rev=1329667&view=rev
Log:
OAK-28 Query implementation (index provider, property index)

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PropertyContentIndex.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndexProvider.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/BTree.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Index.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/IndexWrapper.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Indexer.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PropertyIndex.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/KernelContentRepository.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/Filter.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndex.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PrefixIndexTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PropertyIndexTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/FilterTest.java
    jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/queryTest.txt

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/BTree.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/BTree.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/BTree.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/BTree.java
Tue Apr 24 12:17:27 2012
@@ -288,4 +288,8 @@ public class BTree {
         return minSize + minSize + 1;
     }
 
+    boolean isUnique() {
+        return unique;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Index.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Index.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Index.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Index.java
Tue Apr 24 12:17:27 2012
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.mk.index;
 
+import java.util.Iterator;
 import org.apache.jackrabbit.mk.simple.NodeImpl;
 
 /**
@@ -51,4 +52,21 @@ public interface Index {
     void addOrRemoveProperty(String nodePath, String propertyName,
             String value, boolean add);
 
+    /**
+     * Get an iterator over the paths for the given value. For unique
+     * indexes, the iterator will contain at most one element.
+     *
+     * @param value the value, or null to return all indexed rows
+     * @param revision the revision
+     * @return an iterator of the paths (an empty iterator if not found)
+     */
+    Iterator<String> getPaths(String value, String revision);
+
+    /**
+     * Whether each value may only appear once in the index.
+     *
+     * @return true if unique
+     */
+    boolean isUnique();
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/IndexWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/IndexWrapper.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/IndexWrapper.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/IndexWrapper.java
Tue Apr 24 12:17:27 2012
@@ -50,6 +50,7 @@ public class IndexWrapper extends MicroK
     public IndexWrapper(MicroKernel mk) {
         this.mk = MicroKernelWrapperBase.wrap(mk);
         this.indexer = new Indexer(mk);
+        indexer.init();
     }
 
     @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Indexer.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Indexer.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Indexer.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Indexer.java
Tue Apr 24 12:17:27 2012
@@ -26,17 +26,21 @@ import org.apache.jackrabbit.mk.simple.N
 import org.apache.jackrabbit.mk.util.ExceptionFactory;
 import org.apache.jackrabbit.mk.util.PathUtils;
 import org.apache.jackrabbit.mk.util.SimpleLRUCache;
+import org.apache.jackrabbit.oak.query.index.PropertyContentIndex;
+import org.apache.jackrabbit.oak.query.index.QueryIndex;
+import org.apache.jackrabbit.oak.query.index.QueryIndexProvider;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map.Entry;
 
 /**
  * A index mechanism. An index is bound to a certain repository, and supports
  * one or more indexes.
  */
-public class Indexer {
+public class Indexer implements QueryIndexProvider {
 
     private static final boolean DISABLED = Boolean.getBoolean("mk.indexDisabled");
 
@@ -48,18 +52,33 @@ public class Indexer {
     private HashMap<String, BTreePage> modified = new HashMap<String, BTreePage>();
     private SimpleLRUCache<String, BTreePage> cache = SimpleLRUCache.newInstance(100);
     private String readRevision;
+    private ArrayList<QueryIndexListener> listeners = new ArrayList<QueryIndexListener>();
 
     public Indexer(MicroKernel mk, String indexRootNode) {
         this.mk = mk;
+        this.indexRootNode = indexRootNode;
+    }
+
+    public Indexer(MicroKernel mk) {
+        this(mk, "/index");
+    }
+
+    @Override
+    public void init() {
         if (!PathUtils.isAbsolute(indexRootNode)) {
             indexRootNode = "/" + indexRootNode;
         }
-        this.indexRootNode = indexRootNode;
         revision = mk.getHeadRevision();
         readRevision = revision;
         if (!mk.nodeExists(indexRootNode, revision)) {
             JsopBuilder jsop = new JsopBuilder();
-            jsop.tag('+').key(PathUtils.relativize("/", indexRootNode)).object().endObject();
+            String p = "/";
+            for (String e : PathUtils.elements(indexRootNode)) {
+                p = PathUtils.concat(p, e);
+                if (!mk.nodeExists(p, revision)) {
+                    jsop.tag('+').key(PathUtils.relativize("/", p)).object().endObject().newline();
+                }
+            }
             revision = mk.commit("/", jsop.toString(), revision, null);
         } else {
             String node = mk.getNodes(indexRootNode, revision, 0, 0, Integer.MAX_VALUE, null);
@@ -89,10 +108,6 @@ public class Indexer {
         }
     }
 
-    public Indexer(MicroKernel mk) {
-        this(mk, "/index");
-    }
-
     public PropertyIndex createPropertyIndex(String property, boolean unique) {
         PropertyIndex index = new PropertyIndex(this, property, unique);
         PropertyIndex existing = (PropertyIndex) indexes.get(index.getName());
@@ -100,6 +115,9 @@ public class Indexer {
             return existing;
         }
         buildAndAddIndex(index);
+        for (QueryIndexListener l : listeners) {
+            l.added(new PropertyContentIndex(mk, index));
+        }
         return index;
     }
 
@@ -490,4 +508,28 @@ public class Indexer {
         }
     }
 
+    public List<QueryIndex> getQueryIndexes() {
+        ArrayList<QueryIndex> list = new ArrayList<QueryIndex>();
+        for (Index index : indexes.values()) {
+            QueryIndex qi = null;
+            if (index instanceof PropertyIndex) {
+                qi = new PropertyContentIndex(mk, (PropertyIndex) index);
+            } else if (index instanceof PrefixIndex) {
+                // TODO support prefix indexes?
+            }
+            list.add(qi);
+        }
+        return list;
+    }
+
+    @Override
+    public void addListener(QueryIndexListener listener) {
+        listeners.add(listener);
+    }
+
+    @Override
+    public void removeListener(QueryIndexListener listener) {
+        listeners.remove(listener);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java
Tue Apr 24 12:17:27 2012
@@ -108,6 +108,7 @@ public class PrefixIndex implements Inde
      * @return an iterator of the paths (an empty iterator if not found)
      * @throws IllegalArgumentException if the value doesn't start with the prefix
      */
+    @Override
     public Iterator<String> getPaths(String value, String revision) {
         if (!value.startsWith(prefix)) {
             throw new IllegalArgumentException(
@@ -119,4 +120,9 @@ public class PrefixIndex implements Inde
         return new Cursor.RangeIterator(c, v);
     }
 
+    @Override
+    public boolean isUnique() {
+        return tree.isUnique();
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PropertyIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PropertyIndex.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PropertyIndex.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/PropertyIndex.java
Tue Apr 24 12:17:27 2012
@@ -119,10 +119,16 @@ public class PropertyIndex implements In
      * @param revision the revision
      * @return an iterator of the paths (an empty iterator if not found)
      */
+    @Override
     public Iterator<String> getPaths(String propertyValue, String revision) {
         indexer.updateUntil(revision);
         Cursor c = tree.findFirst(propertyValue);
         return new Cursor.RangeIterator(c, propertyValue);
     }
 
+    @Override
+    public boolean isUnique() {
+        return tree.isUnique();
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java
Tue Apr 24 12:17:27 2012
@@ -28,6 +28,11 @@ import java.util.Map;
 public interface QueryEngine {
 
     /**
+     * Initialize the query engine. This includes reading the list of indexes.
+     */
+    void init();
+
+    /**
      * Parse the query (check if it's valid) and get the list of bind variable names.
      *
      * @param statement
@@ -48,4 +53,9 @@ public interface QueryEngine {
      */
     Result executeQuery(String statement, String language, Map<String, CoreValue> bindings)
throws ParseException;
 
+    /**
+     * Close the query engine.
+     */
+    void close();
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/KernelContentRepository.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/KernelContentRepository.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/KernelContentRepository.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/core/KernelContentRepository.java
Tue Apr 24 12:17:27 2012
@@ -47,13 +47,18 @@ public class KernelContentRepository imp
     private static final String DEFAULT_WORKSPACE_NAME = "default";
 
     private final MicroKernel microKernel;
+    private final CoreValueFactory valueFactory;
+    private final QueryEngine queryEngine;
     private final KernelNodeStore nodeStore;
 
     public KernelContentRepository(MicroKernel mk) {
         microKernel = mk;
         nodeStore = new KernelNodeStore(microKernel);
+        valueFactory = new CoreValueFactoryImpl(microKernel);
+        queryEngine = new QueryEngineImpl(microKernel, valueFactory);
 
         // FIXME: workspace setup must be done elsewhere...
+        queryEngine.init();
         NodeState root = nodeStore.getRoot();
         NodeState wspNode = root.getChildNode(DEFAULT_WORKSPACE_NAME);
         if (wspNode == null) {
@@ -87,9 +92,6 @@ public class KernelContentRepository imp
             throw new LoginException("login failed");
         }
 
-        CoreValueFactory valueFactory = new CoreValueFactoryImpl(microKernel);
-
-        QueryEngine queryEngine = new QueryEngineImpl(microKernel, valueFactory);
         // TODO set revision!?
         NodeState wspRoot = nodeStore.getRoot().getChildNode(workspaceName);
         if (wspRoot == null) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java
Tue Apr 24 12:17:27 2012
@@ -46,6 +46,8 @@ import org.apache.jackrabbit.oak.query.a
 import org.apache.jackrabbit.oak.query.ast.SelectorImpl;
 import org.apache.jackrabbit.oak.query.ast.SourceImpl;
 import org.apache.jackrabbit.oak.query.ast.UpperCaseImpl;
+import org.apache.jackrabbit.oak.query.index.Filter;
+import org.apache.jackrabbit.oak.query.index.QueryIndex;
 
 /**
  * Represents a parsed query. Lifecycle: use the constructor to create a new
@@ -59,6 +61,7 @@ public class Query {
     final ArrayList<SelectorImpl> selectors = new ArrayList<SelectorImpl>();
 
     private MicroKernel mk;
+    private QueryEngineImpl queryEngine;
     private final OrderingImpl[] orderings;
     private ColumnImpl[] columns;
     private boolean explain;
@@ -273,7 +276,7 @@ public class Query {
         if (explain) {
             String plan = source.getPlan();
             columns = new ColumnImpl[] { new ColumnImpl("explain", "plan", "plan")};
-            ResultRowImpl r = new ResultRowImpl(this, new String[0], new CoreValue[] { valueFactory.createValue(plan)
}, null);
+            ResultRowImpl r = new ResultRowImpl(this, new String[0], new CoreValue[] { getValueFactory().createValue(plan)
}, null);
             it = Arrays.asList(r).iterator();
         } else {
             it = new RowIterator(revisionId);
@@ -319,7 +322,7 @@ public class Query {
         return comp;
     }
 
-    private void prepare() {
+    void prepare() {
         if (prepared) {
             return;
         }
@@ -458,4 +461,12 @@ public class Query {
         return new ArrayList<String>(bindVariableMap.keySet());
     }
 
+    public void setQueryEngine(QueryEngineImpl queryEngine) {
+        this.queryEngine = queryEngine;
+    }
+
+    public QueryIndex getBestIndex(Filter filter) {
+        return queryEngine.getBestIndex(filter);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java
Tue Apr 24 12:17:27 2012
@@ -17,27 +17,52 @@
 package org.apache.jackrabbit.oak.query;
 
 import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.index.Indexer;
 import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.api.CoreValueFactory;
 import org.apache.jackrabbit.oak.api.QueryEngine;
+import org.apache.jackrabbit.oak.query.index.Filter;
+import org.apache.jackrabbit.oak.query.index.QueryIndex;
+import org.apache.jackrabbit.oak.query.index.TraversingIndex;
+import org.apache.jackrabbit.oak.query.index.QueryIndexProvider.QueryIndexListener;
 
-public class QueryEngineImpl implements QueryEngine {
+public class QueryEngineImpl implements QueryEngine, QueryIndexListener {
 
     static final String SQL2 = "JCR-SQL2";
     private static final String XPATH = "xpath";
 
+    // TODO discuss where to store index config data
+    private static final String INDEX_CONFIG_ROOT = "/jcr:system/indexes";
+
     private final MicroKernel mk;
     private final CoreValueFactory vf;
     private final SQL2Parser parserSQL2;
+    private final Indexer indexer;
+    private final Map<String, QueryIndex> indexes =
+        Collections.synchronizedMap(new HashMap<String, QueryIndex>());
 
     public QueryEngineImpl(MicroKernel mk, CoreValueFactory valueFactory) {
         this.mk = mk;
         this.vf = valueFactory;
         parserSQL2 = new SQL2Parser(vf);
+        indexer = new Indexer(mk, INDEX_CONFIG_ROOT);
+    }
+
+    public void init() {
+        // TODO the list of index providers should be configurable as well
+        indexer.init();
+        indexer.addListener(this);
+        List<QueryIndex> list = indexer.getQueryIndexes();
+        for (QueryIndex qi : list) {
+            indexes.put(qi.getIndexName(), qi);
+        }
     }
 
     /**
@@ -52,7 +77,6 @@ public class QueryEngineImpl implements 
     public List<String> getBindVariableNames(String statement, String language) throws
ParseException {
         Query q = parseQuery(statement, language);
         return q.getBindVariableNames();
-
     }
 
     private Query parseQuery(String statement, String language) throws ParseException {
@@ -78,7 +102,44 @@ public class QueryEngineImpl implements 
                 q.bindValue(e.getKey(), e.getValue());
             }
         }
+        q.setQueryEngine(this);
+        q.prepare();
         return q.executeQuery(mk.getHeadRevision());
     }
 
+    public QueryIndex getBestIndex(Filter filter) {
+        QueryIndex best = null;
+        double bestCost = Double.MAX_VALUE;
+        for (QueryIndex index : getIndexes()) {
+            double cost = index.getCost(filter);
+            if (cost < bestCost) {
+                best = index;
+            }
+        }
+        if (best == null) {
+            best = new TraversingIndex(mk);
+        }
+        return best;
+    }
+
+    private List<QueryIndex> getIndexes() {
+        // create a copy, so the underlying map can be modified
+        return new ArrayList<QueryIndex>(indexes.values());
+    }
+
+    @Override
+    public void added(QueryIndex index) {
+        indexes.put(index.getIndexName(), index);
+    }
+
+    @Override
+    public void removed(QueryIndex index) {
+        indexes.remove(index.getIndexName());
+    }
+
+    @Override
+    public void close() {
+        indexer.removeListener(this);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
Tue Apr 24 12:17:27 2012
@@ -26,7 +26,6 @@ import org.apache.jackrabbit.oak.query.Q
 import org.apache.jackrabbit.oak.query.index.Cursor;
 import org.apache.jackrabbit.oak.query.index.Filter;
 import org.apache.jackrabbit.oak.query.index.QueryIndex;
-import org.apache.jackrabbit.oak.query.index.TraversingIndex;
 
 public class SelectorImpl extends SourceImpl {
 
@@ -65,7 +64,7 @@ public class SelectorImpl extends Source
 
     @Override
     public void prepare(MicroKernel mk) {
-        index = new TraversingIndex(mk);
+        index = query.getBestIndex(createFilter());
     }
 
     @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/Filter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/Filter.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/Filter.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/Filter.java
Tue Apr 24 12:17:27 2012
@@ -305,6 +305,10 @@ public class Filter {
     }
 
     public void restrictPath(String addedPath, PathRestriction addedPathRestriction) {
+        if (addedPath == null) {
+            // currently unknown (prepare time)
+            addedPath = "/";
+        }
         // calculating the intersection of path restrictions
         // this is ugly code, but I don't currently see a radically simpler method
         switch (addedPathRestriction) {

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PropertyContentIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PropertyContentIndex.java?rev=1329667&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PropertyContentIndex.java
(added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PropertyContentIndex.java
Tue Apr 24 12:17:27 2012
@@ -0,0 +1,137 @@
+/*
+ * 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.query.index;
+
+import java.util.Iterator;
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.index.PropertyIndex;
+import org.apache.jackrabbit.mk.simple.NodeImpl;
+import org.apache.jackrabbit.oak.api.CoreValue;
+import org.apache.jackrabbit.oak.query.index.Filter.PropertyRestriction;
+
+/**
+ * An index that stores the index data in a {@code MicroKernel}.
+ */
+public class PropertyContentIndex implements QueryIndex {
+
+    private final MicroKernel mk;
+    private final PropertyIndex index;
+
+    public PropertyContentIndex(MicroKernel mk, PropertyIndex index) {
+        this.mk = mk;
+        this.index = index;
+    }
+
+    @Override
+    public double getCost(Filter filter) {
+        String propertyName = index.getName();
+        PropertyRestriction restriction = filter.getPropertyRestriction(propertyName);
+        if (restriction == null) {
+            return Double.MAX_VALUE;
+        }
+        boolean unique = index.isUnique();
+        if (restriction.first == restriction.last) {
+            return unique ? 2 : 20;
+        }
+        int estimatedMatches = 1000000;
+        if (restriction.first != null) {
+            estimatedMatches /= 10;
+        }
+        if (restriction.last != null) {
+            estimatedMatches /= 10;
+        }
+        if (unique) {
+            estimatedMatches /= 100;
+        }
+        return estimatedMatches;
+    }
+
+    @Override
+    public String getPlan(Filter filter) {
+        String propertyName = index.getName();
+        PropertyRestriction restriction = filter.getPropertyRestriction(propertyName);
+
+        return "propertyIndex \"" + restriction.propertyName + " " + restriction.toString()
+ '"';
+    }
+
+    @Override
+    public Cursor query(Filter filter, String revisionId) {
+        String propertyName = index.getName();
+        PropertyRestriction restriction = filter.getPropertyRestriction(propertyName);
+        if (restriction == null) {
+            throw new IllegalArgumentException("No restriction for " + propertyName);
+        }
+        CoreValue first = restriction.first;
+        String f = first == null ? null : first.toString();
+        Iterator<String> it = index.getPaths(f, revisionId);
+        return new ContentCursor(mk, revisionId, it);
+    }
+
+
+    @Override
+    public String getIndexName() {
+        return index.getName();
+    }
+
+    /**
+     * The cursor to for this index.
+     */
+    static class ContentCursor implements Cursor {
+
+        private final MicroKernel mk;
+        private final String revisionId;
+        private final Iterator<String> it;
+
+        private String currentPath;
+        private NodeImpl currentNode;
+
+        public ContentCursor(MicroKernel mk, String revisionId, Iterator<String> it)
{
+            this.mk = mk;
+            this.revisionId = revisionId;
+            this.it = it;
+        }
+
+        @Override
+        public NodeImpl currentNode() {
+            // TODO same code as in TraversingCursor
+            if (currentNode == null) {
+                if (currentPath == null) {
+                    return null;
+                }
+                currentNode = NodeImpl.parse(mk.getNodes(currentPath, revisionId));
+                currentNode.setPath(currentPath);
+            }
+            return currentNode;
+        }
+
+        @Override
+        public String currentPath() {
+            return currentPath;
+        }
+
+        @Override
+        public boolean next() {
+            if (it.hasNext()) {
+                currentPath = it.next();
+            }
+            return false;
+        }
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndex.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndex.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndex.java
Tue Apr 24 12:17:27 2012
@@ -51,4 +51,11 @@ public interface QueryIndex {
      */
     String getPlan(Filter filter);
 
+    /**
+     * Get the unique index name.
+     *
+     * @return the index name
+     */
+    String getIndexName();
+
 }

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndexProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndexProvider.java?rev=1329667&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndexProvider.java
(added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/QueryIndexProvider.java
Tue Apr 24 12:17:27 2012
@@ -0,0 +1,78 @@
+/*
+ * 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.query.index;
+
+import java.util.List;
+
+/**
+ * A mechanism to index data. Indexes might be added or removed at runtime,
+ * possibly by changing content in the repository. The provider knows about
+ * indexes, and informs the query engine about indexes that where added or
+ * removed at runtime.
+ */
+public interface QueryIndexProvider {
+
+    /**
+     * Initialize the instance.
+     */
+    void init();
+
+    /**
+     * Get the currently configured indexes.
+     *
+     * @return the list of indexes
+     */
+    List<QueryIndex> getQueryIndexes();
+
+    /**
+     * Add a listener that is notified about added and removed indexes.
+     *
+     * @param listener the listener
+     */
+    void addListener(QueryIndexListener listener);
+
+    /**
+     * Remove a listener.
+     *
+     * @param listener the listener
+     */
+    void removeListener(QueryIndexListener listener);
+
+    /**
+     * A query index listener
+     */
+    public interface QueryIndexListener {
+
+        /**
+         * The given index was added.
+         *
+         * @param index the index
+         */
+        void added(QueryIndex index);
+
+        /**
+         * The given index was removed.
+         *
+         * @param index the index
+         */
+        void removed(QueryIndex index);
+
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java
Tue Apr 24 12:17:27 2012
@@ -63,4 +63,9 @@ public class TraversingIndex implements 
         return "traverse \"" + p + r + '"';
     }
 
+    @Override
+    public String getIndexName() {
+        return "traverse";
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/IndexTest.java
Tue Apr 24 12:17:27 2012
@@ -34,6 +34,7 @@ public class IndexTest {
     @Test
     public void createIndexAfterAddingData() {
         Indexer indexer = new Indexer(mk);
+        indexer.init();
         PropertyIndex indexOld = indexer.createPropertyIndex("x", false);
         mk.commit("/", "+ \"test\": { \"test2\": { \"id\": 1 }, \"id\": 1 }", mk.getHeadRevision(),
"");
         mk.commit("/", "+ \"test3\": { \"test2\": { \"id\": 2 }, \"id\": 2 }", mk.getHeadRevision(),
"");
@@ -50,6 +51,7 @@ public class IndexTest {
     @Test
     public void nonUnique() {
         Indexer indexer = new Indexer(mk);
+        indexer.init();
         PropertyIndex index = indexer.createPropertyIndex("id", false);
         mk.commit("/", "+ \"test\": { \"test2\": { \"id\": 1 }, \"id\": 1 }", mk.getHeadRevision(),
"");
         mk.commit("/", "+ \"test3\": { \"test2\": { \"id\": 2 }, \"id\": 2 }", mk.getHeadRevision(),
"");
@@ -64,6 +66,7 @@ public class IndexTest {
     @Test
     public void nestedAddNode() {
         Indexer indexer = new Indexer(mk);
+        indexer.init();
         PropertyIndex index = indexer.createPropertyIndex("id", true);
 
         mk.commit("/", "+ \"test\": { \"test2\": { \"id\": 2 }, \"id\": 1 }", mk.getHeadRevision(),
"");
@@ -74,6 +77,7 @@ public class IndexTest {
     @Test
     public void move() {
         Indexer indexer = new Indexer(mk);
+        indexer.init();
         PropertyIndex index = indexer.createPropertyIndex("id", true);
 
         mk.commit("/", "+ \"test\": { \"test2\": { \"id\": 2 }, \"id\": 1 }", mk.getHeadRevision(),
"");
@@ -88,6 +92,7 @@ public class IndexTest {
     @Test
     public void copy() {
         Indexer indexer = new Indexer(mk);
+        indexer.init();
         PropertyIndex index = indexer.createPropertyIndex("id", false);
 
         mk.commit("/", "+ \"test\": { \"test2\": { \"id\": 2 }, \"id\": 1 }", mk.getHeadRevision(),
"");
@@ -112,6 +117,7 @@ public class IndexTest {
     @Test
     public void ascending() {
         Indexer indexer = new Indexer(mk);
+        indexer.init();
         BTree tree = new BTree(indexer, "test", true);
         tree.setMinSize(2);
         print(mk, tree);
@@ -154,6 +160,7 @@ public class IndexTest {
 
     private void duplicateKey(boolean unique) {
         Indexer indexer = new Indexer(mk);
+        indexer.init();
         BTree tree = new BTree(indexer, "test", unique);
         tree.setMinSize(2);
 
@@ -197,6 +204,7 @@ public class IndexTest {
     @Test
     public void random() {
         Indexer indexer = new Indexer(mk);
+        indexer.init();
         BTree tree = new BTree(indexer, "test", true);
         tree.setMinSize(2);
         Random r = new Random(1);

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PrefixIndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PrefixIndexTest.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PrefixIndexTest.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PrefixIndexTest.java
Tue Apr 24 12:17:27 2012
@@ -31,6 +31,7 @@ public class PrefixIndexTest {
     public void test() {
         MicroKernel mk = new MicroKernelImpl();
         Indexer indexer = new Indexer(mk, "index");
+        indexer.init();
         PrefixIndex index = indexer.createPrefixIndex("d:");
 
         String head = mk.getHeadRevision();

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PropertyIndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PropertyIndexTest.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PropertyIndexTest.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/index/PropertyIndexTest.java
Tue Apr 24 12:17:27 2012
@@ -30,6 +30,7 @@ public class PropertyIndexTest {
     public void test() {
         MicroKernel mk = new MicroKernelImpl();
         Indexer indexer = new Indexer(mk, "index");
+        indexer.init();
         PropertyIndex index = indexer.createPropertyIndex("id", true);
 
         String head = mk.getHeadRevision();
@@ -61,6 +62,7 @@ public class PropertyIndexTest {
 
         // Recreate the indexer
         indexer = new Indexer(mk);
+        indexer.init();
         index = indexer.createPropertyIndex("id", true);
         head = mk.getHeadRevision();
         Assert.assertEquals("/test/test", index.getPath("3", head));

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
Tue Apr 24 12:17:27 2012
@@ -20,19 +20,12 @@ import org.apache.jackrabbit.mk.api.Micr
 import org.apache.jackrabbit.mk.core.MicroKernelImpl;
 import org.apache.jackrabbit.oak.api.CoreValueFactory;
 import org.apache.jackrabbit.oak.core.CoreValueFactoryImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * AbstractQueryTest...
  */
 public abstract class AbstractQueryTest {
 
-    /**
-     * logger instance
-     */
-    private static final Logger log = LoggerFactory.getLogger(AbstractQueryTest.class);
-
     // TODO improve: use ContentRepository here instead of creating mk instance.
     protected final MicroKernel mk = new MicroKernelImpl();
     protected final CoreValueFactory vf = new CoreValueFactoryImpl(mk);

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/QueryTest.java
Tue Apr 24 12:17:27 2012
@@ -13,11 +13,8 @@
  */
 package org.apache.jackrabbit.oak.query;
 
-import org.apache.jackrabbit.oak.api.CoreValue;
-import org.apache.jackrabbit.oak.api.Result;
-import org.apache.jackrabbit.oak.api.ResultRow;
-import org.junit.Test;
-
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -30,9 +27,11 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import org.apache.jackrabbit.oak.api.CoreValue;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.junit.Before;
+import org.junit.Test;
 
 /**
  * Test the query feature.
@@ -41,6 +40,11 @@ public class QueryTest extends AbstractQ
 
     private QueryEngineImpl qe = new QueryEngineImpl(mk, vf);
 
+    @Before
+    public void setup() {
+        qe.init();
+    }
+
     @Test
     public void script() throws Exception {
         test("queryTest.txt");

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/FilterTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/FilterTest.java?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/FilterTest.java
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/FilterTest.java
Tue Apr 24 12:17:27 2012
@@ -18,19 +18,17 @@
  */
 package org.apache.jackrabbit.oak.query.index;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import java.util.ArrayList;
+import java.util.Random;
 import org.apache.jackrabbit.oak.api.CoreValue;
 import org.apache.jackrabbit.oak.query.AbstractQueryTest;
 import org.apache.jackrabbit.oak.query.ast.Operator;
 import org.apache.jackrabbit.oak.query.index.Filter.PathRestriction;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.Random;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 /**
  * Tests the Filter class.
  */

Modified: jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/queryTest.txt
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/queryTest.txt?rev=1329667&r1=1329666&r2=1329667&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/queryTest.txt
(original)
+++ jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/queryTest.txt
Tue Apr 24 12:17:27 2012
@@ -32,6 +32,8 @@ select * from [nt:base] as b where local
 
 select * from [nt:base] as x where isdescendantnode(x, '/')
 /
+/jcr:system
+/jcr:system/indexes
 /test
 /test/jcr:resource
 /test/resource
@@ -51,6 +53,7 @@ select * from [nt:base] as p where p.[jc
 
 select * from [nt:base] as p inner join [nt:base] as p2 on ischildnode(p2, p) where p.[jcr:path]
= '/'
 /, /children
+/, /jcr:system
 /, /parents
 
 select * from [nt:base] as p inner join [nt:base] as p2 on isdescendantnode(p2, p) where
p.[jcr:path] = '/parents'
@@ -107,6 +110,8 @@ nt:base AS p /* traverse "//*" */ INNER 
 
 select * from [nt:base]
 /
+/jcr:system
+/jcr:system/indexes
 /test
 /test/hello
 /test/world
@@ -124,11 +129,15 @@ select * from [nt:base] where id = '1' o
 
 select * from [nt:base] where not (id = '1' or x = '2')
 /
+/jcr:system
+/jcr:system/indexes
 /test
 /test/hello
 
 select * from [nt:base] where x is null
 /
+/jcr:system
+/jcr:system/indexes
 /test
 
 - "test"
@@ -147,6 +156,8 @@ Hallo
 hello
 World!
 null
+null
+null
 
 select * from [nt:base] where length(name) = 5
 /test



Mime
View raw message