diff --git a/docs/changelog.rst b/docs/changelog.rst index 0ef85ca..8fa2c64 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -23,6 +23,7 @@ Unreleased - ``sqlite-utils convert --dry-run`` now works for table and column names containing closing square brackets. (:issue:`829`) - ``table.indexes`` and ``table.xindexes`` now work for table, index and column names containing double quotes. This also fixes ``table.transform()`` for tables with those identifiers. Thanks, `nyxst4ck `__. (:issue:`824`, `#825 `__) - Improved type annotations throughout the package and added Pyright regression checks to CI. (:issue:`833`) +- Changing a ``TEXT`` column to ``INTEGER``, ``FLOAT`` or ``REAL`` using ``table.transform()`` or ``sqlite-utils transform`` now converts exact empty strings to ``NULL``. Previously they remained empty strings in the numeric column. Thanks, `ikatyal2110 `__. (:issue:`488`, `#805 `__) .. _v3_39_1: diff --git a/docs/cli.rst b/docs/cli.rst index 417911a..78c33b8 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -2236,7 +2236,7 @@ The ``transform`` command allows you to apply complex transformations to a table Every option for this table (with the exception of ``--pk-none``) can be specified multiple times. The options are as follows: ``--type column-name new-type`` - Change the type of the specified column. Valid types are ``integer``, ``text``, ``float``, ``real``, ``blob`` and ``any``. + Change the type of the specified column. Valid types are ``integer``, ``text``, ``float``, ``real``, ``blob`` and ``any``. Changing a ``TEXT`` column to ``INTEGER``, ``FLOAT`` or ``REAL`` converts exact empty-string values to ``NULL``. ``--drop column-name`` Drop the specified column. diff --git a/docs/python-api.rst b/docs/python-api.rst index 88cc3e3..d515642 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -1826,6 +1826,8 @@ To alter the type of a column, use the ``types=`` argument: # Convert the 'age' column to an integer, and 'weight' to a float table.transform(types={"age": int, "weight": float}) +When a ``TEXT`` column is changed to ``INTEGER``, ``FLOAT`` or ``REAL``, exact empty-string values are stored as ``NULL``. Other values, including whitespace-only strings, are copied normally. + See :ref:`python_api_add_column` for a list of available types. .. _python_api_transform_strict: diff --git a/tests/test_transform.py b/tests/test_transform.py index cb48ced..aa53ce2 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -1053,21 +1053,34 @@ def test_transform_with_unique_constraint_implicit_index(fresh_db): ) -@pytest.mark.parametrize("new_type", [int, float, "integer", "float", "REAL"]) -def test_transform_empty_string_to_null_for_numeric_types(fresh_db, new_type): - # Empty strings in TEXT columns should become NULL when transforming to numeric types +@pytest.mark.parametrize( + "new_type,expected_value,expected_type", + [ + (int, 42, int), + (float, 42.0, float), + ("integer", 42, int), + ("float", 42.0, float), + ("REAL", 42.0, float), + ], +) +def test_transform_empty_string_to_null_for_numeric_types( + fresh_db, new_type, expected_value, expected_type +): fresh_db["test"].insert_all( [ {"id": 1, "value": "42"}, {"id": 2, "value": ""}, {"id": 3, "value": None}, + {"id": 4, "value": " "}, ] ) fresh_db["test"].transform(types={"value": new_type}) rows = {r["id"]: r["value"] for r in fresh_db["test"].rows} - assert rows[1] in (42, 42.0) + assert rows[1] == expected_value + assert type(rows[1]) is expected_type assert rows[2] is None assert rows[3] is None + assert rows[4] == " " def test_transform_preserves_view(fresh_db):