Fix .transform() raising TransformError on column-level UNIQUE constraints

SQLite backs a column-level UNIQUE constraint with an auto-index whose
sql in sqlite_master is NULL. The index-rebuild step in transform_sql()
treated any NULL-sql index as an error, causing TransformError on tables
with UNIQUE columns.

The fix adds a 'unique' parameter to create_table_sql so the constraint
is reproduced as an inline UNIQUE column attribute in the new CREATE
TABLE statement. In transform_sql(), auto-UNIQUE indices (origin='u')
are collected before the CREATE TABLE call and passed via unique=; the
index rebuild loop then skips them with a continue instead of raising.
Column renames are applied to the constraint; dropping a UNIQUE column
drops its constraint silently.

Fixes #762
This commit is contained in:
Claude 2026-07-09 22:35:55 +00:00
commit 4d6d51ad02
No known key found for this signature in database
2 changed files with 37 additions and 12 deletions

View file

@ -681,15 +681,20 @@ def test_transform_with_unique_constraint_implicit_index(fresh_db):
""")
dogs.insert({"id": 1, "name": "Cleo", "age": 5})
# Attempt to transform the table without modifying 'name'
with pytest.raises(TransformError) as excinfo:
dogs.transform(types={"age": str})
# Transform should succeed and preserve the UNIQUE constraint
dogs.transform(types={"age": str})
assert "UNIQUE" in dogs.schema
assert (
"Index 'sqlite_autoindex_dogs_1' on table 'dogs' does not have a CREATE INDEX statement."
in str(excinfo.value)
)
assert (
"You must manually drop this index prior to running this transformation and manually recreate the new index after running this transformation."
in str(excinfo.value)
)
# Duplicate name insert should still be rejected
import pytest as _pytest
with _pytest.raises(Exception):
fresh_db.execute("INSERT INTO dogs VALUES (2, 'Cleo', '6')")
# Rename the UNIQUE column: constraint follows the new name
dogs.transform(rename={"name": "dog_name"})
assert "UNIQUE" in dogs.schema
assert [c.name for c in dogs.columns] == ["id", "dog_name", "age"]
# Drop the UNIQUE column: transform completes without error
dogs.transform(drop={"dog_name"})
assert "dog_name" not in [c.name for c in dogs.columns]