Repository navigation
feat(core): mapping types, field caps fallback and index patterns typed (SCHEMA-7) - #459
Conversation
…ed (SCHEMA-7) Story SCHEMA-7. Reviewed: r0:must-fix, r1:must-fix, r2:clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cost table: main (663e6df) against this branch (aaec7d3)Measured on one machine, main and branch interleaved (main, branch, main, branch), 2 rounds each. Unit timings: JDK 11, warm-up iterations discarded, a full GC before each run, inputs built outside the timed loop, 7 timed runs per round. Elasticsearch timings: 8.18.3 with security on, a fresh container per leg, JDK 17. Medians are pooled over the 2 rounds; Δ = branch median against main median. Summary
Plain SELECT, resolve and render (unit)
Pattern schema merge (unit;
Date reads (unit)
Elasticsearch 8:
Elasticsearch 8: statement wall time (not JIT-warm: see the summary)
Requests Every request count is the same on both sides for the privileged client. For the read-only user:
🤖 Generated with Claude Code |
Statement wall time with a warm JIT (follow-up to the cost table)The statement rows of the cost table were not JIT-warm (each shape ran at most 18 times in a fresh JVM). Re-measured: each client first runs every statement shape 200 times, round-robin, with its schema cache warm, as a running application would; then 30 timed round-robin runs per shape. Main and branch interleaved, 2 rounds each (n = 60 per side), Elasticsearch 8.18.3 with security on.
🤖 Generated with Claude Code |
Summary
Seven Elasticsearch mapping types now have a declared SQL type, a user who may read an index but not its mapping gets a typed schema, and index patterns whose indices disagree on a field type it the way DuckDB would (SCHEMA-7).
On main,
scaled_float,half_float,unsigned_long,date_nanos,ip,constant_keywordandflattenedread as ANY inDESCRIBE, JDBC, Arrow and set operations, andconstant_keywordanswers NULL. A user with only the indexreadprivilege (GET <index>403,_field_caps200) has no schema at all, and every SELECT without a LIMIT, UNION ALL or scroll of that user fails with "Failed to get ES version". A pattern field mappedkeywordin one index andlongin another is untyped.What changes
Mapping types. One reader,
SQLTypes.apply, gains the new arms:scaled_floatDouble, the_sourcevaluehalf_floatFloatunsigned_longLong; above 2^63-1 aBigInteger, never wrappeddate_nanosZonedDateTimeUTC, nanoseconds keptipStringconstant_keywordString, read fromdocvalue_fields(the value is not in_source)flattenedElasticsearch-side choices (proven HAVING key kinds, sort
unmapped_type,docvalue_fields) read the column's real mapping type, never the SQL type, so typing a column does not change what is sent for it.constant_keywordanswers its value. A statement reading such a column (or*) adds it todocvalue_fieldsin both bridges, on search, scroll, PIT pages,_msearchlegs and the window base; the hit reader takes it from the hit'sfields.A read-only user is typed from
_field_caps. One query-only seam (IndicesApi.loadQuerySchema) fetches the mapping as today and falls back toGET <source>/_field_capson a 403 or a pattern with no member, inside the same single-flight cache compute: the mapping is asked for at most once per source per cache TTL, never once per statement. DDL never sees a_field_capsschema (newTableType.FieldCapabilities).A read-only user's reads without a LIMIT answer. The scroll strategy and the slicing read the server version through the same once-per-client function the date dialect already uses: the cluster's version, else, when
GET /is denied, the major the client module is built for (ES 6 and 7 modules page withsearch_after; ES 8 and 9 open a PIT, whose_settingslookup is denied once per index set per TTL and leaves one slice).Index patterns. Members are sorted by index name. A field the members disagree on takes the type of DuckDB's union lattice, as set operations do (keyword + long or keyword + date: VARCHAR; DATE + TIMESTAMP: TIMESTAMP), when every member's value reads into it; otherwise it stays ANY, as today (a refused pair, a nested result, two date formats, a member with a non-native mapping type).
DDL round trip.
ALTER TABLE … ADD COLUMNkeeps every field's mapping type and parameters. A retype that changes the mapping type reindexes (ipto VARCHAR mapstext;date_nanosto DATE) or is refused when it cannot be done (constant_keywordto VARCHAR).SHOW CREATE TABLEof a table holding these types re-parses and recreates the same mapping types. A type written in a column's options (OPTIONS (type = 'wildcard')) is kept, shown byDESCRIBE/SHOW CREATE, and re-sent by ALTER (main re-sent the SQL type's own Elasticsearch type, which Elasticsearch refused).date_nanosarithmetic.dn ± n(days), per document, keeps nanoseconds in search and transform scripts.Docs:
documentation/client/gateway.md(mapping types, index patterns, a user who may not read the mapping),documentation/sql/known_limitations.md,documentation/sql/dql_statements.md.Release notes
DESCRIBE, JDBC, Arrow, set-operation slots, GROUP BY keys (ahalf_floatkey is aFloat), WHERE literal rules, MIN / MAX, and CTAS / materialized-view columns.constant_keywordanswers its value (was NULL). Pattern fields the indices disagree on take the lattice type.readprivilege is typed, gets the privileged user's answers and the same type refusals, and its SELECT, UNION ALL and scroll without a LIMIT answer (main: "Failed to get ES version").1.50, values of another type than the column's label, or a Painless error:iporconstant_keywordcolumn (main:ip + 1='10.0.0.11');scaled_float = keyword,ip > long,half_float > date), anddate_nanos * 2,date_nanos / 2.HAVING hf = 0.1on ahalf_floatkey: 0.1 has no exacthalf_floatvalue;ARRAY<X>(ALTER TABLE … ALTER COLUMN … SET DATA TYPE ARRAY<X>) answers it as a list and keeps the mapping type.DOUBLE+scaled_float,TIMESTAMP+date_nanos,REAL+half_float,KEYWORD+iporconstant_keyword), or a pattern column that merges to ANY: Elasticsearch answersNo mapping found for [<field>] in order to sort on(main sent anunmapped_type). A declared type over a different Elasticsearch type (KEYWORD+wildcard,INT+long) keeps main'sunmapped_type.w + 1over awildcard+keywordpattern is sent; main refused it)._field_capscarries no_meta(a declared DATE or TIME reads TIMESTAMP, ARRAY declarations are invisible), no date format (dates are read ISO-first), reportsconstant_keywordaskeyword(NULL as on main, now typed VARCHAR) and lists runtime fields as keyword columns (*shows them, NULL). Literals are forwarded as written. If the firstGET /of a client fails for any reason, the build major stands for that client's life.ColumnTypeChangedgains a second parameter list (fromMappingType,toMappingType), so its constructor,applyandcopysignatures change: binary-incompatible for a caller that constructs it. Extensions, arrow and JDBC do not construct it.TableTypegains a case object (additive).Acceptance evidence
*, WHERE literals, GROUP BY keys, HAVING key filters, MIN / MAX / SUM, ORDER BY over a pattern, arithmetic, CAST and conditionals (MappingTypeReadingSpecand its runners).JavaClientReadOnlyUserSpec— typed from_field_caps, the same answers and refusals as the privileged client except the residuals above, at most one mapping request per source per TTL,SELECT *without a LIMIT, pattern and UNION ALL answer with rows =_countand oneGET /per client.MergedSchemaSpec(every pair, member permutations, lists, formats);SELECT k FROM s7_p*over keyword"007"+ long7answers VARCHAR"007","7"on ES 8 and ES 6.8.GET _mappingafter ADD COLUMN differs by the new field only; the retypes above;SHOW CREATE TABLEre-parses and recreates the mapping types.JavaClientSchemaConsumerSpecreadsdescribeand the rows of a typed index and a pattern as arrow, JDBC and extensions do (plain, scroll, UNION ALL leg).Recorded, not fixed
NestedElements.mappedAsNestedstill reads the SQL type's Elasticsearch type, not the column's mapping type (no new type maps tonested, so nothing is widened).SHOW CREATE TABLEprints the index settings Elasticsearch generated (uuid,creation_date,provided_name,version.created), so running its text as written fails with "unknown setting" (as on main): CREATE TABLE does not strip them, ALTER and CTAS do.dn + 1as a function argument (CASE, COALESCE, GREATEST, CAST, DATE_TRUNC) andCASE … END IS NOT NULLin WHERE ("end of input expected"), as on main.keywordin one index andtextin another is decided by the firsttextmember in index-name order (documented).date_nanos: a shift of a function's result, per-group results and ingest processors stay in milliseconds (documented).COALESCE(scaled_float | half_float | unsigned_long, date)is now refused; main's answer was only rendered, not run.Test plan
sql/test2292/0;core/test1591/0; every bridge (es6, es7, es8, es9 and the shared one) green; es6 Jest, es6 REST, es7, es8 client unit specs green;+ compile(2.12 and 2.13)SELECT *withconstant_keyword, schema load via_field_caps,GET /counts — running; posted here before merge🤖 Generated with Claude Code