Repository navigation
feat(core): declared ResultSchema and hit values read by type (SCHEMA-2) - #453
Merged
Merged
Conversation
Story SCHEMA-2. Reviewed: r0:must-fix, r1:must-fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w Elasticsearch's range rules (SCHEMA-2) Story SCHEMA-2. Reviewed: r0:must-fix, r1:must-fix, r2:must-fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…EMA-2) Story SCHEMA-2. Reviewed: r0:must-fix, r1:must-fix, r2:must-fix, r3:clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fupelaqu
marked this pull request as ready for review
October 30, 2026 20:48
This was referenced Oct 31, 2026
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
Slice 1 of 2 of a schema-aware result path in core (SCHEMA-2). Every statement core executes now gets a declared result schema before any row. The schema rides on the result, and
GatewayApi.describe(sql)returns it without running the statement. Every value read from a hit (_source, scriptfields, inner hits) is read by its column's declared type, where it is first read. The same holds on every hit path: one-shot search,searchAsync,_msearchlegs, the UNION ALL legs, scroll / search_after / PIT pages for the five client page parsers, and inner hits.Aggregation values (bucket keys, metrics, top_hits) and window-enriched statements are slice 2 (SCHEMA-2b). They keep today's reading in this PR.
Refs #418, #421 (both stay open: #418's second part and #421's aggregation side land with the next slices).
What changes
ResultSchema/ResultColumn,GatewayApi.describe(sql): ElasticResult[Option[ResultSchema]],schema: Option[ResultSchema]onQueryRowsandQueryStream,def schemaonQueryStructured.HitReading): one reader per column, built once per statement. It covers numbers and booleans as Elasticsearch coerces them, dates by mapping format (one format build per mapping format per statement), geo_point, binary, STRUCT by declared field and declared ARRAY. A value the reader cannot read is returned as stored.ignore_malformedis returned as stored.SELECT *expands to the cached mapping's columns. An explicitly selected field the mapping lacks triggers one mapping reload, then is returned as stored.Release notes
QueryRows,QueryStreamandElasticResponsegain a field, so positional patterns need one more_.QueryStructuredpatterns compile unchanged. Recommended consumer shape:case q: QueryRows => q.rows.describe(sql)andschemaon results (nullableis always true).Long, DOUBLEDouble, REALFloat(wasDouble).Integer, in core UNION ALL too (wasShort/Byte).java.timeat UTC.LocalDateTimeis gone: an undeclared date holding date-only values answers aZonedDateTimeat midnight UTC (wasLocalDate).{lat, lon}; one-element arrays are unwrapped.GREATEST/LEASTover integers) answerLong(wasInteger).127.9answers127). A value Elasticsearch refused stays as stored.SELECT *returns the cached mapping's columns, in mapping order. A key stored but not mapped is not returned; a field mapped since appears after the schema-cache TTL. Rows hold no leaked parent object._ignored) answer NULL.Acceptance evidence
describe= the result's schema, no search issuedDescribeSpec(unit, counting double = 0 searches);SchemaReadingSpecon ES 8.18.3 and 6.8.23SchemaReadingSpec(ES 8.18.3, ES 6.8.23); canned pages per client page parser (es6 rest, es6 jest, es7 rest, es8 / es9 java)*JavaClient* *ReplGateway*832 passed / 0 failed; ES 6.8.23*RestHighLevelClient*727 / 0SELECT *and unmapped fieldsSchemaReadingSpec(ES 8.18.3)JavaClientSchemaConsumerSpec(consumer forms). Throwaway local publish, deleted afterwards: extensions, JDBC and arrow compile with the consumer shape only and each run one real query on ES 8.18.3Cost (warm JIT, setup split from the row loop, branch runs interleaved with main runs on the same machine):
The per-statement runs were taken under a machine load of 5 to 8. Two of the five runs exceed 25 µs; the median is the bound's measure.
Recorded, not fixed
"5.000000000000000000001","0e20") are returned as stored on both majors, as in 0.24.0.1e400) before core reads them, as in 0.24.0.Test plan
core/test(1,388 passed),sql/testOnly *SetOperation* *Select* *ValueCoercion*,sbt "+ compile"(2.12 + 2.13)headerCheck scalafmtSbtCheck scalafmtCheck test:scalafmtCheck) on the committed bytes🤖 Generated with Claude Code