Removed the alter table dry run feature

It works by doing conn.backup(memory_conn) which could use
a lot of memory for a large database.
This commit is contained in:
Simon Willison 2026-06-22 10:18:01 -07:00
commit 084df1fba2
3 changed files with 12 additions and 47 deletions

View file

@ -22,7 +22,6 @@ from datasette.resources import DatabaseResource, TableResource
from datasette.utils import (
escape_sqlite,
get_outbound_foreign_keys,
sqlite3,
table_column_details,
)
from datasette.utils.asgi import NotFound, Response
@ -602,7 +601,6 @@ AlterTableOperation = Annotated[
class AlterTableRequest(_StrictPydanticModel):
operations: list[AlterTableOperation] = Field(min_length=1)
dry_run: bool = False
def _pydantic_errors(validation_error):
@ -1143,14 +1141,6 @@ class TableAlterView(BaseView):
return _table_schema_from_conn(operation_conn, table_name)
if alter_request.dry_run:
memory_conn = sqlite3.connect(":memory:")
try:
conn.backup(memory_conn)
return before_schema, apply_operations(memory_conn)
finally:
memory_conn.close()
after_schema = apply_operations(conn)
return before_schema, after_schema
@ -1162,7 +1152,7 @@ class TableAlterView(BaseView):
return _error([str(e)], 400)
altered = before_schema != after_schema
if altered and not alter_request.dry_run:
if altered:
await self.ds.track_event(
AlterTableEvent(
request.actor,
@ -1189,10 +1179,7 @@ class TableAlterView(BaseView):
"altered": altered,
"schema": after_schema,
"before_schema": before_schema,
"operations_applied": (
0 if alter_request.dry_run else len(alter_request.operations)
),
"dry_run": alter_request.dry_run,
"operations_applied": len(alter_request.operations),
},
status=200,
)

View file

@ -2285,8 +2285,6 @@ The request body should include an ``operations`` array. Each operation has the
]
}
Set ``"dry_run": true`` to validate the operations and return the schema that would be created without modifying the table.
Supported operations:
* ``add_column`` adds a new column. ``args`` accepts ``name``, optional ``type`` of ``text``, ``integer``, ``float`` or ``blob``, optional ``not_null``, optional literal ``default`` and optional ``default_expr``. If ``not_null`` is ``true`` either a non-null ``default`` or ``default_expr`` is required.
@ -2316,8 +2314,7 @@ A successful response returns the new schema and the previous schema:
"altered": true,
"schema": "CREATE TABLE ...",
"before_schema": "CREATE TABLE ...",
"operations_applied": 7,
"dry_run": false
"operations_applied": 7
}
Any errors will return ``{"errors": ["... descriptive message ..."], "ok": false}``, and a ``400`` status code for a bad input or a ``403`` status code for an authentication or permission error.

View file

@ -898,34 +898,6 @@ async def test_alter_table_operations(ds_write):
assert event.after_schema == data["schema"]
@pytest.mark.asyncio
async def test_alter_table_dry_run(ds_write):
token = write_token(ds_write, permissions=["at"])
db = ds_write.get_database("data")
response = await ds_write.client.post(
"/data/docs/-/alter",
json={
"dry_run": True,
"operations": [
{"op": "add_column", "args": {"name": "slug", "type": "text"}}
],
},
headers=_headers(token),
)
assert response.status_code == 200, response.text
data = response.json()
assert data["ok"] is True
assert data["dry_run"] is True
assert data["altered"] is True
assert data["operations_applied"] == 0
assert "slug" in data["schema"]
columns = (
await db.execute("select name from pragma_table_info('docs') order by cid")
).dicts()
assert [column["name"] for column in columns] == ["id", "title", "score", "age"]
assert last_event(ds_write) is None
@pytest.mark.asyncio
async def test_alter_table_foreign_key_operations(ds_write):
token = write_token(ds_write, permissions=["at"])
@ -1246,6 +1218,15 @@ async def test_alter_table_permission_denied(ds_write):
@pytest.mark.parametrize(
"body,expected_error",
(
(
{
"dry_run": True,
"operations": [
{"op": "add_column", "args": {"name": "slug", "type": "text"}}
],
},
"dry_run: Extra inputs are not permitted",
),
(
{"operations": [{"op": "add_column", "args": {"type": "text"}}]},
"operations.0.add_column.args.name: Field required",