blob: bc19dadca3169e44ed2a76d0ebec9520ddaab2d9 [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 java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.calcite.sql.SqlNode;
import org.apache.ignite.internal.lang.IgniteStringBuilder;
import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
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.EnumSource;
/**
* Tests to verify parsing of comments.
*
* <p>Covers:<ol>
* <li>E161: SQL comments using leading double minus</li>
* <li>T351: Bracketed SQL comments (&#47;*...*&#47; comments)</li>
* </ol>
*
* <p>According to SQL standard, SQL text containing one or more instances of comment is equivalent to the same SQL text with the comment
* replaced with newline.
*/
@SuppressWarnings("ThrowableNotThrown")
public class CommentParsingTest extends AbstractParserTest {
private static final IgniteLogger LOG = Loggers.forClass(CommentParsingTest.class);
private static final String NL = System.lineSeparator();
private static final String SIMPLE_COMMENT = "-- this is simple comment ";
private static final String MULTILINE_COMMENT = "/* this" + NL
+ "is" + NL
+ "multiline" + NL
+ "comment */";
@ParameterizedTest
@EnumSource(Statement.class)
void leadingSimpleComment(Statement statement) {
String originalQueryString = statement.text;
String queryWithComment = SIMPLE_COMMENT + NL + originalQueryString;
assertQueries(
originalQueryString,
queryWithComment
);
}
@ParameterizedTest
@EnumSource(Statement.class)
void leadingMultilineComment(Statement statement) {
String originalQueryString = statement.text;
String queryWithComment = MULTILINE_COMMENT + NL + originalQueryString;
assertQueries(
originalQueryString,
queryWithComment
);
}
@ParameterizedTest
@EnumSource(Statement.class)
void trailingSimpleComment(Statement statement) {
String originalQueryString = statement.text;
String queryWithComment = originalQueryString + NL + SIMPLE_COMMENT;
assertQueries(
originalQueryString,
queryWithComment
);
}
@ParameterizedTest
@EnumSource(Statement.class)
void trailingMultilineComment(Statement statement) {
String originalQueryString = statement.text;
String queryWithComment = originalQueryString + NL + MULTILINE_COMMENT;
assertQueries(
originalQueryString,
queryWithComment
);
}
@Test
void emptyStatementSimpleComment() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Failed to parse query",
() -> parse(SIMPLE_COMMENT)
);
}
@Test
void emptyStatementMultilineComment() {
assertThrowsSqlException(
Sql.STMT_PARSE_ERR,
"Failed to parse query",
() -> parse(MULTILINE_COMMENT)
);
}
/**
* This test injects simple comment before random line break.
*/
@ParameterizedTest
@EnumSource(Statement.class)
void infixSimpleComment(Statement statement) {
int iterations = 50;
long seed = ThreadLocalRandom.current().nextLong();
LOG.info("Seed is {}", seed);
Random rnd = new Random(seed);
// it's well-known query that has less than
// Integer.MAX_VALUE lines
@SuppressWarnings("NumericCastThatLosesPrecision")
int linesCount = (int) statement.text.lines().count();
for (int i = 0; i < iterations; i++) {
int lineToInject = Integer.min(statement.maxLineBreak, rnd.nextInt(linesCount));
int lineNum = 0;
IgniteStringBuilder sb = new IgniteStringBuilder();
for (String line : statement.text.split(NL)) {
sb.app(line);
if (lineNum++ == lineToInject) {
sb.app(SIMPLE_COMMENT);
}
sb.app(NL);
}
assertQueries(statement.text, sb.toString());
}
}
/**
* This test injects simple comment before random line break.
*/
@ParameterizedTest
@EnumSource(Statement.class)
void infixMultilineComment(Statement statement) {
int iterations = 50;
long seed = ThreadLocalRandom.current().nextLong();
LOG.info("Seed is {}", seed);
Random rnd = new Random(seed);
// it's well-known query that has less than
// Integer.MAX_VALUE lines
@SuppressWarnings("NumericCastThatLosesPrecision")
int linesCount = (int) statement.text.lines().count();
for (int i = 0; i < iterations; i++) {
int lineToInject = Integer.min(statement.maxLineBreak, rnd.nextInt(linesCount));
int lineNum = 0;
IgniteStringBuilder sb = new IgniteStringBuilder();
for (String line : statement.text.split(NL)) {
sb.app(line);
if (lineNum++ == lineToInject) {
sb.app(MULTILINE_COMMENT);
}
sb.app(NL);
}
assertQueries(statement.text, sb.toString());
}
}
private static void assertQueries(String expected, String actual) {
SqlNode expectedAst;
SqlNode actualAst;
try {
expectedAst = parse(expected);
} catch (RuntimeException ex) {
LOG.error("Unable to parse statement: \n{}\n", ex, expected);
throw ex;
}
try {
actualAst = parse(actual);
} catch (RuntimeException ex) {
LOG.error("Unable to parse statement: \n{}\n", ex, actual);
throw ex;
}
assertEquals(unparse(expectedAst), unparse(actualAst));
}
private enum Statement {
QUERY(Q15_STRING),
DML_INSERT("INSERT", "INTO", "t", Q15_STRING),
DML_UPDATE("UPDATE", "t", "SET", "a=1", "WHERE", "EXISTS(" + Q15_STRING + ")"),
DML_DELETE("DELETE", "FROM", "t", "WHERE", "EXISTS(" + Q15_STRING + ")"),
DDL("CREATE", "TABLE", "t", "(", "id", "INT", "PRIMARY", "KEY", ",", "val VARCHAR", "NOT", "NULL",
",", "PRIMARY", "KEY (", "id", ")", ")", "WITH", "PRIMARY_ZONE", "=", "mZone"),
EXPLAIN("EXPLAIN", "PLAN", "FOR", Q15_STRING),
TX("START", "TRANSACTION")
;
private final int maxLineBreak;
private final String text;
Statement(String... queryTokes) {
this.text = String.join(NL, queryTokes);
this.maxLineBreak = queryTokes.length;
}
}
// This is q15 from TPC-H. Didn't use TpchHelper here on purpose to make sure test
// won't be affected by accident change in query string. The test is sensible even to
// particular formatting
@SuppressWarnings("ConcatenationWithEmptyString")
private static final String Q15_STRING = ""
+ "WITH revenue (supplier_no, total_revenue) as (" + NL
+ " SELECT" + NL
+ " l_suppkey," + NL
+ " sum(l_extendedprice * (1-l_discount))" + NL
+ " FROM" + NL
+ " lineitem" + NL
+ " WHERE" + NL
+ " l_shipdate >= DATE '1996-01-01'" + NL
+ " AND l_shipdate < DATE '1996-01-01' + INTERVAL '3' MONTH" + NL
+ " GROUP BY" + NL
+ " l_suppkey" + NL
+ ")" + NL
+ "SELECT" + NL
+ " s_suppkey," + NL
+ " s_name," + NL
+ " s_address," + NL
+ " s_phone," + NL
+ " total_revenue" + NL
+ "FROM" + NL
+ " supplier," + NL
+ " revenue" + NL
+ "WHERE" + NL
+ " s_suppkey = supplier_no" + NL
+ " AND total_revenue = (" + NL
+ " SELECT" + NL
+ " max(total_revenue)" + NL
+ " FROM" + NL
+ " revenue" + NL
+ ")" + NL
+ "ORDER BY" + NL
+ " s_suppkey";
}