Uh oh!
There was an error while loading. Please reload this page.
[fix](arrow-flight) Keep the Doris type of a nested LARGEINT in the Arrow schema - #67530
[fix](arrow-flight) Keep the Doris type of a nested LARGEINT in the Arrow schema#67530morningman wants to merge 1 commit into
Conversation
…rrow schema
Arrow has no equivalent for LARGEINT, IPV4, IPV6, JSON or VARIANT, so each of
them travels over Flight SQL as some other Arrow type and is then
indistinguishable from a column that is natively of that type: a LARGEINT and a
STRING both arrive as utf8, an IPV4 and an INT both arrive as int32. The field
metadata (`doris_type`) is the only thing that tells them apart.
That metadata was attached in `get_arrow_schema_from_block` and
`get_arrow_schema_from_expr_ctxs`, which only see the top level columns.
`convert_to_arrow_type` recurses into ARRAY, MAP and STRUCT building Arrow
*types*, and Arrow keeps metadata on the Field rather than on the DataType, so
every nested element lost it: `ListType(item_type)` synthesizes a bare "item"
field, `MapType(key_type, val_type)` synthesizes bare "key"/"value" fields, and
the STRUCT branch built its fields without going through the metadata helper.
A Python ADBC client reading
SELECT CAST(495 AS LARGEINT),
named_struct('count', CAST(495 AS LARGEINT)),
[CAST(495 AS LARGEINT)],
map('k', CAST(495 AS LARGEINT))
therefore got `doris_type=LARGEINT` on the first column only, and had no way to
tell the other three from business strings. A nested IPV4 was worse: it arrives
as its 32 bits read as a signed int32 (192.168.1.1 as -1062731519) with nothing
left to say it was ever an address.
Build the child Fields through `create_arrow_field_with_metadata` at every
level. The names and the nullability are the ones Arrow's own constructors
produced -- "item" nullable, "key" non-nullable, "value" nullable -- so only the
metadata is new, and the record batch builders, which are made from this schema,
are unaffected.
Also complete the lookup: JSONB and VARIANT are serialized as utf8 too and were
carrying no `doris_type` at all, not even at the top level. The values match
what the FE reports for the same column under ARROW:FLIGHT:SQL:TYPE_NAME.
Related Jira: DORIS-28389
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FP36MDAsUyKSSDQXQohQsKhello-stephen
commented
Sep 4, 2026
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
yiguolei
left a comment
There was a problem hiding this comment.
wait, I will refactor these types
morningman
commented
Sep 5, 2026
@yiguolei Sure. Before you start, here is what we need from that refactor, so that our follow-up work can build on it instead of fighting it. Context: we are preparing a proposal that makes MySQL and Arrow Flight SQL equal front ends over one session layer, and its type-mapping part depends on exactly the code this PR touches (
Could you share the intended scope and rough timing? We would rebase our type-mapping PRs on top of it, and it also settles whether this PR should go in first or be folded into the refactor. |
What problem does this PR solve?
Related Jira: DORIS-28389
Problem Summary:
Arrow has no equivalent for LARGEINT, IPV4, IPV6, JSON or VARIANT. Each of them travels over Flight SQL as some other Arrow type and is then indistinguishable from a column that is natively of that type -- a LARGEINT and a STRING both arrive as
utf8, an IPV4 and an INT both arrive asint32. Thedoris_typefield metadata is the only thing that tells them apart.That metadata was attached in
get_arrow_schema_from_blockandget_arrow_schema_from_expr_ctxs, which only see the top level columns.convert_to_arrow_typerecurses into ARRAY / MAP / STRUCT building Arrow types, and Arrow keeps metadata on theFieldrather than on theDataType, so every nested element lost it:ListType(item_type)synthesizes a bare"item"field,MapType(key_type, val_type)synthesizes bare"key"/"value"fields, and the STRUCT branch built its fields without going through the metadata helper.Reading this through a Python ADBC client:
Before -- only the top level column is identifiable:
After:
A nested IPV4 was the worse case of the same defect: it arrives as its 32 bits read as a signed
int32(192.168.1.1as-1062731519) with nothing left to say it was ever an address.What is changed?
convert_to_arrow_typenow builds the childFields throughcreate_arrow_field_with_metadataat every level, for ARRAY, MAP and STRUCT. The names and the nullability are exactly the ones Arrow's own constructors produced --"item"nullable,"key"non-nullable,"value"nullable -- so only the metadata is new, and the record batch builders, which are made from this same schema (FromBlockToRecordBatchConverterreads_schema->field(idx)->type()), are unaffected.DataType::Equalsignores metadata by default, so the batch still type-matches the schema.The metadata lookup is also completed: JSONB and VARIANT are serialized as
utf8too and were carrying nodoris_typeat all, not even at the top level. The values (JSON,VARIANT) match what the FE reports for the same column underARROW:FLIGHT:SQL:TYPE_NAMEinFlightSqlSchemaHelper.No value or Arrow type changes -- this is metadata only.
Release note
Fix Arrow Flight SQL losing the Doris logical type (
doris_typefield metadata) of LARGEINT / IPV4 / IPV6 nested inside STRUCT, ARRAY and MAP, which made a nested LARGEINT indistinguishable from a STRING for ADBC clients. JSON and VARIANT columns now carry the same metadata as well.Check List (For Author)
Test
New
be/test/format/arrow/arrow_row_batch_test.cpp, 7 cases:nested LARGEINT in STRUCT / ARRAY / MAP value / MAP key; every level of
array<struct<largeint>>andmap<string, array<array<largeint>>>;nested IPV4 / IPV6 / JSON / VARIANT; a negative control asserting STRING and
INT carry no metadata nested or not; an assertion that the nested types still
compare equal to
arrow::list(utf8())/arrow::map(utf8(), utf8())withcheck_metadata=falseand differ only withcheck_metadata=true; and onethat runs
convert_to_arrow_batchover the new schema and checksValidateFull(), schema equality including metadata, and the values.Verified the assertions are not vacuous: with the
arrow_row_batch.cppchangereverted, 5 of the 7 fail; the negative control and the batch-building guard
correctly stay green.
Built BE locally and ran the Jira's repro through
adbc_driver_flightsql(Python), plus a real table withlargeint,array<largeint>,struct<count:largeint, name:string>,map<string,largeint>,ipv4andjsoncolumns. Nested fields now carrydoris_typeat every depth, a STRING sibling inside the same STRUCT correctlycarries none, and the values are unchanged (LARGEINT extremes round trip
losslessly as text).
Behavior changed:
The Arrow schema returned over Flight SQL now carries
doris_typefieldmetadata on nested fields, and on top level JSON / VARIANT columns. Arrow
types, field names, nullability and values are unchanged, so a client that
ignores metadata sees no difference.
Does this need documentation?
🤖 Generated with Claude Code
https://claude.ai/code/session_01FP36MDAsUyKSSDQXQohQsK