Skip to content

[common] Read a shredded variant decimal as the same string as an unshredded one - #9818

Open
zhuxiangyi wants to merge 1 commit into
apache:masterfrom
zhuxiangyi:variant-shredded-decimal-string
Open

zhuxiangyi wants to merge 1 commit into
apache:masterfrom
zhuxiangyi:variant-shredded-decimal-string

Conversation

@zhuxiangyi

Copy link
Copy Markdown
Contributor

Purpose

This is a bug fix. Extracting a decimal as a string returns different text depending on whether the file is shredded:

CREATE TABLE T (id INT, v VARIANT);
INSERT INTO T VALUES (1, parse_json('{"price":1.50}')), (2, parse_json('{"price":0.05}'));

SELECT variant_get(v, '$.price', 'string') FROM T;
-- plain file, or Spark evaluating itself : "1.5",  "0.05"
-- file shredded with price DECIMAL(18, 2): "1.50", "0.05"

The unshredded leg (VariantGet.cast) casts from getDecimal(), which strips trailing zeros, so it matches Spark's VariantGet. The shredded leg (BaseVariantReader.ScalarReader) reads the typed_value with the scale of the file schema and its string cast keeps that scale. Only decimal → string is affected; numeric targets agree on both legs.

This matters because the docs promise that "the SQL and result are the same for plain, shredded, and mixed-layout files", 'string' is the one target type a strict variant_get is pushed into the scan for, and with variant.inferShreddingSchema a table routinely mixes plain and shredded files, so a single column yields 1.5 for some rows and 1.50 for others.

The fix extracts the normalization already used by the unshredded leg (strip trailing zeros, fold a negative scale, keep precision at least the scale) into VariantGet.normalizedDecimal and applies it to a typed_value decimal before it is cast, so both legs cast from the same value.

Tests

The new unit and format tests reproduce the bug: on master they fail with expected: +I(10,1.5,0.05,0,...) but was: +I(10.0,1.50,0.05,0.00,...), and pass with this change.

  • PaimonShreddingUtilsTest#testShreddedDecimalCastsLikeUnshredded: the same extractions through a shredded and an unshredded VariantSchema yield identical strings, and decimal / double / bigint targets are unchanged.
  • VariantShreddingReadTest#testReadDecimalAsStringConsistently: parameterised over a plain Parquet file and one shredded with price DECIMAL(18, 1), amount DECIMAL(18, 2).
  • VariantTestBase: variant_get(..., 'string') on values whose inferred scale keeps trailing zeros; runs under all four Spark 4.x configurations (with/without pushdown, with/without inferred shredding). Verified on Spark 4.1.2: 116 tests pass.

API and Format

No.

Documentation

No.

…hredded one

The unshredded leg of a variant extraction casts a decimal after
getDecimal() has stripped its trailing zeros, so 10.0 reads as "10". The
shredded leg reads the typed_value with the scale of the file schema, e.g.
DECIMAL(18, 1), and its string cast keeps that scale: the same value reads
as "10.0" from a shredded file, and as "1.50" instead of "1.5" once the
inferred scale is 2. With inferred shredding a table mixes both layouts,
so one column yields two spellings of the same number, and a pushed-down
variant_get(..., 'string') differs from Spark's own evaluation.

Move the normalization into a helper shared by both legs and apply it to a
typed_value decimal before casting. Numeric targets are unaffected.
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.

1 participant