[python] Render min/max_partition_stats in the $manifests system table#8842
[python] Render min/max_partition_stats in the $manifests system table#8842wombatu-kun wants to merge 3 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Done 9df763e - FLOAT/DOUBLE now render via Java's Float/Double.toString format (uppercase E, no + sign, no zero-padded exponent; e.g. 1.0E7 / 1.0E-5). |
|
The new floating-point format implementation does not fully match Java 8’s |
…xactly Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The float formatter is removed rather than fixed. Both points reproduce, and matching pre-JDK-19 output would mean porting the legacy Auditing the rest the same way, TIMESTAMP WITH LOCAL TIME ZONE is excluded for the same reason (Java formats it in |
Purpose
This closes a TODO in
pypaimon/table/system/manifests_table.py:So
$manifestsreturns NULL formin_partition_statsandmax_partition_statsin pypaimon while Java renders them, and inspecting which manifests cover which partitions gives nothing on the Python side. The data is already parsed:ManifestListManagerbuildsSimpleStatsfrom the manifest list, only the rendering was missing.This adds
cast_row_to_string, a port ofRowToStringCastRuleand the per type*ToStringCastRulerules ofpaimon-common, with fields joined by", "inside braces, the literalnullfor a null field, and{}for an unpartitioned table. The rendered types are BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, DECIMAL, CHAR, VARCHAR, DATE, TIME and TIMESTAMP. Those needing more thanstr()are boolean (lower case), decimal (BigDecimal.toPlainString, never scientific, so a DECIMAL(20, 9) zero is0.000000000and not0E-9), date, time and timestamp. Timestamps followDateTimeUtils.formatTimestampand times followformatTimestampMillis, which generates the fraction digit by digit and stops only once the remainder is exactly zero, so neitherdatetime.isoformat()nor stripping trailing zeros reproduces them.Three types are deliberately not rendered, because their Java output cannot be reproduced. A row holding one of them is not rendered at all, so the two columns keep the NULL they return today:
Float/Double.toString, which up to JDK 18 is the legacyFloatingDecimaland does not produce the shortest round tripping decimal, while JDK 19+ does. Measured against JDK 17 on 200k random values per type, the shortest form differs on 10.86% of float32 and 0.28% of float64 values (2.68873286E11against2.6887329E11), and a JDK 21 JVM prints the shortest form instead, so there is no single Java rendering to target. Matching pre JDK 19 exactly would mean portingFloatingDecimalitself.TimeZone.getDefault(), so the same manifest reads2024-01-02 03:04:05on a UTC JVM and2024-01-02 06:04:05on a Europe/Moscow one.ED A0 80is one in Java and three in Python.One point for a maintainer opinion, deliberately not changed here: pypaimon renders the
partitioncolumn of$filesand$bucketsaspt=v/pt2=v2, while Java renders those two with the brace form and keepspt=vonly for$partitions. Aligning them would change values these tables already return, so it is out of scope for this PR.Tests
New
pypaimon/tests/row_to_string_test.pycovers the rendering per type, a null field, an empty row, the timestamp fraction at precision 0/3/6, the TIME digit generation (.10and not.1for 101 ms at precision 2), the decimal plain form, and that a row holding a FLOAT, DOUBLE, TIMESTAMP_LTZ or BINARY field renders as None whilecast_value_to_stringrejects those types outright.pypaimon/tests/system/manifests_table_test.pyreplaces the assertions that pinned the NULL placeholder: an unpartitioned table yields{}, a table partitioned by INT and STRING yields{1, 2024-01-01}and{2, 2024-01-02}, a DECIMAL(20, 9) partition yields{0.000000000}, and DOUBLE and BYTES partitions keep both columns NULL.Everything still rendered was verified against
RowToStringCastRuleitself, driving the realCastExecutorsfrom a builtpaimon-commonon JDK 8 and comparing whole rows rather than single values: every DATE a Pythondatecan hold, the full TIME precision and millisecond space, all DECIMAL precision and scale pairs, TIMESTAMP precision 0 to 9 over epochs from year 1 to 9999, integer boundaries, strings containing braces and the,separator, and randomly generated multi field rows with nulls. No differences remain, and the same harness does flag the ones the previous commit of this PR had.