This commit is contained in:
ikatyal2110 2026-07-12 21:36:19 -05:00 committed by GitHub
commit c276aa55d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 32 additions and 4 deletions

View file

@ -2866,10 +2866,24 @@ class Table(Queryable):
if "rowid" not in new_cols:
new_cols.insert(0, "rowid")
old_cols.insert(0, "rowid")
# Columns explicitly converted to a numeric type need NULLIF(col, '') so
# that empty strings stored in a previously TEXT column become NULL rather
# than being coerced to 0 or raising a type error.
_numeric_kws = ("INT", "REAL", "FLOA", "DOUB", "NUMERIC", "DECIMAL")
def _col_expr(from_, to_):
if from_ in types:
raw = COLUMN_TYPE_MAPPING.get(types[from_])
if raw is None and isinstance(types[from_], str):
raw = types[from_]
if raw and any(kw in raw.upper() for kw in _numeric_kws):
return "NULLIF({}, '')".format(quote_identifier(from_))
return quote_identifier(from_)
copy_sql = "INSERT INTO {} ({new_cols})\n SELECT {old_cols} FROM {};".format(
quote_identifier(new_table_name),
quote_identifier(self.name),
old_cols=", ".join(quote_identifier(col) for col in old_cols),
old_cols=", ".join(_col_expr(f, t) for f, t in zip(old_cols, new_cols)),
new_cols=", ".join(quote_identifier(col) for col in new_cols),
)
sqls.append(copy_sql)

View file

@ -23,7 +23,7 @@ import pytest
{"types": {"age": int}},
[
'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" INTEGER\n);',
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", NULLIF("age", \'\') FROM "dogs";',
'DROP TABLE "dogs";',
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
],
@ -53,7 +53,7 @@ import pytest
{"types": {"age": int}, "rename": {"age": "dog_age"}},
[
'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "dog_age" INTEGER\n);',
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "dog_age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "dog_age")\n SELECT "rowid", "id", "name", NULLIF("age", \'\') FROM "dogs";',
'DROP TABLE "dogs";',
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
],
@ -146,7 +146,7 @@ def test_transform_sql_table_with_primary_key(
{"types": {"age": int}},
[
'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER,\n "name" TEXT,\n "age" INTEGER\n);',
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", NULLIF("age", \'\') FROM "dogs";',
'DROP TABLE "dogs";',
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
],
@ -880,6 +880,20 @@ def test_transform_with_indexes_errors(fresh_db, transform_params):
)
def test_transform_converts_empty_strings_to_null_for_numeric_types(fresh_db):
# Regression test for: transform should convert '' to NULL when changing
# a TEXT column to an INTEGER or FLOAT type (issue #488).
fresh_db["test"].insert_all([
{"id": "1", "age": "3", "weight": "2.5", "name": "Alice"},
{"id": "2", "age": "", "weight": "", "name": ""},
])
fresh_db["test"].transform(types={"age": int, "weight": float})
rows = list(fresh_db["test"].rows)
assert rows[0] == {"id": "1", "age": 3, "weight": 2.5, "name": "Alice"}
# Empty strings in numeric columns become NULL; text columns are unchanged
assert rows[1] == {"id": "2", "age": None, "weight": None, "name": ""}
def test_transform_with_unique_constraint_implicit_index(fresh_db):
dogs = fresh_db["dogs"]
# Create a table with a UNIQUE constraint on 'name', which creates an implicit index