Index: oak-auth-external/pom.xml =================================================================== --- oak-auth-external/pom.xml (revision 1804638) +++ oak-auth-external/pom.xml (working copy) @@ -114,6 +114,11 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + + + org.apache.jackrabbit oak-core ${project.version} Index: oak-authorization-cug/pom.xml =================================================================== --- oak-authorization-cug/pom.xml (revision 1804638) +++ oak-authorization-cug/pom.xml (working copy) @@ -64,10 +64,14 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + + + org.apache.jackrabbit oak-core ${project.version} - com.google.guava Index: oak-benchmarks/pom.xml =================================================================== --- oak-benchmarks/pom.xml (revision 1804638) +++ oak-benchmarks/pom.xml (working copy) @@ -95,6 +95,11 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + + + org.apache.jackrabbit oak-core-spi ${project.version} Index: oak-core/pom.xml =================================================================== --- oak-core/pom.xml (revision 1804638) +++ oak-core/pom.xml (working copy) @@ -71,8 +71,6 @@ org.apache.jackrabbit.oak.plugins.tree, org.apache.jackrabbit.oak.plugins.value.jcr, org.apache.jackrabbit.oak.plugins.version, - org.apache.jackrabbit.oak.spi.query, - org.apache.jackrabbit.oak.spi.query.fulltext, org.apache.jackrabbit.oak.spi.security, org.apache.jackrabbit.oak.spi.security.authentication, org.apache.jackrabbit.oak.spi.security.authentication.callback, @@ -160,6 +158,11 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + + + org.apache.jackrabbit oak-commons ${project.version} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/CompositeQueryIndexProvider.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursor.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexRow.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryConstants.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryEngineSettings.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryEngineSettings.java (revision 1804821) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryEngineSettings.java (working copy) @@ -29,7 +29,7 @@ public class QueryEngineSettings implements QueryEngineSettingsMBean, QueryLimits { /** - * the flag used to turn on/off the optimisations on top of the {@link org.apache.jackrabbit.oak.query.Query} object. + * the flag used to turn on/off the optimisations on top of the {@code org.apache.jackrabbit.oak.query.Query} object. * {@code -Doak.query.sql2optimisation} */ public static final String SQL2_OPTIMISATION_FLAG = "oak.query.sql2optimisation"; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndexProvider.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryLimits.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/WhiteboardIndexProvider.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextAnd.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextContains.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextExpression.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextOr.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextParser.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextTerm.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextVisitor.java (deleted) =================================================================== Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/LikePattern.java (deleted) =================================================================== Index: oak-it/pom.xml =================================================================== --- oak-it/pom.xml (revision 1804638) +++ oak-it/pom.xml (working copy) @@ -52,6 +52,12 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + test + + + org.apache.jackrabbit oak-segment-tar ${project.version} test Index: oak-it-osgi/pom.xml =================================================================== --- oak-it-osgi/pom.xml (revision 1804638) +++ oak-it-osgi/pom.xml (working copy) @@ -89,6 +89,12 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + test + + + org.apache.jackrabbit oak-core ${project.version} test Index: oak-it-osgi/test-bundles.xml =================================================================== --- oak-it-osgi/test-bundles.xml (revision 1804638) +++ oak-it-osgi/test-bundles.xml (working copy) @@ -46,6 +46,7 @@ org.apache.jackrabbit:oak-blob org.apache.jackrabbit:oak-core-spi org.apache.jackrabbit:oak-store-spi + org.apache.jackrabbit:oak-query-spi org.apache.jackrabbit:oak-blob-plugins io.dropwizard.metrics:metrics-core Index: oak-jcr/pom.xml =================================================================== --- oak-jcr/pom.xml (revision 1804638) +++ oak-jcr/pom.xml (working copy) @@ -235,6 +235,11 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + + + org.apache.jackrabbit oak-store-spi ${project.version} test-jar Index: oak-lucene/pom.xml =================================================================== --- oak-lucene/pom.xml (revision 1804638) +++ oak-lucene/pom.xml (working copy) @@ -184,6 +184,11 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + + + org.apache.jackrabbit oak-core ${project.version} Index: oak-query-spi/pom.xml =================================================================== --- oak-query-spi/pom.xml (nonexistent) +++ oak-query-spi/pom.xml (working copy) @@ -0,0 +1,183 @@ + + + + + + 4.0.0 + + + org.apache.jackrabbit + oak-parent + 1.8-SNAPSHOT + ../oak-parent/pom.xml + + + oak-query-spi + Oak Query SPI + bundle + + + + + org.apache.felix + maven-bundle-plugin + + + + org.apache.jackrabbit.oak.spi.query, + org.apache.jackrabbit.oak.spi.query.fulltext + + + + + + baseline + + baseline + + pre-integration-test + + + oak-core + + + + + + org.apache.felix + maven-scr-plugin + + + maven-failsafe-plugin + + + + src/test/resources/logging.properties + + + + + + org.apache.rat + apache-rat-plugin + + + **/test.json + + + + + + + + + + org.osgi + org.osgi.core + provided + + + org.osgi + org.osgi.compendium + provided + + + org.osgi + org.osgi.annotation + provided + + + org.apache.felix + org.apache.felix.scr.annotations + provided + + + + + javax.jcr + jcr + 2.0 + + + + + org.apache.jackrabbit + oak-api + ${project.version} + + + org.apache.jackrabbit + oak-commons + ${project.version} + + + org.apache.jackrabbit + oak-store-spi + ${project.version} + + + + + com.google.guava + guava + + + commons-io + commons-io + + + commons-codec + commons-codec + 1.5 + + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + + + com.google.code.findbugs + jsr305 + + + + + junit + junit + test + + + org.mockito + mockito-core + 1.10.19 + test + + + + \ No newline at end of file Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/CompositeQueryIndexProvider.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/CompositeQueryIndexProvider.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/CompositeQueryIndexProvider.java (working copy) @@ -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.spi.query; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import javax.annotation.Nonnull; + +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * This {@code QueryIndexProvider} aggregates a list of query index providers + * into a single query index provider. + */ +public class CompositeQueryIndexProvider implements QueryIndexProvider { + + private final List providers; + + private CompositeQueryIndexProvider(List providers) { + this.providers = providers; + } + + public CompositeQueryIndexProvider(QueryIndexProvider... providers) { + this(Arrays.asList(providers)); + } + + @Nonnull + public static QueryIndexProvider compose( + @Nonnull Collection providers) { + if (providers.isEmpty()) { + return new QueryIndexProvider() { + @Override + public List getQueryIndexes(NodeState nodeState) { + return ImmutableList.of(); + } + }; + } else if (providers.size() == 1) { + return providers.iterator().next(); + } else { + return new CompositeQueryIndexProvider( + ImmutableList.copyOf(providers)); + } + } + + @Override @Nonnull + public List getQueryIndexes(NodeState nodeState) { + List indexes = Lists.newArrayList(); + for (QueryIndexProvider provider : providers) { + indexes.addAll(provider.getQueryIndexes(nodeState)); + } + return indexes; + } + + @Override + public String toString() { + return getClass().getName() + ": " + providers.toString(); + } + +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/CompositeQueryIndexProvider.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursor.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursor.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursor.java (working copy) @@ -0,0 +1,65 @@ +/* + * 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.spi.query; + +import java.util.Iterator; + +import org.apache.jackrabbit.oak.api.Result.SizePrecision; + +/** + * A cursor to read a number of nodes sequentially. + */ +public interface Cursor extends Iterator { + + /** + * The next row within this index. + *

+ * The row may only contains the path, if a path is available. It may also + * (or just) contain so-called "pseudo-properties" such as "jcr:score" and + * "rep:excerpt", in case the index supports those properties and if the + * properties were requested when running the query. The query engine will + * indicate that those pseudo properties were requested by setting an + * appropriate (possibly unrestricted) filter condition. + *

+ * The index should return a row with those properties that are stored in + * the index itself, so that the query engine doesn't have to load the whole + * row / node unnecessarily (avoiding to load the whole row is sometimes + * called "index only scan"), specially for rows that are anyway skipped. If + * the index does not have an (efficient) way to return some (or any) of the + * properties, it doesn't have to provide those values. In this case, the + * query engine will load the node itself if required. If all conditions + * match, the query engine will sometimes load the node to do access checks, + * but this is not always the case, and it is not the case if any of the + * (join) conditions do not match. + * + * @return the row + */ + @Override + IndexRow next(); + + /** + * Get the size if known. + * + * @param precision the required precision + * @param max the maximum nodes read (for an exact size) + * @return the size, or -1 if unknown + */ + long getSize(SizePrecision precision, long max); + +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/Cursor.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision Rev URL \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java (working copy) @@ -0,0 +1,525 @@ +/* + * 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.spi.query; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.jcr.PropertyType; + +import org.apache.jackrabbit.oak.api.PropertyValue; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextExpression; + +/** + * The filter for an index lookup that contains a number of restrictions that + * are combined with AND. Possible restrictions are a property restriction, a + * path restriction, a node type restriction, and a fulltext restriction. + *

+ * A property restriction could be that the property must exist, or that the + * property value has to be within a certain range. + *

+ * A path restriction could be a restriction to a certain subtree, a parent of a + * certain path, or equality to a certain path. + */ +public interface Filter { + + /** + * Get the list of property restrictions, if any. Each property may contain + * multiple restrictions, for example x=1 and x=2. For this case, only + * multi-valued properties match that contain both 1 and 2. + * + * @return the conditions (an empty collection if not used) + */ + Collection getPropertyRestrictions(); + + /** + * Get the fulltext search conditions, if any. + * + * @return the conditions (an empty collection if not used) + */ + @Deprecated + Collection getFulltextConditions(); + + /** + * Get the fulltext search condition expression, if any. + * + * @return the condition (null if none) + */ + FullTextExpression getFullTextConstraint(); + + QueryLimits getQueryLimits(); + + /** + * check whether a certain (valid) path is accessible (can be read) from the user associated with the query Session + * @param path a valid JCR path + * @return true if path can be read by the calling user, false otherwise. + */ + boolean isAccessible(String path); + + /** + * Whether the filter contains a native condition. + * + * @return true if it does + */ + boolean containsNativeConstraint(); + + /** + * Get the most restrictive property restriction for the given property, if + * any. + * + * @param propertyName the property name + * @return the first restriction, or null if there is no restriction for + * this property + */ + PropertyRestriction getPropertyRestriction(String propertyName); + + /** + * Get the all property restriction for the given property. + * + * @param propertyName the property name + * @return the list of restrictions (possibly empty, never null) + */ + List getPropertyRestrictions(String propertyName); + + /** + * Get the path restriction type. + * + * @return the path restriction type + */ + PathRestriction getPathRestriction(); + + /** + * Get the path, or "/" if there is no path restriction set. + * + * @return the path + */ + String getPath(); + + /** + * Get the plan for the path. + * + * @return the plan + */ + String getPathPlan(); + + /** + * Returns the name of the filter node type. + * + * @return nodetype name + */ + @Nullable + String getNodeType(); + + /** + * Checks whether nodes of all types can match this filter. + * + * @return {@code true} iff there are no type restrictions + */ + boolean matchesAllTypes(); + + /** + * Returns the names of the filter node type and all its supertypes. + * + * @return supertype name + */ + @Nonnull + Set getSupertypes(); + + /** + * Returns the names of all matching primary node types. + * + * @return primary node type names + */ + @Nonnull + Set getPrimaryTypes(); + + /** + * Returns the names of all matching mixin node types. + * + * @return mixin node type names + */ + @Nonnull + Set getMixinTypes(); + + /** + * Get the complete query statement. The statement should only be used for + * logging purposes. + * + * @return the query statement (possibly null) + */ + @Nullable + String getQueryStatement(); + + /** + * If the filter condition can not possibly match any row, due to a + * contradiction in the query (for example "x=1 and x=2"). + * + * @return true if the filter condition can not match any row + */ + boolean isAlwaysFalse(); + + /** + * A restriction for a property. + */ + class PropertyRestriction { + + /** + * The name of the property. + */ + public String propertyName; + + /** + * The first value to read, or null to read from the beginning. + */ + public PropertyValue first; + + /** + * Whether values that match the first should be returned. + */ + public boolean firstIncluding; + + /** + * The last value to read, or null to read until the end. + */ + public PropertyValue last; + + /** + * Whether values that match the last should be returned. + */ + public boolean lastIncluding; + + /** + * Whether this is a like constraint. in this case only the 'first' + * value should be taken into consideration + */ + public boolean isLike; + + /** + * A list of possible values, for conditions of the type + * "x=1 or x=2 or x=3". + */ + public List list; + + /** + * The property type, if restricted. + * If not restricted, this field is set to PropertyType.UNDEFINED. + */ + public int propertyType = PropertyType.UNDEFINED; + + public boolean isNullRestriction() { + return first == null && last == null && list == null && lastIncluding && firstIncluding; + } + + public boolean isNotNullRestriction() { + return first == null && last == null && list == null && !lastIncluding && !firstIncluding; + } + + @Override + public String toString() { + return (toStringFromTo() + " " + toStringList()).trim(); + } + + private String toStringList() { + if (list == null) { + return ""; + } + StringBuilder buff = new StringBuilder("in("); + int i = 0; + for (PropertyValue p : list) { + if (i++ > 0) { + buff.append(", "); + } + buff.append(p.toString()); + } + buff.append(')'); + return buff.toString(); + } + + private String toStringFromTo() { + if (isNullRestriction()) { + return "is null"; + } else if (isNotNullRestriction()) { + return "is not null"; + } + String f = first == null ? "" : first.toString(); + String l = last == null ? "" : last.toString(); + if (f.equals(l)) { + return f; + } + String fi = first == null ? "" : (firstIncluding ? "[" : "("); + String li = last == null ? "" : (lastIncluding ? "]" : ")"); + return fi + f + ".." + l + li; + } + + /** + * How restrictive a condition is. + * + * @return 0 for "is not null", 10 for equality, and 5 for everything + * else + */ + public int sortOrder() { + if (first == null && last == null) { + if (list == null) { + return 0; + } + return 5; + } + if (first == last) { + return 10; + } + return 5; + } + + @Override + public int hashCode() { + // generated code (Eclipse) + final int prime = 31; + int result = 1; + result = prime * result + ((first == null) ? 0 : first.hashCode()); + result = prime * result + (firstIncluding ? 1231 : 1237); + result = prime * result + (isLike ? 1231 : 1237); + result = prime * result + ((last == null) ? 0 : last.hashCode()); + result = prime * result + (lastIncluding ? 1231 : 1237); + result = prime * result + ((list == null) ? 0 : list.hashCode()); + result = prime * result + + ((propertyName == null) ? 0 : propertyName.hashCode()); + result = prime * result + propertyType; + return result; + } + + @Override + public boolean equals(Object obj) { + // generated code (Eclipse) + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PropertyRestriction other = (PropertyRestriction) obj; + if (first == null) { + if (other.first != null) { + return false; + } + } else if (!first.equals(other.first)) { + return false; + } + if (firstIncluding != other.firstIncluding) { + return false; + } + if (isLike != other.isLike) { + return false; + } + if (last == null) { + if (other.last != null) { + return false; + } + } else if (!last.equals(other.last)) { + return false; + } + if (lastIncluding != other.lastIncluding) { + return false; + } + if (list == null) { + if (other.list != null) { + return false; + } + } else if (!list.equals(other.list)) { + return false; + } + if (propertyName == null) { + if (other.propertyName != null) { + return false; + } + } else if (!propertyName.equals(other.propertyName)) { + return false; + } + if (propertyType != other.propertyType) { + return false; + } + return true; + } + + } + + /** + * The path restriction type. + */ + enum PathRestriction { + + /** + * All nodes. + */ + NO_RESTRICTION("*"), + + /** + * A parent of this node. + */ + PARENT("/.."), + + /** + * This exact node only. + */ + EXACT(""), + + /** + * All direct child nodes. + */ + DIRECT_CHILDREN("/*"), + + /** + * All direct and indirect child nodes (excluding the node with the + * given path). + */ + ALL_CHILDREN("//*"); + + private final String name; + + PathRestriction(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + } + + Filter EMPTY_FILTER = new Filter() { + + private final QueryLimits EMPTY_LIMITS = new QueryLimits() { + + @Override + public long getLimitInMemory() { + return Long.MAX_VALUE; + } + + @Override + public long getLimitReads() { + return Long.MAX_VALUE; + } + + }; + + @Override + public Collection getPropertyRestrictions() { + return Collections.emptyList(); + } + + @Override + public Collection getFulltextConditions() { + return Collections.emptyList(); + } + + @Override + public FullTextExpression getFullTextConstraint() { + return null; + } + + @Override + public QueryLimits getQueryLimits() { + return EMPTY_LIMITS; + } + + @Override + public boolean isAccessible(String path) { + return false; + } + + @Override + public boolean containsNativeConstraint() { + return false; + } + + @Override + public PropertyRestriction getPropertyRestriction(String propertyName) { + return null; + } + + @Override + public List getPropertyRestrictions(String propertyName) { + return Collections.emptyList(); + } + + @Override + public PathRestriction getPathRestriction() { + return PathRestriction.NO_RESTRICTION; + } + + @Override + public String getPath() { + return PathUtils.ROOT_PATH; + } + + @Override + public String getPathPlan() { + return PathRestriction.NO_RESTRICTION.toString(); + } + + @Nullable + @Override + public String getNodeType() { + return null; + } + + @Override + public boolean matchesAllTypes() { + return false; + } + + @Nonnull + @Override + public Set getSupertypes() { + return Collections.emptySet(); + } + + @Nonnull + @Override + public Set getPrimaryTypes() { + return Collections.emptySet(); + } + + @Nonnull + @Override + public Set getMixinTypes() { + return Collections.emptySet(); + } + + @Nullable + @Override + public String getQueryStatement() { + return null; + } + + @Override + public boolean isAlwaysFalse() { + return false; + } + }; + +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision Rev URL \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexRow.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexRow.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexRow.java (working copy) @@ -0,0 +1,52 @@ +/* + * 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.spi.query; + +import org.apache.jackrabbit.oak.api.PropertyValue; + +/** + * A row returned by the index. + */ +public interface IndexRow { + /** + * Marks if the row is virtual and behavior of {@code getPath} is undefined. The implementation may + * choose to return {@code null} or empty string. User of a virtual row should now rely of value of + * {@code getPath} returned from virtual rows. + * @return if path is available for the current row + */ + boolean isVirtualRow(); + + /** + * The path of the node, if available. + * + * @return the path + */ + String getPath(); + + /** + * The value of the given property, if available. This might be a property + * of the given node, or a pseudo-property (a property that is only + * available in the index but not in the node itself, such as "jcr:score"). + * + * @param columnName the column name + * @return the value, or null if not available + */ + PropertyValue getValue(String columnName); + +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexRow.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision Rev URL \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryConstants.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryConstants.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryConstants.java (working copy) @@ -0,0 +1,89 @@ +/* + * 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.spi.query; + +public abstract class QueryConstants { + + /** + * Name of the property restriction used to express query performed + * via NAME and LOCALNAME functions + */ + public static final String RESTRICTION_LOCAL_NAME = ":localname"; + + /** + * Name of the property restriction used to express query performed + * via NAME and LOCALNAME functions + */ + public static final String RESTRICTION_NAME = ":name"; + + /** + * The prefix for restrictions for function-based indexes, for example + * upper(propertyName). Syntax: "function*expression". In order to support + * all kinds of expressions in the future (including nested expressions and + * so on), the format for the expression is written in the Polish notation + * (the RPN, reversed), with "*" as delimiter (as property names may not + * contain "*"), and "@" in front of each property name to distinguish + * between property names and functions. Literals are quoted. Examples: The + * expression "lower(lastName)" is converted to "function*lower {@literal @} + * lastName". The expression "lower(lastName)" is converted to + * "lower(upper(lastName))" is converted to "function*lower*upper* + * {@literal @}lastName". The condition + * "firstName+' '+lastName = 'Tim Cook'" would be "function*+*+ {@literal @} + * firstName*' ' {@literal @}lastName. + */ + public static final String FUNCTION_RESTRICTION_PREFIX = "function*"; + + public static final String SEARCH_ROOT_PATH = "/jcr:root"; + /** + * The "jcr:path" pseudo-property. + */ + // TODO jcr:path isn't an official feature, support it? + public static final String JCR_PATH = "jcr:path"; + + /** + * The "jcr:score" pseudo-property. + */ + public static final String JCR_SCORE = "jcr:score"; + + /** + * The "rep:excerpt" pseudo-property. + */ + public static final String REP_EXCERPT = "rep:excerpt"; + + /** + * The "rep:facet" pseudo-property. + */ + public static final String REP_FACET = "rep:facet"; + + /** + * The "oak:explainScore" pseudo-property. + */ + public static final String OAK_SCORE_EXPLANATION = "oak:scoreExplanation"; + + /** + * The "rep:spellcheck" pseudo-property. + */ + public static final String REP_SPELLCHECK = "rep:spellcheck()"; + + /** + * The "rep:suggest" pseudo-property. + */ + public static final String REP_SUGGEST = "rep:suggest()"; +} \ No newline at end of file Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryConstants.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java (working copy) @@ -0,0 +1,620 @@ +/* + * 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.spi.query; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.annotation.CheckForNull; + +import org.osgi.annotation.versioning.ProviderType; + +import com.google.common.collect.Maps; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import static org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction; + +/** + * Represents an index. The index should use the data in the filter if possible + * to speed up reading. + *

+ * The query engine will pick the index that returns the lowest cost for the + * given filter conditions. + *

+ * The index should only use that part of the filter that speeds up data lookup. + * All other filter conditions should be ignored and not evaluated within this + * index, because the query engine will in any case evaluate the condition (and + * join condition), so that evaluating the conditions within the index would + * actually slow down processing. For example, an index on the property + * "lastName" should not try to evaluate any other restrictions than those on + * the property "lastName", even if the query contains other restrictions. For + * the query "where lastName = 'x' and firstName = 'y'", the query engine will + * set two filter conditions, one for "lastName" and another for "firstName". + * The index on "lastName" should not evaluate the condition on "firstName", + * even thought it will be set in the filter. + */ +public interface QueryIndex { + + /** + * Returns the minimum cost which {@link #getCost(Filter, NodeState)} would return in the best possible case. + *

+ * The implementation should return a static/cached value because it is called very often. + * + * @return the minimum cost for the index + */ + double getMinimumCost(); + + /** + * Estimate the worst-case cost to query with the given filter. The returned + * cost is a value between 1 (very fast; lookup of a unique node) and the + * estimated number of entries to traverse, if the cursor would be fully + * read, and if there could in theory be one network roundtrip or disk read + * operation per node (this method may return a lower number if the data is + * known to be fully in memory). + *

+ * The returned value is supposed to be an estimate and doesn't have to be + * very accurate. Please note this method is called on each index whenever a + * query is run, so the method should be reasonably fast (not read any data + * itself, or at least not read too much data). + *

+ * If an index implementation can not query the data, it has to return + * {@code Double.MAX_VALUE}. + * + * @param filter the filter + * @param rootState root state of the current repository snapshot + * @return the estimated cost in number of read nodes + */ + double getCost(Filter filter, NodeState rootState); + + /** + * Query the index. The returned cursor is supposed to return as few nodes + * as possible, but may return more nodes than necessary. + *

+ * An implementation should only filter the result if it can do so easily + * and efficiently; the query engine will verify the data again (in memory) + * and check for access rights. + *

+ * The method is only called if this index is used for the given query and + * selector, which is only the case if the given index implementation + * returned the lowest cost for the given filter. If the implementation + * returned {@code Double.MAX_VALUE} in the getCost method for the given + * filter, then this method is not called. If it is still called, then it is + * supposed to throw an exception (as it would be an internal error of the + * query engine). + * + * @param filter the filter + * @param rootState root state of the current repository snapshot + * @return a cursor to iterate over the result + */ + Cursor query(Filter filter, NodeState rootState); + + /** + * Get the query plan for the given filter. This method is called when + * running an {@code EXPLAIN SELECT} query, or for logging purposes. The + * result should be human readable. + * + * @param filter the filter + * @param rootState root state of the current repository snapshot + * @return the query plan + */ + String getPlan(Filter filter, NodeState rootState); + + /** + * Get the unique index name. + * + * @return the index name + */ + String getIndexName(); + + /** + * A marker interface which means this index supports executing native queries + */ + interface NativeQueryIndex { + // a marker interface + } + + /** + * A marker interface which means this index supports may support more than + * just the minimal fulltext query syntax. If this index is used, then the + * query engine does not verify the fulltext constraint(s) for the given + * selector. + */ + interface FulltextQueryIndex extends QueryIndex, NativeQueryIndex { + + /** + * Returns the NodeAggregator responsible for providing the aggregation + * settings or null if aggregation is not available/desired. + * + * @return the node aggregator or null + */ + @CheckForNull + NodeAggregator getNodeAggregator(); + + } + + interface AdvanceFulltextQueryIndex extends FulltextQueryIndex, AdvancedQueryIndex { + // a marker interface + } + + /** + * An query index that may support using multiple access orders + * (returning the rows in a specific order), and that can provide detailed + * information about the cost. + */ + interface AdvancedQueryIndex { + + /** + * Return the possible index plans for the given filter and sort order. + * Please note this method is supposed to run quickly. That means it + * should usually not read any data from the storage. + * + * @param filter the filter + * @param sortOrder the sort order or null if no sorting is required + * @param rootState root state of the current repository snapshot + * @return the list of index plans (null if none) + */ + List getPlans(Filter filter, List sortOrder, + NodeState rootState); + + /** + * Get the query plan description (for logging purposes). + *

+ * The index plan is one of the plans that the index returned in the + * getPlans call. + * + * @param plan the index plan + * @param root root state of the current repository snapshot + * @return the query plan description + */ + String getPlanDescription(IndexPlan plan, NodeState root); + + /** + * Start a query. The filter and sort order of the index plan is to be + * used. + *

+ * The index plan is one of the plans that the index returned in the + * getPlans call. + * + * @param plan the index plan to use + * @param rootState root state of the current repository snapshot + * @return a cursor to iterate over the result + */ + Cursor query(IndexPlan plan, NodeState rootState); + + } + + /** + * An index plan. + */ + @ProviderType + interface IndexPlan extends Cloneable{ + + /** + * The cost to execute the query once. The returned value should + * approximately match the number of disk read operations plus the + * number of network roundtrips (worst case). + * + * @return the cost per execution, in estimated number of I/O operations + */ + double getCostPerExecution(); + + /** + * The cost to read one entry from the cursor. The returned value should + * approximately match the number of disk read operations plus the + * number of network roundtrips (worst case). + * + * @return the lookup cost per entry, in estimated number of I/O operations + */ + double getCostPerEntry(); + + /** + * The estimated number of entries in the cursor that is returned by the query method, + * when using this plan. This value does not have to be accurate. + * + * @return the estimated number of entries + */ + long getEstimatedEntryCount(); + + /** + * The filter to use. + * + * @return the filter + */ + Filter getFilter(); + + /** + * Use the given filter. + */ + void setFilter(Filter filter); + + /** + * Whether the index is not always up-to-date. + * + * @return whether the index might be updated asynchronously + */ + boolean isDelayed(); + + /** + * Whether the fulltext part of the filter is evaluated (possibly with + * an extended syntax). If set, the fulltext part of the filter is not + * evaluated any more within the query engine. + * + * @return whether the index supports full-text extraction + */ + boolean isFulltextIndex(); + + /** + * Whether the cursor is able to read all properties from a node. + * If yes, then the query engine will not have to read the data itself. + * + * @return wheter node data is returned + */ + boolean includesNodeData(); + + /** + * The sort order of the returned entries, or null if unsorted. + * + * @return the sort order + */ + List getSortOrder(); + + /** + * The node state with the index definition. + * + * @return the node state with the index definition. + */ + NodeState getDefinition(); + + /** + * The path prefix for this index plan. + */ + String getPathPrefix(); + + /** + * The property restriction for this index plan or null if + * this index plan isn't base on a property restriction. E.g. a plan + * based on an order by clause in the query. + * + * @return the restriction this plan is based on or null. + */ + @CheckForNull + PropertyRestriction getPropertyRestriction(); + + /** + * Creates a cloned copy of current plan. Mostly used when the filter needs to be + * modified for a given call + * + * @return clone of current plan + */ + IndexPlan copy(); + + /** + * Returns the value of the named attribute as an Object, + * or null if no attribute of the given name exists. + * + * @param name String specifying the name of + * the attribute + * + * @return an Object containing the value + * of the attribute, or null if the attribute does not exist + */ + @CheckForNull + Object getAttribute(String name); + + /** + * Get the unique plan name. + * + * @return the plan name + */ + @CheckForNull + String getPlanName(); + + /** + * A builder for index plans. + */ + class Builder { + + protected double costPerExecution = 1.0; + protected double costPerEntry = 1.0; + protected long estimatedEntryCount = 1000000; + protected Filter filter; + protected boolean isDelayed; + protected boolean isFulltextIndex; + protected boolean includesNodeData; + protected List sortOrder; + protected NodeState definition; + protected PropertyRestriction propRestriction; + protected String pathPrefix = "/"; + protected Map attributes = Maps.newHashMap(); + protected String planName; + + public Builder setCostPerExecution(double costPerExecution) { + this.costPerExecution = costPerExecution; + return this; + } + + public Builder setCostPerEntry(double costPerEntry) { + this.costPerEntry = costPerEntry; + return this; + } + + public Builder setEstimatedEntryCount(long estimatedEntryCount) { + this.estimatedEntryCount = estimatedEntryCount; + return this; + } + + public Builder setFilter(Filter filter) { + this.filter = filter; + return this; + } + + public Builder setDelayed(boolean isDelayed) { + this.isDelayed = isDelayed; + return this; + } + + public Builder setFulltextIndex(boolean isFulltextIndex) { + this.isFulltextIndex = isFulltextIndex; + return this; + } + + public Builder setIncludesNodeData(boolean includesNodeData) { + this.includesNodeData = includesNodeData; + return this; + } + + public Builder setSortOrder(List sortOrder) { + this.sortOrder = sortOrder; + return this; + } + + public Builder setDefinition(NodeState definition) { + this.definition = definition; + return this; + } + + public Builder setPropertyRestriction(PropertyRestriction restriction) { + this.propRestriction = restriction; + return this; + } + + public Builder setPathPrefix(String pathPrefix) { + this.pathPrefix = pathPrefix; + return this; + } + + public Builder setAttribute(String key, Object value) { + this.attributes.put(key, value); + return this; + } + + public Builder setPlanName(String name) { + this.planName = name; + return this; + } + + public IndexPlan build() { + + return new IndexPlan() { + + private final double costPerExecution = + Builder.this.costPerExecution; + private final double costPerEntry = + Builder.this.costPerEntry; + private final long estimatedEntryCount = + Builder.this.estimatedEntryCount; + private Filter filter = + Builder.this.filter; + private final boolean isDelayed = + Builder.this.isDelayed; + private final boolean isFulltextIndex = + Builder.this.isFulltextIndex; + private final boolean includesNodeData = + Builder.this.includesNodeData; + private final List sortOrder = + Builder.this.sortOrder == null ? + null : new ArrayList( + Builder.this.sortOrder); + private final NodeState definition = + Builder.this.definition; + private final PropertyRestriction propRestriction = + Builder.this.propRestriction; + private final String pathPrefix = + Builder.this.pathPrefix; + private final Map attributes = + Builder.this.attributes; + private final String planName = Builder.this.planName; + + @Override + public String toString() { + return String.format( + "{ costPerExecution : %s," + + " costPerEntry : %s," + + " estimatedEntryCount : %s," + + " filter : %s," + + " isDelayed : %s," + + " isFulltextIndex : %s," + + " includesNodeData : %s," + + " sortOrder : %s," + + " definition : %s," + + " propertyRestriction : %s," + + " pathPrefix : %s }", + costPerExecution, + costPerEntry, + estimatedEntryCount, + filter, + isDelayed, + isFulltextIndex, + includesNodeData, + sortOrder, + definition, + propRestriction, + pathPrefix + ); + } + + @Override + public double getCostPerExecution() { + return costPerExecution; + } + + @Override + public double getCostPerEntry() { + return costPerEntry; + } + + @Override + public long getEstimatedEntryCount() { + return estimatedEntryCount; + } + + @Override + public Filter getFilter() { + return filter; + } + + @Override + public void setFilter(Filter filter) { + this.filter = filter; + } + + @Override + public boolean isDelayed() { + return isDelayed; + } + + @Override + public boolean isFulltextIndex() { + return isFulltextIndex; + } + + @Override + public boolean includesNodeData() { + return includesNodeData; + } + + @Override + public List getSortOrder() { + return sortOrder; + } + + @Override + public NodeState getDefinition() { + return definition; + } + + @Override + public PropertyRestriction getPropertyRestriction() { + return propRestriction; + } + + @Override + public String getPathPrefix() { + return pathPrefix; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + @Override + public IndexPlan copy() { + try { + return (IndexPlan) super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException(e); + } + } + + @Override + public Object getAttribute(String name) { + return attributes.get(name); + } + + @Override + public String getPlanName() { + return planName; + } + }; + } + + } + + } + + /** + * A sort order entry. + */ + class OrderEntry { + + /** + * The property name on where to sort. + */ + private final String propertyName; + + /** + * The property type. Null if not known. + */ + private final Type propertyType; + + /** + * The sort order (ascending or descending). + */ + public enum Order { ASCENDING, DESCENDING } + + private final Order order; + + public OrderEntry(String propertyName, Type propertyType, Order order) { + this.propertyName = propertyName; + this.propertyType = propertyType; + this.order = order; + } + + public String getPropertyName() { + return propertyName; + } + + public Order getOrder() { + return order; + } + + public Type getPropertyType() { + return propertyType; + } + + @Override + public String toString() { + return String.format( + "{ propertyName : %s, propertyType : %s, order : %s }", + propertyName, + propertyType, + order); + } + } + + interface NodeAggregator { + + Iterator getParents(NodeState rootState, String path); + + } +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision Rev URL \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndexProvider.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndexProvider.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndexProvider.java (working copy) @@ -0,0 +1,43 @@ +/* + * 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.spi.query; + +import java.util.List; + +import javax.annotation.Nonnull; + +import org.apache.jackrabbit.oak.spi.state.NodeState; + +/** + * A mechanism to index data. Indexes might be added or removed at runtime, + * possibly by changing content in the repository. The provider knows about the + * indexes available at a given time. + */ +public interface QueryIndexProvider { + + /** + * Get the currently configured indexes. + * + * @param nodeState the node state of the root + * @return the list of indexes + */ + @Nonnull + List getQueryIndexes(NodeState nodeState); + +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndexProvider.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision Rev URL \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryLimits.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryLimits.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryLimits.java (working copy) @@ -0,0 +1,26 @@ +/* + * 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.spi.query; + +public interface QueryLimits { + + long getLimitInMemory(); + + long getLimitReads(); +} Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/WhiteboardIndexProvider.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/WhiteboardIndexProvider.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/WhiteboardIndexProvider.java (working copy) @@ -0,0 +1,47 @@ +/* + * 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.spi.query; + +import java.util.List; + +import javax.annotation.Nonnull; + +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.whiteboard.AbstractServiceTracker; + +/** + * Dynamic {@link QueryIndexProvider} based on the available + * whiteboard services. + */ +public class WhiteboardIndexProvider + extends AbstractServiceTracker + implements QueryIndexProvider { + + public WhiteboardIndexProvider() { + super(QueryIndexProvider.class); + } + + @Override @Nonnull + public List getQueryIndexes(NodeState nodeState) { + QueryIndexProvider composite = + CompositeQueryIndexProvider.compose(getServices()); + return composite.getQueryIndexes(nodeState); + } + +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/WhiteboardIndexProvider.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextAnd.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextAnd.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextAnd.java (working copy) @@ -0,0 +1,87 @@ +/* + * 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.spi.query.fulltext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * A fulltext "and" condition. + */ +public class FullTextAnd extends FullTextExpression { + + public final List list; + + public FullTextAnd(List list) { + this.list = list; + } + + @Override + public boolean evaluate(String value) { + for (FullTextExpression e : list) { + if (!e.evaluate(value)) { + return false; + } + } + return true; + } + + @Override + public FullTextExpression simplify() { + Set set = FullTextOr.getUniqueSet(list); + if (set.size() == 1) { + return set.iterator().next(); + } + ArrayList l = new ArrayList( + set.size()); + l.addAll(set); + return new FullTextAnd(l); + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + int i = 0; + for (FullTextExpression e : list) { + if (i++ > 0) { + buff.append(' '); + } + if (e.getPrecedence() < getPrecedence()) { + buff.append('('); + } + buff.append(e.toString()); + if (e.getPrecedence() < getPrecedence()) { + buff.append(')'); + } + } + return buff.toString(); + } + + @Override + public int getPrecedence() { + return PRECEDENCE_AND; + } + + @Override + public boolean accept(FullTextVisitor v) { + return v.visit(this); + } + +} \ No newline at end of file Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextAnd.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextContains.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextContains.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextContains.java (working copy) @@ -0,0 +1,82 @@ +/* + * 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.spi.query.fulltext; + +/** + * A group of full-text expressions that reflects a "contains(...)" expression, + * and allows to access the original (unparsed) full text term. + */ +public class FullTextContains extends FullTextExpression { + + private final String propertyName; + private final String rawText; + private final FullTextExpression base; + + public FullTextContains(String propertyName, String rawText, FullTextExpression base) { + this.propertyName = propertyName; + this.rawText = rawText; + this.base = base; + } + + @Override + public int getPrecedence() { + return base.getPrecedence(); + } + + @Override + public boolean evaluate(String value) { + return base.evaluate(value); + } + + @Override + FullTextExpression simplify() { + FullTextExpression s = base.simplify(); + if (s == base) { + return this; + } + return new FullTextContains(propertyName, rawText, s); + } + + @Override + public String toString() { + return base.toString(); + } + + @Override + public boolean accept(FullTextVisitor v) { + return v.visit(this); + } + + public FullTextExpression getBase() { + return base; + } + + public String getPropertyName() { + return propertyName; + } + + public String getRawText() { + return rawText; + } + + @Override + public boolean isNot() { + return base.isNot(); + } +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextContains.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextExpression.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextExpression.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextExpression.java (working copy) @@ -0,0 +1,101 @@ +/* + * 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.spi.query.fulltext; + +/** + * The base class for fulltext condition expression. + */ +public abstract class FullTextExpression { + + /** + * The operator precedence for OR conditions. + */ + public static final int PRECEDENCE_OR = 1; + + /** + * The operator precedence for AND conditions. + */ + public static final int PRECEDENCE_AND = 2; + + /** + * The operator precedence for terms. + */ + public static final int PRECEDENCE_TERM = 3; + + /** + * Get the operator precedence. + * + * @return the precedence + */ + public abstract int getPrecedence(); + + /** + * Evaluate whether the value matches the condition. + * + * @param value the value + * @return true if it matches + */ + public abstract boolean evaluate(String value); + + /** + * Simplify the expression if possible (removing duplicate conditions). + * + * @return the simplified expression + */ + abstract FullTextExpression simplify(); + + /** + * Get the string representation of the condition. + */ + @Override + public abstract String toString(); + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (!(other instanceof FullTextExpression)) { + return false; + } + return toString().equals(other.toString()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + /** + * Let the expression call the applicable visit method of the visitor. + * + * @param v the visitor + * @return true if the visit method returned true + */ + public abstract boolean accept(FullTextVisitor v); + + /** + * Whether the current {@link FullTextExpression} is a {@code NOT} condition or not. Default is + * false + * + * @return true if the current condition represent a NOT, false otherwise. + */ + public boolean isNot() { + return false; + } +} \ No newline at end of file Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextExpression.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextOr.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextOr.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextOr.java (working copy) @@ -0,0 +1,98 @@ +/* + * 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.spi.query.fulltext; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * A fulltext "or" condition. + */ +public class FullTextOr extends FullTextExpression { + + public final List list; + + public FullTextOr(List list) { + this.list = list; + } + + @Override + public boolean evaluate(String value) { + for (FullTextExpression e : list) { + if (e.evaluate(value)) { + return true; + } + } + return false; + } + + @Override + public FullTextExpression simplify() { + Set set = getUniqueSet(list); + if (set.size() == 1) { + return set.iterator().next(); + } + ArrayList l = new ArrayList( + set.size()); + l.addAll(set); + return new FullTextOr(l); + } + + static Set getUniqueSet( + List list) { + // remove duplicates, but keep order + LinkedHashSet set = new LinkedHashSet(list.size()); + for (int i = 0; i < list.size(); i++) { + set.add(list.get(i).simplify()); + } + return set; + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + int i = 0; + for (FullTextExpression e : list) { + if (i++ > 0) { + buff.append(" OR "); + } + if (e.getPrecedence() < getPrecedence()) { + buff.append('('); + } + buff.append(e.toString()); + if (e.getPrecedence() < getPrecedence()) { + buff.append(')'); + } + } + return buff.toString(); + } + + @Override + public int getPrecedence() { + return PRECEDENCE_OR; + } + + @Override + public boolean accept(FullTextVisitor v) { + return v.visit(this); + } + +} \ No newline at end of file Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextOr.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextParser.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextParser.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextParser.java (working copy) @@ -0,0 +1,213 @@ +/* + * 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.spi.query.fulltext; + +import java.text.ParseException; +import java.util.ArrayList; + + +/** + * A parser for fulltext condition literals. The grammar is defined in the + * + * JCR 2.0 specification, 6.7.19 FullTextSearch, + * as follows (a bit simplified): + *

+ * FullTextSearchLiteral ::= Disjunct {' OR ' Disjunct}
+ * Disjunct ::= Term {' ' Term}
+ * Term ::= ['-'] SimpleTerm
+ * SimpleTerm ::= Word | '"' Word {' ' Word} '"'
+ * 
+ */ +public class FullTextParser { + + /** + * Compatibility for Jackrabbit 2.0 single quoted phrase queries. + * (contains(., "word ''hello world'' word") + * These are queries that delimit a phrase with a single quote + * instead, as in the spec, using double quotes. + */ + private static final boolean JACKRABBIT_2_SINGLE_QUOTED_PHRASE = true; + + private String propertyName; + private String text; + private int parseIndex; + + public static FullTextExpression parse(String propertyName, String text) throws ParseException { + FullTextParser p = new FullTextParser(); + p.propertyName = propertyName; + p.text = text; + FullTextExpression e = p.parseOr(); + return e; + } + + FullTextExpression parseOr() throws ParseException { + ArrayList list = new ArrayList(); + list.add(parseAnd()); + while (parseIndex < text.length()) { + if (text.substring(parseIndex).startsWith("OR ")) { + parseIndex += 3; + list.add(parseAnd()); + } else { + break; + } + } + FullTextOr or = new FullTextOr(list); + return or.simplify(); + } + + FullTextExpression parseAnd() throws ParseException { + ArrayList list = new ArrayList(); + list.add(parseTerm()); + while (parseIndex < text.length()) { + if (text.substring(parseIndex).startsWith("OR ")) { + break; + } + list.add(parseTerm()); + } + FullTextAnd and = new FullTextAnd(list); + return and.simplify(); + } + + FullTextExpression parseTerm() throws ParseException { + if (parseIndex >= text.length()) { + throw getSyntaxError("term"); + } + boolean not = false; + StringBuilder buff = new StringBuilder(); + char c = text.charAt(parseIndex); + if (c == '-' && parseIndex < text.length() - 1 && + text.charAt(parseIndex + 1) != ' ') { + c = text.charAt(++parseIndex); + not = true; + } + boolean escaped = false; + String boost = null; + if (c == '\"') { + parseIndex++; + while (true) { + if (parseIndex >= text.length()) { + throw getSyntaxError("double quote"); + } + c = text.charAt(parseIndex++); + if (c == '\\') { + escaped = true; + if (parseIndex >= text.length()) { + throw getSyntaxError("escaped char"); + } + c = text.charAt(parseIndex++); + buff.append(c); + } else if (c == '\"') { + if (parseIndex < text.length()) { + if (text.charAt(parseIndex) == '^') { + boost = ""; + } else if (text.charAt(parseIndex) != ' ') { + throw getSyntaxError("space"); + } + } + parseIndex++; + break; + } else { + buff.append(c); + } + } + } else if (c == '\'' && JACKRABBIT_2_SINGLE_QUOTED_PHRASE) { + // basically the same as double quote + parseIndex++; + while (true) { + if (parseIndex >= text.length()) { + throw getSyntaxError("single quote"); + } + c = text.charAt(parseIndex++); + if (c == '\\') { + escaped = true; + if (parseIndex >= text.length()) { + throw getSyntaxError("escaped char"); + } + c = text.charAt(parseIndex++); + buff.append(c); + } else if (c == '\'') { + if (parseIndex < text.length()) { + if (text.charAt(parseIndex) == '^') { + boost = ""; + } else if (text.charAt(parseIndex) != ' ') { + throw getSyntaxError("space"); + } + } + parseIndex++; + break; + } else { + buff.append(c); + } + } + } else { + do { + c = text.charAt(parseIndex++); + if (c == '\\') { + escaped = true; + if (parseIndex >= text.length()) { + throw getSyntaxError("escaped char"); + } + c = text.charAt(parseIndex++); + buff.append(c); + } else if (c == '^') { + boost = ""; + break; + } else if (c <= ' ') { + while (parseIndex < text.length()) { + c = text.charAt(parseIndex); + if (c > ' ') { + break; + } + parseIndex++; + } + break; + } else { + buff.append(c); + } + } while (parseIndex < text.length()); + } + if (boost != null) { + StringBuilder b = new StringBuilder(); + while (parseIndex < text.length()) { + c = text.charAt(parseIndex++); + if ((c < '0' || c > '9') && c != '.') { + break; + } + b.append(c); + } + boost = b.toString(); + } + if (buff.length() == 0) { + throw getSyntaxError("term"); + } + String text = buff.toString(); + FullTextTerm term = new FullTextTerm(propertyName, text, not, escaped, boost); + return term.simplify(); + } + + private ParseException getSyntaxError(String expected) { + int index = Math.max(0, Math.min(parseIndex, text.length() - 1)); + String query = text.substring(0, index) + "(*)" + text.substring(index).trim(); + if (expected != null) { + query += "; expected: " + expected; + } + return new ParseException("FullText expression: " + query, index); + } + +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextParser.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextTerm.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextTerm.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextTerm.java (working copy) @@ -0,0 +1,219 @@ +/* + * 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.spi.query.fulltext; + +/** + * A fulltext term, or a "not" term. + */ +public class FullTextTerm extends FullTextExpression { + private final boolean not; + private final String propertyName; + private final String text; + private final String filteredText; + private final String boost; + private final LikePattern like; + + public FullTextTerm(String propertyName, FullTextTerm copy) { + this.propertyName = propertyName; + this.not = copy.not; + this.text = copy.text; + this.filteredText = copy.filteredText; + this.boost = copy.boost; + this.like = copy.like; + } + + public FullTextTerm(String propertyName, String text, boolean not, boolean escaped, String boost) { + this.propertyName = propertyName; + this.text = text; + this.not = not; + this.boost = boost; + // for testFulltextIntercapSQL + // filter special characters such as ' + // to make tests pass, for example the + // FulltextQueryTest.testFulltextExcludeSQL, + // which searches for: + // "text ''fox jumps'' -other" + // (please note the two single quotes instead of + // double quotes before for and after jumps) + boolean pattern = false; + if (escaped) { + filteredText = text; + } else { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '*') { + buff.append('%'); + pattern = true; + } else if (c == '?') { + buff.append('_'); + pattern = true; + } else if (c == '_') { + buff.append("\\_"); + pattern = true; + } else if (isFullTextCharacter(c) || " +-:&/.".indexOf(c) >= 0) { + buff.append(c); + } + } + this.filteredText = buff.toString().toLowerCase(); + } + if (pattern) { + like = new LikePattern("%" + filteredText + "%"); + } else { + like = null; + } + } + + /** + * Whether or not the given character is part of a full-text term that + * should be indexed. Not indexed are punctuation, control characters such as tab, + * + * See also Unicode Categories. + * + * @param c the character + * @return true if the character should be indexed + */ + public static boolean isFullTextCharacter(char c) { + switch (Character.getType(c)) { + // Category "Letter" (Lu, Ll, Lt, Lm, Lo) + case Character.UPPERCASE_LETTER: + case Character.LOWERCASE_LETTER: + case Character.TITLECASE_LETTER: + case Character.MODIFIER_LETTER: + case Character.OTHER_LETTER: + return true; + // Category "Number" (Nd, Nl, No) + case Character.DECIMAL_DIGIT_NUMBER: + case Character.LETTER_NUMBER: + case Character.OTHER_NUMBER: + return true; + // Category "Symbol" (Sm, Sc, Sk, So) + case Character.MATH_SYMBOL: + case Character.CURRENCY_SYMBOL: + case Character.MODIFIER_SYMBOL: + case Character.OTHER_SYMBOL: + return true; + // Category "Control" (Cc, Cf) + case Character.CONTROL: + case Character.FORMAT: + return false; + // Category "Control" (Cs, Co, Cn) + case Character.SURROGATE: + case Character.PRIVATE_USE: + case Character.UNASSIGNED: + return true; + // Category "Mark" (Mn, Mc, Me) + case Character.NON_SPACING_MARK: + case Character.COMBINING_SPACING_MARK: + case Character.ENCLOSING_MARK: + return false; + // Category "Punctuation" (Pc, Pd, Ps, Pe, Pi, Pf, Po) + case Character.CONNECTOR_PUNCTUATION: + case Character.DASH_PUNCTUATION: + case Character.START_PUNCTUATION: + case Character.END_PUNCTUATION: + case Character.INITIAL_QUOTE_PUNCTUATION: + case Character.FINAL_QUOTE_PUNCTUATION: + case Character.OTHER_PUNCTUATION: + return false; + // Category "Separator" (Zs, Zl, Zp) + case Character.SPACE_SEPARATOR: + case Character.LINE_SEPARATOR: + case Character.PARAGRAPH_SEPARATOR: + return false; + } + // unknown + return true; + } + + @Override + public boolean evaluate(String value) { + // toLowerCase for testFulltextIntercapSQL + value = value.toLowerCase(); + if (like != null) { + return like.matches(value); + } + if (not) { + return value.indexOf(filteredText) < 0; + } + return value.indexOf(filteredText) >= 0; + } + + @Override + public + FullTextExpression simplify() { + return this; + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + if (not) { + buff.append('-'); + } + if (propertyName != null && !"*".equals(propertyName)) { + buff.append(propertyName).append(':'); + } + buff.append('\"'); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\\') { + buff.append(c); + } else if (c == '\"') { + buff.append('\\'); + } + buff.append(c); + } + buff.append('\"'); + if (boost != null) { + buff.append('^').append(boost); + } + return buff.toString(); + } + + public String getPropertyName() { + return propertyName; + } + + public String getBoost() { + return boost; + } + + @Override + public boolean isNot() { + return not; + } + + public String getText() { + return text; + } + + @Override + public int getPrecedence() { + return PRECEDENCE_TERM; + } + + @Override + public boolean accept(FullTextVisitor v) { + return v.visit(this); + } + +} \ No newline at end of file Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextTerm.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextVisitor.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextVisitor.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextVisitor.java (working copy) @@ -0,0 +1,92 @@ +/* + * 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.spi.query.fulltext; + +/** + * A visitor for full-text expressions. This class is abstract because at least + * one of the methods needs to be implemented to make anything useful, most + * likely visit(FullTextTerm). + */ +public interface FullTextVisitor { + + /** + * Visit an "contains" expression. + * + * @param contains the "contains" expression + * @return true if visiting should continue + */ + boolean visit(FullTextContains contains); + + /** + * Visit an "and" expression. + * + * @param and the "and" expression + * @return true if visiting should continue + */ + boolean visit(FullTextAnd and); + + /** + * Visit an "or" expression. + * + * @param or the "or" expression + * @return true if visiting should continue + */ + boolean visit(FullTextOr or); + + /** + * Visit a term + * + * @param term the term + * @return true if visiting should continue + */ + boolean visit(FullTextTerm term); + + /** + * The base implementation of a full-text visitor. + */ + public abstract static class FullTextVisitorBase implements FullTextVisitor { + + @Override + public boolean visit(FullTextContains contains) { + return contains.getBase().accept(this); + } + + @Override + public boolean visit(FullTextAnd and) { + for (FullTextExpression e : and.list) { + if (!e.accept(this)) { + return false; + } + } + return true; + } + + @Override + public boolean visit(FullTextOr or) { + for (FullTextExpression e : or.list) { + if (!e.accept(this)) { + return false; + } + } + return true; + } + + } + +} Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/FullTextVisitor.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/LikePattern.java =================================================================== --- oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/LikePattern.java (nonexistent) +++ oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/LikePattern.java (working copy) @@ -0,0 +1,186 @@ +/* + * 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.spi.query.fulltext; + +/** + * A pattern matcher. + */ +public class LikePattern { + + // TODO LIKE: optimize condition to '=' when no patterns are used, or 'between x and x+1' + // TODO LIKE: what to do for invalid patterns (patterns ending with a backslash) + + private static final int MATCH = 0, ONE = 1, ANY = 2; + + private String patternString; + private boolean invalidPattern; + private char[] patternChars; + private int[] patternTypes; + private int patternLength; + private String lowerBounds, upperBound; + + public LikePattern(String pattern) { + initPattern(pattern); + initBounds(); + } + + public boolean matches(String value) { + return !invalidPattern && compareAt(value, 0, 0, value.length(), patternChars, patternTypes); + } + + private static boolean compare(char[] pattern, String s, int pi, int si) { + return pattern[pi] == s.charAt(si); + } + + private boolean compareAt(String s, int pi, int si, int sLen, char[] pattern, int[] types) { + for (; pi < patternLength; pi++) { + int type = types[pi]; + switch (type) { + case MATCH: + if (si >= sLen || !compare(pattern, s, pi, si++)) { + return false; + } + break; + case ONE: + if (si++ >= sLen) { + return false; + } + break; + case ANY: + if (++pi >= patternLength) { + return true; + } + while (si < sLen) { + if (compare(pattern, s, pi, si) && compareAt(s, pi, si, sLen, pattern, types)) { + return true; + } + si++; + } + return false; + default: + throw new IllegalArgumentException("Internal error: " + type); + } + } + return si == sLen; + } + + private void initPattern(String p) { + patternLength = 0; + if (p == null) { + patternTypes = null; + patternChars = null; + return; + } + int len = p.length(); + patternChars = new char[len]; + patternTypes = new int[len]; + boolean lastAny = false; + for (int i = 0; i < len; i++) { + char c = p.charAt(i); + int type; + if (c == '\\') { + if (i >= len - 1) { + invalidPattern = true; + return; + } + c = p.charAt(++i); + type = MATCH; + lastAny = false; + } else if (c == '%') { + if (lastAny) { + continue; + } + type = ANY; + lastAny = true; + } else if (c == '_') { + type = ONE; + } else { + type = MATCH; + lastAny = false; + } + patternTypes[patternLength] = type; + patternChars[patternLength++] = c; + } + for (int i = 0; i < patternLength - 1; i++) { + if (patternTypes[i] == ANY && patternTypes[i + 1] == ONE) { + patternTypes[i] = ONE; + patternTypes[i + 1] = ANY; + } + } + patternString = new String(patternChars, 0, patternLength); + } + + @Override + public String toString() { + return patternString; + } + + /** + * Get the lower bound if any. + * + * @return return the lower bound, or null if unbound + */ + public String getLowerBound() { + return lowerBounds; + } + + /** + * Get the upper bound if any. + * + * @return return the upper bound, or null if unbound + */ + public String getUpperBound() { + return upperBound; + } + + private void initBounds() { + if (invalidPattern) { + return; + } + if (patternLength <= 0 || patternTypes[0] != MATCH) { + // can't use an index + return; + } + int maxMatch = 0; + StringBuilder buff = new StringBuilder(); + while (maxMatch < patternLength && patternTypes[maxMatch] == MATCH) { + buff.append(patternChars[maxMatch++]); + } + String lower = buff.toString(); + if (lower.isEmpty()) { + return; + } + if (maxMatch == patternLength) { + lowerBounds = upperBound = lower; + return; + } + lowerBounds = lower; + char next = lower.charAt(lower.length() - 1); + // search the 'next' unicode character (or at least a character + // that is higher) + for (int i = 1; i < 2000; i++) { + String upper = lower.substring(0, lower.length() - 1) + (char) (next + i); + if (upper.compareTo(lower) > 0) { + upperBound = upper; + return; + } + } + } + +} \ No newline at end of file Property changes on: oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/fulltext/LikePattern.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-query-spi/src/test/resources/logging.properties =================================================================== --- oak-query-spi/src/test/resources/logging.properties (nonexistent) +++ oak-query-spi/src/test/resources/logging.properties (working copy) @@ -0,0 +1,16 @@ +# 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. + +handlers = org.slf4j.bridge.SLF4JBridgeHandler Index: oak-run/pom.xml =================================================================== --- oak-run/pom.xml (revision 1804638) +++ oak-run/pom.xml (working copy) @@ -205,6 +205,11 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + + + org.apache.jackrabbit oak-core-spi ${project.version} Index: oak-solr-core/pom.xml =================================================================== --- oak-solr-core/pom.xml (revision 1804638) +++ oak-solr-core/pom.xml (working copy) @@ -127,6 +127,11 @@ org.apache.jackrabbit + oak-query-spi + ${project.version} + + + org.apache.jackrabbit oak-core ${project.version} tests Index: oak-store-composite/pom.xml =================================================================== --- oak-store-composite/pom.xml (revision 1804638) +++ oak-store-composite/pom.xml (working copy) @@ -106,6 +106,11 @@ oak-store-spi ${project.version} + + org.apache.jackrabbit + oak-query-spi + ${project.version} + Index: pom.xml =================================================================== --- pom.xml (revision 1804638) +++ pom.xml (working copy) @@ -41,6 +41,7 @@ oak-api oak-core-spi oak-store-spi + oak-query-spi oak-store-composite oak-blob-plugins