Repository navigation
fix(sql): set operations take DuckDB's types and answer one class per column; aggregates without GROUP BY answer one row - #420
Merged
Conversation
… column; aggregates without GROUP BY answer one row
- One rule types each column of a UNION ALL, UNION, INTERSECT or EXCEPT,
each field of an object and each element of a list, over every branch of
a node of DuckDB's operator tree. The type is DuckDB's where DuckDB gives
one, else VARCHAR, each value spelled as DuckDB spells it. Refused by
name: only what the relational engine fails on when the statement runs,
i.e. a date beside a number (with no text or binary branch) and a list or
object beside another family.
- Every branch of a position whose branches' types differ is converted to
its type, at every depth, so the position holds one class. A value is
first read as its declared type, as Elasticsearch indexed it:
- numbers stored as text;
- dates with their field's mapping format, as each Elasticsearch major
reads it (measured on 6.8.23, 7.17.29 and 8.18.3).
A date whose reading would depend on the client's JDK or time-zone data
fails, naming the field and the format.
- DuckDB's text for doubles and temporals comes from a port of {fmt} 6.1.2
and code translated from DuckDB 1.5.5, each with its MIT notice. The
notices are in THIRD-PARTY-NOTICES, in META-INF of the core jar, and in
the CLI bundles.
- A calculation over aggregates without GROUP BY answers one row, the
whole table being one group (#413):
- COUNT(*) reads the bucket count;
- counts and sums over no document are 0 on 7.17 and later;
- HAVING COUNT(*) = 0 keeps its row.
- A DATE or TIMESTAMP cast over a per-group date calculation applies.
- New private[elastic] MultiSearch.columnTypes / branchTypes over the
operator tree, for the relational engine.
- Binary incompatible: ElasticConversion.jsonToRows and LegProjection gain
a parameter.
Closes #413
Co-Authored-By: Claude Opus 5.5 (1M context) <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.
What
Set operations type their columns as DuckDB does. DuckDB is the engine that runs
UNION,INTERSECT,EXCEPTand everyUNION ALLthe relational engine (arrow) plans, so core's ownUNION ALLnow answers the same types, the same values and the same refusals.One rule types every depth alike: a column, each field of an object, each element of a list. Each node of DuckDB's operator tree is typed over all of its branches at once:
INTERSECT/EXCEPTare binary.BOOLEANTINYINT< … <DOUBLE)DATEandTIMESTAMPTIMESTAMPVARCHAR, each value spelled as DuckDB spells it (250000000000.0,1e-05,2024-01-31 10:30:00)VARBINARYwith text onlyVARBINARY(DuckDB'sBLOB)NULLliteralDATE/TIMESTAMPwith a number orBOOLEAN, no text and noVARBINARYbranchVARBINARYorTIMEwith a number or a date)VARCHARSTRUCT: every field in order of first appearance, each typed by these rules; points stayGEO_POINT, normalised to{lat, lon}One class per position. A position whose branches' types differ converts every branch's value to its type, at every depth. JDBC and Arrow, which read the type from the first value, then report it right.
"5") is a number.What answers differently from 0.23.0:
n UNION ALL xanswers aDOUBLEcolumn,d UNION ALL tsaTIMESTAMPcolumn, andid UNION ALL naVARCHARcolumn, one class each. 0.23.0 mixed the classes.d UNION ALL n). This now also applies inside objects and lists, where 0.23.0 answered each object as stored. That is a deliberate change: DuckDB fails on it,Unimplemented type for cast (INTEGER -> DATE).Also in this PR:
MAX(n) - MIN(n),COUNT(*) * 2,MAX(d) + 1). Elasticsearch refused these searches before.COUNT(*)reads the bucket count.COUNT(x)stays NULL: 6.8 refuses thekeep_valuesgap policy.HAVING COUNT(*) = 0keeps its row.GROUP BYfails in Elasticsearch:SELECT MAX(n) - MIN(n) FROM t#413.DATE/TIMESTAMPcast over a per-group date calculation applies:(MAX(d) + 1)::DATEis the day. It failed in Elasticsearch before.private[elastic]MultiSearch.columnTypes(requests, operators)andbranchTypes(requests, operators)give the type and the verdict over the operator tree (arrow will call them).THIRD-PARTY-NOTICES, inMETA-INF/THIRD-PARTY-NOTICESof the core jar, and in the CLI bundles.Measured
Verdicts against the relational engine (arrow, statement by statement, on Elasticsearch 8.18.3):
0.0forMIN/MAX/AVGof a column a whole group lacks,CREATE TABLE … AS SELECT … GROUP BYstores one empty document per source document, and values come back in the class Elasticsearch's JSON gives them (a date per group as epoch milliseconds) #414;ARRAY<T>reads asANY, A column declaredARRAY<INT>orARRAY<KEYWORD>reads back asANY, so DESCRIBE and the type checks lose its type #419);Against DuckDB 1.5.5: a random 330 statements across 6 depths and every operator all match the rule. Wherever DuckDB answers, the type is DuckDB's, apart from degenerate shapes where a branch has no non-NULL value.
Against main, on Elasticsearch 8.18.3:
Dates: corpora of every pattern letter, width and built-in name.
Tests on the final tree:
sql/test1838 / 0;core/test1197 / 0 on JDK 11 and on JDK 17;The A calculation over aggregates without
GROUP BYfails in Elasticsearch:SELECT MAX(n) - MIN(n) FROM t#413 and cast specs ran on 8.18.3, 7.17.29 and 6.8.23 earlier in this PR. CI runs every major.API changes (rebuild downstream modules)
ElasticConversion.jsonToRowstakes a new last parameter,converters: binary incompatible with code compiled against 0.23.0.LegProjectiongainsconverters(four fields to five).MultiSearch.columnTypes,branchTypes,SetOperationColumn,SetOperationType,StructField;SingleSearch.wholeTableCalculation;ElasticAggregation.wholeTableCalculationAggregation;ElasticConversion.rowProjector.Follow-ups (not in this PR)
0.0forMIN/MAX/AVGof a column a whole group lacks,CREATE TABLE … AS SELECT … GROUP BYstores one empty document per source document, and values come back in the class Elasticsearch's JSON gives them (a date per group as epoch milliseconds) #414:MIN(d)beside a timestamp (per-group dates as doubles).ROUND(x, 1)returns an integer, function results fail to compile under an operator or inGREATEST, a simpleCASEmatches NULL or fails over an expression, andORDER BY GREATEST(…)fails #416:ROUND(x, 1)loses its scale.SELECT 2 AS k FROM t#418: computed columns as one-element arrays. Includes the class mix of computed numeric and date branches in a set operation.ARRAY<INT>orARRAY<KEYWORD>reads back asANY, so DESCRIBE and the type checks lose its type #419:ARRAY<T>columns read asANY; derived-table columns untyped in the set-operation check.🤖 Generated with Claude Code