Author: thomasm
Date: Thu Feb 20 07:18:24 2014
New Revision: 1570093
URL: http://svn.apache.org/r1570093
Log:
OAK-1432 Query: use "union" for complex XPath queries that use multiple "or"
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java
jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java?rev=1570093&r1=1570092&r2=1570093&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Expression.java
Thu Feb 20 07:18:24 2014
@@ -42,7 +42,7 @@ abstract class Expression {
} else if (add == null) {
return old;
}
- return new Expression.Condition(old, "and", add,
Expression.PRECEDENCE_AND);
+ return new Expression.AndCondition(old, add);
}
/**
@@ -55,6 +55,15 @@ abstract class Expression {
}
/**
+ * Pull an OR condition up to the right hand side of an AND condition.
+ *
+ * @return the (possibly rotated) expression
+ */
+ Expression pullOrRight() {
+ return this;
+ }
+
+ /**
* Get the operator / operation precedence. The JCR specification uses:
* 1=OR, 2=AND, 3=condition, 4=operand
*
@@ -255,6 +264,27 @@ abstract class Expression {
super(left, "and", right, Expression.PRECEDENCE_AND);
}
+ @Override
+ AndCondition pullOrRight() {
+ if (right instanceof OrCondition) {
+ return this;
+ } else if (left instanceof OrCondition) {
+ return new AndCondition(right, left);
+ }
+ if (right instanceof AndCondition) {
+ // pull up x:
+ // a and (b and (x)) -> (a and b) and (x)
+ AndCondition r2 = (AndCondition) right;
+ r2 = r2.pullOrRight();
+ AndCondition l2 = new AndCondition(left, r2.left);
+ l2 = l2.pullOrRight();
+ return new AndCondition(l2, r2.right);
+ } else if (left instanceof AndCondition) {
+ return new AndCondition(right, left).pullOrRight();
+ }
+ return this;
+ }
+
}
/**
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java?rev=1570093&r1=1570092&r2=1570093&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/xpath/Statement.java
Thu Feb 20 07:18:24 2014
@@ -58,8 +58,34 @@ public class Statement {
if (where == null) {
return this;
}
- if (where instanceof OrCondition) {
- OrCondition or = (OrCondition) where;
+ ArrayList<Expression> unionList = new ArrayList<Expression>();
+ addToUnionList(where, unionList);
+ if (unionList.size() == 1) {
+ return this;
+ }
+ Statement union = null;
+ for (int i = 0; i < unionList.size(); i++) {
+ Expression e = unionList.get(i);
+ Statement s = new Statement();
+ s.columnSelector = columnSelector;
+ s.selectors = selectors;
+ s.columnList = columnList;
+ s.where = e;
+ if (i == unionList.size() - 1) {
+ s.xpathQuery = xpathQuery;
+ }
+ if (union == null) {
+ union = s;
+ } else {
+ union = new UnionStatement(union.optimize(), s.optimize());
+ }
+ }
+ return union;
+ }
+
+ private static void addToUnionList(Expression condition,
ArrayList<Expression> unionList) {
+ if (condition instanceof OrCondition) {
+ OrCondition or = (OrCondition) condition;
if (or.getCommonLeftPart() != null) {
// @x = 1 or @x = 2
// is automatically converted to
@@ -72,29 +98,17 @@ public class Statement {
// @x = 1 or @y = 2
// or similar are converted to
// (@x = 1) union (@y = 2)
- Statement s1 = new Statement();
- s1.columnSelector = columnSelector;
- s1.selectors = selectors;
- s1.columnList = columnList;
- s1.where = or.left;
- Statement s2 = new Statement();
- s2.columnSelector = columnSelector;
- s2.selectors = selectors;
- s2.columnList = columnList;
- s2.where = or.right;
- s2.xpathQuery = xpathQuery;
- return new UnionStatement(s1.optimize(), s2.optimize());
+ addToUnionList(or.left, unionList);
+ addToUnionList(or.right, unionList);
+ return;
}
- } else if (where instanceof AndCondition) {
+ } else if (condition instanceof AndCondition) {
// conditions of type
// @a = 1 and (@x = 1 or @y = 2)
// are automatically converted to
// (@a = 1 and @x = 1) union (@a = 1 and @y = 2)
- AndCondition and = (AndCondition) where;
- if (and.left instanceof OrCondition && !(and.right instanceof
OrCondition)) {
- // swap left and right
- and = new AndCondition(and.right, and.left);
- }
+ AndCondition and = (AndCondition) condition;
+ and = and.pullOrRight();
if (and.right instanceof OrCondition) {
OrCondition or = (OrCondition) and.right;
if (or.getCommonLeftPart() != null) {
@@ -106,23 +120,13 @@ public class Statement {
// do not optimize "contains"
} else {
// same as above, but with the added "and"
- // TODO avoid code duplication if possible
- Statement s1 = new Statement();
- s1.columnSelector = columnSelector;
- s1.selectors = selectors;
- s1.columnList = columnList;
- s1.where = new AndCondition(and.left, or.left);
- Statement s2 = new Statement();
- s2.columnSelector = columnSelector;
- s2.selectors = selectors;
- s2.columnList = columnList;
- s2.where = new AndCondition(and.left, or.right);
- s2.xpathQuery = xpathQuery;
- return new UnionStatement(s1.optimize(), s2.optimize());
+ addToUnionList(new AndCondition(and.left, or.left),
unionList);
+ addToUnionList(new AndCondition(and.left, or.right),
unionList);
+ return;
}
}
}
- return this;
+ unionList.add(condition);
}
@Override
Modified:
jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt?rev=1570093&r1=1570092&r2=1570093&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/resources/org/apache/jackrabbit/oak/query/xpath.txt
Thu Feb 20 07:18:24 2014
@@ -24,8 +24,11 @@
# * use ascii character only
# "or" problem (OAK-1432)
-#Â xpath2sql /jcr:root/content/dam//element(*,
nt:unstructured)[((@sling:resourceType = 'dam/smartcollection' or
@sling:resourceType = 'dam/collection') or @sling:resourceSuperType =
'dam/collection')]
-# select
+xpath2sql /jcr:root/content//element(*, nt:unstructured)[((@p1 = 'x1' or @p2 =
'x2') or @p3 = 'x3')]
+select [jcr:path], [jcr:score], * from [nt:unstructured] as a where
isdescendantnode(a, '/content') and [p1] = 'x1' union select [jcr:path],
[jcr:score], * from [nt:unstructured] as a where isdescendantnode(a,
'/content') and [p2] = 'x2' union select [jcr:path], [jcr:score], * from
[nt:unstructured] as a where isdescendantnode(a, '/content') and [p3] = 'x3' /*
xpath: /jcr:root/content//element(*, nt:unstructured)[((@p1 = 'x1' or @p2 =
'x2') or @p3 = 'x3')] */
+
+xpath2sql //*[((@jcr:primaryType = 'nt:unstructured' and (@p1 = 'x' or @p2 =
'x')) or (@p3 = 'x' and @jcr:primaryType = 'nt:folder')) ]
+select [jcr:path], [jcr:score], * from [nt:base] as a where [jcr:primaryType]
= 'nt:unstructured' and [p1] = 'x' union select [jcr:path], [jcr:score], * from
[nt:base] as a where [jcr:primaryType] = 'nt:unstructured' and [p2] = 'x' union
select [jcr:path], [jcr:score], * from [nt:base] as a where [p3] = 'x' and
[jcr:primaryType] = 'nt:folder' /* xpath: //*[((@jcr:primaryType =
'nt:unstructured' and (@p1 = 'x' or @p2 = 'x')) or (@p3 = 'x' and
@jcr:primaryType = 'nt:folder')) ] */
# property names with missing @
@@ -38,7 +41,7 @@ select [jcr:path], [jcr:score], * from [
# wildcards in relative property paths
xpath2sql /jcr:root/etc/test//*[@size='M' or */@size='M']
-select [jcr:path], [jcr:score], * from [nt:base] as a where ([size] = 'M' or
[*/size] = 'M') and isdescendantnode(a, '/etc/test') /* xpath:
/jcr:root/etc/test//*[@size='M' or */@size='M'] */
+select [jcr:path], [jcr:score], * from [nt:base] as a where
isdescendantnode(a, '/etc/test') and [size] = 'M' union select [jcr:path],
[jcr:score], * from [nt:base] as a where isdescendantnode(a, '/etc/test') and
[*/size] = 'M' /* xpath: /jcr:root/etc/test//*[@size='M' or */@size='M'] */
# union (complex)
@@ -220,10 +223,10 @@ xpath2sql //element(*,rep:Authorizable)[
select [jcr:path], [jcr:score], * from [rep:Authorizable] as a where
contains([profile/givenName/*], '**') or contains([profile/familyName/*], '**')
or contains([profile/email/*], '**') or [rep:principalName] like '%%' or
name(a) like '%%' order by [rep:principalName] /* xpath:
//element(*,rep:Authorizable)[(((jcr:contains(profile/givenName,'**') or
jcr:contains(profile/familyName,'**')) or jcr:contains(profile/email,'**')) or
(jcr:like(@rep:principalName,'%%') or jcr:like(fn:name(.),'%%')))] order by
@rep:principalName ascending */
xpath2sql //*[@a=1 or @b=1]/sub[@c=1]
-select b.[jcr:path] as [jcr:path], b.[jcr:score] as [jcr:score], b.* from
[nt:base] as a inner join [nt:base] as b on ischildnode(b, a) where (a.[a] = 1
or a.[b] = 1) and b.[c] = 1 and name(b) = 'sub' /* xpath: //*[@a=1 or
@b=1]/sub[@c=1] */
+select b.[jcr:path] as [jcr:path], b.[jcr:score] as [jcr:score], b.* from
[nt:base] as a inner join [nt:base] as b on ischildnode(b, a) where b.[c] = 1
and name(b) = 'sub' and a.[a] = 1 union select b.[jcr:path] as [jcr:path],
b.[jcr:score] as [jcr:score], b.* from [nt:base] as a inner join [nt:base] as b
on ischildnode(b, a) where b.[c] = 1 and name(b) = 'sub' and a.[b] = 1 /*
xpath: //*[@a=1 or @b=1]/sub[@c=1] */
xpath2sql //*[@a=1 or @b=1]/sub[@c=1 or @d=1]
-select b.[jcr:path] as [jcr:path], b.[jcr:score] as [jcr:score], b.* from
[nt:base] as a inner join [nt:base] as b on ischildnode(b, a) where (a.[a] = 1
or a.[b] = 1) and (b.[c] = 1 or b.[d] = 1) and name(b) = 'sub' /* xpath:
//*[@a=1 or @b=1]/sub[@c=1 or @d=1] */
+select b.[jcr:path] as [jcr:path], b.[jcr:score] as [jcr:score], b.* from
[nt:base] as a inner join [nt:base] as b on ischildnode(b, a) where a.[a] = 1
and name(b) = 'sub' and b.[c] = 1 union select b.[jcr:path] as [jcr:path],
b.[jcr:score] as [jcr:score], b.* from [nt:base] as a inner join [nt:base] as b
on ischildnode(b, a) where a.[a] = 1 and name(b) = 'sub' and b.[d] = 1 union
select b.[jcr:path] as [jcr:path], b.[jcr:score] as [jcr:score], b.* from
[nt:base] as a inner join [nt:base] as b on ischildnode(b, a) where a.[b] = 1
and name(b) = 'sub' and b.[c] = 1 union select b.[jcr:path] as [jcr:path],
b.[jcr:score] as [jcr:score], b.* from [nt:base] as a inner join [nt:base] as b
on ischildnode(b, a) where a.[b] = 1 and name(b) = 'sub' and b.[d] = 1 /*
xpath: //*[@a=1 or @b=1]/sub[@c=1 or @d=1] */
xpath2sql
//element(*,rep:Authorizable)[(((jcr:contains(profile/@givenName,'**') or
jcr:contains(profile/@familyName,'**')) or jcr:contains(profile/@email,'**'))
or (jcr:like(@rep:principalName,'%%') or jcr:like(fn:name(.),'%%')))] order by
@rep:principalName ascending
select [jcr:path], [jcr:score], * from [rep:Authorizable] as a where
contains([profile/givenName], '**') or contains([profile/familyName], '**') or
contains([profile/email], '**') or [rep:principalName] like '%%' or name(a)
like '%%' order by [rep:principalName] /* xpath:
//element(*,rep:Authorizable)[(((jcr:contains(profile/@givenName,'**') or
jcr:contains(profile/@familyName,'**')) or jcr:contains(profile/@email,'**'))
or (jcr:like(@rep:principalName,'%%') or jcr:like(fn:name(.),'%%')))] order by
@rep:principalName ascending */
@@ -369,7 +372,7 @@ xpath2sql /jcr:root/content//*[(@sling:r
select [jcr:path], [jcr:score], * from [nt:base] as a where
[sling:resourceType] = 'page' and isdescendantnode(a, '/content') /* xpath:
/jcr:root/content//*[(@sling:resourceType = 'page')] */
xpath2sql /jcr:root/content//*[@offTime >
xs:dateTime('2012-03-28T15:56:18.327+02:00') or @onTime >
xs:dateTime('2012-03-28T15:56:18.327+02:00')]
-select [jcr:path], [jcr:score], * from [nt:base] as a where ([offTime] >
cast('2012-03-28T15:56:18.327+02:00' as date) or [onTime] >
cast('2012-03-28T15:56:18.327+02:00' as date)) and isdescendantnode(a,
'/content') /* xpath: /jcr:root/content//*[@offTime >
xs:dateTime('2012-03-28T15:56:18.327+02:00') or @onTime >
xs:dateTime('2012-03-28T15:56:18.327+02:00')] */
+select [jcr:path], [jcr:score], * from [nt:base] as a where
isdescendantnode(a, '/content') and [offTime] >
cast('2012-03-28T15:56:18.327+02:00' as date) union select [jcr:path],
[jcr:score], * from [nt:base] as a where isdescendantnode(a, '/content') and
[onTime] > cast('2012-03-28T15:56:18.327+02:00' as date) /* xpath:
/jcr:root/content//*[@offTime > xs:dateTime('2012-03-28T15:56:18.327+02:00') or
@onTime > xs:dateTime('2012-03-28T15:56:18.327+02:00')] */
xpath2sql /jcr:root/content/campaigns//*[@jcr:primaryType='Page'] order by
jcr:content/@lastModified descending
select [jcr:path], [jcr:score], * from [nt:base] as a where [jcr:primaryType]
= 'Page' and isdescendantnode(a, '/content/campaigns') order by
[jcr:content/lastModified] desc /* xpath:
/jcr:root/content/campaigns//*[@jcr:primaryType='Page'] order by
jcr:content/@lastModified descending */