/* * Licensed to Crate.io Inc. or its affiliates ("Crate.io") under one or * more contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright ownership. * Crate.io 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. * * However, if you have executed another commercial license agreement with * Crate.io these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.sql.parser; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import io.crate.sql.tree.*; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.util.Locale; import java.util.Optional; import static io.crate.sql.SqlFormatter.formatSql; import static io.crate.sql.tree.QueryUtil.selectList; import static io.crate.sql.tree.QueryUtil.table; import static java.lang.String.format; import static java.util.Collections.nCopies; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class TestSqlParser { @Rule public ExpectedException expectedException = ExpectedException.none(); @Test public void testPossibleExponentialBacktracking() throws Exception { SqlParser.createExpression("(((((((((((((((((((((((((((true)))))))))))))))))))))))))))"); } @Test public void testDouble() throws Exception { assertExpression("123.", new DoubleLiteral("123")); assertExpression("123.0", new DoubleLiteral("123")); assertExpression(".5", new DoubleLiteral(".5")); assertExpression("123.5", new DoubleLiteral("123.5")); assertExpression("123E7", new DoubleLiteral("123E7")); assertExpression("123.E7", new DoubleLiteral("123E7")); assertExpression("123.0E7", new DoubleLiteral("123E7")); assertExpression("123E+7", new DoubleLiteral("123E7")); assertExpression("123E-7", new DoubleLiteral("123E-7")); assertExpression("123.456E7", new DoubleLiteral("123.456E7")); assertExpression("123.456E+7", new DoubleLiteral("123.456E7")); assertExpression("123.456E-7", new DoubleLiteral("123.456E-7")); assertExpression(".4E42", new DoubleLiteral(".4E42")); assertExpression(".4E+42", new DoubleLiteral(".4E42")); assertExpression(".4E-42", new DoubleLiteral(".4E-42")); } @Test public void testParameter() throws Exception { assertExpression("?", new ParameterExpression(1)); for (int i = 0; i < 1000; i++) { assertExpression(String.format(Locale.ENGLISH, "$%d", i), new ParameterExpression(i)); } } @Test public void testDoubleInQuery() { assertStatement("SELECT 123.456E7 FROM DUAL", new Query( Optional.empty(), new QuerySpecification( selectList(new DoubleLiteral("123.456E7")), table(QualifiedName.of("dual")), Optional.empty(), ImmutableList.of(), Optional.empty(), ImmutableList.of(), Optional.empty(), Optional.empty()), ImmutableList.of(), Optional.empty(), Optional.empty()) ); } @Test public void testEmptyExpression() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:1: no viable alternative at input '<EOF>'"); SqlParser.createExpression(""); } @Test public void testEmptyStatement() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:1: no viable alternative at input '<EOF>'"); SqlParser.createStatement(""); } @Test public void testExpressionWithTrailingJunk() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:7: extraneous input 'x' expecting"); SqlParser.createExpression("1 + 1 x"); } @Test public void testTokenizeErrorStartOfLine() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:1: extraneous input '@' expecting"); SqlParser.createStatement("@select"); } @Test public void testTokenizeErrorMiddleOfLine() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:25: no viable alternative at input '@'"); SqlParser.createStatement("select * from foo where @what"); } @Test public void testTokenizeErrorIncompleteToken() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:15: extraneous input ''' expecting"); SqlParser.createStatement("select * from 'oops"); } @Test public void testParseErrorStartOfLine() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 3:1: extraneous input 'from' expecting"); SqlParser.createStatement("select *\nfrom x\nfrom"); } @Test public void testParseErrorMiddleOfLine() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 3:7: no viable alternative at input 'from'"); SqlParser.createStatement("select *\nfrom x\nwhere from"); } @Test public void testParseErrorEndOfInput() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:14: no viable alternative at input '<EOF>'"); SqlParser.createStatement("select * from"); } @Test public void testParseErrorEndOfInputWhitespace() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:16: no viable alternative at input '<EOF>'"); SqlParser.createStatement("select * from "); } @Test public void testParseErrorBackquotes() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:15: backquoted identifiers are not supported; use double quotes to quote identifiers"); SqlParser.createStatement("select * from `foo`"); } @Test public void testParseErrorBackquotesEndOfInput() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:19: backquoted identifiers are not supported; use double quotes to quote identifiers"); SqlParser.createStatement("select * from foo `bar`"); } @Test public void testParseErrorDigitIdentifiers() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:8: identifiers must not start with a digit; surround the identifier with double quotes"); SqlParser.createStatement("select 1x from dual"); } @Test public void testIdentifierWithColon() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:15: identifiers must not contain ':'"); SqlParser.createStatement("select * from foo:bar"); } @Test public void testParseErrorDualOrderBy() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:35: mismatched input 'order'"); SqlParser.createStatement("select fuu from dual order by fuu order by fuu"); } @Test public void testParseErrorReverseOrderByLimit() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:31: mismatched input 'order' expecting <EOF>"); SqlParser.createStatement("select fuu from dual limit 10 order by fuu"); } @Test public void testParseErrorReverseOrderByLimitOffset() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:41: mismatched input 'order' expecting <EOF>"); SqlParser.createStatement("select fuu from dual limit 10 offset 20 order by fuu"); } @Test public void testParseErrorReverseOrderByOffset() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:32: mismatched input 'order' expecting <EOF>"); SqlParser.createStatement("select fuu from dual offset 20 order by fuu"); } @Test public void testParseErrorReverseLimitOffset() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:32: mismatched input 'limit' expecting <EOF>"); SqlParser.createStatement("select fuu from dual offset 20 limit 10"); } @Test public void testParsingExceptionPositionInfo() { try { SqlParser.createStatement("select *\nfrom x\nwhere from"); fail("expected exception"); } catch (ParsingException e) { assertEquals(e.getMessage(), "line 3:7: no viable alternative at input 'from'"); assertEquals(e.getErrorMessage(), "no viable alternative at input 'from'"); assertEquals(e.getLineNumber(), 3); assertEquals(e.getColumnNumber(), 7); } } @Test public void testDate() throws Exception { assertExpression("DATE '2012-03-22'", new DateLiteral("2012-03-22")); } @Test public void testTime() throws Exception { assertExpression("TIME '03:04:05'", new TimeLiteral("03:04:05")); } @Test public void testTimestamp() throws Exception { assertExpression("TIMESTAMP '2016-12-31 01:02:03.123'", new TimestampLiteral("2016-12-31 01:02:03.123")); } @Test public void testCurrentTimestamp() throws Exception { assertExpression("CURRENT_TIMESTAMP", new CurrentTime(CurrentTime.Type.TIMESTAMP)); } @Test public void testSpecialFunctions() throws Exception { assertInstanceOf("CURRENT_SCHEMA", FunctionCall.class); assertInstanceOf("CURRENT_SCHEMA()", FunctionCall.class); } private void assertInstanceOf(String expr, Class<? extends Node> cls) { Expression expression = SqlParser.createExpression(expr); assertThat(expression, instanceOf(cls)); } @Test public void testStackOverflowExpression() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:1: expression is too large (stack overflow while parsing)"); SqlParser.createExpression(Joiner.on(" OR ").join(nCopies(4000, "x = y"))); } @Test public void testStackOverflowStatement() { expectedException.expect(ParsingException.class); expectedException.expectMessage("line 1:1: statement is too large (stack overflow while parsing)"); SqlParser.createStatement("SELECT " + Joiner.on(" OR ").join(nCopies(4000, "x = y"))); } private static void assertStatement(String query, Statement expected) { assertParsed(query, expected, SqlParser.createStatement(query)); } private static void assertExpression(String expression, Expression expected) { assertParsed(expression, expected, SqlParser.createExpression(expression)); } private static void assertParsed(String input, Node expected, Node parsed) { if (!parsed.equals(expected)) { fail(format("expected%n%n%s%n%nto parse as%n%n%s%n%nbut was%n%n%s%n", indent(input), indent(formatSql(expected)), indent(formatSql(parsed)))); } } private static String indent(String value) { String indent = " "; return indent + value.trim().replaceAll("\n", "\n" + indent); } }