blob: a4f6fde83f26b49a9857471b8ea92ed4565327ff [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.ignite.internal.sql.engine.sql;
import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.Arrays;
import java.util.List;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.ignite.lang.ErrorGroups.Sql;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests for sql parser.
*/
public class IgniteSqlParserSelfTest {
@Test
public void testStatementMode() {
StatementParseResult result = IgniteSqlParser.parse("SELECT 1 + ?", StatementParseResult.MODE);
assertEquals(1, result.dynamicParamsCount());
assertNotNull(result.statement());
}
@ParameterizedTest
@MethodSource("multiStatementQueries")
public void testScriptMode(String query, int[] expectedParamsCountPerStatement) {
ScriptParseResult scriptParseResult = IgniteSqlParser.parse(query, ScriptParseResult.MODE);
int expectedTotalParams = Arrays.stream(expectedParamsCountPerStatement).sum();
int expectedStatementsCount = expectedParamsCountPerStatement.length;
assertEquals(expectedTotalParams, scriptParseResult.dynamicParamsCount());
assertEquals(expectedStatementsCount, scriptParseResult.results().size());
for (int i = 0; i < scriptParseResult.results().size(); i++) {
StatementParseResult res = scriptParseResult.results().get(i);
assertNotNull(res.statement());
assertEquals(expectedParamsCountPerStatement[i], res.dynamicParamsCount());
}
}
/**
* {@link StatementParseResult#MODE} does not allow input that contains multiple statements.
*/
@Test
public void testStatementModeRejectMultipleStatements() {
assertThrowsSqlException(
Sql.STMT_VALIDATION_ERR,
"Multiple statements are not allowed",
() -> IgniteSqlParser.parse("SELECT 1; SELECT 2", StatementParseResult.MODE));
}
@Test
public void testGrammarViolation() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Failed to parse query: Encountered \"TABEL\" at line 1, column 7",
() -> IgniteSqlParser.parse("ALTER TABEL foo ADD COLUMN bar INT", StatementParseResult.MODE));
}
@Test
public void testGrammarViolationButExceptionIsReplacedInsideParser() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Failed to parse query: Incorrect syntax near the keyword 'FROM' at line 1, column 12",
() -> IgniteSqlParser.parse("SELECT ALL FROM foo", StatementParseResult.MODE));
}
@Test
public void testEmptyString() {
assertThrowsSqlException(Sql.STMT_PARSE_ERR,
"Failed to parse query: Encountered \"<EOF>\" at line 0, column 0",
() -> IgniteSqlParser.parse("", StatementParseResult.MODE));
}
@Test
public void testEmptyStatements() {
assertThrowsSqlException(Sql.STMT_PARSE_ERR,
"Failed to parse query: Encountered \";\" at line 1, column 1",
() -> IgniteSqlParser.parse(";", ScriptParseResult.MODE));
assertThrowsSqlException(Sql.STMT_PARSE_ERR,
"Failed to parse query: Encountered \";\" at line 2, column 1",
() -> IgniteSqlParser.parse("--- comment\n;", ScriptParseResult.MODE));
assertThrowsSqlException(Sql.STMT_PARSE_ERR,
"Failed to parse query: Encountered \"<EOF>\" at line 1, column 11",
() -> IgniteSqlParser.parse("--- comment", ScriptParseResult.MODE));
}
@Test
public void testCommentedQuery() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Failed to parse query: Encountered \"<EOF>\" at line 1, column 11",
() -> IgniteSqlParser.parse("-- SELECT 1", StatementParseResult.MODE));
}
@Test
public void testTokenizationError() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Failed to parse query: Lexical error at line 1, column 11. Encountered: \"#\" (35), after : \"\"",
() -> IgniteSqlParser.parse("SELECT foo#bar", StatementParseResult.MODE));
}
@Test
public void testExpressionInsteadOfStatement() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Failed to parse query: Non-query expression encountered in illegal context. At line 1, column 1",
() -> IgniteSqlParser.parse("a = 2", StatementParseResult.MODE));
}
@Test
public void testExpressionInsteadOfSubStatement() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Failed to parse query: Non-query expression encountered in illegal context. At line 1, column 21",
() -> IgniteSqlParser.parse("INSERT INTO t (c1) (a = 2)", StatementParseResult.MODE));
}
@Test
public void testInvalidDecimalLiteral() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Literal '2a' can not be parsed to type 'DECIMAL'",
() -> IgniteSqlParser.parse("SELECT decimal '2a'", StatementParseResult.MODE));
}
@ParameterizedTest
@MethodSource("dmlStatements")
public void testDmlStatementsClone(String stmt) {
StatementParseResult single = IgniteSqlParser.parse(stmt, StatementParseResult.MODE);
SqlNode statement = single.statement();
SqlParserPos pos = statement.getParserPosition();
assertEquals(statement.clone(pos).toString(), statement.toString());
}
@ParameterizedTest
@MethodSource("dmlStatements")
public void testScriptDmlStatementsClone(String stmt) {
ScriptParseResult script = IgniteSqlParser.parse(stmt, ScriptParseResult.MODE);
StatementParseResult single = script.results().get(0);
SqlNode statement = single.statement();
SqlParserPos pos = statement.getParserPosition();
assertEquals(statement.clone(pos).toString(), statement.toString());
}
private static List<Arguments> dmlStatements() {
return List.of(
Arguments.of("UPDATE t SET x = 1"),
Arguments.of("UPDATE t SET x = 1 WHERE y = 2"),
Arguments.of("DELETE FROM t"),
Arguments.of("DELETE FROM t WHERE y = 2"),
Arguments.of("MERGE INTO T2 dst USING t1 src ON dst.c1 = src.c1"
+ " WHEN MATCHED THEN UPDATE SET c2 = 1"),
Arguments.of("MERGE INTO T2 dst USING t1 src ON dst.c1 = src.c1 "
+ "WHEN NOT MATCHED THEN INSERT (c1, c2, c3) VALUES (src.c1, src.c2, 1)"),
Arguments.of("MERGE INTO T2 dst USING t1 src ON dst.c1 = src.c1 "
+ "WHEN MATCHED THEN UPDATE SET c2 = 1 "
+ "WHEN NOT MATCHED THEN INSERT (c1, c2, c3) VALUES (src.c1, src.c2, 1)")
);
}
private static List<Arguments> multiStatementQueries() {
return List.of(
Arguments.of(
"-- insert\n"
+ "INSERT INTO TEST VALUES(1, 1);;\n"
+ "--- select\n"
+ ";;;SELECT 1 + 2",
new int[]{0, 0}
),
Arguments.of("SELECT 1 + ? - ?", new int[]{2}),
Arguments.of("SELECT 1; INSERT INTO TEST VALUES(?, ?, ?)", new int[] {0, 3}),
Arguments.of("INSERT INTO TEST VALUES(?, ?); SELECT 1; SELECT 2 + ?", new int[] {2, 0, 1})
);
}
}