SEPURI-SAI-KRISHNA commented on PR #11724:
URL: https://github.com/apache/seatunnel/pull/11724#issuecomment-5294127957

   Thanks for catching this, and for saying plainly that it was a carryover 
from your earlier pass — but I don't think the miss is mainly yours. You named 
the scale gap in writing in the 2026-08-10 review and I read that sentence, 
agreed with "pre-existing, out of scope", and moved on without tracing where 
the value goes either. You wrote it down; I had the same information and drew 
the same wrong conclusion. The difference is that you went back and checked.
   
   **Issue 1 is confirmed.** I reproduced it before changing anything, by 
asking the transform for its own declared output type and comparing it against 
what it actually emits, on `DECIMAL(38,2)` operands `10.00 * 3.00`:
   
   | column | declared | emitted | |
   |---|---|---|---|
   | `a + b` | `Decimal(38, 2)` | `13.00` scale 2 | ok |
   | `a - b` | `Decimal(38, 2)` | `7.00` scale 2 | ok |
   | `a * b` | `Decimal(38, 2)` | `30.0000` scale 4 | **mismatch** |
   | `a / b` | `Decimal(38, 2)` | `3.33` scale 2 | ok |
   
   Your reading of the before/after is exactly right, and I checked that too 
rather than assuming. Under the old lossy conversion `10.00 * 3.00` went 
through `BigDecimal.valueOf(10.0)` → `"10.0"` (scale 1), so the product landed 
on scale 2 and happened to match; but `10.25 * 3.75` produced scale 4 and 
mismatched even then. So this was intermittent and value-dependent before, and 
deterministic after. Not a new class of bug, but "guaranteed" is what breaks 
jobs that were running, and your Parquet/Avro trace is the path that turns it 
into a hard failure rather than a formatting difference.
   
   ## Fix: Option A
   
   I took Option A — normalize the product to the declared scale, the way 
`Division` already does:
   
   ```java
   if (binaryExpression instanceof Multiplication) {
       // BigDecimal.multiply returns a value whose scale is leftScale + 
rightScale,
       // but the column type declared for this expression is
       // DECIMAL(max(precision), max(scale)). Normalise to the declared scale, 
as
       // Division already does, so the emitted value matches the schema the 
transform
       // advertises: a sink that encodes against that schema (Parquet through 
Avro,
       // for example) rejects a value whose scale differs from the declared 
one.
       DecimalType decimalType = (DecimalType) resultType;
       return leftBigDecimal
               .multiply(rightBigDecimal)
               .setScale(decimalType.getScale(), RoundingMode.HALF_UP);
   }
   ```
   
   Reasoning for A over B, since you asked for the choice to be explicit rather 
than implicit:
   
   Option B is the more principled fix in isolation — `sLeft + sRight` is what 
standard SQL decimal multiplication does, and it keeps the value exact. But it 
changes the **declared output schema** of every existing multiplication job, 
and under this project's backward-compatibility rules a schema change is a 
strictly bigger breakage than a value change: a sink whose target column was 
created from the old declared type now gets a wider type, and precision would 
also need a widening rule with an answer for what happens past `DECIMAL(38, 
…)`. It would also mean touching `ZetaSQLType`, which #11697 modified two days 
ago. That is a real design change to decimal type inference and it deserves its 
own PR, its own `incompatible-changes.md` entry, and a maintainer's decision — 
not a fourth-round amendment to a precision bug fix.
   
   On the tension you flagged with this PR's "keep full precision" framing — 
I'd argue Option A doesn't actually give any of it back. What changed is 
*where* the rounding happens. The old code rounded the **operands** and 
multiplied the damaged values; the new code multiplies exactly and rounds the 
**result** once. For the existing test case the old code emitted 
`1234567890123456.8` where the true product is `1234567890123456.7899`; Option 
A emits `1234567890123456.79`. That is the correctly rounded value rather than 
an error-propagated one, which is the same relationship `Division` already has 
to its inputs. The PR's claim is that operands are converted exactly, and that 
still holds.
   
   ## Regression test
   
   I added `testEmittedScaleMatchesDeclaredType`, which is your recommended 
test: it pulls the transformed `CatalogTable`'s column type and asserts it 
agrees with the emitted `BigDecimal`'s scale, across all four operators in one 
query.
   
   I verified it actually catches the bug rather than just passing — reverting 
the one-line fix and re-running gives:
   
   ```
   declared and emitted scale differ for column mul_val (value 38.4375) ==> 
expected: <2> but was: <4>
   ```
   
   That is the guard you asked for: whichever way a future change goes, 
schema/value agreement is now pinned rather than assumed, and the failure 
message names the column and the value.
   
   ## Docs
   
   Both `docs/en` and `docs/zh` `incompatible-changes.md` gain a bullet on the 
multiplication result scale — that it now rounds to the declared scale with 
`HALF_UP`, why (the declared type is `max(scale)`, not the sum), and a worked 
example. It is user-visible independently of the sink failure, so it belongs 
there whichever option had been chosen.
   
   ## Issue 2 — `toBigDecimal` duplication
   
   Agreed, and agreed the deferral reason has expired now that #11697 is in. 
I'd rather not fold a refactor into this PR at round four, since it would put a 
non-behavioural change into a diff you have now reviewed carefully several 
times — but you're right that "later" needs to be a PR rather than an 
intention, so I'll open it against `dev` once this lands and link it here. 
While tracing `NumericFunction` for this I also noticed `abs()` returns 
`Integer.MIN_VALUE` for `ABS(-2147483648)` (`Math.abs` overflow, same shape as 
the bug in #11721) and that `round()`'s type switch has no `BYTE` case, so 
`ROUND(tinyint_col, -1)` is a silent no-op. Those are separate bugs and I'll 
file them separately rather than smuggle them into a unification PR.
   
   ## Current state
   
   Full `seatunnel-transforms-v2` suite: 1087 tests, 0 failures, on JDK 11 
locally. `spotless:check` clean.
   
   On CI: I won't claim green. Run `31773166956` on the previous head had 
`unit-test (8, windows-latest)` fail for the third consecutive time, and 
because that matrix is fail-fast it cancelled all three sibling legs — 
including both ubuntu ones, which are where `SQLDecimalArithmeticTest` actually 
runs. So CI has not yet executed these tests on any push; the numbers above are 
local only. I'll open the Windows/JDK 8 flake issue as you suggested — with 
that fail-fast masking effect included, since it's costing every PR that 
touches this repo a matrix, not just this one.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to