Author: thomasm
Date: Wed Oct 9 13:58:39 2013
New Revision: 1530610
URL: http://svn.apache.org/r1530610
Log:
OAK-1085 Compatibility for queries with not(child/@prop) conditions
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.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/AstVisitor.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java?rev=1530610&r1=1530609&r2=1530610&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
Wed Oct 9 13:58:39 2013
@@ -48,6 +48,7 @@ import org.apache.jackrabbit.oak.query.a
import org.apache.jackrabbit.oak.query.ast.OrImpl;
import org.apache.jackrabbit.oak.query.ast.OrderingImpl;
import org.apache.jackrabbit.oak.query.ast.PropertyExistenceImpl;
+import org.apache.jackrabbit.oak.query.ast.PropertyInexistenceImpl;
import org.apache.jackrabbit.oak.query.ast.PropertyValueImpl;
import org.apache.jackrabbit.oak.query.ast.SameNodeImpl;
import org.apache.jackrabbit.oak.query.ast.SameNodeJoinConditionImpl;
@@ -207,6 +208,13 @@ public class QueryImpl implements Query
node.bindSelector(source);
return true;
}
+
+ @Override
+ public boolean visit(PropertyInexistenceImpl node) {
+ node.setQuery(query);
+ node.bindSelector(source);
+ return true;
+ }
@Override
public boolean visit(PropertyValueImpl node) {
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=1530610&r1=1530609&r2=1530610&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
Wed Oct 9 13:58:39 2013
@@ -42,6 +42,7 @@ import org.apache.jackrabbit.oak.query.a
import org.apache.jackrabbit.oak.query.ast.Operator;
import org.apache.jackrabbit.oak.query.ast.OrderingImpl;
import org.apache.jackrabbit.oak.query.ast.PropertyExistenceImpl;
+import org.apache.jackrabbit.oak.query.ast.PropertyInexistenceImpl;
import org.apache.jackrabbit.oak.query.ast.PropertyValueImpl;
import org.apache.jackrabbit.oak.query.ast.SelectorImpl;
import org.apache.jackrabbit.oak.query.ast.SourceImpl;
@@ -399,9 +400,10 @@ public class SQL2Parser {
throw getSyntaxError("propertyName (NOT NULL is only supported
for properties)");
}
PropertyValueImpl p = (PropertyValueImpl) left;
- c = getPropertyExistence(p);
- if (!not) {
- c = factory.not(c);
+ if (not) {
+ c = getPropertyExistence(p);
+ } else {
+ c = getPropertyInexistence(p);
}
} else if (readIf("NOT")) {
if (readIf("IS")) {
@@ -427,6 +429,10 @@ public class SQL2Parser {
private PropertyExistenceImpl getPropertyExistence(PropertyValueImpl p)
throws ParseException {
return factory.propertyExistence(p.getSelectorName(),
p.getPropertyName());
}
+
+ private PropertyInexistenceImpl getPropertyInexistence(PropertyValueImpl
p) throws ParseException {
+ return factory.propertyInexistence(p.getSelectorName(),
p.getPropertyName());
+ }
private ConstraintImpl parseConditionFunctionIf(String functionName)
throws ParseException {
ConstraintImpl c;
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=1530610&r1=1530609&r2=1530610&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
Wed Oct 9 13:58:39 2013
@@ -114,6 +114,10 @@ public class AstElementFactory {
public PropertyExistenceImpl propertyExistence(String selectorName, String
propertyName) {
return new PropertyExistenceImpl(selectorName, propertyName);
}
+
+ public PropertyInexistenceImpl propertyInexistence(String selectorName,
String propertyName) {
+ return new PropertyInexistenceImpl(selectorName, propertyName);
+ }
public PropertyValueImpl propertyValue(String selectorName, String
propertyName) {
return new PropertyValueImpl(selectorName, propertyName);
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java?rev=1530610&r1=1530609&r2=1530610&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/AstVisitor.java
Wed Oct 9 13:58:39 2013
@@ -67,6 +67,8 @@ public interface AstVisitor {
boolean visit(PropertyExistenceImpl node);
+ boolean visit(PropertyInexistenceImpl node);
+
boolean visit(PropertyValueImpl node);
boolean visit(SameNodeImpl node);
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java?rev=1530610&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java
Wed Oct 9 13:58:39 2013
@@ -0,0 +1,163 @@
+/*
+ * 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.ast;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.query.index.FilterImpl;
+
+/**
+ * A condition to check if the property does not exist ("is null").
+ * <p>
+ * For Jackrabbit 2.x compatibility: if the property is relative (as in
+ * "child/propertyName"), then this requires that the given child node exists.
+ */
+public class PropertyInexistenceImpl extends ConstraintImpl {
+
+ private final String selectorName;
+ private final String propertyName;
+ private SelectorImpl selector;
+
+ public PropertyInexistenceImpl(SelectorImpl selector, String selectorName,
String propertyName) {
+ this.selector = selector;
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ }
+
+ public PropertyInexistenceImpl(String selectorName, String propertyName) {
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ }
+
+ @Override
+ public boolean evaluate() {
+ boolean relative = propertyName.indexOf('/') >= 0;
+ if (!relative) {
+ return selector.currentProperty(propertyName) == null;
+ }
+ Tree t = selector.currentTree();
+ if (t == null) {
+ return true;
+ }
+ String relativePath = PathUtils.getParentPath(propertyName);
+ String name = PathUtils.getName(propertyName);
+ for (String p : PathUtils.elements(relativePath)) {
+ if (t == null || !t.exists()) {
+ return false;
+ }
+ if (p.equals("..")) {
+ t = t.isRoot() ? null : t.getParent();
+ } else if (p.equals(".")) {
+ // same node
+ } else {
+ t = t.getChild(p);
+ }
+ }
+ return t != null && t.exists() && !t.hasProperty(name);
+ }
+
+ @Override
+ public Set<PropertyExistenceImpl> getPropertyExistenceConditions() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<SelectorImpl> getSelectors() {
+ return Collections.singleton(selector);
+ }
+
+ @Override
+ public Map<DynamicOperandImpl, Set<StaticOperandImpl>> getInMap() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ boolean accept(AstVisitor v) {
+ return v.visit(this);
+ }
+
+ @Override
+ public String toString() {
+ return quote(selectorName) + '.' + quote(propertyName) + " is null";
+ }
+
+ public void bindSelector(SourceImpl source) {
+ selector = source.getExistingSelector(selectorName);
+ }
+
+ @Override
+ public void restrict(FilterImpl f) {
+ // we don't support covering indexes,
+ // so there is no optimization anyway, and
+ // we need to be careful with "property IS NULL"
+ // because this might cause an index
+ // to ignore the join condition "property = x"
+ // for example in:
+ // "select * from a left outer join b on a.x = b.y
+ // where b.y is null"
+ // must not result in the index to check for
+ // "b.y is null", because that would alter the
+ // result
+ }
+
+ @Override
+ public void restrictPushDown(SelectorImpl s) {
+ if (s.outerJoinRightHandSide) {
+ // we need to be careful with "property IS NULL"
+ // because this might cause an index
+ // to ignore the join condition "property = x"
+ // for example in:
+ // "select * from a left outer join b on a.x = b.y
+ // where b.y is null"
+ // must not check for "b.y is null" too early,
+ // because that would alter the result
+ return;
+ }
+ if (s == selector) {
+ s.restrictSelector(this);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return ((selectorName == null) ? 0 : selectorName.hashCode()) * 31 +
+ ((propertyName == null) ? 0 : propertyName.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ } else if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ PropertyInexistenceImpl other = (PropertyInexistenceImpl) obj;
+ return equalsStrings(selectorName, other.selectorName) &&
+ equalsStrings(propertyName, other.propertyName);
+ }
+
+ private static boolean equalsStrings(String a, String b) {
+ return a == null || b == null ? a == b : a.equals(b);
+ }
+
+}
\ No newline at end of file
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java?rev=1530610&r1=1530609&r2=1530610&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/SelectorImpl.java
Wed Oct 9 13:58:39 2013
@@ -315,21 +315,18 @@ public class SelectorImpl extends Source
public String currentPath() {
return cursor == null ? null : currentRow.getPath();
}
-
- public PropertyValue currentProperty(String propertyName) {
- boolean relative = propertyName.indexOf('/') >= 0;
- if (cursor == null) {
- return null;
- }
- IndexRow r = currentRow;
- if (r == null) {
- return null;
- }
- String path = r.getPath();
+
+ public Tree currentTree() {
+ String path = currentPath();
if (path == null) {
return null;
}
- Tree t = getTree(path);
+ return getTree(path);
+ }
+
+ public PropertyValue currentProperty(String propertyName) {
+ boolean relative = propertyName.indexOf('/') >= 0;
+ Tree t = currentTree();
if (relative) {
for (String p :
PathUtils.elements(PathUtils.getParentPath(propertyName))) {
if (t == null) {
@@ -349,6 +346,7 @@ public class SelectorImpl extends Source
return null;
}
if (propertyName.equals(QueryImpl.JCR_PATH)) {
+ String path = currentPath();
String local = getLocalPath(path);
if (local == null) {
// not a local path
Modified:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java?rev=1530610&r1=1530609&r2=1530610&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
(original)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
Wed Oct 9 13:58:39 2013
@@ -55,6 +55,23 @@ public class QueryTest extends AbstractR
public QueryTest(NodeStoreFixture fixture) {
super(fixture);
}
+
+ @Test
+ public void relativeNotExistsProperty() throws Exception {
+ Session session = getAdminSession();
+ Node content = session.getRootNode().addNode("test");
+ content.addNode("one").addNode("child").setProperty("prop", "hello");
+ content.addNode("two").addNode("child");
+ session.save();
+ String query = "//*[not(child/@prop)]";
+ QueryResult r = session.getWorkspace().getQueryManager().createQuery(
+ query, "xpath").execute();
+ NodeIterator it = r.getNodes();
+ assertTrue(it.hasNext());
+ String path = it.nextNode().getPath();
+ assertEquals("/test/two", path);
+ assertFalse(it.hasNext());
+ }
@SuppressWarnings("deprecation")
@Test