SEPURI-SAI-KRISHNA opened a new issue, #12571:
URL: https://github.com/apache/seatunnel/issues/12571

   ### Search before asking
   
   - [X] I had searched in the 
[issues](https://github.com/apache/seatunnel/issues?q=is%3Aissue+label%3A%22bug%22)
 and found no similar issues.
   
   ### What happened
   
   `CAST(bigint_col AS INT)` on a value above `Integer.MAX_VALUE` silently 
returns a wrapped negative number. `CAST('3000000000' AS INT)`, the same target 
type with a string source, raises an error instead.
   
   So the same `CAST ... AS INT` either fails loudly or silently corrupts the 
value, depending only on whether the source column is a string or a number.
   
   I measured this on `dev` at `53733a112c9e935c5d97f7f24be249caa43b4fd5` by 
running the SQL through `ZetaSQLEngine` rather than reading it off the source. 
Columns are `b BIGINT`, `i INT`, `s STRING`, `d DOUBLE`.
   
   | expression | input | plan time | runtime result |
   | --- | --- | --- | --- |
   | `CAST(b AS INT)` | `3000000000` | allowed | **`-1294967296`** |
   | `CAST(s AS INT)` | `'3000000000'` | allowed | error |
   | `CAST(d AS FLOAT)` | `1.0e40` | allowed | **`Infinity`** |
   | `CAST(s AS FLOAT)` | `'1e40'` | allowed | **`Infinity`** |
   | `CAST(s AS TINYINT)` | `'300'` | allowed | error |
   | `CAST(s AS SMALLINT)` | `'70000'` | allowed | error |
   | `CAST(i AS SMALLINT)` | `70000` | **rejected** | n/a |
   | `CAST(i AS TINYINT)` | `70000` | **rejected** | n/a |
   | `CAST(b AS SMALLINT)` | `3000000000` | **rejected** | n/a |
   | `CAST(b AS TINYINT)` | `3000000000` | **rejected** | n/a |
   
   Controls behave correctly: `CAST(b AS INT)` on `5` gives `5`, `CAST(s AS 
TINYINT)` on `'5'` gives `5`, `CAST(s AS BIGINT)` on `'3000000000'` gives 
`3000000000`.
   
   The plan-time refusals read `Unsupported CAST FROM INT AS type: SMALLINT`.
   
   ### Why the silent cases look like a gap rather than a decision
   
   `TRY_CAST` already encodes the intended contract: a cast that cannot 
represent the value fails, and `TRY_CAST` turns that failure into null. It 
works on the paths that report failure and is defeated on the paths that do not.
   
   | expression | input | result |
   | --- | --- | --- |
   | `TRY_CAST(s AS TINYINT)` | `'300'` | `null`, correct |
   | `TRY_CAST(b AS INT)` | `3000000000` | **`-1294967296`** |
   | `TRY_CAST(d AS FLOAT)` | `1.0e40` | **`Infinity`** |
   
   `TRY_CAST` cannot return null for those two because nothing underneath ever 
signals that the value did not fit. That is why I read this as a path that 
never joined the contract, rather than a deliberate choice of wraparound 
semantics.
   
   ### Where it comes from
   
   `CastFunction.getCastType` decides what is allowed at plan time. 
`INT_CAST_TYPE` contains `BIGINT` and `FLOAT_CAST_TYPES` contains `DOUBLE`, so 
those two narrowings pass. The `TINYINT` and `SMALLINT` branches accept only 
widening sources plus `STRING`, which is why the numeric narrowings into them 
are refused.
   
   `SystemFunction.castAs` then converts. For `INT` a string goes through 
`Integer.parseInt`, which reports overflow, while a number goes through 
`((Number) v1).intValue()`, which truncates silently. That single difference is 
the inconsistency in the first table. For `FLOAT` both paths end at 
`Float.parseFloat`, which returns `Infinity` rather than reporting anything. 
`TINYINT` and `SMALLINT` use `Byte.parseByte` and `Short.parseShort`, which do 
report.
   
   ### Related but not the same
   
   #12507 reports unsigned DuckDB integers silently wrapping in the JDBC 
source. Same class of problem, different code: that one is automatic type 
inference in `DuckDBTypeConverter` in the JDBC connector, this one is an 
explicit `CAST` in the Zeta SQL transform going through `CastFunction` and 
`SystemFunction`. Neither fix touches the other's path.
   
   ### What I expected
   
   A value the target type cannot represent should fail the same way regardless 
of the source type, so `TRY_CAST` can return null for it.
   
   ### The part I would like a maintainer to decide
   
   The diagnosis is measured, but the remedy is a compatibility call:
   
   1. Range-check `BIGINT -> INT` and the `FLOAT` conversions so they fail like 
the string paths, which also makes `TRY_CAST` work for them. Most consistent, 
but a job relying on the current wraparound or `Infinity` would start failing.
   2. Leave the values as they are and instead allow `INT -> SMALLINT` and `INT 
-> TINYINT` at plan time, removing only the surprise that some narrowings 
cannot be expressed.
   3. Document the current behaviour and change nothing.
   
   I lean towards 1, because `TRY_CAST` already implies out-of-range is meant 
to be a failure, and a silently wrapped integer in a data integration tool 
tends to be discovered long after the data has moved. Happy to be overruled, 
which is why I am asking before writing a patch.
   
   ### SeaTunnel Version
   
   `dev` (3.0.0-SNAPSHOT), verified at 
`53733a112c9e935c5d97f7f24be249caa43b4fd5`.
   
   ### Are you willing to submit PR?
   
   - [X] Yes I am willing to submit a PR, once the direction is settled.
   
   ### Code of Conduct
   
   - [X] I agree to follow this project's [Code of 
Conduct](https://www.apache.org/foundation/policies/conduct)
   


-- 
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