Skip to content

Integrate syntax errors with error report#5371

Draft
ritvibhatt wants to merge 10 commits intoopensearch-project:mainfrom
ritvibhatt:syntax-exception-error-message
Draft

Integrate syntax errors with error report#5371
ritvibhatt wants to merge 10 commits intoopensearch-project:mainfrom
ritvibhatt:syntax-exception-error-message

Conversation

@ritvibhatt
Copy link
Copy Markdown
Contributor

Description

[Describe what this change achieves]

Related Issues

Resolves #[Issue number to be closed when this PR is merged]

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • New functionality has javadoc added.
  • New functionality has a user manual doc added.
  • New PPL command checklist all confirmed.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff or -s.
  • Public documentation issue/PR created.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

PR Reviewer Guide 🔍

(Review updated until commit 3cd01e4)

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 Multiple PR themes

Sub-PR theme: Wrap ANTLR syntax errors in ErrorReport and update call sites

Relevant files:

  • common/src/main/java/org/opensearch/sql/common/antlr/SyntaxAnalysisErrorListener.java
  • common/src/main/java/org/opensearch/sql/common/antlr/SyntaxCheckException.java
  • api/src/main/java/org/opensearch/sql/api/UnifiedQueryPlanner.java
  • async-query-core/src/main/java/org/opensearch/sql/spark/utils/SQLQueryUtils.java
  • protocol/src/main/java/org/opensearch/sql/protocol/response/format/ErrorFormatter.java

Sub-PR theme: Add pluggable syntax error suggestion provider framework

Relevant files:

  • common/src/main/java/org/opensearch/sql/common/antlr/suggestion/SyntaxErrorContext.java
  • common/src/main/java/org/opensearch/sql/common/antlr/suggestion/SyntaxErrorSuggestionProvider.java
  • common/src/main/java/org/opensearch/sql/common/antlr/suggestion/SyntaxErrorSuggestionRegistry.java
  • common/src/main/java/org/opensearch/sql/common/antlr/suggestion/EmptyAggregateSuggestionProvider.java
  • common/src/main/java/org/opensearch/sql/common/antlr/suggestion/ExpectedTokensSuggestionProvider.java
  • common/src/main/java/org/opensearch/sql/common/antlr/suggestion/ShowTablesLikeSuggestionProvider.java
  • common/src/main/java/org/opensearch/sql/common/antlr/suggestion/UnquotedTableNameSuggestionProvider.java
  • sql/src/test/java/org/opensearch/sql/sql/antlr/suggestion/ContextFactory.java
  • sql/src/test/java/org/opensearch/sql/sql/antlr/suggestion/EmptyAggregateSuggestionProviderTest.java
  • sql/src/test/java/org/opensearch/sql/sql/antlr/suggestion/ExpectedTokensSuggestionProviderTest.java
  • sql/src/test/java/org/opensearch/sql/sql/antlr/suggestion/ShowTablesLikeSuggestionProviderTest.java
  • sql/src/test/java/org/opensearch/sql/sql/antlr/suggestion/SyntaxErrorSuggestionRegistryTest.java
  • sql/src/test/java/org/opensearch/sql/sql/antlr/suggestion/UnquotedTableNameSuggestionProviderTest.java

⚡ Recommended focus areas for review

Global Mutable State

The PROVIDERS list is a static mutable field. The static initializer registers default providers, and the public register() method allows adding more at runtime. Tests that call register() (e.g., SyntaxErrorSuggestionRegistryTest) will permanently mutate the shared registry, potentially causing test pollution and non-deterministic behavior across test runs. There is no unregister() or reset mechanism.

private static final CopyOnWriteArrayList<SyntaxErrorSuggestionProvider> PROVIDERS =
    new CopyOnWriteArrayList<>();

static {
  register(
      new EmptyAggregateSuggestionProvider(),
      new ShowTablesLikeSuggestionProvider(),
      new UnquotedTableNameSuggestionProvider(),
      new ExpectedTokensSuggestionProvider());
}

private SyntaxErrorSuggestionRegistry() {}

public static void register(SyntaxErrorSuggestionProvider... providers) {
  PROVIDERS.addAll(Arrays.asList(providers));
  PROVIDERS.sort(Comparator.comparingInt(SyntaxErrorSuggestionProvider::getPriority));
}
Duplicate Logic

The getDetails method in SyntaxAnalysisErrorListener duplicates the expected-token formatting logic already present in ExpectedTokensSuggestionProvider. Both use the same EXPECTED_TOKENS_THRESHOLD (5) and produce nearly identical output strings. This duplication may lead to inconsistencies and maintenance burden.

private static String getDetails(
    Recognizer<?, ?> recognizer, String msg, RecognitionException e) {
  if (e == null) return msg == null ? "" : msg;
  IntervalSet expected = e.getExpectedTokens();
  if (expected == null || expected.size() == 0) return msg == null ? "" : msg;
  List<Integer> types = expected.toList();
  if (types.isEmpty()) return msg == null ? "" : msg;
  Vocabulary vocab = recognizer.getVocabulary();
  List<String> names = new ArrayList<>(EXPECTED_TOKENS_THRESHOLD);
  for (int type : types.subList(0, Math.min(types.size(), EXPECTED_TOKENS_THRESHOLD))) {
    names.add(vocab.getDisplayName(type));
  }
  if (types.size() > EXPECTED_TOKENS_THRESHOLD) {
    return String.format(
        Locale.ROOT,
        "Expecting one of %d possible tokens. Some examples: %s, ...",
        types.size(),
        String.join(", ", names));
  }
  return "Expecting tokens: " + String.join(", ", names);
}
Exception Wrapping

The syntaxError method now throws an ErrorReport (wrapping a SyntaxCheckException) instead of throwing SyntaxCheckException directly. Any existing catch sites that catch SyntaxCheckException but not ErrorReport will silently miss these errors and fall through to generic exception handlers, potentially masking syntax errors as internal server errors. All callers need to be audited.

SyntaxCheckException cause = new SyntaxCheckException(details);
throw ErrorReport.wrap(cause)
    .code(ErrorCode.SYNTAX_ERROR)
    .stage(QueryProcessingStage.ANALYZING)
    .location(LOCATION)
    .details(details)
    .context("offending_token", offendingText)
    .context("line", line)
    .context("column", charPositionInLine)
    .context("query_context", queryContext)
    .context("suggestions", suggestions)
    .build();

Suggestion Swallowed
When SyntaxErrorSuggestionRegistry.findSuggestions() throws an exception, it is silently caught and replaced with an empty list. While the comment says this prevents a 400 turning into a 500, the exception is not logged at all, making it very hard to diagnose bugs in suggestion providers.

Test Pollution

SyntaxErrorSuggestionRegistryTest.lowerPriorityProviderWinsOverHigherPriorityProvider calls SyntaxErrorSuggestionRegistry.register() with stub providers that always return suggestions. Because the registry is a global static list with no cleanup, these stubs remain registered for all subsequent tests, potentially interfering with other tests that rely on the registry's default state.

SyntaxErrorSuggestionRegistry.register(highPrioritySuggestion, lowPrioritySuggestion);

// Provide a context that both will match (both stubs ignore the context).
SyntaxErrorContext ctx = ContextFactory.contextFor("SELECT FROM t");
List<String> suggestions = SyntaxErrorSuggestionRegistry.findSuggestions(ctx);

assertEquals("low-wins", suggestions.get(0));

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

PR Code Suggestions ✨

Latest suggestions up to 3cd01e4

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix race condition in provider registration

CopyOnWriteArrayList.sort() replaces the internal array atomically, but between
addAll and sort there is a window where the list contains unsorted providers. Under
concurrent access, a findSuggestions call in that window may use an incorrectly
ordered list. Use a synchronized block or replace with a sorted structure to make
registration atomic.

common/src/main/java/org/opensearch/sql/common/antlr/suggestion/SyntaxErrorSuggestionRegistry.java [29-32]

 public static void register(SyntaxErrorSuggestionProvider... providers) {
-  PROVIDERS.addAll(Arrays.asList(providers));
-  PROVIDERS.sort(Comparator.comparingInt(SyntaxErrorSuggestionProvider::getPriority));
+  synchronized (PROVIDERS) {
+    PROVIDERS.addAll(Arrays.asList(providers));
+    PROVIDERS.sort(Comparator.comparingInt(SyntaxErrorSuggestionProvider::getPriority));
+  }
 }
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies a real race condition window between addAll and sort on a CopyOnWriteArrayList. However, since CopyOnWriteArrayList.sort() is itself atomic (replaces the array), the window is narrow and only affects ordering during concurrent registration, which is unlikely in practice. The fix is valid but the impact is low in typical usage.

Low
Guard against null query text from token stream

tokens.getText() returns the full text of all tokens in the stream, which may be
null if the stream has not been fully consumed. If query is null,
truncateQueryAtOffendingToken and the String.format call will throw a
NullPointerException, masking the original syntax error. Add a null-safe fallback
for query.

common/src/main/java/org/opensearch/sql/common/antlr/SyntaxAnalysisErrorListener.java [47-48]

 String offendingText = offendingToken != null ? offendingToken.getText() : "<unknown>";
+String query = tokens.getText();
+if (query == null) query = "";
 String queryContext = truncateQueryAtOffendingToken(query, offendingToken);
Suggestion importance[1-10]: 3

__

Why: The suggestion addresses a potential NullPointerException if tokens.getText() returns null, but the improved_code introduces a re-declaration of query that conflicts with the existing query variable already declared at line 45 in the PR diff. The fix logic is sound but the improved_code is not accurately reflecting the existing code structure.

Low
General
Prevent shared static registry pollution between tests

The test registers additional providers into the shared static PROVIDERS list
without any cleanup, which will pollute the registry for all subsequent tests in the
same JVM run. The stub providers always return suggestions, so they will interfere
with other tests that rely on the registry's default behavior. The test should save
and restore the registry state, or use a dedicated test-scoped registry instance.

sql/src/test/java/org/opensearch/sql/sql/antlr/suggestion/SyntaxErrorSuggestionRegistryTest.java [23]

+// Use a local registry or ensure cleanup after the test to avoid polluting the shared static state.
+// For example, track the registered stubs and remove them in an @AfterEach, or refactor
+// SyntaxErrorSuggestionRegistry to support a test-scoped instance.
 SyntaxErrorSuggestionRegistry.register(highPrioritySuggestion, lowPrioritySuggestion);
+// ... test assertions ...
+// After test: remove the registered stubs from PROVIDERS (requires an unregister API).
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that registering stub providers into the shared static PROVIDERS list without cleanup will pollute the registry for subsequent tests. However, the improved_code only adds comments rather than implementing an actual fix, making it not actionable as written.

Low
Handle negative stop index for synthetic tokens

offendingToken.getStopIndex() can return -1 for synthetic tokens (e.g. EOF), making
end equal to 0. This causes query.substring(0) to return the entire query instead of
an empty string, which is misleading. Add a guard for negative stopIndex values.

common/src/main/java/org/opensearch/sql/common/antlr/suggestion/SyntaxErrorContext.java [31-35]

 public String getRemainingQuery() {
   if (offendingToken == null) return "";
-  int end = offendingToken.getStopIndex() + 1;
+  int stopIndex = offendingToken.getStopIndex();
+  if (stopIndex < 0) return query;
+  int end = stopIndex + 1;
   return end >= query.length() ? "" : query.substring(end);
 }
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies that getStopIndex() can return -1 for synthetic tokens like EOF, which would cause end = 0 and return the full query unexpectedly. The fix is valid and the improved_code accurately reflects the change, though the impact is minor since getRemainingQuery() is a utility method.

Low

Previous suggestions

Suggestions up to commit bd46b1e
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix thread-safety of static mutable provider list

The PROVIDERS list is a mutable static field that can be modified concurrently by
multiple threads calling register() or findSuggestions(). This is a thread-safety
issue. Use a CopyOnWriteArrayList or synchronize access to prevent race conditions
in concurrent environments.

common/src/main/java/org/opensearch/sql/common/antlr/suggestion/SyntaxErrorSuggestionRegistry.java [15]

-private static final List<SyntaxErrorSuggestionProvider> PROVIDERS = new ArrayList<>();
+private static final List<SyntaxErrorSuggestionProvider> PROVIDERS = new java.util.concurrent.CopyOnWriteArrayList<>();
Suggestion importance[1-10]: 5

__

Why: The PROVIDERS list is modified via register() and read via findSuggestions(), which could cause race conditions in concurrent environments. Using CopyOnWriteArrayList is a valid fix, though the sort() call in register() would still need synchronization since CopyOnWriteArrayList doesn't support in-place sorting atomically.

Low
Guard against null query string causing NPE

The query variable is obtained from tokens.getText(), which may return null if the
token stream has no text. Passing a null query to truncateQueryAtOffendingToken will
cause a NullPointerException when calling query.length() or query.substring(...).
Add a null check for query before using it.

common/src/main/java/org/opensearch/sql/common/antlr/SyntaxAnalysisErrorListener.java [47-48]

 String offendingText = offendingToken != null ? offendingToken.getText() : "<unknown>";
-String queryContext = truncateQueryAtOffendingToken(query, offendingToken);
+String safeQuery = query != null ? query : "";
+String queryContext = truncateQueryAtOffendingToken(safeQuery, offendingToken);
Suggestion importance[1-10]: 4

__

Why: The query obtained from tokens.getText() could theoretically be null, and passing it to truncateQueryAtOffendingToken without a null check could cause an NPE. The fix is straightforward and defensive, though in practice ANTLR's CommonTokenStream.getText() rarely returns null.

Low
Guard against null query in remaining query extraction

The getRemainingQuery method accesses query directly without a null check. If query
is null (which can happen if tokens.getText() returns null), calling query.length()
will throw a NullPointerException. Add a null guard for query.

common/src/main/java/org/opensearch/sql/common/antlr/suggestion/SyntaxErrorContext.java [31-35]

 public String getRemainingQuery() {
-  if (offendingToken == null) return "";
+  if (offendingToken == null || query == null) return "";
   int end = offendingToken.getStopIndex() + 1;
   return end >= query.length() ? "" : query.substring(end);
 }
Suggestion importance[1-10]: 3

__

Why: The query field in SyntaxErrorContext is passed via constructor and could be null if the caller passes null. Adding a null guard is a minor defensive improvement, but since query is a @RequiredArgsConstructor field, callers should ensure it's non-null.

Low
General
Prevent false positives in FROM clause detection

The followsFromClause method scans backwards from the offending token but returns
true for any FROM keyword found anywhere before the offending token, even if there
are other non-whitespace tokens between FROM and the offending position. This can
produce false positives. The scan should stop as soon as it encounters a non-hidden,
non-identifier token that isn't FROM.

common/src/main/java/org/opensearch/sql/common/antlr/suggestion/UnquotedTableNameSuggestionProvider.java [29-39]

 private static boolean followsFromClause(SyntaxErrorContext ctx) {
   Token offending = ctx.getOffendingToken();
   if (offending == null) return false;
   List<Token> all = ctx.getAllTokens();
   for (int i = offending.getTokenIndex() - 1; i >= 0; i--) {
     Token t = all.get(i);
     if (t.getChannel() != Token.DEFAULT_CHANNEL) continue;
     if ("from".equalsIgnoreCase(t.getText())) return true;
+    // Stop at the first non-hidden token that is not an identifier (table name)
+    if (!t.getText().matches("[A-Za-z0-9_`.]+")) return false;
   }
   return false;
 }
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies that the backward scan could produce false positives by matching any FROM keyword before the offending token. The improved code adds a stop condition for non-identifier tokens, which reduces false positives, though the regex pattern used may need tuning for edge cases.

Low
Suggestions up to commit 71ad3bb
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix incorrect expected exception type in test

ErrorReport is not a RuntimeException subclass and is likely not thrown directly by
the parser. The parser still throws SyntaxCheckException, so the test should expect
SyntaxCheckException.class unless ErrorReport is explicitly thrown or wraps it.
Using ErrorReport.class here will cause the test to fail or pass for the wrong
reason.

api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java [114-117]

-@Test(expected = ErrorReport.class)
+@Test(expected = SyntaxCheckException.class)
 public void testPlanPropagatingSyntaxCheckException() {
   planner.plan("source = catalog.employees | eval"); // Trigger syntax error from parser
 }
Suggestion importance[1-10]: 7

__

Why: The test now expects ErrorReport.class instead of SyntaxCheckException.class, but without seeing the ErrorReport class definition and how the planner wraps exceptions, it's unclear if ErrorReport is actually thrown. If ErrorReport is not a RuntimeException or is not thrown by the planner, the test would fail or pass incorrectly. This is a potentially significant correctness issue.

Medium
General
Guard against null query string

The null check for offendingToken before setting token start/end is correct, but
getOffendingText and truncateQueryAtOffendingToken are called before this check and
already handle null. However, errorContext is derived from
truncateQueryAtOffendingToken(query, offendingToken) which may return a substring of
query even when offendingToken is null — but query itself could be null if
tokens.getText() returns null. Consider adding a null/empty guard for query to
prevent a NullPointerException.

common/src/main/java/org/opensearch/sql/common/antlr/SyntaxAnalysisErrorListener.java [38-65]

-if (offendingToken != null) {
-  syntaxException.setTokenStart(offendingToken.getStartIndex());
-  syntaxException.setTokenEnd(offendingToken.getStopIndex());
-}
+String query = tokens.getText();
+if (query == null) query = "";
 
+String offendingText = getOffendingText(offendingToken);
+String errorContext = truncateQueryAtOffendingToken(query, offendingToken);
+String details = getDetails(recognizer, msg, e);
+
Suggestion importance[1-10]: 4

__

Why: Adding a null guard for query is a defensive improvement, but CommonTokenStream.getText() rarely returns null in practice, making this a low-priority edge case. The improved_code also only shows a partial snippet that doesn't fully reflect the existing code structure.

Low
Suggestions up to commit 8e8ab9e
CategorySuggestion                                                                                                                                    Impact
Possible issue
Guard against null offending symbol cast

The tokens.getText() call and (Token) offendingSymbol cast happen before null
checks, but offendingSymbol could be null (ANTLR passes null in some error
scenarios). The cast (Token) offendingSymbol will throw a NullPointerException or
ClassCastException before the null-safe getOffendingText method is even called. Move
the cast inside a null check or guard it before use.

common/src/main/java/org/opensearch/sql/common/antlr/SyntaxAnalysisErrorListener.java [45-48]

+Token offendingToken = offendingSymbol instanceof Token ? (Token) offendingSymbol : null;
 String offendingText = getOffendingText(offendingToken);
 String errorContext = truncateQueryAtOffendingToken(query, offendingToken);
 String details = getDetails(recognizer, msg, e);
 int expectedTokenCount = getExpectedTokenCount(recognizer, e);
Suggestion importance[1-10]: 7

__

Why: The existing code casts offendingSymbol to Token unconditionally before null checks, which could cause a NullPointerException or ClassCastException in some ANTLR error scenarios. The improved code uses instanceof for a safe cast, which is a valid defensive fix.

Medium
General
Narrow fallback condition for ErrorReport instances

Since ErrorReport now wraps SyntaxCheckException, checking e instanceof
SyntaxCheckException may never be true if all syntax errors are now thrown as
ErrorReport. However, more critically, any ErrorReport will now fall back to the
legacy handler regardless of its error code or type — including non-syntax
ErrorReport instances that should not fall back. Consider checking the error code or
cause type before falling back.

legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSQLQueryAction.java [135]

-if (e instanceof SyntaxCheckException || e instanceof UnsupportedCursorRequestException || e instanceof ErrorReport) {
+if (e instanceof SyntaxCheckException
+    || e instanceof UnsupportedCursorRequestException
+    || (e instanceof ErrorReport && ((ErrorReport) e).getCause() instanceof SyntaxCheckException)) {
Suggestion importance[1-10]: 6

__

Why: The current code falls back to the legacy handler for any ErrorReport, regardless of its error code or cause, which could incorrectly route non-syntax errors to the legacy handler. Narrowing the condition to only ErrorReport instances wrapping a SyntaxCheckException is a valid concern, though the impact depends on what other ErrorReport types may be thrown.

Low
Remove potentially dead exception catch clause

Since ErrorReport is now thrown instead of SyntaxCheckException from the parser,
catching SyntaxCheckException here may be dead code. Additionally, if ErrorReport
extends RuntimeException, it would already bypass the catch (Exception e) re-wrap
block, making the explicit catch redundant. Verify whether SyntaxCheckException can
still be thrown directly, and remove it if not, to avoid misleading code.

api/src/main/java/org/opensearch/sql/api/UnifiedQueryPlanner.java [65-66]

-} catch (SyntaxCheckException | UnsupportedOperationException | ErrorReport e) {
+} catch (UnsupportedOperationException | ErrorReport e) {
   throw e;
 }
Suggestion importance[1-10]: 4

__

Why: Since the parser now throws ErrorReport instead of SyntaxCheckException, catching SyntaxCheckException may be dead code. However, it's possible that SyntaxCheckException is still thrown from other code paths, so removing it requires careful verification of all callers.

Low
Suggestions up to commit 575202d
CategorySuggestion                                                                                                                                    Impact
Possible issue
Guard token cast against null or wrong type

The offendingToken cast from offendingSymbol is done before null-checking, but
offendingSymbol could be null in some ANTLR error scenarios. The cast (Token)
offendingSymbol should be guarded with a null check to avoid a NullPointerException
or ClassCastException before the null-safe methods are called.

common/src/main/java/org/opensearch/sql/common/antlr/SyntaxAnalysisErrorListener.java [45-48]

+Token offendingToken = offendingSymbol instanceof Token ? (Token) offendingSymbol : null;
 String offendingText = getOffendingText(offendingToken);
 String errorContext = truncateQueryAtOffendingToken(query, offendingToken);
 String details = getDetails(recognizer, msg, e);
 int expectedTokenCount = getExpectedTokenCount(recognizer, e);
Suggestion importance[1-10]: 7

__

Why: The cast (Token) offendingSymbol on line 42 could throw a ClassCastException if offendingSymbol is null or not a Token. The PR already adds null-safe handling in helper methods, but the cast itself is unguarded. This is a valid defensive improvement.

Medium
Narrow fallback condition to syntax-related errors only

This condition will now fall back for ALL ErrorReport exceptions, not just those
wrapping syntax errors. ErrorReport may be thrown for other error types (e.g.,
semantic errors, execution errors), which should not trigger a fallback to the
legacy handler. Consider checking whether the ErrorReport wraps a
SyntaxCheckException specifically.

legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSQLQueryAction.java [135]

-if (e instanceof SyntaxCheckException || e instanceof UnsupportedCursorRequestException || e instanceof ErrorReport) {
+if (e instanceof SyntaxCheckException || e instanceof UnsupportedCursorRequestException
+    || (e instanceof ErrorReport && ((ErrorReport) e).getCause() instanceof SyntaxCheckException)) {
Suggestion importance[1-10]: 7

__

Why: Catching all ErrorReport instances for fallback is overly broad and could route non-syntax errors (e.g., execution errors) to the legacy handler. Restricting to ErrorReport wrapping SyntaxCheckException is more precise and avoids unintended behavior.

Medium
Avoid silently swallowing non-syntax ErrorReport exceptions

Catching all ErrorReport exceptions here will suppress non-syntax errors (e.g.,
internal errors wrapped in ErrorReport) and silently return false, masking real
problems. Only ErrorReport instances that wrap a SyntaxCheckException should be
treated as "not a Flint extension query".

async-query-core/src/main/java/org/opensearch/sql/spark/utils/SQLQueryUtils.java [89-91]

-} catch (SyntaxCheckException | ErrorReport syntaxCheckException) {
+} catch (SyntaxCheckException syntaxCheckException) {
   return false;
+} catch (ErrorReport errorReport) {
+  if (errorReport.getCause() instanceof SyntaxCheckException) {
+    return false;
+  }
+  throw errorReport;
 }
Suggestion importance[1-10]: 7

__

Why: Catching all ErrorReport exceptions and returning false could mask non-syntax errors, making debugging harder. Re-throwing ErrorReport instances that don't wrap a SyntaxCheckException is a more correct approach.

Medium
Suggestions up to commit f7cbe56
CategorySuggestion                                                                                                                                    Impact
General
Remove potentially dead exception type check

Since ErrorReport now wraps SyntaxCheckException, checking e instanceof
SyntaxCheckException may never be true if all syntax errors are thrown as
ErrorReport. The SyntaxCheckException check could become dead code. Consider
removing the redundant SyntaxCheckException check, or verify that
SyntaxCheckException can still be thrown directly elsewhere in the call chain.

legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSQLQueryAction.java [135]

-if (e instanceof SyntaxCheckException || e instanceof UnsupportedCursorRequestException || e instanceof ErrorReport) {
+if (e instanceof UnsupportedCursorRequestException || e instanceof ErrorReport) {
Suggestion importance[1-10]: 5

__

Why: Since SyntaxAnalysisErrorListener now throws ErrorReport instead of SyntaxCheckException, the SyntaxCheckException check in this catch may indeed be dead code. However, SyntaxCheckException could still be thrown from other parts of the call chain not modified in this PR, so removing it requires careful verification.

Low
Remove unreachable exception type from catch clause

Since SyntaxCheckException is now wrapped inside ErrorReport and thrown as
ErrorReport, the SyntaxCheckException branch in this catch may never be reached.
This could cause syntax errors to fall through to the generic Exception handler and
be wrapped in IllegalStateException, losing the structured error information. Verify
whether SyntaxCheckException can still be thrown directly, and if not, remove it
from the multi-catch.

api/src/main/java/org/opensearch/sql/api/UnifiedQueryPlanner.java [65-68]

-} catch (SyntaxCheckException | UnsupportedOperationException | ErrorReport e) {
+} catch (UnsupportedOperationException | ErrorReport e) {
   throw e;
 } catch (Exception e) {
   throw new IllegalStateException("Failed to plan query", e);
 }
Suggestion importance[1-10]: 5

__

Why: Similar to suggestion 2, since SyntaxAnalysisErrorListener now throws ErrorReport wrapping SyntaxCheckException, the SyntaxCheckException branch may be unreachable. However, other parts of the codebase may still throw SyntaxCheckException directly, so this needs verification before removal.

Low
Possible issue
Guard against negative stop index for EOF tokens

The truncateQueryAtOffendingToken method uses offendingToken.getStopIndex() + 1 to
substring the query, but if offendingToken.getStopIndex() returns -1 (which can
happen for EOF tokens), this will produce an incorrect substring. Add a guard to
handle this edge case before calling the method.

common/src/main/java/org/opensearch/sql/common/antlr/SyntaxAnalysisErrorListener.java [45-48]

 String offendingText = getOffendingText(offendingToken);
-String errorContext = truncateQueryAtOffendingToken(query, offendingToken);
+String errorContext = (offendingToken != null && offendingToken.getStopIndex() >= 0)
+    ? truncateQueryAtOffendingToken(query, offendingToken)
+    : (query.length() > CONTEXT_TRUNCATION_THRESHOLD
+        ? "..." + query.substring(query.length() - CONTEXT_TRUNCATION_THRESHOLD)
+        : query);
 String details = getDetails(recognizer, msg, e);
 int expectedTokenCount = getExpectedTokenCount(recognizer, e);
Suggestion importance[1-10]: 4

__

Why: The suggestion raises a valid edge case concern about EOF tokens with getStopIndex() == -1, but the truncateQueryAtOffendingToken method already handles the offendingToken == null case. The EOF token edge case is a real but minor concern, and the improved code duplicates logic already present in truncateQueryAtOffendingToken.

Low

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 14475e5

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit c9ad1f2

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit e473f7f

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit f7cbe56

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 575202d

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 8e8ab9e

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 71ad3bb

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit bd46b1e

Signed-off-by: Ritvi Bhatt <ribhatt@amazon.com>
@github-actions
Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 3cd01e4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant