diff --git a/docs/python-api.rst b/docs/python-api.rst index 62662c9..53a47dd 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -1995,7 +1995,7 @@ Tables that are referenced by views can be safely transformed - the view definit A view that references a column which the transform renamed or dropped will remain defined but will raise a ``no such column`` error when it is next queried. This is inherent to SQLite views, whose SQL is stored as text - if you rename or drop columns that a view depends on you should update that view definition yourself. -To achieve this, the SQL produced by ``transform_sql()`` brackets its ``ALTER TABLE ... RENAME TO`` statements with ``PRAGMA legacy_alter_table=ON`` and ``PRAGMA legacy_alter_table=OFF`` - without this, SQLite would attempt to rewrite references to the renamed table in every view definition, which fails when a view references the table that was just dropped. One consequence is that ``PRAGMA legacy_alter_table`` is reset to ``OFF`` (the SQLite default) after a transform, even if it was previously set to ``ON`` for the connection. +To achieve this, the SQL produced by ``transform_sql()`` turns on ``PRAGMA legacy_alter_table`` for its ``ALTER TABLE ... RENAME TO`` statements, then restores the pragma to the value it had when the SQL was generated - without this, SQLite would attempt to rewrite references to the renamed table in every view definition, which fails when a view references the table that was just dropped. .. _python_api_transform_sql: diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 97d16ff..1b25969 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -2830,7 +2830,12 @@ class Table(Queryable): # references the table that was just dropped - and with keep_table= # would silently repoint views at the backup table. These renames are # an implementation detail of transform(), so use legacy_alter_table - # to leave view definitions untouched. + # to leave view definitions untouched, restoring the connection's + # current value afterwards. + legacy_alter_table_row = self.db.execute("PRAGMA legacy_alter_table").fetchone() + legacy_alter_table_was_on = bool( + legacy_alter_table_row and legacy_alter_table_row[0] + ) if keep_table: sqls.append("PRAGMA legacy_alter_table=ON;") sqls.append( @@ -2842,7 +2847,11 @@ class Table(Queryable): sqls.append( f"ALTER TABLE {quote_identifier(new_table_name)} RENAME TO {quote_identifier(self.name)};" ) - sqls.append("PRAGMA legacy_alter_table=OFF;") + sqls.append( + "PRAGMA legacy_alter_table={};".format( + "ON" if legacy_alter_table_was_on else "OFF" + ) + ) # Re-add existing indexes for index in self.indexes: if index.origin != "pk": diff --git a/tests/test_transform.py b/tests/test_transform.py index 9fc9751..b07e7da 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -1049,3 +1049,17 @@ def test_transform_with_view_in_open_transaction(fresh_db): "select sql from sqlite_master where name = 'dogs_view'" ).fetchone()[0] assert view_sql == "CREATE VIEW dogs_view as select id, name from dogs" + + +def test_transform_restores_legacy_alter_table_setting(fresh_db): + dogs = fresh_db["dogs"] + dogs.insert({"id": 1, "name": "Cleo"}, pk="id") + # Default is OFF, reset to OFF afterwards + dogs.transform(types={"name": str}) + assert fresh_db.execute("PRAGMA legacy_alter_table").fetchone()[0] == 0 + # If the connection has it ON, it should be restored to ON + fresh_db.execute("PRAGMA legacy_alter_table=ON") + sqls = dogs.transform_sql(types={"name": str}, tmp_suffix="suffix") + assert sqls[-1] == "PRAGMA legacy_alter_table=ON;" + dogs.transform(types={"name": str}) + assert fresh_db.execute("PRAGMA legacy_alter_table").fetchone()[0] == 1