Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java (revision 1660335) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java (working copy) @@ -131,7 +131,7 @@ String operation = null; PropertyValue value = null; // TODO support pr.list - if (pr.first == null && pr.last == null) { + if (pr.isNotNullRestriction()) { // open query: [property] is not null operation = "is not null"; } else if (pr.first != null && pr.first.equals(pr.last) && pr.firstIncluding Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java (revision 1660335) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java (working copy) @@ -268,7 +268,7 @@ if (definition != null) { PropertyValue value = null; boolean createPlan = false; - if (pr.first == null && pr.last == null) { + if (pr.isNotNullRestriction()) { // open query: [property] is not null value = null; createPlan = true; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (revision 1660335) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (working copy) @@ -121,7 +121,7 @@ return values; } - private PropertyIndexPlan plan(NodeState root, Filter filter) { + private static PropertyIndexPlan plan(NodeState root, Filter filter) { PropertyIndexPlan bestPlan = null; // TODO support indexes on a path Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (revision 1660335) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (working copy) @@ -138,6 +138,10 @@ } if (restriction != null) { + if (restriction.isNullRestriction()) { + // covering indexes are not currently supported + continue; + } Set values = getValues(restriction); double cost = strategy.count(filter, root, definition, values, MAX_COST); if (cost < bestCost) { @@ -227,7 +231,7 @@ } return values; } else { - // processed as "[property] is not null" + // "[property] is not null" or "[property] is null" return null; } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java (revision 1660335) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/EquiJoinConditionImpl.java (working copy) @@ -106,9 +106,14 @@ p2 = null; } } - // always set the condition, even if unkown ( -> is not null) String p1n = normalizePropertyName(property1Name); - f.restrictProperty(p1n, Operator.EQUAL, p2); + if (p2 == null) { + // always set the condition, + // even if unknown (in which case it is converted to "is not null") + f.restrictProperty(p1n, Operator.NOT_EQUAL, null); + } else { + f.restrictProperty(p1n, Operator.EQUAL, p2); + } } if (f.getSelector().equals(selector2)) { PropertyValue p1 = selector1.currentProperty(property1Name); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java (revision 1660335) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/ast/PropertyInexistenceImpl.java (working copy) @@ -102,8 +102,6 @@ @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" @@ -113,6 +111,13 @@ // must not result in the index to check for // "b.y is null", because that would alter the // result + if (selector.isOuterJoinRightHandSide()) { + return; + } + if (f.getSelector().equals(selector)) { + String pn = normalizePropertyName(propertyName); + f.restrictProperty(pn, Operator.EQUAL, null); + } } @Override Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java (revision 1660335) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/Filter.java (working copy) @@ -219,12 +219,26 @@ * "x=1 or x=2 or x=3". */ public List list; + + /** + * The excluded value, for conditions of the type "x!=1". + */ + // public PropertyValue excluding; /** * 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 && lastIncluding && firstIncluding; + } + + public boolean isNotNullRestriction() { + return first == null && last == null && !lastIncluding && !firstIncluding; + } @Override public String toString() { @@ -248,6 +262,14 @@ } private String toStringFromTo() { + // if (excluding != null) { + // return "!" + excluding; + // } + 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)) { @@ -280,6 +302,7 @@ @Override public int hashCode() { // generated code (Eclipse) + ;; // add "excluding" final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); @@ -297,6 +320,7 @@ @Override public boolean equals(Object obj) { // generated code (Eclipse) + ;; // add "excluding" if (this == obj) { return true; } Index: oak-core/src/test/java/org/apache/jackrabbit/oak/query/FilterTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/query/FilterTest.java (revision 1660335) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/query/FilterTest.java (working copy) @@ -21,6 +21,7 @@ import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM; import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_NODE_TYPES; import static org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent.INITIAL_CONTENT; +import static org.junit.Assert.assertEquals; import java.text.ParseException; @@ -52,5 +53,19 @@ Filter f = createFilter("//*[(@prop = 'aaa' and @prop = 'bbb' and @prop = 'ccc')]"); assertFalse(f.isAlwaysFalse()); } + + @Test + public void isNull() throws Exception { + // this can refer to a multi-valued property + Filter f = createFilter("//*[not(@c)]"); + assertEquals("[is null]", f.getPropertyRestrictions("c").toString()); + } + @Test + public void isNotNull() throws Exception { + // this can refer to a multi-valued property + Filter f = createFilter("//*[@c]"); + assertEquals("[is not null]", f.getPropertyRestrictions("c").toString()); + } + } Index: oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/FilterTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/FilterTest.java (revision 1660335) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/FilterTest.java (working copy) @@ -123,18 +123,18 @@ f = new FilterImpl(); f.restrictProperty("x", Operator.NOT_EQUAL, null); assertEquals( - "Filter(, path=*, property=[x=[]])", + "Filter(, path=*, property=[x=[is not null]])", f.toString()); f.restrictProperty("x", Operator.LESS_THAN, one); assertEquals( - "Filter(, path=*, property=[x=[, ..1)]])", + "Filter(, path=*, property=[x=[is not null, ..1)]])", f.toString()); // this should replace the range with an equality // (which is faster, and correct even when using multi-valued properties) f.restrictProperty("x", Operator.EQUAL, two); assertEquals( - "Filter(, path=*, property=[x=[, ..1), 2]])", + "Filter(, path=*, property=[x=[is not null, ..1), 2]])", f.toString()); } Index: oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt =================================================================== --- oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt (revision 1660335) +++ oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2.txt (working copy) @@ -29,7 +29,7 @@ commit / + "test": { "a": { "name": "Hello" }, "b": { "name" : "World" }} -select * from [nt:base] +select * from [nt:base] where [a] = 1 and [b] = 2 and [b] = 3 or [c] = 4 select [jcr:path] Index: oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2_index.txt =================================================================== --- oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2_index.txt (revision 1660335) +++ oak-core/src/test/resources/org/apache/jackrabbit/oak/query/sql2_index.txt (working copy) @@ -24,6 +24,21 @@ # * new tests are typically be added on top, after the syntax docs # * use ascii character only +explain select * from [nt:base] as a + left outer join [nt:base] as b on a.x=b.y + where a.y is null and b.z = 1 +[nt:base] as [a] /* traverse "*" + where [a].[y] is null */ left outer join [nt:base] as [b] /* traverse "*" + where [b].[z] = 1 */ + on [a].[x] = [b].[y] + +explain select * from [nt:base] as a + right outer join [nt:base] as b on a.x=b.y + where a.y is null and b.z = 1 +[nt:base] as [b] /* traverse "*" + where [b].[z] = 1 */ left outer join [nt:base] as [a] /* traverse "*" */ + on [a].[x] = [b].[y] + explain select * from [nt:base] where (p=1 or p=2) and (p=3 or p=4) [nt:base] as [nt:base] /* traverse "*" where ([nt:base].[p] in(1, 2)) Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java (revision 1660335) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java (working copy) @@ -139,6 +139,10 @@ //for property index if (indexingRule.propertyIndexEnabled) { for (PropertyRestriction pr : filter.getPropertyRestrictions()) { + if (pr.isNullRestriction()) { + // ignore for planning + continue; + } PropertyDefinition pd = indexingRule.getConfig(pr.propertyName); if (pd != null && pd.propertyIndexEnabled()) { indexedProps.add(pr.propertyName); Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (revision 1660335) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (working copy) @@ -604,8 +604,8 @@ IndexingRule rule = indexDefinition.getApplicableIndexingRule(JcrConstants.NT_BASE); for (PropertyRestriction pr : filter.getPropertyRestrictions()) { - if (pr.first == null && pr.last == null) { - // ignore property existence checks, Lucene can't to 'property + if (pr.isNullRestriction() || pr.isNotNullRestriction()) { + // ignore property existence checks, Lucene can't do 'property // is not null' queries (OAK-1208) continue; } Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (revision 1660335) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (working copy) @@ -742,7 +742,7 @@ in.add(NumericRangeQuery.newLongRange(pr.propertyName, dateVal, dateVal, true, true), BooleanClause.Occur.SHOULD); } return in; - } else if (pr.first == null && pr.last == null ) { + } else if (pr.isNotNullRestriction()) { // not null. For date lower bound of zero can be used return NumericRangeQuery.newLongRange(pr.propertyName, 0L, Long.MAX_VALUE, true, true); }