feat(client): result rows no longer surface Elasticsearch hit metadata - #226
Merged
Conversation
_index, _score and _sort are removed for good — no consumer exists anywhere in the ecosystem (core, testkit, REPL, jdbc, arrow, extensions). _id becomes opt-in through the new HOCON setting elastic.include-document-id (default false, env override ELASTIC_INCLUDE_DOCUMENT_ID). The retention decision is made once per parse, never per row: _id is injected only when it will actually be kept (flag enabled, _id selected explicitly, or a window-enrichment base query — the ranking ordinal lookup matches rows by document id). The plain search and scroll hot paths pay zero per-row overhead; only window-enriched rows are stripped, fused into the row rebuild enrichment already performs. The retain bit reaches client-side scroll page parsing through ScrollConfig.retainDocumentId (es6 rest/jest, es7, es8, es9). New HitMetadataSpec (testkit + 5 client subclasses, 8 cases each) pins: exactly-selected columns on the one-shot, scroll-routed, async and UNION ALL paths; exact ROW_NUMBER ordinals on both window paths; explicit SELECT _id with the flag disabled; _id-only surfacing with the flag enabled (both spec clients SPI-instantiated with the flag pinned). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SQL result rows no longer surface Elasticsearch hit metadata.
_index,_score,_sortare removed for good — investigation across the whole ecosystem (core, testkit, REPL, softclient4es-jdbc, softclient4es-arrow, softclient4es-extensions, softclient4es-web) found zero consumers: every downstream surface either ignored them or stripped them defensively. The JDBC driver and the Arrow Flight sidecar actually leaked them to BI tools / advertised them as extra Arrow fields — removal narrows those schemas to the selected columns._idbecomes opt-in through a new HOCON setting, disabled by default:Design: decide at injection time, never strip per row
_idhas exactly one internal consumer: ranking windows (ROW_NUMBER/RANK/DENSE_RANK, #101) match base rows to their per-partition ordinals by document id (SearchApi.enrichDocumentWithWindowValues). An earlier draft carried_idon every parsed row and stripped it at the public egresses — rejected because it costs aListMaprebuild per row on the scroll hot path (the exact path arrow#139 is fighting).Instead, the decision is made once per parse/page and
_idis injected only when it will actually be kept:parseSimpleHitscomputes a single boolean —retainDocumentId || includeDocumentId || fields.contains("_id")— before the per-row loop. Default case: no injection, no strip, zero added per-row work anywhere.parseResponse(and the chain below it) gains aretainDocumentId: Boolean = falseparameter. Only the window-enrichment base query sets it:SearchApi.singleSearchInternal(retainDocumentId = true)on the search side, andScrollConfig.retainDocumentIdon the scroll side (the client modules' page parsers passconfig.retainDocumentIdthrough — es6 rest/jest, es7, es8, es9)._idstrip are window-enriched ones, where the strip rides the row rebuild the enrichment already performs (enrichResponseWithWindowValues/scrollWithWindowEnrichment).top_hitsobjects) gate_idat extraction too; the rankingtop_hitsbranch always keeps it (internal only — the ordinal replaces the window column value).Changes
ElasticConversion:extractHitMetadata→extractHitId(only_id, never_index/_score/_sort);includeDocumentIdhook +keepsDocumentId/stripDocumentIdhelpers;retainDocumentIdthreaded throughparseResponse/jsonToRows/parseSimpleHits;ElasticConversion.DocumentIdFieldconstant.ElasticConfig(both 2.12Configsand 2.13ConfigReadervariants): newincludeDocumentId: Boolean = false;softnetwork-elastic.confgainsinclude-document-id+ELASTIC_INCLUDE_DOCUMENT_ID;ElasticClientApiwires it into the conversion layer for every client.ScrollConfig: internalretainDocumentIdflag (set byScrollApi, not by callers).extractAllResults/extractHitsOnly) passconfig.retainDocumentIdtoparseResponse.AggregateApimetadata-key filter reduced to_id;IndicesApiINSERT … AS SELECT keeps a defensive- "_id"so an enabled flag never writes ids into_source.normalizeRow) now strip only_id/_version, keeping expected-row assertions independent of the client configuration.documentation/client/common_principles.md(config reference + env var),documentation/client/indices.md.Tests
ElasticConversionSpec+5 — default parse injects nothing (even with_index/_score/sortin the raw response);retainDocumentId = truecarries_id; explicitSELECT _idand the enabled flag carry_id; window-strip semantics; inner-hit_idgating. 742 core + 500 sql + 120 bridge unit tests green;+ compile(2.12 + 2.13),scalafmtCheck,headerCheckgreen.HitMetadataSpec(testkit template + 5 client subclasses: es6 rest/jest, es7 rest, es8/es9 java), 3-shard index, 8 cases per client:LIMIT), scroll-routed (noLIMIT, SELECT without LIMIT returns only 10 rows on the non-scroll search path #209), async, and UNION ALL row queries surface exactly the selected columns;ROW_NUMBERkeeps exact per-partition ordinals on both the one-shot and scroll paths — proves the retain channel feeds the ordinal lookup on every client;SELECT _idsurfaces the id even with the flag disabled;GROUP BYrows carry no metadata;include-document-id = true,_id(and only_id) appears on every path and equals the bulk-indexed id. Both spec clients are SPI-instantiated with the flag pinned (the factory caches per cluster URL; ambientELASTIC_INCLUDE_DOCUMENT_IDcan't flip assertions).JavaClient*integration suite: 299 passed, 0 failed. ES9/ES7/ES6(rest+jest): HitMetadata + Select/Scroll/WindowPartition completeness + WindowFunction guards green on real clusters.Known limitations (adversarial review outcome — accepted, flag restores access)
SELECT _id AS aliasyields a null alias column (it was already null before this change; the id itself is reachable via un-aliasedSELECT _idor the flag)._idprojection (UNNEST-styleSELECT o._id) requires the flag — nested rows are gated at extraction.top_hitsper-hit maps, which carry_id(internal to the ordinal machinery; pre-change they carried all four metadata keys)._idper the head request's projection — consistent with the engine's existing UNION semantics (all legs are normalized to the head's column shape).Downstream notes (0.20.x release note material)
_id/_index/_score/_sortfrom result rows implicitly must either select_idexplicitly, or enableelastic.include-document-id. TypedsearchAs[T]entities with a required_idfield fall in this category.SELECTschemas narrow by the metadata fields (bothQueryRowsandQueryStreamchange together —effectiveSchema's subset check stays consistent).ResultSetMetaDatafor ES-path queries now reports only the selected columns (strictly safer for positional access; JdbcIntegrationSpec's>= 3column bound can be tightened to== 3on the next core bump).🤖 Generated with Claude Code