Skip to content

Numeric.result_processor ignores decimal_return_scale on non-native-decimal DBAPIs (e.g. sqlite3) #13424

Description

@zzzeek

Bug

Numeric.result_processor computes the scale for the float-to-Decimal conversion inline, bypassing the decimal_return_scale parameter entirely. The sibling class Float.result_processor already uses self._effective_decimal_return_scale correctly; Numeric does not.

The bug only surfaces when the DBAPI does not support native decimals (i.e. dialect.supports_native_decimal is False), which is the case for sqlite3 and several other backends. In that path Numeric.result_processor runs:

# lib/sqlalchemy/sql/sqltypes.py  – Numeric.result_processor (current)
return processors.to_decimal_processor_factory(
    decimal.Decimal,
    self.scale if self.scale is not None else self._default_decimal_return_scale,
)

self._effective_decimal_return_scale (defined in NumericCommon) checks self.decimal_return_scale first, then falls back to self.scale, then _default_decimal_return_scale. The inline expression in Numeric skips the first step entirely, so decimal_return_scale is silently ignored.

Reproducer (sqlite3, no extra dependencies)

The script below adds a Float(asdecimal=True) column alongside Numeric to contrast the two behaviors. Both are given decimal_return_scale=5 and the same input value; only Float honours the parameter.

import decimal
import sqlalchemy as sa

engine = sa.create_engine("sqlite://")
metadata = sa.MetaData()

t = sa.Table(
    "t",
    metadata,
    sa.Column("numeric_val", sa.Numeric(10, 2, decimal_return_scale=5)),
    sa.Column("float_val", sa.Float(decimal_return_scale=5, asdecimal=True)),
)
metadata.create_all(engine)

value = decimal.Decimal("1.23456")

with engine.begin() as conn:
    conn.execute(t.insert(), {"numeric_val": value, "float_val": value})
    numeric_result, float_result = conn.execute(
        sa.select(t.c.numeric_val, t.c.float_val)
    ).one()

print(f"Numeric(10, 2, decimal_return_scale=5):        {numeric_result!r}")
print(f"Float(decimal_return_scale=5, asdecimal=True): {float_result!r}")
print(f"Expected (both):                               Decimal('1.23456')")

Output (current)

Numeric(10, 2, decimal_return_scale=5):        Decimal('1.23')
Float(decimal_return_scale=5, asdecimal=True): Decimal('1.23456')
Expected (both):                               Decimal('1.23456')

Float returns the correct value; Numeric silently falls back to scale=2.

Root cause

In sqltypes.py line 657–663, Numeric.result_processor bypasses decimal_return_scale:

# Numeric – wrong
return processors.to_decimal_processor_factory(
    decimal.Decimal,
    self.scale if self.scale is not None else self._default_decimal_return_scale,
)

Float.result_processor (line 770–771) already does it correctly:

# Float – correct
return processors.to_decimal_processor_factory(
    decimal.Decimal, self._effective_decimal_return_scale
)

The one-line fix is to apply the same pattern to Numeric:

return processors.to_decimal_processor_factory(
    decimal.Decimal, self._effective_decimal_return_scale
)

Environment

  • SQLAlchemy version: 2.1.0b4 (also present in 2.0.x)
  • Python version: 3.x
  • Database: SQLite / sqlite3 (any dialect with supports_native_decimal = False)

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

    bugSomething isn't workingdatatypesthings to do with database types, like VARCHAR and others

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions