| /* |
| * 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; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.text.ParseException; |
| |
| import org.apache.jackrabbit.oak.query.xpath.XPathToSQL2Converter; |
| import org.junit.Test; |
| |
| /** |
| * Tests the XPathToSQL2Converter |
| */ |
| public class XPathTest { |
| |
| private final SQL2Parser parser = SQL2ParserTest.createTestSQL2Parser(); |
| |
| @Test |
| public void complexQuery() throws ParseException { |
| for(int n = 1; n < 15; n++) { |
| for(int m = 1; m < 15; m++) { |
| complexQuery(n, m); |
| } |
| } |
| } |
| |
| private void complexQuery(int n, int m) throws ParseException { |
| StringBuilder buff = new StringBuilder(); |
| buff.append("/jcr:root//*["); |
| for (int i = 0; i < n; i++) { |
| if (i > 0) { |
| buff.append("and "); |
| } |
| buff.append("("); |
| for (int j = 0; j < m; j++) { |
| if (j > 0) { |
| buff.append("or "); |
| } |
| buff.append("@x" + j + " = " + j); |
| } |
| buff.append(")\n"); |
| } |
| buff.append("]"); |
| String xpath = buff.toString(); |
| String sql2 = new XPathToSQL2Converter().convert(xpath); |
| assertTrue("Length: " + sql2.length(), sql2.length() < 200000); |
| Query q = parser.parse(sql2, false); |
| q.buildAlternativeQuery(); |
| } |
| |
| @Test |
| public void queryOptions() throws ParseException { |
| verify("(/jcr:root/a//* | /jcr:root/b//*) order by @jcr:score", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where isdescendantnode(a, '/a') " + |
| "/* xpath: /jcr:root/a//* \n" + |
| "order by @jcr:score */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where isdescendantnode(a, '/b') " + |
| "/* xpath: /jcr:root/b//* " + |
| "order by @jcr:score */ " + |
| "order by [jcr:score]"); |
| verify("(/jcr:root/a//* | /jcr:root/b//* | /jcr:root/c//*) order by @jcr:score", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where isdescendantnode(a, '/a') " + |
| "/* xpath: /jcr:root/a//* \n" + |
| "order by @jcr:score */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where isdescendantnode(a, '/b') " + |
| "/* xpath: /jcr:root/b//* \n" + |
| "order by @jcr:score */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where isdescendantnode(a, '/c') " + |
| "/* xpath: /jcr:root/c//* " + |
| "order by @jcr:score */ " + |
| "order by [jcr:score]"); |
| verify("//(element(*, nt:address))", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:address] as a " + |
| "/* xpath: //element(*, nt:address) */"); |
| verify("//(element(*, nt:address) | element(*, nt:folder))", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:address] as a " + |
| "/* xpath: //element(*, nt:address) */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:folder] as a " + |
| "/* xpath: // element(*, nt:folder) */"); |
| verify("(//element(*, nt:address) | //element(*, nt:folder))", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:address] as a " + |
| "/* xpath: //element(*, nt:address) */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:folder] as a " + |
| "/* xpath: //element(*, nt:folder) */"); |
| verify("/jcr:root/content//*[@a] order by @c option(traversal fail)", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "and isdescendantnode(a, '/content') " + |
| "order by [c] option(traversal FAIL) " + |
| "/* xpath: /jcr:root/content//*[@a] " + |
| "order by @c " + |
| "option(traversal fail) */"); |
| verify("//*[@a or @b] order by @c option(traversal warn)", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [b] is not null " + |
| "order by [c] option(traversal WARN) " + |
| "/* xpath: //*[@a or @b] " + |
| "order by @c " + |
| "option(traversal warn) */"); |
| verify("/jcr:root/(content|libs)//*[@a] order by @c option(traversal ok)", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "and isdescendantnode(a, '/content') " + |
| "/* xpath: /jcr:root/content//*[@a] " + |
| "order by @c option(traversal ok) */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "and isdescendantnode(a, '/libs') " + |
| "/* xpath: /jcr:root/libs//*[@a] " + |
| "order by @c option(traversal ok) */ " + |
| "order by [c] " + |
| "option(traversal OK)"); |
| } |
| |
| @Test |
| public void chainedConditions() throws ParseException { |
| verify("/jcr:root/x[@a][@b][@c]", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "and [b] is not null " + |
| "and [c] is not null " + |
| "and issamenode(a, '/x') " + |
| "/* xpath: /jcr:root/x[@a][@b][@c] */"); |
| } |
| |
| @Test |
| public void union() throws ParseException { |
| verify("(//*[@a=1 or @b=1] | //*[@c=1])", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] = 1 " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [b] = 1 " + |
| "/* xpath: //*[@a=1 or @b=1] */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [c] = 1 " + |
| "/* xpath: //*[@c=1] */"); |
| verify("//(a|(b|c))", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where name(a) = 'a' " + |
| "/* xpath: //a */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where name(a) = 'b' " + |
| "/* xpath: //b */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where name(a) = 'c' " + |
| "/* xpath: //c */"); |
| verify("(//*[jcr:contains(., 'some')])", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where contains(*, 'some') " + |
| "/* xpath: //*[jcr:contains(., 'some')] */"); |
| verify("(//*[jcr:contains(., 'x')] | //*[jcr:contains(., 'y')])", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where contains(*, 'x') " + |
| "/* xpath: //*[jcr:contains(., 'x')] */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where contains(*, 'y') " + |
| "/* xpath: //*[jcr:contains(., 'y')] */"); |
| try { |
| verify("(/jcr:root/x[@a][@b][@c]",""); |
| fail(); |
| } catch (ParseException e) { |
| // expected |
| } |
| verify("(/jcr:root/x[@a] | /jcr:root/y[@b])[@c]", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "and [c] is not null " + |
| "and issamenode(a, '/x') " + |
| "/* xpath: /jcr:root/x[@a] [@c] */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [b] is not null " + |
| "and [c] is not null " + |
| "and issamenode(a, '/y') " + |
| "/* xpath: /jcr:root/y[@b][@c] */"); |
| verify("(/jcr:root/x | /jcr:root/y)", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where issamenode(a, '/x') " + |
| "/* xpath: /jcr:root/x */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where issamenode(a, '/y') " + |
| "/* xpath: /jcr:root/y */"); |
| verify("(/jcr:root/x | /jcr:root/y )", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where issamenode(a, '/x') " + |
| "/* xpath: /jcr:root/x */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where issamenode(a, '/y') " + |
| "/* xpath: /jcr:root/y */"); |
| verify("(/jcr:root/content//*[@a] | /jcr:root/lib//*[@b]) order by @c", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "and isdescendantnode(a, '/content') " + |
| "/* xpath: /jcr:root/content//*[@a] " + |
| "order by @c */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [b] is not null " + |
| "and isdescendantnode(a, '/lib') " + |
| "/* xpath: /jcr:root/lib//*[@b] " + |
| "order by @c */ " + |
| "order by [c]"); |
| verify("/jcr:root/(content|lib)/element(*, nt:base) order by @title", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where ischildnode(a, '/content') " + |
| "/* xpath: /jcr:root/content/element(*, nt:base) " + |
| "order by @title */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where ischildnode(a, '/lib') " + |
| "/* xpath: /jcr:root/lib/element(*, nt:base) " + |
| "order by @title */ " + |
| "order by [title]"); |
| verify("/jcr:root/(content|lib)", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where issamenode(a, '/content') " + |
| "/* xpath: /jcr:root/content */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where issamenode(a, '/lib') " + |
| "/* xpath: /jcr:root/lib */"); |
| verify("(/jcr:root/content|/jcr:root/lib)//*", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where isdescendantnode(a, '/content') " + |
| "/* xpath: /jcr:root/content//* */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where isdescendantnode(a, '/lib') " + |
| "/* xpath: /jcr:root/lib//* */"); |
| verify("/jcr:root/content/(a|b|c)/thumbnails/*", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where ischildnode(a, '/content/a/thumbnails') " + |
| "/* xpath: /jcr:root/content/a/thumbnails/* */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where ischildnode(a, '/content/b/thumbnails') " + |
| "/* xpath: /jcr:root/content/b/thumbnails/* */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where ischildnode(a, '/content/c/thumbnails') " + |
| "/* xpath: /jcr:root/content/c/thumbnails/* */"); |
| verify("/jcr:root/(content|lib)//*[@a]", |
| "select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "and isdescendantnode(a, '/content') " + |
| "/* xpath: /jcr:root/content//*[@a] */ " + |
| "union select [jcr:path], [jcr:score], * " + |
| "from [nt:base] as a " + |
| "where [a] is not null " + |
| "and isdescendantnode(a, '/lib') " + |
| "/* xpath: /jcr:root/lib//*[@a] */"); |
| // "order by @jcr:score" is kept on xpath to sql2 conversion |
| // (because the default is ascending) |
| verify("/jcr:root/content//(element(*, nt:base) | element(*, nt:folder)) order by @jcr:score", |
| "select [jcr:path], [jcr:score], * from [nt:base] as a where isdescendantnode(a, '/content') " + |
| "/* xpath: /jcr:root/content//element(*, nt:base) order by @jcr:score */ " + |
| "union select [jcr:path], [jcr:score], * from [nt:folder] as a where isdescendantnode(a, '/content') " + |
| "/* xpath: /jcr:root/content// element(*, nt:folder) order by @jcr:score */ " + |
| "order by [jcr:score]"); |
| // "order by @jcr:score descending" is ignored on xpath to sql2 conversion |
| verify("/jcr:root/content//(element(*, nt:base) | element(*, nt:folder)) order by @jcr:score descending", |
| "select [jcr:path], [jcr:score], * from [nt:base] as a where isdescendantnode(a, '/content') " + |
| "/* xpath: /jcr:root/content//element(*, nt:base) order by @jcr:score descending */ " + |
| "union select [jcr:path], [jcr:score], * from [nt:folder] as a where isdescendantnode(a, '/content') " + |
| "/* xpath: /jcr:root/content// element(*, nt:folder) order by @jcr:score descending */"); |
| } |
| |
| private void verify(String xpath, String expectedSql2) throws ParseException { |
| String sql2 = new XPathToSQL2Converter().convert(xpath); |
| sql2 = formatSQL(sql2); |
| expectedSql2 = formatSQL(expectedSql2); |
| assertEquals(expectedSql2, sql2); |
| parser.parse(sql2); |
| } |
| |
| static String formatSQL(String sql) { |
| sql = sql.replace('\n', ' '); |
| sql = sql.replaceAll(" from ", "\nfrom "); |
| sql = sql.replaceAll(" where ", "\nwhere "); |
| sql = sql.replaceAll(" and ", "\nand "); |
| sql = sql.replaceAll(" union ", "\nunion "); |
| sql = sql.replaceAll(" order by ", "\norder by "); |
| sql = sql.replaceAll(" option\\(", "\noption\\("); |
| sql = sql.replaceAll(" \\/\\* ", "\n/* "); |
| return sql; |
| } |
| |
| } |