Skip to content

Commit

Permalink
Fix bool coercion to 0/1 (#4512) (#4516)
Browse files Browse the repository at this point in the history
* Fix bool coercion

* Fix unit test

Co-authored-by: Jeremy Cohen <jeremy@dbtlabs.com>
  • Loading branch information
leahwicz and jtcohen6 committed Dec 20, 2021
1 parent 39d4e72 commit 29fa687
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Reimplement log message to use adapter name instead of the object method. ([#4501](https://github.com/dbt-labs/dbt-core/pull/4501))
- Issue better error message for incompatible schemas ([#4470](https://github.com/dbt-labs/dbt-core/pull/4442), [#4497](https://github.com/dbt-labs/dbt-core/pull/4497))
- Remove secrets from error related to packages. ([#4507](https://github.com/dbt-labs/dbt-core/pull/4507))
- Prevent coercion of boolean values (`True`, `False`) to numeric values (`0`, `1`) in query results ([#4511](https://github.com/dbt-labs/dbt-core/issues/4511), [#4512](https://github.com/dbt-labs/dbt-core/pull/4512))

### Docs
- Fix missing data on exposures in docs ([#4467](https://github.com/dbt-labs/dbt-core/issues/4467))
Expand Down
14 changes: 13 additions & 1 deletion core/dbt/clients/agate_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
BOM = BOM_UTF8.decode('utf-8') # '\ufeff'


class Number(agate.data_types.Number):
# undo the change in https://github.com/wireservice/agate/pull/733
# i.e. do not cast True and False to numeric 1 and 0
def cast(self, d):
if type(d) == bool:
raise agate.exceptions.CastError(
'Do not cast True to 1 or False to 0.'
)
else:
return super().cast(d)


class ISODateTime(agate.data_types.DateTime):
def cast(self, d):
# this is agate.data_types.DateTime.cast with the "clever" bits removed
Expand Down Expand Up @@ -41,7 +53,7 @@ def build_type_tester(
) -> agate.TypeTester:

types = [
agate.data_types.Number(null_values=('null', '')),
Number(null_values=('null', '')),
agate.data_types.Date(null_values=('null', ''),
date_format='%Y-%m-%d'),
agate.data_types.DateTime(null_values=('null', ''),
Expand Down
25 changes: 25 additions & 0 deletions test/unit/test_agate_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,28 @@ def test_nocast_string_types(self):

for i, row in enumerate(tbl):
self.assertEqual(list(row), expected[i])

def test_nocast_bool_01(self):
# True and False values should not be cast to 1 and 0, and vice versa
# See: https://github.com/dbt-labs/dbt-core/issues/4511

column_names = ['a', 'b']
result_set = [
{'a': True, 'b': 1},
{'a': False, 'b': 0},
]

tbl = agate_helper.table_from_data_flat(data=result_set, column_names=column_names)
self.assertEqual(len(tbl), len(result_set))

assert isinstance(tbl.column_types[0], agate.data_types.Boolean)
assert isinstance(tbl.column_types[1], agate.data_types.Number)

expected = [
[True, Decimal(1)],
[False, Decimal(0)],
]

for i, row in enumerate(tbl):
self.assertEqual(list(row), expected[i])

2 changes: 1 addition & 1 deletion test/unit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def _get_tester_for(self, column_type):
return agate.TimeDelta()

for instance in agate_helper.DEFAULT_TYPE_TESTER._possible_types:
if type(instance) is column_type:
if isinstance(instance, column_type): # include child types
return instance

raise ValueError(f'no tester for {column_type}')
Expand Down

0 comments on commit 29fa687

Please sign in to comment.