Skip to content

[refactor](arrow-flight) Extract the Doris-to-Arrow type mapping into DorisArrowTypeMapping - #68315

Open
morningman wants to merge 3 commits into
apache:masterfrom
morningman:flight-arrow-type-mapping
Open

morningman wants to merge 3 commits into
apache:masterfrom
morningman:flight-arrow-type-mapping

Conversation

@morningman

@morningman morningman commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: #67577

Related PR: #66344 (the GetTables schema fix whose nested-type tests move here), #67966 / #68266 (the Flight SQL session work the next interfaces build on)

Problem Summary:

Context. Doris speaks two client protocols: MySQL and Arrow Flight SQL (the Flight SQL JDBC driver, the ADBC drivers, Python clients). Over Flight the rows come as Arrow batches produced by BE, and convert_to_arrow_type in be/src/format/arrow/arrow_row_batch.cpp decides which Arrow type each Doris type has on the wire.

Besides data, Flight SQL has metadata commands. GetTables(include_schema = true) is the client asking "which columns does this table have, and what Arrow type is each one?", and FE answers with a serialized Arrow schema in the table_schema column. Clients trust that schema: the ADBC driver types its columns from it and then decodes the batches of later queries as those types. So what FE says has to match what BE sends; a mismatch is not a degraded answer but a failed read (DATEV2 described as date64 while BE emitted date32 broke exactly that way, fixed in #66344).

For that FE keeps a Doris-to-Arrow mapping, which until now was a private method of FlightSqlSchemaHelper, the class that serves GetTables: getArrowType, plus the buildField / arrowChildren pair that builds a field and its nested children.

1. The problem, and what it cost

  • A second and a third caller are about to arrive. The next Flight SQL work all produces Arrow schemas: more metadata commands, the parameter and result schemas of prepared statements, ExecuteSchema. They ask the same question, "what Arrow type is this Doris type", but the answer was a private method of another class, so each of them could only copy the switch. Copies drift: it already happened once between FE and BE ([feature](timestamp_ns) Add end-to-end TIMESTAMP_NS support #66761 added TIMESTAMP_NS as one line on each side), and another copy inside FE would let GetTables describe a column as one type and a prepared statement as another.
  • The table was not pinned. The existing tests covered nested types and DATEV2; the scalar table itself could change without any test noticing. And some cells are known to disagree with BE and are kept that way on purpose: TIMESTAMPTZ carries the literal zone "UTC" (BE stamps the session time zone), and TIMEV2 / VARBINARY / AGG_STATE come back as Null (BE emits float64 / binary / binary). They are kept because the BE Arrow type layer is being reworked by another team and changing the mapping now would collide with that, so the correction is scheduled as one step after it (recorded in [Tracking] Protocol-agnostic session and execution layer: MySQL and Arrow Flight SQL as equal front ends #67577). Without a test pinning them, nothing stopped a well-meant one-cell "fix" that would change behaviour piecemeal and out of step.

2. What this PR does, and why it helps

The mapping moves into a class of its own, org.apache.doris.arrow.DorisArrowTypeMapping, the one place in FE that maps a Doris type to an Arrow type, with no value changed. Three commits:

  1. A table-driven unit test against the old code first: one row per PrimitiveType (41 values; the types whose unit depends on the scale get a row per band, 47 rows in all), the known-wrong cells pinned as they are and marked "kept as is ([Tracking] Protocol-agnostic session and execution layer: MySQL and Arrow Flight SQL as equal front ends #67577)", plus a check that every PrimitiveType has a row. Green against the old private method.
  2. The move. The switch, the nested-type rules and the column metadata are byte for byte what they were; the test's rows do not change and stay green, which is the evidence for "no value changed".
  3. A regression suite, arrow_flight_sql_p0/test_get_tables_schema: a raw Flight SQL client asks a live cluster for GetTables of a table that declares a column of every type (three-level nesting, BITMAP / HLL / AGG_STATE, DECIMAL256 included) and pins, field by field, the type, the nullability and the column metadata a client sees. It covers what the unit test cannot: the whole path from describeTables' descriptors through the mapping to the serialization and the client's decoding.

What it buys:

  • The coming callers call, they do not copy; every schema FE hands out agrees with every other by construction.
  • When the BE rework lands and the mapping is corrected, the change is made in this one class (plus the tests' expectations) and every schema changes together, instead of call site by call site.
  • The known-wrong cells are pinned by two layers of tests that say why; any change to them fails a test, so it can only be made deliberately.
  • A new PrimitiveType without a mapping fails the test instead of silently falling into default -> Null.
  • Nothing changes for users: GetTables returns the same bytes. Checked by dumping every field of the GetTables result with pyarrow from an FE built before this PR and from one built at its head: 52 lines, identical.

3. The classes, and how they call each other

  • DorisFlightSqlProducer (existing): the Flight SQL server; on CommandGetTables, getStreamTables creates a FlightSqlSchemaHelper.
  • FlightSqlSchemaHelper (existing, slimmed): the GetTables plumbing only. It lists databases and tables and calls describeTables through FrontendServiceImpl to get each column's TColumnDesc (a thrift descriptor with precision, scale and nested children), hands each column to the mapping for an Arrow Field, and getSerializedSchema writes the fields as Arrow IPC bytes into table_schema.
  • DorisArrowTypeMapping (new):
    • toArrowType(PrimitiveType, precision, scale): the table itself (the switch), a mirror of BE's convert_to_arrow_type.
    • toArrowType(TColumnDesc): reads precision and scale off the descriptor and calls the above.
    • toField(db, table, TColumnDesc): builds the Field: the type, the nullability, the Flight SQL column metadata a JDBC ResultSetMetaData reads (TYPE_NAME / PRECISION / SCALE / SCHEMA_NAME / TABLE_NAME ...), and the children, recursively (an ARRAY's item, a MAP's entries<key, value> with the key forced non-nullable, a STRUCT's fields).
  • FrontendServiceImpl.getColumnDesc (existing, untouched): turns a catalog Column into a TColumnDesc.
  • BE convert_to_arrow_type (untouched): the sole authority on the shape of the data on the wire; FE's table mirrors it.
  • Tests: DorisArrowTypeMappingTest (table + nesting), FlightSqlSchemaHelperSerializedSchemaTest (serialization round trips), regression arrow_flight_sql_p0/test_get_tables_schema.
Flight SQL client (JDBC / ADBC)
   |  GetTables(include_schema = true)
   v
DorisFlightSqlProducer.getStreamTables
   |  new FlightSqlSchemaHelper(ctx).getTables(...)
   v
FlightSqlSchemaHelper ----> FrontendServiceImpl.getDbNames / listTableStatus / describeTables
   |                                    '- Column --getColumnDesc--> TColumnDesc (precision / scale / children)
   |  per column: DorisArrowTypeMapping.toField(db, table, desc)        <-- the new class, moved out of the helper
   |                 |- toArrowType(desc) --> toArrowType(primitiveType, precision, scale)   [the table = mirror of BE convert_to_arrow_type]
   |                 |- flightSqlColumnMetadata(...)                                          [TYPE_NAME / PRECISION / SCALE / SCHEMA_NAME / TABLE_NAME]
   |                 '- children(...) --> toField on each child, recursively                 [ARRAY item / MAP entries<key, value> / STRUCT fields]
   |  getSerializedSchema(fields) --> Arrow IPC bytes
   v
table_schema column --> the client deserializes it and types its columns from it
                    --> decodes the batches of later queries with those types (batches produced by BE per convert_to_arrow_type)

Callers after this PR: metadata commands / prepared statement parameter & result schemas / ExecuteSchema --> the same DorisArrowTypeMapping

Not touched, so nobody goes looking: BE is unchanged, and FE-side results (SHOW ..., all utf8 in FlightSqlChannel) are unchanged too; that is another recorded item that also waits for the BE rework.

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)

    Unit tests. DorisArrowTypeMappingTest: 47 table rows (every PrimitiveType; DATETIMEV2 at scales 0 / 1 / 3 / 4 / 6, TIMESTAMPTZ at 0 / 3 / 6, DECIMALV2 with a declared precision it ignores), the every-type-has-a-row check, and the seven nested-type cases moved from FlightSqlSchemaHelperArrowTypeTest; FlightSqlSchemaHelperSerializedSchemaTest: the two round trips. 57 cases, the same 57 the first commit runs against the old code.

    Regression. arrow_flight_sql_p0/test_get_tables_schema (new): the scalars, DATETIME and TIMESTAMPTZ at three scales each, TIMESTAMP_NS, DECIMAL(9,2) / (18,4) / (38,10) / (76,20), JSON, VARIANT, IPV4 / IPV6, BITMAP / HLL / QUANTILE_STATE / AGG_STATE, ARRAY / MAP / STRUCT and a three-level nesting, one expected line per field; it fails on a single changed cell (checked by breaking one on purpose). Whole arrow_flight_sql_p0 green locally against an FE built from this branch (14 suites, 0 failed); test_select also reads DatabaseMetaData.getColumns, which is GetTables(include_schema) through the Flight SQL JDBC driver.

    Manual. The same GetTables(include_schema = true) dump (type, nullability, metadata, children of every field) taken over raw Flight with pyarrow from an FE built before this PR and from one built at its head: identical, 52 lines each. The suite's expected blocks are that dump.

  • Behavior changed:

    • No.
  • Does this need documentation?

    • No.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

morningman and others added 3 commits September 21, 2026 15:05
…, one row per PrimitiveType

The schema in the table_schema column of Flight SQL GetTables is the only
statement FE makes about the Arrow type of a column, and a client that
types its columns from it reads the batches BE emits as that type. Until
now only the nested-type rules and DATEV2 were covered; the scalar table
itself, including the cells known to disagree with BE (TIMESTAMPTZ with a
literal "UTC" zone, Null for TIMEV2 / VARBINARY / AGG_STATE, see apache#67577),
could change without any test noticing.

This records the table exactly as it is, one row per PrimitiveType plus
one per precision / scale band where the band picks the Arrow unit, and
fails when a PrimitiveType has no row, so a new type gets a deliberate
mapping rather than the default. It is the baseline for extracting the
mapping into a class of its own with no value changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… DorisArrowTypeMapping

FE is about to hand clients more Arrow schemas than the one in GetTables:
the result and parameter schemas of prepared statements, ExecuteSchema.
Each of them must describe a column exactly as the others do, or a client
that types its columns from one of them fails to read the batches another
promised. Until now the only mapping lived as private methods of
FlightSqlSchemaHelper, where a second caller could only copy it.

This moves getArrowType, the column-descriptor overload and the field
builder with its nested-type rules into org.apache.doris.arrow.
DorisArrowTypeMapping, unchanged: the switch, the children rules and the
Flight SQL column metadata are byte for byte what they were, and the
table-driven test recorded in the previous commit passes against the new
class with no row touched. The cells known to disagree with BE (the
literal "UTC" zone on TIMESTAMPTZ, Null for TIMEV2 / VARBINARY /
AGG_STATE) are carried over as they are: they are fixed in one step
against a golden shared with BE once the BE Arrow type layer has been
reworked, not one call site at a time (apache#67577).

FlightSqlSchemaHelper keeps the GetTables plumbing and the schema
serialization; the tests split the same way, the mapping and nesting
cases into DorisArrowTypeMappingTest, the serialized-schema round trips
into FlightSqlSchemaHelperSerializedSchemaTest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s, one table of every type

DorisArrowTypeMappingTest records the mapping as a Java table; this
records what reaches a client. A raw Flight SQL client asks
GetTables(include_schema) for three tables that together declare a
column of every type an internal table can hold -- the scalars, DATETIME
and TIMESTAMPTZ at three scales each, TIMESTAMP_NS, DECIMAL(9,2) /
(18,4) / (38,10) / (76,20), JSON, VARIANT, IPV4 / IPV6, BITMAP / HLL /
QUANTILE_STATE / AGG_STATE, ARRAY / MAP / STRUCT and a three-level
nesting -- decodes each table_schema the way a client does and compares
it, field by field and down to the leaves, with the Arrow type, the
nullability and the Flight SQL column metadata as they are served today.
The path it covers is the whole of it: describeTables' descriptors,
DorisArrowTypeMapping, the schema serialization.

The cells known to disagree with BE are pinned as they are and say so:
the literal "UTC" zone on TIMESTAMPTZ and Null for AGG_STATE (TIMEV2 and
VARBINARY cannot be declared on an internal table; the unit test pins
those). Correcting them is one deliberate step in DorisArrowTypeMapping
once the BE Arrow type layer has been reworked (apache#67577), and this suite
is what makes a stray change to any of them visible in CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 27876 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit a197bf5f88ba27f53d765170813b7761e2f1add1, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17761	3910	4126	3910
q2	2179	369	308	308
q3	10135	1399	798	798
q4	4685	481	353	353
q5	7499	815	554	554
q6	185	176	141	141
q7	773	769	588	588
q8	9360	1522	1458	1458
q9	5319	4158	4134	4134
q10	6821	1334	1021	1021
q11	434	272	238	238
q12	626	416	301	301
q13	18105	2626	1984	1984
q14	259	263	237	237
q15	q16	725	716	660	660
q17	1832	1188	1050	1050
q18	6453	5579	5540	5540
q19	1155	1264	1057	1057
q20	464	394	266	266
q21	5573	3418	2972	2972
q22	423	366	306	306
Total cold run time: 100766 ms
Total hot run time: 27876 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4532	4442	4398	4398
q2	735	546	541	541
q3	4742	5316	4550	4550
q4	2338	2301	1482	1482
q5	4587	4367	4561	4367
q6	231	176	141	141
q7	1860	1637	1497	1497
q8	2333	1970	2082	1970
q9	7306	6841	6807	6807
q10	3638	3556	3085	3085
q11	515	371	340	340
q12	711	706	515	515
q13	2304	2624	1987	1987
q14	267	283	263	263
q15	q16	656	684	615	615
q17	7289	6714	6661	6661
q18	11908	11056	11704	11056
q19	1095	1026	975	975
q20	2211	2168	1902	1902
q21	4931	4040	4275	4040
q22	504	460	388	388
Total cold run time: 64693 ms
Total hot run time: 57580 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152680 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit a197bf5f88ba27f53d765170813b7761e2f1add1, data reload: false

query5	4329	598	463	463
query6	423	209	195	195
query7	4821	567	290	290
query8	319	178	165	165
query9	8776	4014	4007	4007
query10	451	297	250	250
query11	5846	3545	3215	3215
query12	144	90	88	88
query13	1244	583	412	412
query14	6513	4611	4272	4272
query14_1	4080	3998	4000	3998
query15	204	203	176	176
query16	990	458	435	435
query17	920	678	548	548
query18	2440	472	342	342
query19	202	178	143	143
query20	85	80	82	80
query21	218	137	119	119
query22	13011	12950	12754	12754
query23	13962	12976	12479	12479
query23_1	12474	12438	12473	12438
query24	7252	1116	666	666
query24_1	696	682	690	682
query25	525	414	345	345
query26	863	285	161	161
query27	2699	557	333	333
query28	4504	1945	1934	1934
query29	1443	695	506	506
query30	303	218	183	183
query31	863	751	665	665
query32	144	90	95	90
query33	524	304	232	232
query34	1158	1088	624	624
query35	727	739	651	651
query36	795	800	698	698
query37	142	107	90	90
query38	1833	1747	1728	1728
query39	688	664	668	664
query39_1	631	647	661	647
query40	217	122	100	100
query41	66	64	63	63
query42	95	96	96	96
query43	334	345	300	300
query44	1362	705	707	705
query45	191	176	168	168
query46	1071	1167	721	721
query47	1492	1509	1405	1405
query48	402	384	290	290
query49	568	408	294	294
query50	978	346	262	262
query51	10728	10386	10446	10386
query52	86	91	76	76
query53	237	246	173	173
query54	250	203	187	187
query55	78	74	68	68
query56	240	227	212	212
query57	1501	1408	1412	1408
query58	281	263	247	247
query59	1984	2089	1849	1849
query60	281	242	225	225
query61	155	148	143	143
query62	387	324	262	262
query63	216	176	175	175
query64	2256	1087	959	959
query65	3478	3420	3403	3403
query66	1836	431	321	321
query67	20093	19896	19659	19659
query68	3326	1486	883	883
query69	425	316	263	263
query70	900	826	844	826
query71	311	233	214	214
query72	2787	2819	2260	2260
query73	832	751	409	409
query74	4602	4490	4313	4313
query75	2314	2286	1974	1974
query76	2340	1136	776	776
query77	365	398	310	310
query78	9160	9256	8566	8566
query79	1361	1243	819	819
query80	726	433	356	356
query81	548	325	280	280
query82	622	164	127	127
query83	273	221	199	199
query84	312	139	115	115
query85	877	451	377	377
query86	335	239	222	222
query87	1992	1956	1847	1847
query88	3589	2743	2709	2709
query89	348	292	243	243
query90	1866	184	181	181
query91	168	156	128	128
query92	104	88	117	88
query93	1515	1534	929	929
query94	563	348	293	293
query95	654	445	342	342
query96	1075	783	336	336
query97	2422	2465	2338	2338
query98	167	153	147	147
query99	715	734	625	625
Total cold run time: 235284 ms
Total hot run time: 152680 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 23.87 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit a197bf5f88ba27f53d765170813b7761e2f1add1, data reload: false

query1	0.00	0.00	0.01
query2	0.09	0.05	0.05
query3	0.26	0.14	0.14
query4	1.61	0.14	0.13
query5	0.24	0.22	0.21
query6	1.16	0.94	0.93
query7	0.04	0.01	0.00
query8	0.05	0.03	0.03
query9	0.39	0.34	0.34
query10	0.54	0.58	0.57
query11	0.22	0.15	0.13
query12	0.18	0.14	0.14
query13	0.48	0.47	0.48
query14	0.95	0.94	0.94
query15	0.61	0.59	0.58
query16	0.30	0.35	0.32
query17	1.09	1.10	1.08
query18	0.22	0.21	0.21
query19	1.96	1.85	1.93
query20	0.02	0.01	0.01
query21	15.48	0.23	0.13
query22	4.84	0.06	0.06
query23	16.13	0.31	0.12
query24	2.97	0.44	0.33
query25	0.11	0.06	0.05
query26	0.75	0.21	0.14
query27	0.03	0.04	0.03
query28	3.53	0.80	0.33
query29	12.49	4.06	3.19
query30	0.27	0.15	0.18
query31	2.76	0.58	0.32
query32	3.23	0.59	0.49
query33	3.24	3.17	3.17
query34	15.63	3.98	3.26
query35	3.25	3.23	3.20
query36	0.57	0.44	0.42
query37	0.09	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.19	0.15	0.14
query41	0.08	0.03	0.02
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.22 s
Total hot run time: 23.87 s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants