Skip to content

[BugFix] Handle opaque NullPointerException for unresolvable alias-type field path#5536

Merged
ahkcs merged 4 commits into
opensearch-project:mainfrom
RyanL1997:fix/5535-alias-unresolved-path-npe
Jun 10, 2026
Merged

[BugFix] Handle opaque NullPointerException for unresolvable alias-type field path#5536
ahkcs merged 4 commits into
opensearch-project:mainfrom
RyanL1997:fix/5535-alias-unresolved-path-npe

Conversation

@RyanL1997

@RyanL1997 RyanL1997 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Description

When a mapping contains a field of "type": "alias" whose "path" points to a target that is absent from the flattened mapping — for example a text multi-field (field.keyword) or a removed/renamed field — OpenSearchDataType.validateAliasType() passed a null target into the OpenSearchAliasType constructor, which immediately dereferenced it at super(type.getExprCoreType()). The result was an opaque NullPointerException surfaced to the user with no indication that the real problem is a malformed alias in their mapping:

{
  "error": {
    "reason": "Invalid SQL query",
    "details": "Cannot invoke \"org.opensearch.sql.opensearch.data.type.OpenSearchDataType.getExprCoreType()\" because \"type\" is null",
    "type": "NullPointerException"
  },
  "status": 400
}

Root cause: traverseAndFlatten only expands object/nested properties into dotted keys; it does not add text multi-fields (.keyword), which are stored under OpenSearchTextType.fields. So an alias whose path targets such a field (or a field that no longer exists) resolves to null. The OpenSearchAliasType constructor already guards the alias-to-alias and alias-to-object cases, but those checks run after the super() dereference, so the unresolvable-path case fell through to the NPE.

Fix: Guard the null target in validateAliasType and throw a SemanticCheckException that names the offending alias field and its unresolved path. SemanticCheckException extends QueryEngineException, which JdbcResponseFormatter#getStatus maps to HTTP 400 — an invalid alias mapping is a client error, so this also avoids the misleading HTTP 500 ("internal problem at backend") that a generic exception type would have produced.

After the fix:

{
  "error": {
    "reason": "Invalid SQL query",
    "details": "Alias field [source_alias] refers to unresolved path [source.keyword]. The alias path must point to an existing field in the mapping; a text multi-field (e.g. \"source.keyword.keyword\") or a removed/renamed field is not a valid alias target.",
    "type": "SemanticCheckException"
  },
  "status": 400
}

Valid aliases (including SELECT * over aliases on plain text/keyword fields) are unaffected — the new branch only triggers when the resolved target is null, which previously always threw.

Related Issues

Resolves #5535

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.


Update: adopt the ErrorReport report-builder interface (#5266)

Per reviewer feedback, the fix now wraps the SemanticCheckException in an ErrorReport (introduced in #5266) instead of throwing it directly. This attaches structured context as the error bubbles up: code = FIELD_NOT_FOUND, stage = ANALYZING, a location chain, the alias_field / alias_path context, and a fix suggestion.

Verified live on both endpoints (both HTTP 400, no regression for valid aliases):

  • PPL (_plugins/_ppl) renders the full structured error:
    {
      "error": {
        "reason": "Alias field [source_alias] refers to unresolved path [source.keyword].",
        "code": "FIELD_NOT_FOUND",
        "suggestion": "The alias path must point to an existing field in the mapping; a text multi-field (e.g. \"source.keyword.keyword\") or a removed/renamed field is not a valid alias target.",
        "context": { "stage": "analyzing", "stage_description": "Parsing and validating the query", "alias_field": "source_alias", "alias_path": "source.keyword" },
        "location": ["while preparing and validating the query plan", "while resolving alias fields in the index mapping"],
        "type": "SemanticCheckException"
      },
      "status": 400
    }
  • SQL (_plugins/_sql) still returns a clear 400 — RestSqlAction unwraps ErrorReport to its SemanticCheckException cause for status classification. Note: JdbcResponseFormatter does not yet render the ErrorReport structure (it reports type: "ErrorReport" with just details); teaching the JDBC formatter to render the structured fields is a reasonable follow-up but is intentionally out of scope here to keep this change focused.

Tests: unit test asserts the ErrorReport code/stage/context/cause; a PPL IT in CalciteErrorReportStageIT asserts the structured FIELD_NOT_FOUND error; the SQL IT in QueryValidationIT asserts the 400.

When a mapping contains a field of "type": "alias" whose "path" points to
a target absent from the flattened mapping (a text multi-field such as
field.keyword, or a removed/renamed field), validateAliasType passed a null
target into the OpenSearchAliasType constructor, which dereferenced it at
super(type.getExprCoreType()) and surfaced an opaque NullPointerException.

Guard the null target and throw a SemanticCheckException naming the alias
field and its unresolved path. SemanticCheckException extends
QueryEngineException, so JdbcResponseFormatter maps it to HTTP 400 (client
error) rather than the misleading 500 a generic exception would produce.

Add unit tests covering the .keyword multi-field and missing-field cases.

Fixes opensearch-project#5535

Signed-off-by: Jialiang Liang <jiallian@amazon.com>
@RyanL1997 RyanL1997 changed the title Fix opaque NullPointerException for unresolvable alias-type field path [BugFix] Fix opaque NullPointerException for unresolvable alias-type field path Jun 10, 2026
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit 9295c92)

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ No major issues detected

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Latest suggestions up to 9295c92

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Use generic placeholder in example

The suggestion message concatenates originalPath directly into the example string,
which could produce confusing output if originalPath already ends with .keyword.
Consider using a generic placeholder instead of embedding the actual path value in
the example.

opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataType.java [305-321]

 OpenSearchDataType target = result.get(originalPath);
 if (target == null) {
   throw ErrorReport.wrap(
           new SemanticCheckException(
               String.format(
                   "Alias field [%s] refers to unresolved path [%s].",
                   key, originalPath)))
       .code(ErrorCode.FIELD_NOT_FOUND)
       .stage(QueryProcessingStage.ANALYZING)
       .location("while resolving alias fields in the index mapping")
       .context("alias_field", key)
       .context("alias_path", originalPath)
       .suggestion(
           "The alias path must point to an existing field in the mapping; a text"
-              + " multi-field (e.g. \""
-              + originalPath
-              + ".keyword\") or a removed/renamed field is not a valid alias target.")
+              + " multi-field (e.g. \"field.keyword\") or a removed/renamed field is not a valid alias target.")
       .build();
 }
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that embedding originalPath in the example could be confusing if it already ends with .keyword (e.g., "source.keyword.keyword"). Using a generic placeholder like "field.keyword" improves clarity. However, this is a minor improvement to error message quality rather than a functional fix.

Low

Previous suggestions

Suggestions up to commit 0d76d1c
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix format string argument mismatch

The error message uses originalPath twice in the format string but provides it three
times as arguments. This causes the third argument to be ignored and creates a
confusing example that duplicates the path. Remove the duplicate originalPath
argument to match the format placeholders.

opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataType.java [303-309]

 throw new SemanticCheckException(
     String.format(
         "Alias field [%s] refers to unresolved path [%s]. The alias path must point"
             + " to an existing field in the mapping; a text multi-field (e.g."
             + " \"%s.keyword\") or a removed/renamed field is not a valid alias"
             + " target.",
-        key, originalPath, originalPath));
+        key, originalPath));
Suggestion importance[1-10]: 10

__

Why: The format string contains three %s placeholders but four arguments are provided (key, originalPath, originalPath). This is a critical bug that will cause incorrect error messages. The third originalPath argument should be removed to match the three placeholders in the format string.

High
Suggestions up to commit e533a78
CategorySuggestion                                                                                                                                    Impact
General
Fix confusing error message example

The error message uses originalPath twice in the example, which creates a confusing
placeholder like "source.keyword.keyword". The example should demonstrate the
pattern more clearly by using a generic placeholder or the actual field name without
duplication.

opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataType.java [302-309]

 if (target == null) {
   throw new SemanticCheckException(
       String.format(
           "Alias field [%s] refers to unresolved path [%s]. The alias path must point"
               + " to an existing field in the mapping; a text multi-field (e.g."
-              + " \"%s.keyword\") or a removed/renamed field is not a valid alias"
+              + " \"fieldname.keyword\") or a removed/renamed field is not a valid alias"
               + " target.",
-          key, originalPath, originalPath));
+          key, originalPath));
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that using originalPath in the example creates confusing output like "source.keyword.keyword" (as seen in the test). Using a generic placeholder like "fieldname.keyword" would make the error message clearer and less confusing for users.

Medium

Add a QueryValidationIT case asserting that SELECT * over an index whose
alias field targets a text multi-field (source.keyword) returns a 400
SemanticCheckException with the descriptive message. An alias pointing at a
truly missing field is rejected by OpenSearch at index-creation time, so it
is not reachable through the SQL plugin and is covered by the unit test only.

Shorten the unit-test comments and drop inline issue references.

Signed-off-by: Jialiang Liang <jiallian@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 0d76d1c

@RyanL1997 RyanL1997 marked this pull request as ready for review June 10, 2026 18:20
@RyanL1997 RyanL1997 changed the title [BugFix] Fix opaque NullPointerException for unresolvable alias-type field path [BugFix] Handle opaque NullPointerException for unresolvable alias-type field path Jun 10, 2026
Signed-off-by: Jialiang Liang <jiallian@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 4d1c8e3

Wrap the SemanticCheckException in an ErrorReport (the report-builder
interface from opensearch-project#5266) so the error carries structured context as it bubbles
up: FIELD_NOT_FOUND code, ANALYZING stage, a location chain, the alias field
and path as context, and a fix suggestion. On the PPL/Calcite path this
renders as a rich structured error; on the SQL JDBC path it still returns a
clear 400 (RestSqlAction unwraps to the SemanticCheckException cause), though
the JdbcResponseFormatter does not yet render the ErrorReport structure.

Update the unit test to assert the ErrorReport code/stage/context/cause, the
SQL IT for the ErrorReport type, and add a PPL IT asserting the structured
FIELD_NOT_FOUND error in CalciteErrorReportStageIT.

Signed-off-by: Jialiang Liang <jiallian@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 9295c92

@ahkcs ahkcs merged commit 30af8b2 into opensearch-project:main Jun 10, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] SELECT * throws opaque NullPointerException when an alias-type field has an unresolvable path

3 participants