Skip to content

[CALCITE-7801] JSON_VALUE(..., RETURNING DOUBLE) throws ClassCastException when the JSON number is an integer - #5276

Open
wasabii wants to merge 1 commit into
apache:mainfrom
wasabii:calcite-7801-json-value-returning-conversion
Open

wasabii wants to merge 1 commit into
apache:mainfrom
wasabii:calcite-7801-json-value-returning-conversion

Conversation

@wasabii

@wasabii wasabii commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

JSON_VALUE never converted the value it pulled out of the document to the type in the RETURNING clause. Its return type is always nullable, so code generation asks EnumUtils.convert for Object to a boxed primitive, and that case falls through to a plain (Double) x. The cast only worked when the JSON value already happened to be that class:

JSON_VALUE('{"c":0}', '$.c' RETURNING DOUBLE)     -- Integer cannot be cast to Double
JSON_VALUE('{"c":0.5}', '$.c' RETURNING INTEGER)  -- Double cannot be cast to Integer

INTEGER, VARCHAR and DECIMAL happened to work; everything else threw. And because the exception escaped the runtime function, ON ERROR never saw it. JSON_QUERY has the same problem with RETURNING ... ARRAY, where the elements are never converted at all, so I fixed both.

The conversion has to happen inside the runtime function, since that is where the ON EMPTY/ON ERROR logic lives. It can't simply call CAST: that is generated code, chosen from the statically known source and target types, and here the source is only known at run time — the scalar may arrive as an Integer, Double, BigInteger, String or Boolean. None of that is specific to JSON, so I put it in SqlFunctions, next to the toInt(Object) family it delegates to:

SqlFunctions.cast(value, typeName, precision, scale, roundingMode)
SqlFunctions.castArray(value, elementType, precision, scale, roundingMode)

These dispatch on the target type and call the same methods cast's generated code would have: Primitive.integerCast for the exact numerics, Primitive.charToDecimalCast and friends for DECIMAL, truncate/truncateOrPad for character types, the DateTimeUtils parsers for datetimes. The type, its precision and scale, and the type system's rounding mode come down from the two implementors as constants. castArray converts a nested array at every level, so RETURNING DOUBLE ARRAY ARRAY works where it used to throw, and it checks the value against the declared type rather than following whatever shape the value happens to have.

Precision and scale were being dropped entirely, and now aren't:

JSON_VALUE('{"c":100}',      '$.c' RETURNING DECIMAL(5,2))  -- was 100,    now 100.00
JSON_VALUE('{"c":"abcdef"}', '$.c' RETURNING VARCHAR(3))    -- was abcdef, now abc
JSON_VALUE('{"c":"2020-01-01 10:20:30.987"}', '$.c' RETURNING TIMESTAMP(0))  -- was .987, now truncated

Datetimes parse from the JSON string, including both WITH LOCAL TIME ZONE types — no time zone needs supplying, because cast passes one only when converting a TIME or TIMESTAMP, which is a local reading of a clock, and a JSON datetime is always a string. A JSON number is not a datetime, so converting one is an error; Oracle and MySQL agree, PostgreSQL doesn't.

cast raises for any target type it doesn't handle, so a conversion failure can no longer escape ON ERROR. Cases that used to throw an uncatchable ClassCastException now go through it: RETURNING VARBINARY(n); RETURNING <type> ARRAY on JSON_VALUE, which cannot match anything as Calcite implements it, since JSON_VALUE returns a scalar; and a value whose shape does not fit an array RETURNING type, such as a JSON object, or an array nested more or less deeply than the type says.

RETURNING DECIMAL that yields NULL still doesn't work, here and on main alike, because linq4j's constant-hoisting lifts the conversion out of its null guard. That can be fixed separately.

@wasabii
wasabii force-pushed the calcite-7801-json-value-returning-conversion branch from 95af1ba to 9e44450 Compare September 20, 2026 22:05
@sonarqubecloud

Copy link
Copy Markdown

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