diff --git a/redash/query_runner/query_results.py b/redash/query_runner/query_results.py index 5bc0eed29a0..028b7bc2299 100644 --- a/redash/query_runner/query_results.py +++ b/redash/query_runner/query_results.py @@ -109,9 +109,12 @@ def flatten(value): return json_dumps(value) elif isinstance(value, decimal.Decimal): return float(value) - elif isinstance(value, datetime.timedelta): + elif isinstance(value, (datetime.date, datetime.time, datetime.datetime, datetime.timedelta)): return str(value) else: + if logger.isEnabledFor(logging.DEBUG): + if not isinstance(value, (type(None), str, float, int, bool)): + logger.debug("flatten() found unhandled type: %s", str(type(value))) return value @@ -134,6 +137,7 @@ def create_table(connection, table_name, query_results): column_list=column_list, place_holders=",".join(["?"] * len(columns)), ) + logger.debug("INSERT template: %s", insert_template) for row in query_results["rows"]: values = [flatten(row.get(column)) for column in columns] diff --git a/tests/query_runner/test_query_results.py b/tests/query_runner/test_query_results.py index 1c7ff70cc87..49ade3e0e79 100644 --- a/tests/query_runner/test_query_results.py +++ b/tests/query_runner/test_query_results.py @@ -1,5 +1,6 @@ import datetime import decimal +import logging import sqlite3 from unittest import TestCase @@ -15,6 +16,7 @@ extract_query_ids, extract_query_params, fix_column_name, + flatten, get_query_results, prepare_parameterized_query, replace_query_parameters, @@ -248,3 +250,59 @@ def test_non_cached_query_result(self): query_result_data = {"columns": [], "rows": []} qr.return_value = (query_result_data, None) self.assertEqual(query_result_data, get_query_results(self.factory.user, query.id, False)) + + +class TestFlatten(TestCase): + def test_flatten_with_string(self): + self.assertEqual(flatten("hello"), "hello") + + def test_flatten_with_integer(self): + self.assertEqual(flatten(10), 10) + + def test_flatten_with_float(self): + self.assertEqual(flatten(10.5), 10.5) + + def test_flatten_with_boolean(self): + self.assertEqual(flatten(True), True) + + def test_flatten_with_decimal(self): + self.assertEqual(flatten(decimal.Decimal("10.5")), 10.5) + + def test_flatten_with_date(self): + date = datetime.date(2021, 1, 1) + self.assertEqual(flatten(date), "2021-01-01") + + def test_flatten_with_time(self): + time = datetime.time(12, 30) + self.assertEqual(flatten(time), "12:30:00") + + def test_flatten_with_datetime(self): + datetime_obj = datetime.datetime(2021, 1, 1, 12, 30) + self.assertEqual(flatten(datetime_obj), "2021-01-01 12:30:00") + + def test_flatten_with_timedelta(self): + timedelta_obj = datetime.timedelta(days=2) + self.assertEqual(flatten(timedelta_obj), "2 days, 0:00:00") + + def test_flatten_with_list(self): + self.assertEqual(flatten([1, 2, 3]), "[1, 2, 3]") + + def test_flatten_with_dictionary(self): + self.assertEqual(flatten({"key": "value"}), '{"key": "value"}') + + def test_flatten_with_none(self): + self.assertEqual(flatten(None), None) + + @pytest.fixture(autouse=True) + def inject_fixtures(self, caplog): + self._caplog = caplog + + def test_flatten_unhandled_type(self): + class CustomType: + pass + + instance = CustomType() + with self._caplog.at_level(logging.DEBUG): + result = flatten(instance) + self.assertEqual(result, instance) + self.assertIn("flatten() found unhandled type: %s" % str(type(instance)), self._caplog.records[0].message)