Skip to content

Support flat_object field type in PPL projection - #5715

Draft
RyanL1997 wants to merge 1 commit into
opensearch-project:mainfrom
RyanL1997:feat/flat-object-field-type
Draft

Support flat_object field type in PPL projection#5715
RyanL1997 wants to merge 1 commit into
opensearch-project:mainfrom
RyanL1997:feat/flat-object-field-type

Conversation

@RyanL1997

Copy link
Copy Markdown
Collaborator

Description

flat_object fields are currently invisible to PPL — not merely unsupported. OpenSearchDataType#parseMapping normalizes the mapping type with type.replace("_", "")"flatobject", fails EnumUtils.isValidEnumIgnoreCase, and executes a bare return:

// opensearch/.../data/type/OpenSearchDataType.java
var type = ((String) innerMap.getOrDefault("type", "object")).replace("_", "");
if (!EnumUtils.isValidEnumIgnoreCase(OpenSearchDataType.MappingType.class, type)) {
  // unknown type, skip it.
  return;
}

The field therefore never reaches traverseAndFlatten, so it is absent from the Calcite row type, from OpenSearchExprValueFactory's type mapping, and from DESCRIBE. Users see the field silently disappear from results, and any reference to it fails symbol resolution.

Change: register FlatObject("flat_object", ExprCoreType.STRUCT) in MappingType. Because @EqualsAndHashCode excludes mappingType, the existing struct machinery then applies with no further edits — OpenSearchExprValueFactory routes it to parseStruct, and OpenSearchTypeFactory maps STRUCT to MAP(VARCHAR, ANY). The value factory is untouched.

Two supporting changes:

  • flat_object is routed through the Object/Nested arm of of() rather than the default: arm. The default arm hands out the cached singleton whose properties field is the class-level ImmutableMap, which fails cross-index merging in DESCRIBE (DeepMergeRuleLatestRule.putUnsupportedOperationException). The Object arm yields a fresh instance with a mutable LinkedHashMap.
  • OpenSearchAliasType.objectFieldTypes now includes FlatObject, so alias -> flat_object is rejected instead of producing a field that DESCRIBE lists but SOURCE cannot select.

Behavior change

Measured at the schema layer with the same mapping, before and after:

BEFORE  parseMapping=[eventName]           flat=ABSENT
AFTER   parseMapping=[eventName, flat]     flat=FLAT_OBJECT, core=STRUCT, props=LinkedHashMap

User-visible, for {"flat": {"s": "x", "n": 7, "a": {"b": 1}}}:

Query Before After
source=idx field omitted entirely {s=x, n=7, a={"b":1}}
describe idx row absent flat | struct
fields flat.s resolution failure x
fields flat.n resolution failure 7 (_source type preserved)
where flat.s = 'x' resolution failure filters correctly
fields flat.a resolution failure {"b":1} — JSON text, not a struct
fields flat.a.b resolution failure NULL

Scope

This PR covers querying the field. It does not implement searching sub-fields.

Subfield names are not present in the index mapping — OpenSearch resolves them lazily per query via FlatObjectFieldType.keyedFieldType, and _field_caps does not expose them either. Since row types are built eagerly from IndexMappingparseMappingtraverseAndFlatten, a flat_object contributes exactly one column, and no plan-time hook can produce flat.a as a schema column.

Two known limitations follow, and both are pinned by tests so any future change is deliberate:

  • Nested values are returned as JSON text. OpenSearchExprValueFactory#parseContent has no object branch and falls through to new ExprStringValue(content.objectValue().toString()). The Content interface has no isObject() predicate, so a recursive branch cannot be written without extending Content and both implementations. Related: [FEATURE] Support creating typed arrays & structs with Calcite #3751.
  • Paths deeper than one level resolve to NULL. QualifiedNameResolver#resolveFieldAccess joins the remaining parts into a single ITEM key ('a.b'), which cannot match a nested tuple.

Also out of scope, and documented rather than worked around:

  • Aggregation on a subfield cannot be pushed downFlatObjectFieldType.isAggregatable() returns false upstream (OpenSearch#14225 is open). This is an external limit, not a gap in this plugin.
  • Sorting on a subfield is not meaningful — OpenSearch orders on an internal representation spanning every subfield of the object.
  • No predicate pushdown for subpaths. SqlKind.ITEM is whitelisted in PredicateAnalyzer.supportedRexCall but has no case in visitCall's switch. Filters on flat.* currently run on the coordinator.

Note on the DESCRIBE type name

PPL reports struct and SQL reports flat_object, because OpenSearchDescribeIndexRequest uses langSpec.typeName(getExprType()) for PPL and legacyTypeName() for SQL. This mirrors the existing contract for object fields and is deliberately unchanged here. Happy to revisit if reviewers would rather DESCRIBE distinguish flat_object, since the query semantics do differ.

Testing

  • integ-test/.../rest-api-spec/test/issues/1604.yml — 5 cases covering projection with Calcite enabled and disabled, subfield addressability, DESCRIBE, and the two pinned limitations. tests=5 skipped=0 failures=0 errors=0.
  • Unit tests: 338 suites / 6969 tests, 0 failures.
  • Rebased on d9d15b1b3 (Migrate to Jackson 3.x APIs) and re-verified, since that commit touches OpenSearchJsonContent, ObjectContent and OpenSearchExprValueFactory — the decode path these tests assert on.

Related Issues

Addresses item 1 of #1604 ("Support querying such fields, for example in select * query"). Item 2 ("Support search for sub-fields") is out of scope — see Scope above.
Addresses the primary complaint in #3067 (flat_object fields missing from query results).
Part of #3695.

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.

flat_object fields were dropped by OpenSearchDataType#parseMapping, which
normalizes the mapping type to "flatobject", fails the MappingType lookup and
executes a bare return. The field never reached the type environment, so it was
absent from DESCRIBE and from query results, and any reference to it failed
symbol resolution.

Register flat_object as a STRUCT-backed MappingType. The existing struct
machinery then applies unchanged: OpenSearchExprValueFactory routes it to
parseStruct, and the Calcite type factory maps STRUCT to MAP(VARCHAR, ANY).

Route it through the Object/Nested arm of of() rather than the default arm, so
that it receives a fresh instance with a mutable property map. The default arm
hands out the cached singleton whose properties field is an ImmutableMap, which
fails cross-index merging in DESCRIBE. OpenSearchAliasType now also rejects an
alias pointing at a flat_object, which would otherwise yield a field that
DESCRIBE lists but SOURCE cannot select.

This covers querying the field. Searching sub-fields is not addressed: subfield
names are absent from the index mapping, so a flat_object contributes exactly
one column. Nested values are returned as JSON text and paths deeper than one
level resolve to NULL; both are pinned by tests and documented.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jialiang Liang <jiallian@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

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

@RyanL1997 RyanL1997 added enhancement New feature or request PPL Piped processing language labels Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request PPL Piped processing language

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant