Skip to content

Commit

Permalink
fix: boolean type into SQL 'in' operator (#16107)
Browse files Browse the repository at this point in the history
* fix: boolean type into SQL 'in' operator

* fix ut

* fix ut again

* update url

* remove blank line
  • Loading branch information
zhaoyongjie authored Aug 10, 2021
1 parent 79e8d77 commit bb1d8fe
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 1 deletion.
2 changes: 1 addition & 1 deletion superset-frontend/src/datasource/DatasourceEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const ColumnButtonWrapper = styled.div`
const checkboxGenerator = (d, onChange) => (
<CheckboxControl value={d} onChange={onChange} />
);
const DATA_TYPES = ['STRING', 'NUMERIC', 'DATETIME'];
const DATA_TYPES = ['STRING', 'NUMERIC', 'DATETIME', 'BOOLEAN'];

const DATASOURCE_TYPES_ARR = [
{ key: 'physical', label: t('Physical (table or view)') },
Expand Down
2 changes: 2 additions & 0 deletions superset/connectors/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ def handle_single_value(value: Optional[FilterValue]) -> Optional[FilterValue]:
return None
if value == "<empty string>":
return ""
if target_column_type == utils.GenericDataType.BOOLEAN:
return utils.cast_to_boolean(value)
return value

if isinstance(values, (list, tuple)):
Expand Down
29 changes: 29 additions & 0 deletions superset/utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,35 @@ def cast_to_num(value: Optional[Union[float, int, str]]) -> Optional[Union[float
return None


def cast_to_boolean(value: Any) -> bool:
"""Casts a value to an int/float
>>> cast_to_boolean(1)
True
>>> cast_to_boolean(0)
False
>>> cast_to_boolean(0.5)
True
>>> cast_to_boolean('true')
True
>>> cast_to_boolean('false')
False
>>> cast_to_boolean('False')
False
>>> cast_to_boolean(None)
False
:param value: value to be converted to boolean representation
:returns: value cast to `bool`. when value is 'true' or value that are not 0
converte into True
"""
if isinstance(value, (int, float)):
return value != 0
if isinstance(value, str):
return value.strip().lower() == "true"
return False


def list_minus(l: List[Any], minus: List[Any]) -> List[Any]:
"""Returns l without what is in minus
Expand Down
39 changes: 39 additions & 0 deletions tests/integration_tests/sqla_models_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from unittest.mock import patch
import pytest

import sqlalchemy as sa

from superset import db
from superset.connectors.sqla.models import SqlaTable, TableColumn
from superset.db_engine_specs.bigquery import BigQueryEngineSpec
Expand Down Expand Up @@ -264,6 +266,43 @@ class FilterTestCase(NamedTuple):
else:
self.assertIn(filter_.expected, sql)

@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_boolean_type_where_operators(self):
table = self.get_table(name="birth_names")
db.session.add(
TableColumn(
column_name="boolean_gender",
expression="case when gender = 'boy' then True else False end",
type="BOOLEAN",
table=table,
)
)
query_obj = {
"granularity": None,
"from_dttm": None,
"to_dttm": None,
"groupby": ["boolean_gender"],
"metrics": ["count"],
"is_timeseries": False,
"filter": [
{
"col": "boolean_gender",
"op": FilterOperator.IN,
"val": ["true", "false"],
}
],
"extras": {},
}
sqla_query = table.get_sqla_query(**query_obj)
sql = table.database.compile_sqla_query(sqla_query.sqla_query)
dialect = table.database.get_dialect()
operand = "(true, false)"
# override native_boolean=False behavior in MySQLCompiler
# https://github.com/sqlalchemy/sqlalchemy/blob/master/lib/sqlalchemy/dialects/mysql/base.py
if not dialect.supports_native_boolean and dialect.name != "mysql":
operand = "(1, 0)"
self.assertIn(f"IN {operand}", sql)

def test_incorrect_jinja_syntax_raises_correct_exception(self):
query_obj = {
"granularity": None,
Expand Down

0 comments on commit bb1d8fe

Please sign in to comment.