Fixed issue where floor division (``//``) between a :class:`.Float` or
:class:`.Numeric` numerator and an :class:`.Integer` denominator would omit
the ``FLOOR()`` SQL wrapper on dialects where
:attr:`.Dialect.div_is_floordiv` is ``True`` (the default, including
PostgreSQL and SQLite). ``FLOOR()`` is now applied if either the
denominator or the numerator is a non-integer, so that expressions such as
``float_col // int_col`` render as ``FLOOR(float_col / int_col)`` instead
of the incorrect ``float_col / int_col``. Pull request courtesy r266-tech.
Fixes: #10528
Closes: #13191
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13191
Pull-request-sha:
c9cbc47c877e19c91f912556b4ead6cd26e3cfe6
Change-Id: I5f9f02d966aa6ccee214a2c5cc27a73a4292da03
--- /dev/null
+.. change::
+ :tags: bug, sql
+ :tickets: 10528
+
+ Fixed issue where floor division (``//``) between a :class:`.Float` or
+ :class:`.Numeric` numerator and an :class:`.Integer` denominator would omit
+ the ``FLOOR()`` SQL wrapper on dialects where
+ :attr:`.Dialect.div_is_floordiv` is ``True`` (the default, including
+ PostgreSQL and SQLite). ``FLOOR()`` is now applied if either the
+ denominator or the numerator is a non-integer, so that expressions such as
+ ``float_col // int_col`` render as ``FLOOR(float_col / int_col)`` instead
+ of the incorrect ``float_col / int_col``. Pull request courtesy r266-tech.
For the default backend as well as backends such as PostgreSQL, the SQL ``/`` operator normally
behaves this way for integer values::
- >>> print(column("x") // 5)
+ >>> print(column("x", Integer) // 5)
{printsql}x / :x_1{stop}
>>> print(5 // column("x", Integer))
{printsql}:x_1 / x{stop}
if (
self.dialect.div_is_floordiv
and binary.right.type._type_affinity is sqltypes.Integer
+ and binary.left.type._type_affinity is sqltypes.Integer
):
return (
self.process(binary.left, **kw)
"top_regions AS (SELECT "
"regional_sales.region AS region FROM regional_sales "
"WHERE regional_sales.total_sales > "
- "(SELECT sum(regional_sales.total_sales) / :sum_1 AS "
+ "(SELECT FLOOR(sum(regional_sales.total_sales) / :sum_1) AS "
"anon_1 FROM regional_sales)) "
"SELECT orders.region, orders.product, "
"sum(orders.quantity) AS product_units, "
def test_floordiv_op_numeric(self):
self.assert_compile(5.10 // literal(5.5), "FLOOR(:param_1 / :param_2)")
+ def test_floordiv_float_by_int(self):
+ """float // int must include FLOOR even with div_is_floordiv dialect"""
+ expr = column("a", Float()) // column("b", Integer())
+ self.assert_compile(
+ expr,
+ "FLOOR(a / b)",
+ dialect=postgresql.dialect(),
+ )
+
+ def test_floordiv_numeric_by_int(self):
+ """numeric // int must include FLOOR even with div_is_floordiv
+ dialect"""
+ expr = column("a", Numeric()) // column("b", Integer())
+ self.assert_compile(
+ expr,
+ "FLOOR(a / b)",
+ dialect=postgresql.dialect(),
+ )
+
+ def test_floordiv_int_by_int_pg(self):
+ """int // int can skip FLOOR when dialect has div_is_floordiv"""
+ expr = column("a", Integer()) // column("b", Integer())
+ self.assert_compile(
+ expr,
+ "a / b",
+ dialect=postgresql.dialect(),
+ )
+
@testing.combinations(
("format", "mytable.myid %% %s"),
("qmark", "mytable.myid % ?"),
no_group = column("a") // type_coerce(column("x"), Integer)
group = column("b") // type_coerce(column("y") * column("w"), Integer)
- self.assert_compile(no_group, "a / x")
- self.assert_compile(group, "b / (y * w)")
+ self.assert_compile(no_group, "FLOOR(a / x)")
+ self.assert_compile(group, "FLOOR(b / (y * w))")
def test_subquery_on_table(self):
sel = (