[fix](arrow-flight) Keep the Doris type of a nested LARGEINT in the Arrow schema - #67530
Open
morningman wants to merge 1 commit into
Open
[fix](arrow-flight) Keep the Doris type of a nested LARGEINT in the Arrow schema#67530morningman wants to merge 1 commit into
morningman 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_01FP36MDAsUyKSSDQXQohQsK
Contributor
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
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 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