blob: c7e80a3e97d20d7610f9daae6937dd3e3d58df3b [file] [log] [blame]
/*
* 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;
}
}