Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,6 @@ iotdb-core/tsfile/src/main/antlr4/org/apache/tsfile/parser/gen/
.mvn/.gradle-enterprise/
.mvn/.develocity/
.run/

# Relational Grammar ANTLR
iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/.antlr/
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ public void syntaxError(

Result result = analyze(e, parser);

// pick the candidate tokens associated largest token index processed (i.e., the path that
// pick the candidate tokens associated largest token index processed (i.e., the
// path that
// consumed the most input)
String expected = result.getExpected().stream().sorted().collect(Collectors.joining(", "));

Expand Down Expand Up @@ -209,10 +210,13 @@ public Result process(ATNState currentState, int tokenIndex, RuleContext context
RuleStartState startState = atn.ruleToStartState[currentState.ruleIndex];

if (isReachable(currentState, startState)) {
// We've been dropped inside a rule in a state that's reachable via epsilon transitions.
// We've been dropped inside a rule in a state that's reachable via epsilon
// transitions.
// This is,
// effectively, equivalent to starting at the beginning (or immediately outside) the rule.
// In that case, backtrack to the beginning to be able to take advantage of logic that
// effectively, equivalent to starting at the beginning (or immediately outside)
// the rule.
// In that case, backtrack to the beginning to be able to take advantage of
// logic that
// remaps
// some rules to well-known names for reporting purposes
currentState = startState;
Expand All @@ -238,6 +242,8 @@ public Result process(ATNState currentState, int tokenIndex, RuleContext context
private boolean isReachable(ATNState target, RuleStartState from) {
Deque<ATNState> activeStates = new ArrayDeque<>();
activeStates.add(from);
Set<Integer> visited = new HashSet<>();
visited.add(from.stateNumber);

while (!activeStates.isEmpty()) {
ATNState current = activeStates.pop();
Expand All @@ -250,7 +256,10 @@ private boolean isReachable(ATNState target, RuleStartState from) {
Transition transition = current.transition(i);

if (transition.isEpsilon()) {
activeStates.push(transition.target);
if (!visited.contains(transition.target.stateNumber)) {
activeStates.push(transition.target);
visited.add(transition.target.stateNumber);
}
}
}
}
Expand Down Expand Up @@ -336,13 +345,17 @@ private Set<Integer> process(ParsingState start, int precedence) {
labels.complement(IntervalSet.of(Token.MIN_USER_TOKEN_TYPE, atn.maxTokenType));
}

// Surprisingly, TokenStream (i.e. BufferedTokenStream) may not have loaded all the
// Surprisingly, TokenStream (i.e. BufferedTokenStream) may not have loaded all
// the
// tokens from the
// underlying stream. TokenStream.get() does not force tokens to be buffered -- it just
// underlying stream. TokenStream.get() does not force tokens to be buffered --
// it just
// returns what's
// in the current buffer, or fail with an IndexOutOfBoundsError. Since Antlr decided the
// in the current buffer, or fail with an IndexOutOfBoundsError. Since Antlr
// decided the
// error occurred
// within the current set of buffered tokens, stop when we reach the end of the buffer.
// within the current set of buffered tokens, stop when we reach the end of the
// buffer.
if (labels.contains(currentToken) && tokenIndex < stream.size() - 1) {
activeStates.push(new ParsingState(transition.target, tokenIndex + 1, false, parser));
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.iotdb.db.queryengine.plan.relational.sql.ast;

import org.apache.iotdb.db.queryengine.plan.relational.sql.parser.ParsingException;
import org.apache.iotdb.db.queryengine.plan.relational.sql.parser.SqlParser;

import org.junit.Test;

import java.time.ZoneId;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class SqlParserErrorHandlerTest {

@Test
public void testParseLimitOffsetError() {
String sql = "select * from information_schema.queries offset 48 limit1";
SqlParser sqlParser = new SqlParser();
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future =
executor.submit(
() -> {
try {
sqlParser.createStatement(sql, ZoneId.systemDefault(), null);
} catch (ParsingException e) {
assertTrue(e.getMessage().contains("mismatched input 'limit1'"));
}
});

try {
// The parsing should fail quickly. If it hangs (OOM), this timeout will
// trigger.
future.get(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
fail("Interrupted");
} catch (ExecutionException e) {
// If parsing exception propagates here (which it shouldn't as it's caught in
// the task), handle it
if (e.getCause() instanceof ParsingException) {
assertTrue(e.getMessage().contains("mismatched input 'limit1'"));
} else {
fail("Unexpected exception: " + e.getCause());
}
} catch (TimeoutException e) {
fail("Parsing timed out - potential endless loop/OOM detected");
} finally {
executor.shutdownNow();
}
}
}
Loading