Repository: ibis-project/ibis

Issue:
bug: comparing bool expr to bool literal generates invalid sql
It looks like one of the recent refactorings may have broken comparisons of boolean to boolean:

This test:

```python
def test_bool_bool():
    import ibis
    from ibis.backends.base.sql.compiler import Compiler

    t = ibis.table(
        [('dest', 'string'), ('origin', 'string'), ('arrdelay', 'int32')],
        'airlines',
    )

    x = ibis.literal(True)
    top = t[(t.dest.cast('int64') == 0) == x]

    result = Compiler.to_sql(top)
    print(result)
```

produces this SQL:

```sql
SELECT *
FROM airlines
WHERE CAST(`dest` AS bigint) = 0 = TRUE
```


Discussion:

Resolving patch:
diff --git a/ibis/backends/base/sql/registry/helpers.py b/ibis/backends/base/sql/registry/helpers.py
--- a/ibis/backends/base/sql/registry/helpers.py
+++ b/ibis/backends/base/sql/registry/helpers.py
@@ -22,32 +22,34 @@ def quote_identifier(name, quotechar='`', force=False):
         return name
 
 
-def needs_parens(op):
-    if isinstance(op, ir.Expr):
-        op = op.op()
-    op_klass = type(op)
-    # function calls don't need parens
-    return op_klass in {
-        ops.Negate,
-        ops.IsNull,
-        ops.NotNull,
-        ops.Add,
-        ops.Subtract,
-        ops.Multiply,
-        ops.Divide,
-        ops.Power,
-        ops.Modulus,
-        ops.Equals,
-        ops.NotEquals,
-        ops.GreaterEqual,
-        ops.Greater,
-        ops.LessEqual,
-        ops.Less,
-        ops.IdenticalTo,
-        ops.And,
-        ops.Or,
-        ops.Xor,
-    }
+_NEEDS_PARENS_OPS = (
+    ops.Negate,
+    ops.IsNull,
+    ops.NotNull,
+    ops.Add,
+    ops.Subtract,
+    ops.Multiply,
+    ops.Divide,
+    ops.Power,
+    ops.Modulus,
+    ops.Equals,
+    ops.NotEquals,
+    ops.GreaterEqual,
+    ops.Greater,
+    ops.LessEqual,
+    ops.Less,
+    ops.IdenticalTo,
+    ops.And,
+    ops.Or,
+    ops.Xor,
+)
+
+
+def needs_parens(expr: ir.Expr):
+    op = expr.op()
+    if isinstance(op, ops.Alias):
+        op = op.arg.op()
+    return isinstance(op, _NEEDS_PARENS_OPS)
 
 
 parenthesize = '({})'.format


Test patch:
diff --git a/ibis/tests/sql/test_select_sql.py b/ibis/tests/sql/test_select_sql.py
--- a/ibis/tests/sql/test_select_sql.py
+++ b/ibis/tests/sql/test_select_sql.py
@@ -783,7 +783,8 @@ def test_filter_subquery_derived_reduction(filter_subquery_derived_reduction):
     expr3, expr4 = filter_subquery_derived_reduction
 
     result = Compiler.to_sql(expr3)
-    expected = """SELECT *
+    expected = """\
+SELECT *
 FROM star1
 WHERE `f` > ln((
   SELECT avg(`f`) AS `mean`
@@ -793,13 +794,14 @@ def test_filter_subquery_derived_reduction(filter_subquery_derived_reduction):
     assert result == expected
 
     result = Compiler.to_sql(expr4)
-    expected = """SELECT *
+    expected = """\
+SELECT *
 FROM star1
-WHERE `f` > ln((
+WHERE `f` > (ln((
   SELECT avg(`f`) AS `mean`
   FROM star1
   WHERE `foo_id` = 'foo'
-)) + 1"""
+)) + 1)"""
     assert result == expected
 
 
@@ -931,6 +933,26 @@ def test_topk_to_aggregate():
     assert result == expected
 
 
+def test_bool_bool():
+    import ibis
+    from ibis.backends.base.sql.compiler import Compiler
+
+    t = ibis.table(
+        [('dest', 'string'), ('origin', 'string'), ('arrdelay', 'int32')],
+        'airlines',
+    )
+
+    x = ibis.literal(True)
+    top = t[(t.dest.cast('int64') == 0) == x]
+
+    result = Compiler.to_sql(top)
+    expected = """\
+SELECT *
+FROM airlines
+WHERE (CAST(`dest` AS bigint) = 0) = TRUE"""
+    assert result == expected
+
+
 def test_case_in_projection(alltypes):
     t = alltypes