Author: thomasm Date: Tue Aug 21 14:15:04 2012 New Revision: 1375560 URL: http://svn.apache.org/viewvc?rev=1375560&view=rev Log: OAK-261 Support querying on property types
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PrefixContentIndex.java Modified: 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/oak/query/SQL2Parser.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.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/Indexer.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/index/Indexer.java?rev=1375560&r1=1375559&r2=1375560&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 Aug 21 14:15:04 2012 @@ -26,6 +26,7 @@ import org.apache.jackrabbit.mk.simple.N import org.apache.jackrabbit.mk.simple.NodeMap; import org.apache.jackrabbit.mk.util.SimpleLRUCache; import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.query.index.PrefixContentIndex; import org.apache.jackrabbit.oak.query.index.PropertyContentIndex; import org.apache.jackrabbit.oak.spi.QueryIndex; import org.apache.jackrabbit.oak.spi.QueryIndexProvider; @@ -674,7 +675,7 @@ public class Indexer implements QueryInd if (index instanceof PropertyIndex) { qi = new PropertyContentIndex((PropertyIndex) index); } else if (index instanceof PrefixIndex) { - // TODO support prefix indexes in the query engine? + qi = new PrefixContentIndex((PrefixIndex) index); } queryIndexList.add(qi); } Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java?rev=1375560&r1=1375559&r2=1375560&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java Tue Aug 21 14:15:04 2012 @@ -503,8 +503,12 @@ public class SQL2Parser { op = factory.lowerCase(parseDynamicOperand()); } else if ("UPPER".equalsIgnoreCase(functionName)) { op = factory.upperCase(parseDynamicOperand()); + } else if ("PROPERTY".equalsIgnoreCase(functionName)) { + PropertyValueImpl pv = parsePropertyValue(readName()); + read(","); + op = factory.propertyValue(pv.getSelectorName(), pv.getPropertyName(), readString().getString()); } else { - throw getSyntaxError("LENGTH, NAME, LOCALNAME, SCORE, LOWER, UPPER, or CAST"); + throw getSyntaxError("LENGTH, NAME, LOCALNAME, SCORE, LOWER, UPPER, or PROPERTY"); } read(")"); return op; @@ -609,35 +613,81 @@ public class SQL2Parser { } private CoreValue parseCastAs(CoreValue value) throws ParseException { - if (readIfPropertyType(PropertyType.STRING)) { + if (currentTokenQuoted) { + throw getSyntaxError("data type (STRING|BINARY|...)"); + } + int propertyType = getPropertyTypeFromName(currentToken); + read(); + switch (propertyType) { + case PropertyType.STRING: return valueFactory.createValue(value.getString()); - } else if (readIfPropertyType(PropertyType.BINARY)) { + case PropertyType.BINARY: return valueFactory.createValue(value.getString(), PropertyType.BINARY); - } else if (readIfPropertyType(PropertyType.DATE)) { + case PropertyType.DATE: return valueFactory.createValue(value.getString(), PropertyType.DATE); - } else if (readIfPropertyType(PropertyType.LONG)) { + case PropertyType.LONG: return valueFactory.createValue(value.getLong()); - } else if (readIfPropertyType(PropertyType.DOUBLE)) { + case PropertyType.DOUBLE: return valueFactory.createValue(value.getDouble()); - } else if (readIfPropertyType(PropertyType.DECIMAL)) { + case PropertyType.DECIMAL: return valueFactory.createValue(value.getDecimal()); - } else if (readIfPropertyType(PropertyType.BOOLEAN)) { + case PropertyType.BOOLEAN: return valueFactory.createValue(value.getBoolean()); - } else if (readIfPropertyType(PropertyType.NAME)) { + case PropertyType.NAME: return valueFactory.createValue(value.getString(), PropertyType.NAME); - } else if (readIfPropertyType(PropertyType.PATH)) { + case PropertyType.PATH: return valueFactory.createValue(value.getString(), PropertyType.PATH); - } else if (readIfPropertyType(PropertyType.REFERENCE)) { + case PropertyType.REFERENCE: return valueFactory.createValue(value.getString(), PropertyType.REFERENCE); - } else if (readIfPropertyType(PropertyType.WEAKREFERENCE)) { + case PropertyType.WEAKREFERENCE: return valueFactory.createValue(value.getString(), PropertyType.WEAKREFERENCE); - } else if (readIfPropertyType(PropertyType.URI)) { + case PropertyType.URI: return valueFactory.createValue(value.getString(), PropertyType.URI); - } else { + default: throw getSyntaxError("data type (STRING|BINARY|...)"); } } + /** + * Get the property type from the given case insensitive name. + * + * @param name the property type name (case insensitive) + * @return the type, or {@code PropertyType.UNDEFINED} if unknown + */ + public static int getPropertyTypeFromName(String name) { + if (matchesPropertyType(PropertyType.STRING, name)) { + return PropertyType.STRING; + } else if (matchesPropertyType(PropertyType.BINARY, name)) { + return PropertyType.BINARY; + } else if (matchesPropertyType(PropertyType.DATE, name)) { + return PropertyType.DATE; + } else if (matchesPropertyType(PropertyType.LONG, name)) { + return PropertyType.LONG; + } else if (matchesPropertyType(PropertyType.DOUBLE, name)) { + return PropertyType.DOUBLE; + } else if (matchesPropertyType(PropertyType.DECIMAL, name)) { + return PropertyType.DECIMAL; + } else if (matchesPropertyType(PropertyType.BOOLEAN, name)) { + return PropertyType.BOOLEAN; + } else if (matchesPropertyType(PropertyType.NAME, name)) { + return PropertyType.NAME; + } else if (matchesPropertyType(PropertyType.PATH, name)) { + return PropertyType.PATH; + } else if (matchesPropertyType(PropertyType.REFERENCE, name)) { + return PropertyType.REFERENCE; + } else if (matchesPropertyType(PropertyType.WEAKREFERENCE, name)) { + return PropertyType.WEAKREFERENCE; + } else if (matchesPropertyType(PropertyType.URI, name)) { + return PropertyType.URI; + } + return PropertyType.UNDEFINED; + } + + private static boolean matchesPropertyType(int propertyType, String name) { + String typeName = PropertyType.nameFromValue(propertyType); + return typeName.equalsIgnoreCase(name); + } + private OrderingImpl[] parseOrder() throws ParseException { ArrayList<OrderingImpl> orderList = new ArrayList<OrderingImpl>(); do { @@ -715,11 +765,6 @@ public class SQL2Parser { return array; } - private boolean readIfPropertyType(int propertyType) throws ParseException { - String propertyName = PropertyType.nameFromValue(propertyType); - return readIf(propertyName); - } - private boolean readIf(String token) throws ParseException { if (isToken(token)) { read(); Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java?rev=1375560&r1=1375559&r2=1375560&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstElementFactory.java Tue Aug 21 14:15:04 2012 @@ -116,6 +116,10 @@ public class AstElementFactory { return new PropertyValueImpl(selectorName, propertyName); } + public PropertyValueImpl propertyValue(String selectorName, String propertyName, String propertyType) { + return new PropertyValueImpl(selectorName, propertyName, propertyType); + } + public SameNodeImpl sameNode(String selectorName, String path) { return new SameNodeImpl(selectorName, path); } Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java?rev=1375560&r1=1375559&r2=1375560&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyValueImpl.java Tue Aug 21 14:15:04 2012 @@ -18,10 +18,16 @@ */ package org.apache.jackrabbit.oak.query.ast; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import javax.jcr.PropertyType; import org.apache.jackrabbit.oak.api.CoreValue; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.memory.MultiPropertyState; +import org.apache.jackrabbit.oak.query.SQL2Parser; import org.apache.jackrabbit.oak.query.index.FilterImpl; /** @@ -31,11 +37,19 @@ public class PropertyValueImpl extends D private final String selectorName; private final String propertyName; + private final int propertyType; private SelectorImpl selector; public PropertyValueImpl(String selectorName, String propertyName) { + this(selectorName, propertyName, null); + } + + public PropertyValueImpl(String selectorName, String propertyName, String propertyType) { this.selectorName = selectorName; this.propertyName = propertyName; + this.propertyType = propertyType == null ? + PropertyType.UNDEFINED : + SQL2Parser.getPropertyTypeFromName(propertyType); } public String getSelectorName() { @@ -54,33 +68,77 @@ public class PropertyValueImpl extends D @Override public String toString() { // TODO quote property names? - return getSelectorName() + '.' + propertyName; + String s = getSelectorName() + '.' + propertyName; + if (propertyType != PropertyType.UNDEFINED) { + s = "property(" + s + ", '" + + PropertyType.nameFromValue(propertyType).toLowerCase(Locale.ENGLISH) + + "')"; + } + return s; } @Override public PropertyState currentProperty() { - if (propertyName.indexOf('/') < 0) { - return selector.currentProperty(propertyName); + boolean relative = propertyName.indexOf('/') >= 0; + boolean asterisk = propertyName.equals("*"); + if (!relative && !asterisk) { + PropertyState p = selector.currentProperty(propertyName); + return matchesPropertyType(p) ? p : null; } - // TODO really support relative properties? Tree tree = getTree(selector.currentPath()); - for (String p : PathUtils.elements(PathUtils.getParentPath(propertyName))) { + if (relative) { + for (String p : PathUtils.elements(PathUtils.getParentPath(propertyName))) { + if (tree == null) { + return null; + } + if (!tree.hasChild(p)) { + return null; + } + tree = tree.getChild(p); + } if (tree == null) { return null; } - if (!tree.hasChild(p)) { + } + if (!asterisk) { + String name = PathUtils.getName(propertyName); + if (!tree.hasProperty(name)) { return null; } - tree = tree.getChild(p); + PropertyState p = tree.getProperty(name); + return matchesPropertyType(p) ? p : null; } - if (tree == null) { - return null; + // asterisk - create a multi-value property + // warning: the returned property state may have a mixed type + // (not all values may have the same type) + ArrayList<CoreValue> values = new ArrayList<CoreValue>(); + for (PropertyState p : tree.getProperties()) { + if (matchesPropertyType(p)) { + if (p.isArray()) { + values.addAll(p.getValues()); + } else { + values.add(p.getValue()); + } + } } - String name = PathUtils.getName(propertyName); - if (!tree.hasProperty(name)) { - return null; + MultiPropertyState mv = new MultiPropertyState("*", values); + return mv; + } + + private boolean matchesPropertyType(PropertyState state) { + if (propertyType == PropertyType.UNDEFINED) { + return true; } - return tree.getProperty(name); + if (state.isArray()) { + List<CoreValue> values = state.getValues(); + if (values.isEmpty()) { + // TODO how to retrieve the property type of an empty multi-value property? + // currently it matches all property types + return true; + } + return values.get(0).getType() == propertyType; + } + return state.getValue().getType() == propertyType; } public void bindSelector(SourceImpl source) { @@ -95,6 +153,9 @@ public class PropertyValueImpl extends D return; } f.restrictProperty(propertyName, operator, v); + if (propertyType != PropertyType.UNDEFINED) { + f.restrictPropertyType(propertyName, operator, propertyType); + } } } Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java?rev=1375560&r1=1375559&r2=1375560&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java Tue Aug 21 14:15:04 2012 @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; +import javax.jcr.PropertyType; /** * A filter or lookup condition. @@ -58,11 +59,6 @@ public class FilterImpl implements Filte private String nodeType; /** - * The value prefix, or null if not set. - */ - private String valuePrefix; - - /** * The fulltext search conditions, if any. */ private final ArrayList<String> fulltextConditions = new ArrayList<String>(); @@ -108,14 +104,6 @@ public class FilterImpl implements Filte this.nodeType = nodeType; } - public String getValuePrefix() { - return valuePrefix; - } - - public void setValuePrefix(String valuePrefix) { - this.valuePrefix = valuePrefix; - } - public boolean isDistinct() { return distinct; } @@ -126,7 +114,6 @@ public class FilterImpl implements Filte public void setAlwaysFalse() { propertyRestrictions.clear(); - valuePrefix = "none"; nodeType = ""; path = "/"; pathRestriction = PathRestriction.EXACT; @@ -175,6 +162,25 @@ public class FilterImpl implements Filte } } + public void restrictPropertyType(String propertyName, Operator operator, + int propertyType) { + if (propertyType == PropertyType.UNDEFINED) { + // not restricted + return; + } + PropertyRestriction x = propertyRestrictions.get(propertyName); + if (x == null) { + x = new PropertyRestriction(); + x.propertyName = propertyName; + propertyRestrictions.put(propertyName, x); + } + if (x.propertyType != PropertyType.UNDEFINED && x.propertyType != propertyType) { + // already restricted to another property type - always false + setAlwaysFalse(); + } + x.propertyType = propertyType; + } + public void restrictProperty(String propertyName, Operator op, CoreValue value) { PropertyRestriction x = propertyRestrictions.get(propertyName); if (x == null) { Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PrefixContentIndex.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PrefixContentIndex.java?rev=1375560&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PrefixContentIndex.java (added) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/PrefixContentIndex.java Tue Aug 21 14:15:04 2012 @@ -0,0 +1,124 @@ +/* + * 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 javax.jcr.PropertyType; +import org.apache.jackrabbit.mk.index.PrefixIndex; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.kernel.CoreValueMapper; +import org.apache.jackrabbit.oak.spi.Cursor; +import org.apache.jackrabbit.oak.spi.Filter; +import org.apache.jackrabbit.oak.spi.IndexRow; +import org.apache.jackrabbit.oak.spi.QueryIndex; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +/** + * An index that stores the index data in a {@code MicroKernel}. + */ +public class PrefixContentIndex implements QueryIndex { + + private final PrefixIndex index; + + public PrefixContentIndex(PrefixIndex index) { + this.index = index; + } + + @Override + public double getCost(Filter filter) { + Filter.PropertyRestriction restriction = filter.getPropertyRestriction("*"); + if (restriction == null) { + return Double.MAX_VALUE; + } + if (restriction.first != restriction.last) { + // only support equality matches (for now) + return Double.MAX_VALUE; + } + if (restriction.propertyType == PropertyType.UNDEFINED) { + return Double.MAX_VALUE; + } + String hint = CoreValueMapper.getHintForType(restriction.propertyType); + String prefix = hint + ":"; + if (!prefix.equals(index.getPrefix())) { + // wrong prefix (wrong property type) + return Double.MAX_VALUE; + } + return 100; + } + + @Override + public String getPlan(Filter filter) { + Filter.PropertyRestriction restriction = filter.getPropertyRestriction("*"); + if (restriction == null) { + throw new IllegalArgumentException("No restriction for *"); + } + // TODO need to use correct json representation + String v = restriction.first.getString(); + v = index.getPrefix() + v; + return "prefixIndex \"" + v + '"'; + } + + @Override + public Cursor query(Filter filter, String revisionId, NodeState root) { + Filter.PropertyRestriction restriction = filter.getPropertyRestriction("*"); + if (restriction == null) { + throw new IllegalArgumentException("No restriction for *"); + } + // TODO need to use correct json representation + String v = restriction.first.getString(); + v = index.getPrefix() + v; + Iterator<String> it = index.getPaths(v, revisionId); + return new ContentCursor(it); + } + + @Override + public String getIndexName() { + return index.getIndexNodeName(); + } + + /** + * The cursor to for this index. + */ + static class ContentCursor implements Cursor { + + private final Iterator<String> it; + + private String currentPath; + + public ContentCursor(Iterator<String> it) { + this.it = it; + } + + @Override + public IndexRow currentRow() { + return new IndexRowImpl(currentPath); + } + + @Override + public boolean next() { + if (it.hasNext()) { + String pathAndProperty = it.next(); + currentPath = PathUtils.getParentPath(pathAndProperty); + return true; + } + return false; + } + } + +} Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java?rev=1375560&r1=1375559&r2=1375560&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java Tue Aug 21 14:15:04 2012 @@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.spi; import java.util.Collection; +import javax.jcr.PropertyType; import org.apache.jackrabbit.oak.api.CoreValue; @@ -93,6 +94,12 @@ public interface Filter { */ public boolean lastIncluding; + /** + * The property type, if restricted. + * If not restricted, this field is set to PropertyType.UNDEFINED. + */ + public int propertyType = PropertyType.UNDEFINED; + @Override public String toString() { return (first == null ? "" : ((firstIncluding ? "[" : "(") + first)) + ".." + 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=1375560&r1=1375559&r2=1375560&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 Aug 21 14:15:04 2012 @@ -21,6 +21,27 @@ # * new tests are typically be added on top, after the syntax docs # * use ascii character only +# property type (value prefix) index + +commit / + "test": { "a": { "id": "ref:123" }, "b": { "id" : "str:123" }} + +explain select * from [nt:base] where property([*], 'REFERENCE') = CAST('123' AS REFERENCE) +nt:base as nt:base /* traverse "//*" */ + +select * from [nt:base] where property([*], 'REFERENCE') = CAST('123' AS REFERENCE) +/test/a + +commit /jcr:system/indexes + "prefix@ref:": {} + +explain select * from [nt:base] where property([*], 'REFERENCE') = CAST('123' AS REFERENCE) +nt:base as nt:base /* prefixIndex "ref:123" */ + +select * from [nt:base] where property([*], 'REFERENCE') = CAST('123' AS REFERENCE) +/test/a + +commit / - "test" +commit /jcr:system/indexes - "prefix@ref:" + # sql-1 query (nt:unstructured needs to be escaped in sql-2) sql1 select prop1 from nt:unstructured where prop1 is not null order by prop1 asc