You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2012/03/08 16:08:18 UTC
svn commit: r1298425 - in /jackrabbit/sandbox/jackrabbit-microkernel/src:
main/java/org/apache/jackrabbit/query/
test/java/org/apache/jackrabbit/query/ test/resources/
Author: thomasm
Date: Thu Mar 8 15:08:17 2012
New Revision: 1298425
URL: http://svn.apache.org/viewvc?rev=1298425&view=rev
Log:
XPath to SQL2 converter
Removed:
jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/query/XPathTest.java
Modified:
jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/XPathToSQL2Converter.java
jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/query/QueryTest.java
jackrabbit/sandbox/jackrabbit-microkernel/src/test/resources/queryXpathTest.txt
Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/XPathToSQL2Converter.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/XPathToSQL2Converter.java?rev=1298425&r1=1298424&r2=1298425&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/XPathToSQL2Converter.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/XPathToSQL2Converter.java Thu Mar 8 15:08:17 2012
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import javax.jcr.RepositoryException;
import javax.jcr.query.InvalidQueryException;
+import org.apache.jackrabbit.mk.util.PathUtils;
/**
* This class can can convert a XPATH query to a SQL2 query.
@@ -27,12 +28,12 @@ import javax.jcr.query.InvalidQueryExcep
public class XPathToSQL2Converter {
// Character types, used during the tokenizer phase
- private static final int CHAR_END = -1, CHAR_VALUE = 2, CHAR_QUOTED = 3;
+ private static final int CHAR_END = -1, CHAR_VALUE = 2;
private static final int CHAR_NAME = 4, CHAR_SPECIAL_1 = 5, CHAR_SPECIAL_2 = 6;
private static final int CHAR_STRING = 7, CHAR_DECIMAL = 8;
// Token types
- private static final int KEYWORD = 1, IDENTIFIER = 2, PARAMETER = 3, END = 4, VALUE_STRING = 5, VALUE_NUMBER = 6;
+ private static final int KEYWORD = 1, IDENTIFIER = 2, END = 4, VALUE_STRING = 5, VALUE_NUMBER = 6;
private static final int MINUS = 12, PLUS = 13, OPEN = 14, CLOSE = 15;
// The query as an array of characters and character types
@@ -59,20 +60,20 @@ public class XPathToSQL2Converter {
expected = new ArrayList<String>();
read();
String path = "";
- Condition condition = null;
+ Expression condition = null;
String from = "nt:base";
ArrayList<Expression> columnList = new ArrayList<Expression>();
+ boolean includeChildren = true;
while (true) {
String nodeType;
if (readIf("/")) {
if (readIf("/")) {
- path += "%";
+ includeChildren = true;
} else if (readIf("jcr:root")) {
- if (path.length() > 0) {
- throw new InvalidQueryException("jcr:root with a path: " + path);
- }
path = "/";
read("/");
+ } else {
+ includeChildren = false;
}
if (readIf("*")) {
nodeType = "nt:base";
@@ -80,9 +81,13 @@ public class XPathToSQL2Converter {
} else if (readIf("element")) {
read("(");
if (readIf("*")) {
- if (!path.endsWith("%")) {
- path += "%";
+ // any
+ if (!includeChildren) {
+ path = PathUtils.concat(path, "%");
}
+ } else {
+ String name = readIdentifier();
+ path = PathUtils.concat(path, name);
}
read(",");
nodeType = readIdentifier();
@@ -100,20 +105,36 @@ public class XPathToSQL2Converter {
read(")");
} else {
String name = readIdentifier();
- path += name + "/";
+ path = PathUtils.concat(path, name);
continue;
}
- if (!path.equals("%")) {
- Condition c = new Condition(new Property("jcr:path"), "like", Literal.newString(path));
+ if (readIf("[")) {
+ Expression c = parseConstraint();
condition = add(condition, c);
+ read("]");
}
} else {
break;
}
}
+ if (path.equals("")) {
+ // no condition
+ } else if (path.equals("%")) {
+ // ignore
+ } else {
+ Condition c = new Condition(new Property("jcr:path"), "like", Literal.newString(path));
+ if (!includeChildren && path.endsWith("%")) {
+ Condition c2 = new Condition(new Property("jcr:path"), "like", Literal.newString(path + "/"));
+ c = new Condition(c, "and", new Condition(null, "not", c2));
+ } else if (includeChildren && !path.endsWith("%")) {
+ Condition c2 = new Condition(new Property("jcr:path"), "like", Literal.newString(path + "/%"));
+ c = new Condition(c, "or", c2);
+ }
+ condition = add(condition, c);
+ }
+ ArrayList<Order> orderList = new ArrayList<Order>();
if (readIf("order")) {
read("by");
- ArrayList<Order> orderList = new ArrayList<Order>();
do {
Order order = new Order();
order.expr = parseExpression();
@@ -140,29 +161,98 @@ public class XPathToSQL2Converter {
}
}
buff.append(" from ");
- buff.append(from);
+ buff.append("[" + from + "]");
if (condition != null) {
buff.append(" where ").append(condition);
}
+ if (orderList.size() > 0) {
+ buff.append(" order by ");
+ for (int i = 0; i < orderList.size(); i++) {
+ if (i > 0) {
+ buff.append(", ");
+ }
+ buff.append(orderList.get(i));
+ }
+ }
return buff.toString();
}
- private static Condition add(Condition old, Condition add) {
+ private static Expression add(Expression old, Expression add) {
if (old == null) {
return add;
}
- return new Condition(old, "AND", add);
+ return new Condition(old, "and", add);
}
- private Expression parseExpression() throws RepositoryException {
- if (currentTokenType == IDENTIFIER) {
- String name = currentToken;
- read();
- if (readIf("(")) {
- return parseFunction(name);
+ private Expression parseConstraint() throws RepositoryException {
+ Expression a = parseAnd();
+ while (readIf("or")) {
+ a = new Condition(a, "or", parseAnd());
+ }
+ return a;
+ }
+
+ private Expression parseAnd() throws RepositoryException {
+ Expression a = parseCondition();
+ while (readIf("and")) {
+ a = new Condition(a, "and", parseCondition());
+ }
+ return a;
+ }
+
+ private Expression parseCondition() throws RepositoryException {
+ Expression a;
+ if (readIf("not")) {
+ read("(");
+ a = parseConstraint();
+ if (a instanceof Condition && ((Condition) a).operator.equals("is not null")) {
+ // not(@property) -> @property is null
+ Condition c = (Condition) a;
+ c = new Condition(c.left, "is null", null);
+ a = c;
} else {
- return new Property(name);
+ Function f = new Function();
+ f.name = "not";
+ f.params.add(a);
+ a = f;
}
+ read(")");
+ } else if (readIf("(")) {
+ a = new Parenthesis(parseConstraint());
+ read(")");
+ } else {
+ Expression e = parseExpression();
+ if (e.isCondition()) {
+ return e;
+ }
+ a = parseCondition(e);
+ }
+ return a;
+ }
+
+ private Condition parseCondition(Expression left) throws RepositoryException {
+ Condition c;
+ if (readIf("=")) {
+ c = new Condition(left, "=", parseExpression());
+ } else if (readIf("<>")) {
+ c = new Condition(left, "<>", parseExpression());
+ } else if (readIf("<")) {
+ c = new Condition(left, "<", parseExpression());
+ } else if (readIf(">")) {
+ c = new Condition(left, ">", parseExpression());
+ } else if (readIf("<=")) {
+ c = new Condition(left, "<=", parseExpression());
+ } else if (readIf(">=")) {
+ c = new Condition(left, ">=", parseExpression());
+ } else {
+ c = new Condition(left, "is not null", null);
+ }
+ return c;
+ }
+
+ private Expression parseExpression() throws RepositoryException {
+ if (readIf("@")) {
+ return new Property(readIdentifier());
} else if (currentTokenType == VALUE_NUMBER) {
Literal l = Literal.newNumber(currentToken);
read();
@@ -171,8 +261,24 @@ public class XPathToSQL2Converter {
Literal l = Literal.newString(currentToken);
read();
return l;
+ } else if (currentTokenType == IDENTIFIER) {
+ String name = readIdentifier();
+ read("(");
+ return parseFunction(name);
+ } else if (readIf("-")) {
+ if (currentTokenType != VALUE_NUMBER) {
+ throw getSyntaxError();
+ }
+ Literal l = Literal.newNumber("-" + currentToken);
+ read();
+ return l;
+ } else if (readIf("+")) {
+ if (currentTokenType != VALUE_NUMBER) {
+ throw getSyntaxError();
+ }
+ return parseExpression();
} else {
- throw getSyntaxError("identifier");
+ throw getSyntaxError();
}
}
@@ -186,7 +292,12 @@ public class XPathToSQL2Converter {
} else if ("jcr:contains".equals(functionName)) {
Function f = new Function();
f.name = "contains";
- f.params.add(parseExpression());
+ if (readIf(".")) {
+ // special case: jcr:contains(., expr)
+ f.params.add(new Literal("*"));
+ } else {
+ f.params.add(parseExpression());
+ }
read(",");
f.params.add(parseExpression());
read(")");
@@ -213,7 +324,7 @@ public class XPathToSQL2Converter {
}
private boolean isToken(String token) {
- boolean result = token.equalsIgnoreCase(currentToken) && !currentTokenQuoted;
+ boolean result = token.equals(currentToken) && !currentTokenQuoted;
if (result) {
return true;
}
@@ -222,7 +333,7 @@ public class XPathToSQL2Converter {
}
private void read(String expected) throws RepositoryException {
- if (!expected.equalsIgnoreCase(currentToken) || currentTokenQuoted) {
+ if (!expected.equals(currentToken) || currentTokenQuoted) {
throw getSyntaxError(expected);
}
read();
@@ -237,15 +348,6 @@ public class XPathToSQL2Converter {
return s;
}
- private String readString() throws RepositoryException {
- if (currentTokenType != VALUE_STRING) {
- throw getSyntaxError("string value");
- }
- String s = currentToken;
- read();
- return s;
- }
-
private void addExpected(String token) {
if (expected != null) {
expected.add(token);
@@ -304,14 +406,6 @@ public class XPathToSQL2Converter {
checkRunOver(i, len, startLoop);
}
break;
- case '\"':
- type = CHAR_QUOTED;
- types[i] = CHAR_QUOTED;
- startLoop = i;
- while (command[++i] != '\"') {
- checkRunOver(i, len, startLoop);
- }
- break;
case ':':
case '_':
type = CHAR_NAME;
@@ -384,9 +478,6 @@ public class XPathToSQL2Converter {
case CHAR_SPECIAL_1:
currentToken = statement.substring(start, i);
switch (c) {
- case '$':
- currentTokenType = PARAMETER;
- break;
case '+':
currentTokenType = PLUS;
break;
@@ -442,9 +533,6 @@ public class XPathToSQL2Converter {
case CHAR_STRING:
readString(i, '\'');
return;
- case CHAR_QUOTED:
- readString(i, '\"');
- return;
case CHAR_END:
currentToken = "";
currentTokenType = END;
@@ -536,13 +624,15 @@ public class XPathToSQL2Converter {
return new InvalidQueryException("Query:\n" + query);
}
- static class Expression {
- // base class
+ static abstract class Expression {
+ boolean isCondition() {
+ return false;
+ }
}
static class Literal extends Expression {
String value;
- private Literal(String value) {
+ Literal(String value) {
this.value = value;
}
static Literal newNumber(String s) {
@@ -562,7 +652,17 @@ public class XPathToSQL2Converter {
this.name = name;
}
public String toString() {
- return name;
+ return "[" + name + "]";
+ }
+ }
+
+ static class Parenthesis extends Expression {
+ Expression expr;
+ public Parenthesis(Expression expr) {
+ this.expr = expr;
+ }
+ public String toString() {
+ return "(" + expr + ")";
}
}
@@ -576,8 +676,13 @@ public class XPathToSQL2Converter {
this.right = right;
}
public String toString() {
- return (left == null ? "" : (left + " ")) +
- operator + " " + right;
+ return
+ (left == null ? "" : (left + " ")) +
+ operator +
+ (right == null ? "" : (" " + right));
+ }
+ boolean isCondition() {
+ return true;
}
}
@@ -597,6 +702,9 @@ public class XPathToSQL2Converter {
buff.append(')');
return buff.toString();
}
+ boolean isCondition() {
+ return name.equals("contains") || name.equals("not");
+ }
}
static class Order {
@@ -607,6 +715,5 @@ public class XPathToSQL2Converter {
}
}
-
}
Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/query/QueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/query/QueryTest.java?rev=1298425&r1=1298424&r2=1298425&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/query/QueryTest.java (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/test/java/org/apache/jackrabbit/query/QueryTest.java Thu Mar 8 15:08:17 2012
@@ -20,6 +20,7 @@ import java.io.LineNumberReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Iterator;
+import javax.jcr.query.InvalidQueryException;
import org.apache.jackrabbit.mk.MicroKernelFactory;
import org.apache.jackrabbit.mk.api.MicroKernel;
import org.junit.After;
@@ -72,7 +73,12 @@ public class QueryTest {
line = line.substring("xpath".length()).trim();
w.println("xpath " + line);
XPathToSQL2Converter c = new XPathToSQL2Converter();
- String got = c.convert(line);
+ String got;
+ try {
+ got = c.convert(line);
+ } catch (InvalidQueryException e) {
+ got = "invalid: " + e.getMessage().replace('\n', ' ');
+ }
line = r.readLine().trim();
w.println(got);
if (!line.equals(got)) {
Modified: jackrabbit/sandbox/jackrabbit-microkernel/src/test/resources/queryXpathTest.txt
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/test/resources/queryXpathTest.txt?rev=1298425&r1=1298424&r2=1298425&view=diff
==============================================================================
--- jackrabbit/sandbox/jackrabbit-microkernel/src/test/resources/queryXpathTest.txt (original)
+++ jackrabbit/sandbox/jackrabbit-microkernel/src/test/resources/queryXpathTest.txt Thu Mar 8 15:08:17 2012
@@ -4,17 +4,103 @@
# * use ascii character only
xpath //*
-select * from nt:base
+select * from [nt:base]
xpath //element(*, my:type)
-select * from my:type
+select * from [my:type]
xpath //element(*, my:type)/@my:title
-select my:title from my:type
+select [my:title] from [my:type]
xpath //element(*, my:type)/(@my:title | @my:text)
-select my:title, my:text from my:type
+select [my:title], [my:text] from [my:type]
xpath /jcr:root/nodes//element(*, my:type)
-select * from my:type where jcr:path like '/nodes/%'
+select * from [my:type] where [jcr:path] like '/nodes' or [jcr:path] like '/nodes/%'
+xpath /jcr:root/some/element(nodes, my:type)
+select * from [my:type] where [jcr:path] like '/some/nodes'
+
+xpath /jcr:root/some/nodes/element(*, my:type)
+select * from [my:type] where [jcr:path] like '/some/nodes/%' and not [jcr:path] like '/some/nodes/%/'
+
+xpath /jcr:root/some/nodes//element(*, my:type)
+select * from [my:type] where [jcr:path] like '/some/nodes' or [jcr:path] like '/some/nodes/%'
+
+xpath //element(*, my:type)[@my:title = 'JSR 170']
+select * from [my:type] where [my:title] = 'JSR 170'
+
+xpath //element(*, my:type)[jcr:like(@title,'%Java%')]
+select * from [my:type] where [title] like '%Java%'
+
+xpath //element(*, my:type)[jcr:contains(., 'JSR 170')]
+select * from [my:type] where contains(*, 'JSR 170')
+
+xpath //element(*, my:type)[@my:title]
+select * from [my:type] where [my:title] is not null
+
+xpath //element(*, my:type)[not(@my:title)]
+select * from [my:type] where [my:title] is null
+
+xpath //element(*, my:type)[@my:value < -1.0]
+select * from [my:type] where [my:value] < -1.0
+
+xpath //element(*, my:type)[@my:value > +10123123123]
+select * from [my:type] where [my:value] > 10123123123
+
+xpath //element(*, my:type)[@my:value <= 10.3e-3]
+select * from [my:type] where [my:value] <= 10.3e-3
+
+xpath //element(*, my:type)[@my:value >= 0e3]
+select * from [my:type] where [my:value] >= 0e3
+
+xpath //element(*, my:type)[@my:value <> 'Joe''s Caffee']
+select * from [my:type] where [my:value] <> 'Joe''s Caffee'
+
+xpath //element(*, my:type)[(not(@my:title) and @my:subject)]
+select * from [my:type] where ([my:title] is null and [my:subject] is not null)
+
+xpath //element(*, my:type)[not(@my:title) or @my:subject]
+select * from [my:type] where [my:title] is null or [my:subject] is not null
+
+xpath //element(*, my:type)[not(@my:value > 0 and @my:value < 100)]
+select * from [my:type] where not([my:value] > 0 and [my:value] < 100)
+
+xpath //element(*, my:type) order by @jcr:lastModified
+select * from [my:type] order by [jcr:lastModified]
+
+xpath //element(*, my:type) order by @my:date descending, @my:title ascending
+select * from [my:type] order by [my:date] desc, [my:title]
+
+xpath //element(*, my:type)[jcr:contains(., 'jcr')] order by jcr:score() descending
+select * from [my:type] where contains(*, 'jcr') order by score() desc
+
+xpath //element(*, my:type)[jcr:contains(@my:title, 'jcr')] order by jcr:score() descending
+select * from [my:type] where contains([my:title], 'jcr') order by score() desc
+
+xpath invalid/query
+invalid: Query: invalid(*)/query; expected: <end>
+
+xpath //element(*, my:type)[@my:value = -'x']
+invalid: Query: //element(*, my:type)[@my:value = -'x'(*)]
+
+xpath //element(-1, my:type)
+invalid: Query: //element(-(*)1, my:type); expected: identifier
+
+xpath //element(*, my:type)[not @my:title]
+invalid: Query: //element(*, my:type)[not @(*)my:title]; expected: (
+
+xpath //element(*, my:type)[@my:value = +'x']
+invalid: Query: //element(*, my:type)[@my:value = +'x'(*)]
+
+xpath //element(*, my:type)[@my:value = ['x']
+invalid: Query: //element(*, my:type)[@my:value = [(*)'x']; expected: @, -, +
+
+xpath //element(*, my:type)[jcr:strike(@title,'%Java%')]
+invalid: Query: //element(*, my:type)[jcr:strike(@(*)title,'%Java%')]; expected: jcr:like | jcr:contains | jcr:score | jcr:deref
+
+xpath //element(*, my:type)[
+invalid: Query: //element(*, my:type)(*)[; expected: not, (, @, -, +
+
+xpath //element(*, my:type)[@my:value >= .]
+invalid: Query: //element(*, my:type)[@my:value >= .(*)]; expected: @, -, +