From 360e715db991b579470a6428cb71f1bc6cf64306 Mon Sep 17 00:00:00 2001 From: Antonio Yanez Date: Wed, 1 Jul 2026 11:40:43 +0000 Subject: [PATCH] Fix sql.false()/sql.true() rendering under use_native_boolean=False MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HANAStatementCompiler.visit_is_true_unary_operator and visit_is_false_unary_operator unconditionally emitted `... = TRUE` / `... = FALSE`. With use_native_boolean=False, SQLAlchemy's base visit_true/visit_false renders the wrapped element as an INT literal (1/0), so a WHERE became e.g. `WHERE 0 = TRUE` — which HANA rejects with error 266 "BOOLEAN type is not comparable with INT". Branch on self.dialect.supports_native_boolean and fall back to an INT compare in non-native mode. Native-mode behaviour is unchanged. --- CHANGES.rst | 13 +++++++++++++ sqlalchemy_hana/dialect.py | 9 +++++++++ test/test_sql_compile.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 11b7c4c..ae85b2f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 ----- diff --git a/sqlalchemy_hana/dialect.py b/sqlalchemy_hana/dialect.py index 66da87a..cdd0007 100644 --- a/sqlalchemy_hana/dialect.py +++ b/sqlalchemy_hana/dialect.py @@ -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 diff --git a/test/test_sql_compile.py b/test/test_sql_compile.py index 6c3e5ca..5a2be2e 100644 --- a/test/test_sql_compile.py +++ b/test/test_sql_compile.py @@ -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" @@ -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),