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: @, -, +