Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
Changelog
=========

4.6.1
-----

Fixes
~~~~~

- Fixed an issue causing ``WHERE 0 = TRUE``/``WHERE 1 = TRUE`` to be
emitted for ``sql.false()``/``sql.true()`` predicates when the dialect
was configured with ``use_native_boolean=False``, which SAP HANA
rejects with error 266 ("BOOLEAN type is not comparable with INT").
``visit_is_true_unary_operator``/``visit_is_false_unary_operator`` now
emit an INT compare (``= 1``/``= 0``) in that mode.

4.6.0
-----

Expand Down
9 changes: 9 additions & 0 deletions sqlalchemy_hana/dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,16 +285,25 @@ def visit_is_not_distinct_from_binary(
# where clause like:
# SELECT 1 FROM DUMMY WHERE TRUE
# SELECT 1 FROM DUMMY WHERE FALSE
# When the dialect is configured with use_native_boolean=False (schemas
# that store booleans as integer/tinyint), sql.true()/sql.false() render
# as 1/0; comparing those to the boolean literals TRUE/FALSE raises HANA
# error 266 (BOOLEAN type is not comparable with INT), so fall back to
# an INT compare in that mode.
@override
def visit_is_true_unary_operator(
self, element: UnaryExpression[Any], operator: Any, **kw: Any
) -> str:
if not self.dialect.supports_native_boolean:
return f"{self.process(element.element, **kw)} = 1"
return f"{self.process(element.element, **kw)} = TRUE"

@override
def visit_is_false_unary_operator(
self, element: UnaryExpression[Any], operator: Any, **kw: Any
) -> str:
if not self.dialect.supports_native_boolean:
return f"{self.process(element.element, **kw)} = 0"
return f"{self.process(element.element, **kw)} = FALSE"

# SAP HANA does not support JSON based operations
Expand Down
32 changes: 31 additions & 1 deletion test/test_sql_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

from __future__ import annotations

from sqlalchemy import func, literal, select, true
from sqlalchemy import and_, false, func, literal, literal_column, select, true
from sqlalchemy.sql.expression import column, table
from sqlalchemy.testing.assertions import AssertsCompiledSQL
from sqlalchemy.testing.fixtures import TestBase

from sqlalchemy_hana.dialect import HANAHDBCLIDialect


class SQLCompileTest(TestBase, AssertsCompiledSQL):
__dialect__ = "hana"
Expand Down Expand Up @@ -56,6 +58,34 @@ def test_sql_unary_boolean(self) -> None:
"SELECT __[POSTCOMPILE_param_1] AS anon_1 FROM DUMMY WHERE true = TRUE",
)

def test_sql_unary_boolean_non_native(self) -> None:
# With use_native_boolean=False, sql.true()/sql.false() render as
# 1/0 (INT). Comparing INT to the boolean literals TRUE/FALSE raises
# HANA error 266 (BOOLEAN type is not comparable with INT), so the
# is_true/is_false unary wrappers must emit an INT compare instead.
dialect = HANAHDBCLIDialect(use_native_boolean=False)

self.assert_compile(
select(literal_column("id")).where(false()),
"SELECT id FROM DUMMY WHERE 0 = 1",
dialect=dialect,
)
self.assert_compile(
select(literal_column("id")).where(true()),
"SELECT id FROM DUMMY WHERE 1 = 1",
dialect=dialect,
)
# and_(false(), ...) is short-circuited by SQLAlchemy to just
# false(); make sure that resulting predicate still renders as
# a safe INT compare.
self.assert_compile(
select(literal_column("id")).where(
and_(false(), literal_column("flag") == literal_column("1"))
),
"SELECT id FROM DUMMY WHERE 0 = 1",
dialect=dialect,
)

def test_sql_offset_without_limit(self) -> None:
self.assert_compile(
select(literal(1)).offset(100),
Expand Down
Loading