/* * 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.core.query; import java.util.HashSet; import java.util.Random; import java.util.Set; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.query.InvalidQueryException; import javax.jcr.query.QueryResult; import org.apache.jackrabbit.commons.query.qom.Operator; /** * <code>UpperLowerCaseQueryTest</code> tests the functions fn:lower-case() and * fn:upper-case() in XPath, LOWER() and UPPER() in SQL and UpperCase and * LowerCase in JQOM. */ public class UpperLowerCaseQueryTest extends AbstractQueryTest { public void testEqualsGeneralComparison() throws RepositoryException { check(new String[]{"foo", "Foo", "fOO", "FOO", "fooBar", "fo", "fooo"}, Operator.EQ, "foo", new boolean[]{true, true, true, true, false, false, false}); check(new String[]{"foo"}, Operator.EQ, "", new boolean[]{false}); check(new String[]{""}, Operator.EQ, "", new boolean[]{true}); } public void testGreaterThanGeneralComparison() throws RepositoryException { // check edges check(new String[]{"foo", "FOO", "FoO", "fOo", "FON", "fon", "fo", "FO"}, Operator.GT, "foo", new boolean[]{false, false, false, false, false, false, false, false}); check(new String[]{"foo ", "FOOa", "FoOO", "fOo1", "FOp", "foP", "fp", "g", "G"}, Operator.GT, "foo", new boolean[]{true, true, true, true, true, true, true, true, true}); // check combinations check(new String[]{"foo", "fooo", "FooO", "fo", "FON", "fon"}, Operator.GT, "foo", new boolean[]{false, true, true, false, false, false}); } public void testLessThanGeneralComparison() throws RepositoryException { // check edges check(new String[]{"foo", "FOO", "FoO", "fOo", "foOo", "foo ", "fooa", "fop"}, Operator.LT, "foo", new boolean[]{false, false, false, false, false, false, false, false}); check(new String[]{"fo", "FOn", "FoN", "fO", "FO1", "fn", "fN", "E", "e"}, Operator.LT, "foo", new boolean[]{true, true, true, true, true, true, true, true, true}); // check combinations check(new String[]{"foo", "fooo", "FooO", "fo", "FON", "fon"}, Operator.LT, "foo", new boolean[]{false, false, false, true, true, true}); } public void testGreaterEqualsGeneralComparison() throws RepositoryException { // check edges check(new String[]{"fo", "FO", "Fon", "fONo", "FON", "fO", "fo", "FO"}, Operator.GE, "foo", new boolean[]{false, false, false, false, false, false, false, false}); check(new String[]{"foo", "FoO", "FoOO", "fOo1", "FOp", "foP", "fp", "g", "G"}, Operator.GE, "foo", new boolean[]{true, true, true, true, true, true, true, true, true}); // check combinations check(new String[]{"foo", "fooo", "FOo", "fo", "FON", "fon"}, Operator.GE, "foo", new boolean[]{true, true, true, false, false, false}); } public void testLessEqualsGeneralComparison() throws RepositoryException { // check edges check(new String[]{"fooo", "FOoo", "Fop", "fOpo", "FOP", "fOo ", "fp", "G"}, Operator.LE, "foo", new boolean[]{false, false, false, false, false, false, false, false}); check(new String[]{"foo", "FoO", "Foo", "fOn", "FO", "fo", "f", "E", "e"}, Operator.LE, "foo", new boolean[]{true, true, true, true, true, true, true, true, true}); // check combinations check(new String[]{"foo", "fo", "FOo", "fop", "FOP", "fooo"}, Operator.LE, "foo", new boolean[]{true, true, true, false, false, false}); } public void testNotEqualsGeneralComparison() throws RepositoryException { // check edges check(new String[]{"fooo", "FOoo", "Fop", "fOpo", "FOP", "fOo ", "fp", "G", ""}, Operator.NE, "foo", new boolean[]{true, true, true, true, true, true, true, true, true}); check(new String[]{"foo", "FoO", "Foo", "foO", "FOO"}, Operator.NE, "foo", new boolean[]{false, false, false, false, false}); // check combinations check(new String[]{"foo", "fo", "FOo", "fop", "FOP", "fooo"}, Operator.NE, "foo", new boolean[]{false, true, false, true, true, true}); } public void testLikeComparison() throws RepositoryException { check(new String[]{"foo", "Foo", "fOO", "FO "}, Operator.LIKE, "fo_", new boolean[]{true, true, true, true}); check(new String[]{"foo", "Foo", "fOO", "FOO"}, Operator.LIKE, "f_o", new boolean[]{true, true, true, true}); check(new String[]{"foo", "Foo", "fOO", " OO"}, Operator.LIKE, "_oo", new boolean[]{true, true, true, true}); check(new String[]{"foo", "Foa", "fOO", "FO", "foRm", "fPo", "fno", "FPo", "Fno"}, Operator.LIKE, "fo%", new boolean[]{true, true, true, true, true, false, false, false, false}); } public void testLikeComparisonRandom() throws RepositoryException { String abcd = "abcd"; Random random = new Random(); for (int i = 0; i < 50; i++) { String pattern = ""; pattern += getRandomChar(abcd, random); pattern += getRandomChar(abcd, random); // create 10 random values with 4 characters String[] values = new String[10]; boolean[] matches = new boolean[10]; for (int n = 0; n < 10; n++) { // at least the first character always matches String value = String.valueOf(pattern.charAt(0)); for (int r = 1; r < 4; r++) { char c = getRandomChar(abcd, random); if (random.nextBoolean()) { c = Character.toUpperCase(c); } value += c; } matches[n] = value.toLowerCase().startsWith(pattern); values[n] = value; } pattern += "%"; check(values, Operator.LIKE, pattern, matches); } } public void testRangeWithEmptyString() throws RepositoryException { check(new String[]{" ", "a", "A", "1", "3", "!", "@"}, Operator.GT, "", new boolean[]{true, true, true, true, true, true, true}); check(new String[]{"", "a", "A", "1", "3", "!", "@"}, Operator.GE, "", new boolean[]{true, true, true, true, true, true, true}); check(new String[]{"", "a", "A", "1", "3", "!", "@"}, Operator.LT, "", new boolean[]{false, false, false, false, false, false, false}); check(new String[]{"", "a", "A", "1", "3", "!", "@"}, Operator.LE, "", new boolean[]{true, false, false, false, false, false, false}); } public void testInvalidQuery() throws RepositoryException { try { executeXPathQuery("//*[fn:lower-case(@foo) = 123]", new Node[]{}); fail("must throw InvalidQueryException"); } catch (InvalidQueryException e) { // correct } try { executeSQLQuery("select * from nt:base where LOWER(foo) = 123", new Node[]{}); fail("must throw InvalidQueryException"); } catch (InvalidQueryException e) { // correct } } public void testWrongCaseNeverMatches() throws RepositoryException { Node n = testRootNode.addNode("node"); n.setProperty("foo", "Bar"); testRootNode.save(); executeXPathQuery(testPath + "/*[jcr:like(fn:lower-case(@foo), 'BA%')]", new Node[]{}); } //----------------------------< internal >---------------------------------- private void check(String[] values, Operator operator, String queryTerm, boolean[] matches) throws RepositoryException { if (values.length != matches.length) { throw new IllegalArgumentException("values and matches must have same length"); } // create log message StringBuffer logMsg = new StringBuffer(); logMsg.append("queryTerm: ").append(queryTerm); logMsg.append(" values: "); String separator = ""; for (int i = 0; i < values.length; i++) { logMsg.append(separator); separator = ", "; if (matches[i]) { logMsg.append("+"); } else { logMsg.append("-"); } logMsg.append(values[i]); } log.println(logMsg.toString()); for (NodeIterator it = testRootNode.getNodes(); it.hasNext();) { it.nextNode().remove(); } Set<Node> matchingNodes = new HashSet<Node>(); for (int i = 0; i < values.length; i++) { Node n = testRootNode.addNode("node" + i); n.setProperty(propertyName1, values[i]); if (matches[i]) { matchingNodes.add(n); } } testRootNode.save(); Node[] nodes = matchingNodes.toArray(new Node[matchingNodes.size()]); // run queries with lower-case String xpath = operator.formatXpath( "fn:lower-case(@" + propertyName1 + ")", "'" + queryTerm.toLowerCase() + "'"); executeXPathQuery(testPath + "/*[" + xpath + "]", nodes); String sql = "select * from nt:base where " + "jcr:path like '" + testRoot + "/%' and " + operator.formatSql( "LOWER(" + propertyName1 + ")", "'" + queryTerm.toLowerCase() + "'"); executeSQLQuery(sql, nodes); QueryResult result = qomFactory.createQuery( qomFactory.selector(testNodeType, "s"), qomFactory.and( qomFactory.childNode("s", testRoot), qomFactory.comparison( qomFactory.lowerCase( qomFactory.propertyValue("s", propertyName1)), operator.toString(), qomFactory.literal( superuser.getValueFactory().createValue( queryTerm.toLowerCase())) ) ), null, null).execute(); checkResult(result, nodes); // run queries with upper-case xpath = operator.formatXpath( "fn:upper-case(@" + propertyName1 + ")", "'" + queryTerm.toUpperCase() + "'"); executeXPathQuery(testPath + "/*[" + xpath + "]", nodes); sql = "select * from nt:base where " + "jcr:path like '" + testRoot + "/%' and " + operator.formatSql( "UPPER(" + propertyName1 + ")", "'" + queryTerm.toUpperCase() + "'"); executeSQLQuery(sql, nodes); result = qomFactory.createQuery( qomFactory.selector(testNodeType, "s"), qomFactory.and( qomFactory.childNode("s", testRoot), operator.comparison( qomFactory, qomFactory.upperCase( qomFactory.propertyValue("s", propertyName1)), qomFactory.literal( superuser.getValueFactory().createValue( queryTerm.toUpperCase())) ) ), null, null).execute(); checkResult(result, nodes); } private char getRandomChar(String pool, Random random) { return pool.charAt(random.nextInt(pool.length())); } }