Repository navigation
feat(sql): ARRAY<X> round trip, derived and WITH columns typed, struct order (SCHEMA-6) - #456
Merged
Merged
Conversation
…t order (SCHEMA-6) Story SCHEMA-6, rebased onto SCHEMA-5. Reviewed: r0:must-fix, r1:must-fix, r2:clean, r3:clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fupelaqu
marked this pull request as ready for review
November 5, 2026 06:42
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
ARRAY<X>columns, derived-column typing and struct order in core (SCHEMA-6, after #455). A column declaredARRAY<X>reads back asARRAY<X>(DESCRIBE, schema, type checks) and its values are lists. A column not declared a list that holds several values in a document now fails the statement and names theALTER TABLE … SET DATA TYPE ARRAY<X>that declares it a list. That ALTER changes only_metaand runs in place. Derived-table and CTE columns are typed from their body,ARRAY_AGGisARRAY<elem>, and sub-fields keep their declared order.Closes #419
Covers #419's list read-back, derived / CTE typing, object lists merged field by field, sub-field order, and list value loss (a declared list reads lists, and an undeclared one fails with its remedy).
What changes
array<…>recursively; an inner list orANYreadsANY.CHARreads backCHAR.ARRAY<VARBINARY>maps tobinary, andARRAY<GEO_POINT>parses in DDL. One check refuses a list of lists in CREATE TABLE, ADD COLUMN, SET DATA TYPE, SET FIELD and a CTAS column.SET DATA TYPE ARRAY<X>on a field of X's Elasticsearch type is not a type change. The_metadata_type/field_orderkeys are safe, so the update runs in place (no reindex, same uuid). Changing a list's element type, or turning a scalar into a list of another Elasticsearch type, staysImpossible._id, the hit's index (the ES 8 / 9 envelope now carries_index) and an ALTER that parses:SET DATA TYPE ARRAY<X>, orSET FIELDwith the sub-field's own options. It is raised on the top-hits path too (FIRST_VALUE / LAST_VALUE / ARRAY_AGG). WHERE-subquery inner statements are unaffected. Main's text (ALTER COLUMN c TYPE ARRAY<...>) did not parse and named the pattern.obj.aunder it collects the elements' values in order (it was NULL), also besideobjor*.ARRAY<X>column (X scalar) as X where Elasticsearch works per element: comparisons and their queries, the Painless type, GROUP BY key columns, MIN / MAX / SUM / AVG, function argument types. Declared positions keepARRAY<X>: schema, DESCRIBE, CTAS, FIRST / LAST / ARRAY_AGG. PARTITION BY over a list reads as on 0.24.0. Where a script reads the whole value (ingest, transform, computed-column validation), it staysARRAY<X>, so CREATE and ALTER judge a computed column alike, with 0.24.0's processor bytes.ARRAY<X>report, render and guard overflow as over X. A quoted decimal besideARRAY<INT>is sent rounded as besideINT(arr = '1.5'sends2) in comparisons, IN, BETWEEN and HAVING key filters.ARRAY<elem>for describe, set-operation slots and CTAS (_metaARRAY<KEYWORD>, mappingkeyword)._metarecordsfield_order. Sub-fields read in that order, with unlisted ones after them. SET FIELD rewrites a sub-field in place.DerivedTable.attachtypes every derived body (CTE references included) by the projection-item rule.SearchApi.attachDerived(private[elastic]) serves it to the relational engine. It executes nothing. Core's refusal to run derived statements is unchanged, and plainupdatenever types a derived branch.Release notes
ARRAY<X>columns reportARRAY<X>in DESCRIBE, the schema and JDBC metadata (wereANY). Values are lists; one value is a list of one.ARRAY<X>fail and name the in-place ALTER. This includes 0.24.0 CTAS / view columns built from ARRAY_AGG (declared as the element): run the ALTER, or redeploy the view. A list of objects in a STRUCT column keeps answering, so a STRUCT column may hold aList. Itsobj.areads Elasticsearch's values (was NULL) and fails like a scalar when several.ARRAY<elem>. CTAS_metafollows (mapping unchanged).CHARreads backCHAR(it readANY).ARRAY<VARBINARY>maps tobinary(wasobject).ARRAY<GEO_POINT>is accepted in DDL. Lists of lists are refused.ad + 1overARRAY<DATE>(a Painless error before) now answers as over DATE.c DATE SCRIPT AS (CAST(<list> AS DATE))overARRAY<KEYWORD|TIMESTAMP|INT>now writes CREATE's processor, which copies the value. 0.24.0's ALTER wrote.toLocalDate(), which cannot run on an ingest String or list, so it stored nothing.ARRAY<X>column no longer re-maps it toobject. On 0.24.0 and main, lists then failed to index; with documents, the reindex rollback failed and the index was deleted.HitReading(private[client], not a case class) gains two defaulted constructor parameters.Answered before, refused now (accepted)
ES 8.18.3 gate against v0.24.0; main answers the first six rows as v0.24.0 does. The computed-column rows were re-measured on the final bytes (verdicts and processor bytes, no cluster). Columns:
arr ARRAY<INT>,arrd ARRAY<DOUBLE>,arrk ARRAY<KEYWORD>,ad ARRAY<DATE>,ats ARRAY<TIMESTAMP>,n INT,d DATE.SELECT *, LIMIT, scroll, index pattern, UNION ALL, window base, FIRST_VALUE, ARRAY_AGG;obj.aalone, besideobj, beside*; nesteditems.q; reading a CTAS and an INSERT … SELECT copyobj.a,items.qNULL_id, index andSET DATA TYPE ARRAY<KEYWORD>/SET FIELD a ARRAY<INT>/SET FIELD q ARRAY<INT>arr ∪ d,arr ∪ n,arrk ∪ s,arrk ∪ dList,Integer,LocalDate,String)ARRAY_AGG(k) … GROUP BY g UNION ALL SELECT k …ARRAY<KEYWORD>besideKEYWORD)c ARRAY<ARRAY<INT>>by CREATE TABLE, ADD COLUMN, SET FIELDobjectfield; indexing a list then failedCREATE TABLE … AS SELECT g, ARRAY_AGG(arr) AS l … GROUP BY glmappedobjectlarrk + 1a1,b1,c1(concatenation)ANY), but the ALTER re-mapped the list toobject; with documents the index was deletedThe 33 computed-column cells:
arr + 1,arr * 2,arr - n,arr % 2,arr / 2,ABS(arr),SIGN(arr),SQRT(arr),POWER(arr, 2),LOG(arr),ROUND(arrd),ROUND(arrd, 1),FLOOR(arrd),CEIL(arrd);YEAR(ad),EXTRACT(YEAR FROM ad),DATE_ADD(ad, INTERVAL 1 DAY),DATE_SUB(ad, INTERVAL 1 DAY),ad + INTERVAL 1 DAY,ad * 2,DATE_TRUNC(ad, MONTH),LAST_DAY(ad),DATEDIFF(ad, d),DATEDIFF(d, ad),DATE_FORMAT(ad, '%Y'),MONTH(ats),ats + INTERVAL 1 HOUR,DATE_TRUNC(ats, DAY);NULLIF(arr, 0),NULLIF(arr, arr),NULLIF(n, arr),NULLIF(arrk, 'a');ad - d.Acceptance evidence
ArrayRoundTripSpec, RUN on ES 8.18.3 and 6.8.23MultiValuedFailSpec(unit); ES 8.18.3 gateORDER BY:unmapped_typeonly, as on 0.24.0). ES 8.18.3:'1.5'in=, IN, BETWEEN,>, HAVING gives the scalar's rows;ROUND(arr, -1)=ROUND(n, -1)(Long)DESCRIBEDerivedColumnTypeMatrixSpec(DuckDB answers embedded, divergences listed with reasons);JavaClientSchemaConsumerSpecES 8.18.3 13/13, incl. #455's testsStructFieldOrderSpec,ArrayDeclarationDiffSpec,ArrayTypeMappingSpec; ES 8.18.3 / 6.8.23 legssql/test2246/0,core/test1429/0, bridges es6 360/0, es8 353/0, es9 353/0,sbt "+ compile"OK; ES 8.18.3 fulles8java/test879/0 (3 canceled: legacy templates, ES < 7.8); ES 6.8.23 67/0headerCheck scalafmtSbtCheck scalafmtCheck test:scalafmtCheck)Final bytes against main, one forked JVM per run, main and branch alternated twice per item (main, branch, branch, main). Each figure is the median of the per-run medians (18 timed blocks after 60 warm-up blocks). The 16-CPU machine was not quiet: the 1-minute load was 6 to 12 during the runs, so differences of a few percent are within noise.
ARRAYcolumns, 10,000 rows, ns/rowFor the ES 8 envelope, three runs disturbed by load (two on main, one on the branch) are left out; with them the medians are not comparable.
Downstream
Against a throwaway local publish, extensions, JDBC and arrow (main) compile and test-compile with only the edits #453 / #454 already require.
DeclaredLeg*11/0, 7/0, 5/0, 5/0;DuckDBJoinExecutorSpec84/0. ES 8 Flight spec: 108 passed, 3 failed, the same 3 as on main 6a06d48.getColumnsreports ARRAY columns (DATA_TYPE 2003), andgetObjectreturnsListvalues.ARRAY<KEYWORD>/ARRAY<INT>sources deploys. It describes as lists, returns the lists and re-deploys.Adoption items, not this PR's:
ResultSetMetaDatainfersARRAY<ANY>from the values, whilegetColumnsreportsARRAY<KEYWORD>.IllegalStateException, notSQLException(core's message, unchanged). The driver's streaming path has had this gap on main since the typed-rows work.Recorded, not fixed
ARRAY<DATE|TIMESTAMP>against a DATE value (CAST(ts AS DATE), CURRENT_DATE), and its mirror, keeps main's comparison and fails loudly as on main.CASE c WHENoverARRAY<DOUBLE|REAL>(compares as strings),ARRAY<TIMESTAMP|DATETIME>+ INTERVAL vs CURRENT_DATE (truncates to a date).ARRAY_AGG(<expression>)does not parse (grammar unchanged). Its type over ROUND / DATEDIFF is pinned at the typing function.ARRAY_AGG(x) OVER (PARTITION BY g)is NULL. A globalARRAY_AGG(k)answers 3 of 5 values.COUNT(*) OVER (PARTITION BY <list>)is mostly NULL.BOOLEAN BETWEENthrows.Test plan
sql/test2246/0,core/test1429/0, bridges es6 360/0, es8 353/0, es9 353/0,sbt "+ compile"(2.12 + 2.13)es8java/test, 879 passed, 0 failed (3 version-gated cancels)🤖 Generated with Claude Code