Repository: ibis-project/ibis

Issue:
Bug - OmniSci/Mapd: CAST from boolean causes summary() op to fail
On the MapD backend, we can't do this today: 
```
con.execute(zips.est_pop.summary()) 
```


as we get
```
Exception: Exception: Exception occurred: org.apache.calcite.runtime.CalciteContextException: From line 1, column 37 to line 1, column 58: Cannot apply 'SUM' to arguments of type 'SUM(<BOOLEAN>)'. Supported form(s): 'SUM(<NUMERIC>)': SELECT count("est_pop") AS "count", sum("est_pop" IS NULL) AS "nulls",
       min("est_pop") AS "min", max("est_pop") AS "max",
       sum("est_pop") AS "sum", avg("est_pop") AS "mean",
       approx_count_distinct("est_pop", 100) AS approx_nunique
FROM zipcode_demographics
LIMIT 100000
```

or 
```
zips.est_pop.isnull().sum()
```

```
Exception: Exception: Exception occurred: org.apache.calcite.runtime.CalciteContextException: From line 1, column 12 to line 1, column 45: Cast function cannot convert value of type BOOLEAN to type INTEGER: SELECT sum(CAST("est_pop" IS NULL AS INT)) AS "sum"
```

Mapd(Calcite) doesn't support casts on booleans, but this could be fixed via a CASE, like `case when boolean then 1 else 0 end`

For reference, similar to postgres compiler - `https://github.com/ibis-project/ibis/blob/fbed2caf1b93e248bb2209588fd99444605a10e6/ibis/sql/postgres/compiler.py#L389`




Discussion:

Resolving patch:
diff --git a/ibis/mapd/operations.py b/ibis/mapd/operations.py
--- a/ibis/mapd/operations.py
+++ b/ibis/mapd/operations.py
@@ -164,7 +164,13 @@ def formatter(translator, expr):
 
         # HACK: support trailing arguments
         where = op.where
-        args = [arg for arg in op.args if arg is not where]
+        args = []
+
+        for arg in op.args:
+            if arg is not where:
+                if arg.type().equals(dt.boolean):
+                    arg = arg.ifelse(1, 0)
+                args.append(arg)
 
         return _reduction_format(
             translator,
@@ -858,6 +864,10 @@ def formatter(translator, expr):
     ops.HLLCardinality: approx_count_distinct,
     ops.DistinctColumn: unary_prefix_op('distinct'),
     ops.Arbitrary: _arbitrary,
+    ops.Sum: _reduction('sum'),
+    ops.Mean: _reduction('avg'),
+    ops.Min: _reduction('min'),
+    ops.Max: _reduction('max'),
 }
 
 # GENERAL


Test patch:
diff --git a/ibis/mapd/tests/test_operations.py b/ibis/mapd/tests/test_operations.py
--- a/ibis/mapd/tests/test_operations.py
+++ b/ibis/mapd/tests/test_operations.py
@@ -1,3 +1,5 @@
+import re
+
 import numpy as np
 import pandas as pd
 import pytest
@@ -160,3 +162,19 @@ def test_arbitrary_none(alltypes, df_alltypes, result_fn, expected_fn):
     result = expr.execute()
     expected = expected_fn(df_alltypes)
     np.testing.assert_allclose(result, expected)
+
+
+@pytest.mark.parametrize(
+    ('ibis_op', 'sql_op'),
+    [('sum', 'sum'), ('mean', 'avg'), ('max', 'max'), ('min', 'min')],
+)
+def test_agg_with_bool(alltypes, ibis_op, sql_op):
+    regex = re.compile(r'\s{2}|\n')
+
+    expr = getattr(alltypes.bool_col, ibis_op)()
+    sql_check = (
+        'SELECT {}(CASE WHEN "bool_col" THEN 1 ELSE 0 END) AS "{}"'
+        'FROM functional_alltypes'
+    ).format(sql_op, ibis_op)
+
+    assert regex.sub('', expr.compile()) == regex.sub('', sql_check)