Skip to content

Comments

[Feature] Add grammar bundle generation API for PPL language features#5162

Open
mengweieric wants to merge 13 commits intoopensearch-project:mainfrom
mengweieric:feature/grammar-bundle
Open

[Feature] Add grammar bundle generation API for PPL language features#5162
mengweieric wants to merge 13 commits intoopensearch-project:mainfrom
mengweieric:feature/grammar-bundle

Conversation

@mengweieric
Copy link
Collaborator

@mengweieric mengweieric commented Feb 20, 2026

Description

Implements the backend grammar metadata API for PPL autocomplete support.

This endpoint serves a versioned grammar bundle containing all ANTLR metadata required for downstream consumers (e.g. OpenSearch Dashboards) to reconstruct a functional PPL lexer and parser at runtime using antlr4ng's interpreter APIs. This enables full client-side parsing and autocomplete with zero per-keystroke server calls, with the backend as the single source of truth for the PPL grammar.

What the bundle contains:

  • Serialized lexer and parser ATNs via ATNSerializer.serialize().toArray(), compatible with antlr4ng's ATNDeserializer.deserialize()
  • Token vocabulary (literal and symbolic names), rule names, channel names, and mode names for LexerInterpreter/ParserInterpreter reconstruction
  • grammarHash (SHA-256 of ATN data + ANTLR version) for client-side change detection
  • bundleVersion and antlrVersion for compatibility validation

Backend behavior:

  • Lazy initialization with double-checked locking — bundle is built on first request and cached in a volatile field for the node lifecycle
  • Deterministic output — all nodes running the same plugin JAR produce identical bundles
  • Marked @experimentalapi as the response schema may evolve

Also included:

  • THIRD-PARTY updated to reflect ANTLR 4.13.2

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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

Warning

Rate limit exceeded

@mengweieric has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between fe41aea and a14b255.

📒 Files selected for processing (1)
  • plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java
📝 Walkthrough

Walkthrough

Adds GET /_plugins/_ppl/_grammar to serve serialized ANTLR grammar metadata with a cached GrammarBundle; introduces GrammarBundle model, PPLGrammarBundleBuilder, RestPPLGrammarAction handler, unit tests, and updates ANTLR third‑party metadata to 4.13.2.

Changes

Cohort / File(s) Summary
Third‑party notices
THIRD-PARTY
Updated ANTLR version from 4.7.1 to 4.13.2 and copyright year range from 2012-2017 to 2012-2024 in license headers.
Plugin registration
plugin/src/main/java/org/opensearch/sql/plugin/SQLPlugin.java
Imported and registered RestPPLGrammarAction to expose the new PPL grammar endpoint.
REST handler
plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java
New REST handler serving GET /_plugins/_ppl/_grammar with lazy, thread‑safe caching (double‑checked locking), JSON serialization, error handling (500), and testing hooks (buildBundle, invalidateCache).
REST handler tests
plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java
Unit tests for route/name, successful response structure, caching behavior, cache invalidation, and error path (500). Includes MockRestChannel.
Grammar data model
ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/GrammarBundle.java
New immutable Lombok-annotated container for serialized ANTLR grammar data (versions, grammarHash, lexer/parser ATNs, rule names, channels, modes, vocabulary).
Bundle builder
ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilder.java
New builder that inspects ANTLR lexer/parser, serializes ATNs, extracts names, computes SHA-256 grammar hash, and constructs GrammarBundle.
Builder tests
ppl/src/test/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilderTest.java
Tests validating bundle contents, deterministic builds, hash format, and non‑empty ATNs/names.
Manifest
pom.xml
Minor manifest change (dependency/metadata related to the above updates).

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as RestPPLGrammarAction
    participant Cache
    participant Builder as PPLGrammarBundleBuilder
    participant Serializer as XContentBuilder

    Client->>Handler: GET /_plugins/_ppl/_grammar
    Handler->>Cache: check cached bundle
    alt cache hit
        Cache-->>Handler: return Bundle
    else cache miss
        Handler->>Builder: buildBundle()
        Builder->>Builder: inspect lexer/parser, serialize ATNs, compute hash
        Builder-->>Handler: Bundle
        Handler->>Cache: store Bundle
    end
    Handler->>Serializer: serialize Bundle to JSON
    Serializer-->>Handler: JSON payload
    Handler-->>Client: HTTP 200 + JSON
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • kavithacm
  • ps48
  • penghuo
  • derek-ho
  • joshuali925
  • GumpacG
  • Swiddis
  • dai-chen
  • vamsimanohar
  • anirudha
  • LantaoJin
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a grammar bundle generation API for PPL language features, matching the primary functionality introduced across the new classes and tests.
Description check ✅ Passed The description is directly related to the changeset, providing detailed context about the grammar bundle API, its contents, backend behavior, and rationale for supporting PPL autocomplete.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mengweieric mengweieric changed the title Feature/grammar bundle [Feature] Add grammar bundle generation for PPL autocomplete features Feb 20, 2026
@mengweieric mengweieric force-pushed the feature/grammar-bundle branch from d288392 to eabe8ec Compare February 20, 2026 23:00
@mengweieric mengweieric added PPL Piped processing language feature labels Feb 20, 2026
@mengweieric mengweieric changed the title [Feature] Add grammar bundle generation for PPL autocomplete features [Feature] Add grammar bundle generation API for PPL language features Feb 20, 2026
@mengweieric mengweieric force-pushed the feature/grammar-bundle branch from eabe8ec to eb12632 Compare February 22, 2026 21:25
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
@mengweieric mengweieric force-pushed the feature/grammar-bundle branch from 73883a5 to 3f36846 Compare February 23, 2026 06:46
…y, and tests

- Hash full 32-bit ints in grammarHash to avoid collisions with ANTLR 4.13.2 ATN serialization
- Use RuntimeMetaData.getRuntimeVersion() instead of unreliable JAR manifest lookup
- Make GrammarBundle immutable with @value instead of @DaTa
- Update THIRD-PARTY to reflect ANTLR 4.13.2
- Harden tests with JSON parsing and add antlrVersion assertion

Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
@mengweieric mengweieric force-pushed the feature/grammar-bundle branch from 3f36846 to c838750 Compare February 23, 2026 06:49
@mengweieric mengweieric marked this pull request as ready for review February 23, 2026 07:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java (1)

87-93: invalidateCache() is public but is a testing-only hook — restrict its visibility.

A public method on a production REST handler that voids the bundle cache can be called inadvertently by any code. Since @VisibleForTesting alone is not a compile-time guard, the visibility should be protected (or package-private) to match buildBundle().

🔧 Proposed fix
-  public void invalidateCache() {
+  protected void invalidateCache() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java`
around lines 87 - 93, The invalidateCache() method is currently public but only
intended for tests; change its visibility to a non-public scope (e.g., protected
or package-private) to prevent external production code from calling it, while
keeping its synchronized body that nulls cachedBundle under bundleLock; this
should mirror the visibility of buildBundle() and retain use of the bundleLock
and cachedBundle fields.
ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/GrammarBundle.java (1)

27-51: @Value does not deep-copy array fields — the cached bundle could be mutated.

Lombok's generated constructor stores int[]/String[] references directly. For the long-lived cached singleton this means any caller holding the same array reference can corrupt the bundle. The lexer/parserSerializedATN already arrive as fresh copies from IntegerList.toArray(), but the String[] fields (lexerRuleNames, channelNames, modeNames, parserRuleNames, literalNames, symbolicNames) are references into static arrays of the generated ANTLR classes.

Add @Builder.Default defensive copies, or add a @EqualsAndHashCode.Include note. At minimum, document that fields must be treated as read-only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/GrammarBundle.java`
around lines 27 - 51, The GrammarBundle holds array fields (lexerRuleNames,
channelNames, modeNames, parserRuleNames, literalNames, symbolicNames) that
Lombok's `@Value` constructor stores by reference, allowing external mutation of
the cached singleton; fix by making defensive copies for these String[] fields
(and int[] if not already copied) when constructing the bundle (e.g. in a
`@Builder.Default` initializer or custom constructor/factory used by
GrammarBundle) so each field is Arrays.copyOf(original, original.length) or
wrapped as an unmodifiable List, or otherwise ensure immutability; update the
GrammarBundle construction path (the class and any builder/factory) to perform
the copies and/or add a short Javadoc on the fields if you choose to only
document the read-only contract.
ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilder.java (1)

29-30: Prefer the static ATNSerializer.getSerialized() over the instance constructor.

The ANTLR 4.13.2 API exposes public static IntegerList getSerialized(ATN atn) as the canonical convenience entry point. Using it removes the need to instantiate ATNSerializer manually.

♻️ Proposed refactor
-    int[] lexerATN = new ATNSerializer(lexer.getATN()).serialize().toArray();
-    int[] parserATN = new ATNSerializer(parser.getATN()).serialize().toArray();
+    int[] lexerATN = ATNSerializer.getSerialized(lexer.getATN()).toArray();
+    int[] parserATN = ATNSerializer.getSerialized(parser.getATN()).toArray();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilder.java`
around lines 29 - 30, Update PPLGrammarBundleBuilder to use the static
convenience method instead of constructing ATNSerializer instances: replace the
two lines that call new ATNSerializer(lexer.getATN()).serialize().toArray() and
new ATNSerializer(parser.getATN()).serialize().toArray() with calls to
ATNSerializer.getSerialized(lexer.getATN()).toArray() and
ATNSerializer.getSerialized(parser.getATN()).toArray() respectively so the
serialized ATNs are obtained via the static API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java`:
- Around line 25-29: The class-level comment for RestPPLGrammarAction is using a
block comment (/* ... */) so the `@opensearch.experimental` tag is not emitted as
Javadoc; change the top-of-class comment to a Javadoc comment (/** ... */) so
the `@opensearch.experimental` annotation is processed correctly — locate the
comment immediately above the RestPPLGrammarAction class declaration and replace
the opening /* with /** (keeping the existing text and tag intact).

In
`@plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java`:
- Around line 242-245: The test's override of bytesOutput() in
RestPPLGrammarAction returns null which can cause a NullPointerException if any
code (e.g., BaseRestHandler or test logic) calls channel.bytesOutput(); replace
the null return with a real BytesStreamOutput instance (e.g., new
BytesStreamOutput()) so channel.bytesOutput() is safe to use; update the
bytesOutput() method in RestPPLGrammarAction to return a non-null
BytesStreamOutput to prevent latent NPEs.

In
`@ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilder.java`:
- Around line 33-39: The code writes vocabulary.getLiteralName(i) /
vocabulary.getSymbolicName(i) into arrays that will contain null entries for
tokens without a literal/symbolic form; update the contract by adding
documentation on GrammarBundle.literalNames and GrammarBundle.symbolicNames (or
a Javadoc comment on the class/fields) stating that array elements may be null
and clients must handle sparse arrays, and reference the behavior coming from
PPLGrammarBundleBuilder (use the symbols vocabulary.getLiteralName,
vocabulary.getSymbolicName, literalNames, symbolicNames, and the
GrammarBundle.literalNames/GrammarBundle.symbolicNames fields to locate where to
document).

In
`@ppl/src/test/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilderTest.java`:
- Around line 24-107: Rename the test methods in PPLGrammarBundleBuilderTest so
each follows the test<Functionality><Condition> convention (prefix with "test");
e.g., rename bundleIsNotNull -> testBundleIsNotNull, bundleVersionIsSet ->
testBundleVersionIsSet, antlrVersionIsSet -> testAntlrVersionIsSet,
grammarHashHasExpectedFormat -> testGrammarHashHasExpectedFormat,
startRuleIndexIsZero -> testStartRuleIndexIsZero, lexerATNIsNonEmpty ->
testLexerATNIsNonEmpty, parserATNIsNonEmpty -> testParserATNIsNonEmpty,
lexerRuleNamesAreNonEmpty -> testLexerRuleNamesAreNonEmpty,
parserRuleNamesAreNonEmpty -> testParserRuleNamesAreNonEmpty,
channelNamesAreNonEmpty -> testChannelNamesAreNonEmpty, modeNamesAreNonEmpty ->
testModeNamesAreNonEmpty, vocabularyIsNonEmpty -> testVocabularyIsNonEmpty,
buildIsDeterministic -> testBuildIsDeterministic; update any references to these
methods (e.g., test runner annotations or logs) so PPLGrammarBundleBuilderTest
and references to bundle/second/GrammarBundle remain consistent.

---

Nitpick comments:
In
`@plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java`:
- Around line 87-93: The invalidateCache() method is currently public but only
intended for tests; change its visibility to a non-public scope (e.g., protected
or package-private) to prevent external production code from calling it, while
keeping its synchronized body that nulls cachedBundle under bundleLock; this
should mirror the visibility of buildBundle() and retain use of the bundleLock
and cachedBundle fields.

In `@ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/GrammarBundle.java`:
- Around line 27-51: The GrammarBundle holds array fields (lexerRuleNames,
channelNames, modeNames, parserRuleNames, literalNames, symbolicNames) that
Lombok's `@Value` constructor stores by reference, allowing external mutation of
the cached singleton; fix by making defensive copies for these String[] fields
(and int[] if not already copied) when constructing the bundle (e.g. in a
`@Builder.Default` initializer or custom constructor/factory used by
GrammarBundle) so each field is Arrays.copyOf(original, original.length) or
wrapped as an unmodifiable List, or otherwise ensure immutability; update the
GrammarBundle construction path (the class and any builder/factory) to perform
the copies and/or add a short Javadoc on the fields if you choose to only
document the read-only contract.

In
`@ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilder.java`:
- Around line 29-30: Update PPLGrammarBundleBuilder to use the static
convenience method instead of constructing ATNSerializer instances: replace the
two lines that call new ATNSerializer(lexer.getATN()).serialize().toArray() and
new ATNSerializer(parser.getATN()).serialize().toArray() with calls to
ATNSerializer.getSerialized(lexer.getATN()).toArray() and
ATNSerializer.getSerialized(parser.getATN()).toArray() respectively so the
serialized ATNs are obtained via the static API.

- Assert ATN serialization version 4 for both lexer and parser to enforce
  antlr4ng compatibility contract
- Resolve startRuleIndex by looking up "root" rule name instead of hardcoding 0
- Fix MockRestChannel.bytesOutput() to return real BytesStreamOutput
- Document nullable elements in literalNames/symbolicNames Javadoc
- Rename test methods to follow testXxx() convention per ppl/plugin modules

Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
@mengweieric mengweieric force-pushed the feature/grammar-bundle branch from fd3529d to fe41aea Compare February 23, 2026 07:29
@mengweieric mengweieric self-assigned this Feb 23, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java (1)

87-93: @VisibleForTesting + public is contradictory — use protected.

public makes invalidateCache() part of the effective API surface, allowing any production code holding a RestPPLGrammarAction reference to force cache invalidation and trigger an expensive PPLGrammarBundleBuilder.build() on the next request. buildBundle() correctly uses protected for the same testing purpose — invalidateCache() should match.

♻️ Proposed fix
-  public void invalidateCache() {
+  protected void invalidateCache() {
     synchronized (bundleLock) {
       cachedBundle = null;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java`
around lines 87 - 93, Change the visibility of invalidateCache() from public to
protected to match the testing-only intent indicated by `@VisibleForTesting` and
to avoid exposing cache invalidation in the public API; locate the method named
invalidateCache in class RestPPLGrammarAction and change its modifier to
protected, preserving the synchronized(bundleLock) block and the existing
assignment cachedBundle = null so tests can still call it (and buildBundle()
remains protected).
ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c838750 and fd3529d.

📒 Files selected for processing (1)
  • plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java`:
- Line 62: The method visibility for invalidateCache in RestPPLGrammarAction
should be narrowed from public to protected since it is only exposed for tests;
update the method signature to protected while keeping the `@VisibleForTesting`
annotation and the synchronized (bundleLock) { cachedBundle = null; } body
intact so RestPPLGrammarActionTest can still call it but it is no longer part of
the public API surface.

---

Nitpick comments:
In
`@plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java`:
- Around line 87-93: Change the visibility of invalidateCache() from public to
protected to match the testing-only intent indicated by `@VisibleForTesting` and
to avoid exposing cache invalidation in the public API; locate the method named
invalidateCache in class RestPPLGrammarAction and change its modifier to
protected, preserving the synchronized(bundleLock) block and the existing
assignment cachedBundle = null so tests can still call it (and buildBundle()
remains protected).

channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder));
} catch (Exception e) {
log.error("Error building or serializing PPL grammar", e);
channel.sendResponse(new BytesRestResponse(channel, RestStatus.INTERNAL_SERVER_ERROR, e));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Search for all BytesRestResponse usages in the Java codebase
echo "=== BytesRestResponse usages in the codebase ==="
rg -n --type java 'new BytesRestResponse' -C 2

echo ""
echo "=== Build configuration files ==="
# 2. Find build configuration to identify OpenSearch version
fd -e gradle -e xml | head -10

echo ""
echo "=== OpenSearch dependency declaration ==="
# 3. Check for OpenSearch version in gradle files
rg -n 'opensearch|OpenSearch' --type gradle | grep -i 'version\|dependencies' | head -20

Repository: opensearch-project/sql

Length of output: 36203


🏁 Script executed:

#!/bin/bash
# Find OpenSearch version in gradle.properties or build.gradle
echo "=== OpenSearch version ==="
fd 'gradle.properties' -o build.gradle | xargs grep -h 'opensearch_version' | head -5

echo ""
echo "=== Identifying which constructor forms are used in the codebase ==="
# Find patterns of BytesRestResponse(channel, RestStatus, Exception)
rg 'new BytesRestResponse\(channel\s*,' --type java -A 1 | head -20

echo ""
# Compare with 2-arg form BytesRestResponse(channel, e)
echo "=== 2-arg form with channel and exception ==="
rg 'new BytesRestResponse\(channel\s*,\s*[a-zA-Z]' --type java | grep -v RestStatus | head -10

Repository: opensearch-project/sql

Length of output: 1865


🏁 Script executed:

#!/bin/bash
# Find gradle.properties properly
echo "=== Find gradle.properties ==="
find . -maxdepth 2 -name 'gradle.properties' -type f

echo ""
echo "=== Check for OpenSearch version in root gradle.properties ==="
cat gradle.properties 2>/dev/null | grep -i opensearch | head -10

echo ""
echo "=== Check imports and actual usages of BytesRestResponse in RestPPLGrammarAction ==="
head -30 plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java

echo ""
echo "=== Check the exact line 62 and surrounding context ==="
sed -n '50,65p' plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java

Repository: opensearch-project/sql

Length of output: 1916


🏁 Script executed:

#!/bin/bash
# Get the full gradle.properties content to find OpenSearch version
echo "=== Full gradle.properties ==="
cat gradle.properties

echo ""
echo "=== Check if there's version info in the main build.gradle ==="
head -50 build.gradle | grep -i version

Repository: opensearch-project/sql

Length of output: 1158


🌐 Web query:

OpenSearch 3.6.0 BytesRestResponse constructor signature

💡 Result:

OpenSearch core doesn’t have a 3.6.0 release yet (it’s scheduled for Mar 24–Apr 7, 2026), so there’s no 3.6.0 BytesRestResponse to reference. [2] The latest released 3.x core version shown in the docs is 3.4.0. [3]

In OpenSearch 3.4.0, org.opensearch.rest.BytesRestResponse has these constructors: [1]

public BytesRestResponse(RestStatus status, XContentBuilder builder)

public BytesRestResponse(RestStatus status, String content)

public BytesRestResponse(RestStatus status, String contentType, String content)

public BytesRestResponse(RestStatus status, String contentType, byte[] content)

public BytesRestResponse(RestStatus status, String contentType, BytesReference content)

public BytesRestResponse(RestChannel channel, Exception e) throws IOException

public BytesRestResponse(RestChannel channel, RestStatus status, Exception e) throws IOException

Sources: [1] [2] [3]


🏁 Script executed:

#!/bin/bash
echo "=== Check invalidateCache() method definition and visibility ==="
sed -n '87,93p' plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java

echo ""
echo "=== Check if invalidateCache() is called anywhere outside the class ==="
rg 'invalidateCache' --type java -B 2 -A 2

Repository: opensearch-project/sql

Length of output: 1529


Change invalidateCache() visibility from public to protected.

The @VisibleForTesting annotation (line 87) indicates that the method's visibility was widened solely for testing purposes. However, public is the widest possible scope and unnecessarily exposes the method as part of the REST handler's API surface. Since it is only called from tests (RestPPLGrammarActionTest.java), change it to protected:

`@VisibleForTesting`
protected void invalidateCache() {
  synchronized (bundleLock) {
    cachedBundle = null;
  }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/src/main/java/org/opensearch/sql/plugin/rest/RestPPLGrammarAction.java`
at line 62, The method visibility for invalidateCache in RestPPLGrammarAction
should be narrowed from public to protected since it is only exposed for tests;
update the method signature to protected while keeping the `@VisibleForTesting`
annotation and the synchronized (bundleLock) { cachedBundle = null; } body
intact so RestPPLGrammarActionTest can still call it but it is no longer part of
the public API surface.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java (1)

163-182: Consider adding a test for buildBundle() returning null.

The current error path only tests an exception. If buildBundle() returns null (e.g., an implementation that silently returns null instead of throwing), the action's behavior is untested and could produce an NPE or an unexpected response rather than a clean 500. A one-line variant of the failing action covering that path would close the gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java`
around lines 163 - 182, Add a test variant to cover the case where
RestPPLGrammarAction.buildBundle() returns null: create a failingAction subclass
of RestPPLGrammarAction that overrides buildBundle() to return null, invoke
failingAction.handleRequest(...) with the same FakeRestRequest/MockRestChannel
setup used in testGetGrammar_ErrorPath_Returns500, and assert that
channel.getResponse().status() equals RestStatus.INTERNAL_SERVER_ERROR to ensure
a null bundle produces a 500 response rather than an NPE or other status.
ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/GrammarBundle.java (1)

27-57: Shallow immutability: array fields are mutable through getters.

@Value only ensures "no setters are generated and the fields are defined as final so that their references can't be changed" — but the contents of int[] and String[] fields remain fully mutable. Since the PR caches a single GrammarBundle for the entire node lifecycle, a caller that mutates getLexerSerializedATN()[0] = … would silently corrupt the cached bundle for all future requests.

In practice, current consumers only read (serialize) the arrays, so the immediate risk is low; but the class uses @Value to signal immutability. Consider wrapping arrays with Arrays.copyOf in the generated getter or storing unmodifiable wrappers:

♻️ Suggested defensive-copy approach
-  `@NonNull` private int[] lexerSerializedATN;
+  `@NonNull` `@Getter`(AccessLevel.NONE) private int[] lexerSerializedATN;

Then add an explicit getter:

public int[] getLexerSerializedATN() {
    return Arrays.copyOf(lexerSerializedATN, lexerSerializedATN.length);
}

Apply the same pattern for all other array fields (parserSerializedATN, lexerRuleNames, channelNames, modeNames, parserRuleNames, literalNames, symbolicNames).

Alternatively, switch array types to List<Integer> / List<String> (wrapped with Collections.unmodifiableList or List.of) to gain true shallow immutability with no extra boilerplate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/GrammarBundle.java`
around lines 27 - 57, The array fields (lexerSerializedATN, parserSerializedATN,
lexerRuleNames, channelNames, modeNames, parserRuleNames, literalNames,
symbolicNames) are mutable via generated getters; make them defensively
immutable by returning copies or unmodifiable collections: either replace array
types with List<Integer>/List<String> and store them as unmodifiable lists
(Collections.unmodifiableList or List.of) or add explicit getters (e.g.,
getLexerSerializedATN, getParserSerializedATN, getLexerRuleNames,
getChannelNames, getModeNames, getParserRuleNames, getLiteralNames,
getSymbolicNames) that return Arrays.copyOf(...) / Arrays.copyOf(...) converted
to arrays or return the unmodifiable List view, and ensure the constructor/state
stores copies as well to avoid exposing internal arrays.
ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd3529d and fe41aea.

📒 Files selected for processing (4)
  • plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java
  • ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/GrammarBundle.java
  • ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilder.java
  • ppl/src/test/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilderTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • ppl/src/test/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilderTest.java
  • ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/PPLGrammarBundleBuilder.java
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java`:
- Around line 133-161: Modify testInvalidateCache to assert that buildBundle()
is invoked twice by instrumenting the bundle builder with an AtomicInteger
counter (same approach as testGetGrammar_BundleIsCached): add an AtomicInteger,
increment it inside the component/method that constructs the grammar bundle
(buildBundle), call action.handleRequest(...) once, call
action.invalidateCache(), call action.handleRequest(...) again, and then assert
the counter equals 2 to prove rebuild occurred; reference testInvalidateCache,
action.invalidateCache(), and buildBundle() when making the change.
- Around line 1-30: The plugin module's JUnit4 tests (e.g.,
RestPPLGrammarActionTest using `@Before`, `@Test`, and org.junit.Assert) are skipped
because only JUnit Jupiter is declared; add the JUnit Vintage engine to the
plugin's build.gradle test runtime so JUnit 4 tests run on the JUnit 5 platform
by adding testRuntimeOnly("org.junit.vintage:junit-vintage-engine") with the
exclusion of org.hamcrest:hamcrest-core as shown in the review comment.

---

Nitpick comments:
In
`@plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java`:
- Around line 163-182: Add a test variant to cover the case where
RestPPLGrammarAction.buildBundle() returns null: create a failingAction subclass
of RestPPLGrammarAction that overrides buildBundle() to return null, invoke
failingAction.handleRequest(...) with the same FakeRestRequest/MockRestChannel
setup used in testGetGrammar_ErrorPath_Returns500, and assert that
channel.getResponse().status() equals RestStatus.INTERNAL_SERVER_ERROR to ensure
a null bundle produces a 500 response rather than an NPE or other status.

In `@ppl/src/main/java/org/opensearch/sql/ppl/autocomplete/GrammarBundle.java`:
- Around line 27-57: The array fields (lexerSerializedATN, parserSerializedATN,
lexerRuleNames, channelNames, modeNames, parserRuleNames, literalNames,
symbolicNames) are mutable via generated getters; make them defensively
immutable by returning copies or unmodifiable collections: either replace array
types with List<Integer>/List<String> and store them as unmodifiable lists
(Collections.unmodifiableList or List.of) or add explicit getters (e.g.,
getLexerSerializedATN, getParserSerializedATN, getLexerRuleNames,
getChannelNames, getModeNames, getParserRuleNames, getLiteralNames,
getSymbolicNames) that return Arrays.copyOf(...) / Arrays.copyOf(...) converted
to arrays or return the unmodifiable List view, and ensure the constructor/state
stores copies as well to avoid exposing internal arrays.

Comment on lines +1 to +30
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.plugin.rest;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.opensearch.common.io.stream.BytesStreamOutput;
import org.opensearch.common.xcontent.XContentType;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.core.xcontent.MediaType;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestResponse;
import org.opensearch.sql.ppl.autocomplete.GrammarBundle;
import org.opensearch.test.rest.FakeRestRequest;
import org.opensearch.transport.client.node.NodeClient;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check other tests in the plugin module for JUnit usage patterns
rg -n --type java "import org\.junit\.(Before|Test|Assert|After)\b|import org\.junit\.jupiter" \
  plugin/src/test/java --include='*.java' | head -50

Repository: opensearch-project/sql

Length of output: 507


🏁 Script executed:

# Also check for JUnit 5 annotations
rg -n --type java "@BeforeEach|@AfterEach|@org\.junit\.jupiter" \
  plugin/src/test/java --include='*.java' | head -30

Repository: opensearch-project/sql

Length of output: 507


🏁 Script executed:

# Find gradle build files to check JUnit dependencies
find . -name "build.gradle*" -o -name "pom.xml" | grep -E "(build\.gradle|pom\.xml)"

Repository: opensearch-project/sql

Length of output: 598


🏁 Script executed:

# Search for JUnit imports in plugin test directory
rg "import org\.junit\.(Before|Test|Assert|After)" plugin/src/test/java -t java -c

Repository: opensearch-project/sql

Length of output: 336


🏁 Script executed:

# Search for JUnit 5 imports in plugin test directory
rg "import org\.junit\.jupiter" plugin/src/test/java -t java -c

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Check the plugin/build.gradle for JUnit configuration
cat plugin/build.gradle

Repository: opensearch-project/sql

Length of output: 11415


🏁 Script executed:

# Also check the root build.gradle for test dependencies
head -100 build.gradle

Repository: opensearch-project/sql

Length of output: 4014


🏁 Script executed:

# Check for junit-vintage-engine in all build files
rg "junit-vintage-engine" . -t gradle

Repository: opensearch-project/sql

Length of output: 441


🏁 Script executed:

# Check all test files to see JUnit 4 vs JUnit 5 usage across entire project
rg "import org\.junit\.(Before|Test|Assert|After)" . -t java -c | wc -l

Repository: opensearch-project/sql

Length of output: 69


🏁 Script executed:

# Count JUnit 5 imports across the project
rg "import org\.junit\.jupiter" . -t java -c | wc -l

Repository: opensearch-project/sql

Length of output: 69


🏁 Script executed:

# Verify plugin/build.gradle does not have junit-vintage-engine by checking the exact test dependencies section
sed -n '/^dependencies {/,/^}/p' plugin/build.gradle | grep -i "junit\|vintage"

Repository: opensearch-project/sql

Length of output: 235


🏁 Script executed:

# Check one of the modules that has junit-vintage-engine to confirm the pattern
sed -n '/^dependencies {/,/^}/p' async-query/build.gradle | grep -A 2 "junit-vintage"

Repository: opensearch-project/sql

Length of output: 198


Add junit-vintage-engine dependency to plugin/build.gradle to enable JUnit 4 tests on JUnit 5 platform.

The plugin module declares org.junit.jupiter:junit-jupiter:5.9.3 but all three tests use JUnit 4 APIs (@Before, @Test, org.junit.Assert). Without the junit-vintage-engine compatibility layer, these tests will be silently skipped at runtime. Other modules in the project (async-query, async-query-core, direct-query, direct-query-core) properly include this dependency. Add to the dependencies block in plugin/build.gradle:

testRuntimeOnly("org.junit.vintage:junit-vintage-engine") {
    exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/src/test/java/org/opensearch/sql/plugin/rest/RestPPLGrammarActionTest.java`
around lines 1 - 30, The plugin module's JUnit4 tests (e.g.,
RestPPLGrammarActionTest using `@Before`, `@Test`, and org.junit.Assert) are skipped
because only JUnit Jupiter is declared; add the JUnit Vintage engine to the
plugin's build.gradle test runtime so JUnit 4 tests run on the JUnit 5 platform
by adding testRuntimeOnly("org.junit.vintage:junit-vintage-engine") with the
exclusion of org.hamcrest:hamcrest-core as shown in the review comment.

…mmarActionTest.java

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Eric Wei <menwe@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature PPL Piped processing language

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant