Skip to content

Numeric type mappings emit literals Calcite does not type as their store type #28

Description

@wasabii

What

A numeric type mapping's GenerateNonNullSqlLiteral writes a bare number. Calcite types a bare
exact-numeric literal as INTEGER (or DECIMAL, once it has a decimal point) regardless of the
column it sits beside, so the literal's SQL type is not the mapping's StoreType. In a comparison
that is invisible — Calcite coerces the operands — but a projected literal reaches the reader as
whatever Calcite typed it, and the shaper asks for the model type. The read then fails:

Cannot convert value of type 'Integer' to 'Byte'

byte is fixed in #26 by writing CAST(<value> AS TINYINT UNSIGNED), which is the same cast
CalciteQuerySqlGenerator.VisitSqlParameter already writes around a byte parameter — which is why
a parameter reached the reader as a byte where a constant did not.

Still open

The rest of the family has the same shape and no GenerateNonNullSqlLiteral override:

mapping store type literal written Calcite's type for it reader wants
CalciteSByteTypeMapping TINYINT 5 INTEGER java.lang.Byte
CalciteShortTypeMapping SMALLINT 5 INTEGER java.lang.Short
CalciteLongTypeMapping BIGINT 5 INTEGER java.lang.Long
CalciteUShortTypeMapping SMALLINT UNSIGNED 5 INTEGER org.joou.UShort
CalciteUIntTypeMapping INTEGER UNSIGNED 5 INTEGER org.joou.UInteger
CalciteULongTypeMapping BIGINT UNSIGNED 5 INTEGER org.joou.ULong
CalciteFloatTypeMapping REAL 5 / 5.25 INTEGER / DECIMAL java.lang.Float
CalciteDoubleTypeMapping DOUBLE 5.0 DECIMAL java.lang.Double

CalciteIntTypeMapping is the one that is already right: a bare literal is INTEGER.

CalciteDecimalTypeMapping is a special case. It overrides the literal already, to keep a 19-digit
integral value inside Calcite's maximum DECIMAL precision of 19, and a CAST(… AS DECIMAL(19, 4))
would put that value back over the limit. It needs its own answer, not this one.

CalciteIntTypeMapping must stay bare for a second reason: VisitLimitOffsetValue sends a constant
row count straight to the literal generator, and Calcite's parser does not accept a CAST in a
FETCH / OFFSET position.

Wider than literals

Typing the literal closes the constant case, not the general one. Calcite promotes operand types in
arithmetic, COALESCE and CASE, so a + b over two TINYINT UNSIGNED columns is an INTEGER
expression even when both columns are correctly typed — and the shaper still asks for byte.
CalciteQuerySqlGenerator already casts back to the store type in the places this was hit
(VisitLeftShift, VisitRightShift, VisitCountFunction); whether that should generalize to any
expression whose result mapping is narrower than what Calcite infers is the real question here.

Note on the reader

This is not a regression in Apache.Calcite.Data. Its accessors used to take any java.lang.Number
that fit, which is looser than ADO.NET means by GetByte — SqlDataReader.GetByte throws where the
column is not a byte. Tightening them (ikvmnet/calcite-dotnet@63f24cf) removed a leniency this
provider had been leaning on without saying so. The generated SQL was already wrong; the reader had
been quietly covering for it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions