/*
* 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.spi.commons.query.sql2;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.UnsupportedEncodingException;
import java.util.Random;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.QueryResult;
import javax.jcr.query.qom.Column;
import javax.jcr.query.qom.Constraint;
import javax.jcr.query.qom.Ordering;
import javax.jcr.query.qom.QueryObjectModel;
import javax.jcr.query.qom.Source;
import javax.jcr.version.VersionException;
import junit.framework.TestCase;
import org.apache.jackrabbit.commons.query.sql2.Parser;
import org.apache.jackrabbit.commons.query.sql2.QOMFormatter;
import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
import org.apache.jackrabbit.spi.commons.conversion.DummyNamespaceResolver;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.query.qom.QueryObjectModelFactoryImpl;
import org.apache.jackrabbit.spi.commons.query.qom.QueryObjectModelTree;
import org.apache.jackrabbit.spi.commons.value.ValueFactoryQImpl;
import org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl;
/**
* This class runs function tests on the JCR-SQL2 parser.
*/
public class ParserTest extends TestCase {
protected org.apache.jackrabbit.commons.query.sql2.Parser parser;
protected Random random = new Random();
public static class QOM implements QueryObjectModel {
protected QueryObjectModelTree qomTree;
QOM(QueryObjectModelTree qomTree) {
this.qomTree = qomTree;
}
public Source getSource() {
return qomTree.getSource();
}
public Constraint getConstraint() {
return qomTree.getConstraint();
}
public Ordering[] getOrderings() {
return qomTree.getOrderings();
}
public Column[] getColumns() {
return qomTree.getColumns();
}
public void bindValue(String varName, Value value) throws IllegalArgumentException, RepositoryException {
// ignore
}
public QueryResult execute() throws InvalidQueryException, RepositoryException {
return null;
}
public String[] getBindVariableNames() throws RepositoryException {
return null;
}
public String getLanguage() {
return null;
}
public String getStatement() {
return null;
}
public String getStoredQueryPath() throws ItemNotFoundException, RepositoryException {
return null;
}
public void setLimit(long limit) {
// ignore
}
public void setOffset(long offset) {
// ignore
}
public Node storeAsNode(String absPath) throws ItemExistsException, PathNotFoundException, VersionException,
ConstraintViolationException, LockException, UnsupportedRepositoryOperationException,
RepositoryException {
return null;
}
}
static class QOMF extends QueryObjectModelFactoryImpl {
public QOMF(NamePathResolver resolver) {
super(resolver);
}
protected QueryObjectModel createQuery(QueryObjectModelTree qomTree) throws InvalidQueryException,
RepositoryException {
return new QOM(qomTree);
}
}
protected void setUp() throws Exception {
super.setUp();
NamePathResolver resolver = new DefaultNamePathResolver(new DummyNamespaceResolver());
QueryObjectModelFactoryImpl factory = new QOMF(resolver);
ValueFactory vf = new ValueFactoryQImpl(QValueFactoryImpl.getInstance(), resolver);
parser = new Parser(factory, vf);
}
private LineNumberReader openScript(String name) {
try {
return new LineNumberReader(new InputStreamReader(
getClass().getResourceAsStream(name), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 not supported", e);
}
}
public void testFormatLiterals() throws Exception {
formatLiteral("true", "true");
formatLiteral("false", "false");
formatLiteral("CAST('2000-01-01T12:00:00.000Z' AS DATE)",
"CAST('2000-01-01T12:00:00.000Z' AS DATE)");
formatLiteral("CAST(0 AS DATE)",
"CAST('1970-01-01T00:00:00.000Z' AS DATE)");
formatLiteral("1", "CAST('1' AS LONG)");
formatLiteral("-1", "CAST('-1' AS LONG)");
formatLiteral("CAST(" + Long.MAX_VALUE + " AS LONG)",
"CAST('" + Long.MAX_VALUE + "' AS LONG)");
formatLiteral("CAST(" + Long.MIN_VALUE + " AS LONG)",
"CAST('" + Long.MIN_VALUE + "' AS LONG)");
formatLiteral("1.0", "CAST('1.0' AS DECIMAL)");
formatLiteral("-1.0", "CAST('-1.0' AS DECIMAL)");
formatLiteral("100000000000000000000",
"CAST('100000000000000000000' AS DECIMAL)");
formatLiteral("-100000000000000000000",
"CAST('-100000000000000000000' AS DECIMAL)");
formatLiteral("CAST(1.0 AS DOUBLE)", "CAST('1.0' AS DOUBLE)");
formatLiteral("CAST(-1.0 AS DOUBLE)", "CAST('-1.0' AS DOUBLE)");
formatLiteral("CAST('X' AS NAME)", "CAST('X' AS NAME)");
formatLiteral("CAST('X' AS PATH)", "CAST('X' AS PATH)");
formatLiteral("CAST('X' AS REFERENCE)", "CAST('X' AS REFERENCE)");
formatLiteral("CAST('X' AS WEAKREFERENCE)", "CAST('X' AS WEAKREFERENCE)");
formatLiteral("CAST('X' AS URI)", "CAST('X' AS URI)");
formatLiteral("''", "''");
formatLiteral("' '", "' '");
formatLiteral("CAST(0 AS STRING)", "'0'");
formatLiteral("CAST(-1000000000000 AS STRING)", "'-1000000000000'");
formatLiteral("CAST(false AS STRING)", "'false'");
formatLiteral("CAST(true AS STRING)", "'true'");
}
private void formatLiteral(String literal, String cast) throws Exception {
String s = "SELECT TEST.* FROM TEST WHERE ID=" + literal;
QueryObjectModel qom = parser.createQueryObjectModel(s);
String s2 = QOMFormatter.format(qom);
String cast2 = s2.substring(s2.indexOf('=') + 1).trim();
assertEquals(cast, cast2);
qom = parser.createQueryObjectModel(s);
s2 = QOMFormatter.format(qom);
cast2 = s2.substring(s2.indexOf('=') + 1).trim();
assertEquals(cast, cast2);
}
public void testParseScript() throws Exception {
LineNumberReader reader = openScript("test.sql2.txt");
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
line = line.trim();
if (line.length() == 0 || line.startsWith("#")) {
continue;
}
// System.out.println(line);
String query = line;
try {
QueryObjectModel qom = parser.createQueryObjectModel(line);
String s = QOMFormatter.format(qom);
qom = parser.createQueryObjectModel(s);
String s2 = QOMFormatter.format(qom);
assertEquals(s, s2);
fuzz(line);
} catch (Exception e) {
line = reader.readLine();
String message = e.getMessage();
message = message.replace('\n', ' ');
if (line == null || !line.startsWith("> exception")) {
e.printStackTrace();
assertTrue("Unexpected exception for query " + query + ": "
+ e, false);
}
assertEquals("Expected exception message: " + message, "> exception: " + message, line);
}
}
reader.close();
}
public void fuzz(String query) throws Exception {
for (int i = 0; i < 100; i++) {
StringBuffer buff = new StringBuffer(query);
int changes = 1 + (int) Math.abs(random.nextGaussian() * 2);
for (int j = 0; j < changes; j++) {
char newChar;
if (random.nextBoolean()) {
String s = "<>_.+\"*%&/()=?[]{}_:;,.-1234567890.qersdf";
newChar = s.charAt(random.nextInt(s.length()));
} else {
newChar = (char) random.nextInt(255);
}
int pos = random.nextInt(buff.length());
if (random.nextBoolean()) {
// 50%: change one character
buff.setCharAt(pos, newChar);
} else {
if (random.nextBoolean()) {
// 25%: delete one character
buff.deleteCharAt(pos);
} else {
// 25%: insert one character
buff.insert(pos, newChar);
}
}
}
String q = buff.toString();
try {
parser.createQueryObjectModel(q);
} catch (ValueFormatException e) {
// OK
} catch (InvalidQueryException e) {
// OK
} catch (NamespaceException e) {
// OK?
} catch (Throwable t) {
t.printStackTrace();
assertTrue("Unexpected exception for query " + q + ": " + t,
false);
}
}
}
}