Fire insert-rows on /db/-/create if rows were inserted, refs #2260

This commit is contained in:
Simon Willison 2024-02-16 13:58:33 -08:00
commit 3a999a85fb
2 changed files with 69 additions and 15 deletions

View file

@ -10,7 +10,7 @@ import re
import sqlite_utils import sqlite_utils
import textwrap import textwrap
from datasette.events import AlterTableEvent, CreateTableEvent from datasette.events import AlterTableEvent, CreateTableEvent, InsertRowsEvent
from datasette.database import QueryInterrupted from datasette.database import QueryInterrupted
from datasette.utils import ( from datasette.utils import (
add_cors_headers, add_cors_headers,
@ -1022,6 +1022,17 @@ class TableCreateView(BaseView):
request.actor, database=db.name, table=table_name, schema=schema request.actor, database=db.name, table=table_name, schema=schema
) )
) )
if rows:
await self.ds.track_event(
InsertRowsEvent(
request.actor,
database=db.name,
table=table_name,
num_rows=len(rows),
ignore=ignore,
replace=replace,
)
)
return Response.json(details, status=201) return Response.json(details, status=201)

View file

@ -857,13 +857,14 @@ async def test_drop_table(ds_write, scenario):
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input,expected_status,expected_response", "input,expected_status,expected_response,expected_events",
( (
# Permission error with a bad token # Permission error with a bad token
( (
{"table": "bad", "row": {"id": 1}}, {"table": "bad", "row": {"id": 1}},
403, 403,
{"ok": False, "errors": ["Permission denied"]}, {"ok": False, "errors": ["Permission denied"]},
[],
), ),
# Successful creation with columns: # Successful creation with columns:
( (
@ -910,6 +911,7 @@ async def test_drop_table(ds_write, scenario):
")" ")"
), ),
}, },
["create-table"],
), ),
# Successful creation with rows: # Successful creation with rows:
( (
@ -945,6 +947,7 @@ async def test_drop_table(ds_write, scenario):
), ),
"row_count": 2, "row_count": 2,
}, },
["create-table", "insert-rows"],
), ),
# Successful creation with row: # Successful creation with row:
( (
@ -973,6 +976,7 @@ async def test_drop_table(ds_write, scenario):
), ),
"row_count": 1, "row_count": 1,
}, },
["create-table", "insert-rows"],
), ),
# Create with row and no primary key # Create with row and no primary key
( (
@ -992,6 +996,7 @@ async def test_drop_table(ds_write, scenario):
"schema": ("CREATE TABLE [four] (\n" " [name] TEXT\n" ")"), "schema": ("CREATE TABLE [four] (\n" " [name] TEXT\n" ")"),
"row_count": 1, "row_count": 1,
}, },
["create-table", "insert-rows"],
), ),
# Create table with compound primary key # Create table with compound primary key
( (
@ -1013,6 +1018,7 @@ async def test_drop_table(ds_write, scenario):
), ),
"row_count": 1, "row_count": 1,
}, },
["create-table", "insert-rows"],
), ),
# Error: Table is required # Error: Table is required
( (
@ -1024,6 +1030,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["Table is required"], "errors": ["Table is required"],
}, },
[],
), ),
# Error: Invalid table name # Error: Invalid table name
( (
@ -1036,6 +1043,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["Invalid table name"], "errors": ["Invalid table name"],
}, },
[],
), ),
# Error: JSON must be an object # Error: JSON must be an object
( (
@ -1045,6 +1053,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["JSON must be an object"], "errors": ["JSON must be an object"],
}, },
[],
), ),
# Error: Cannot specify columns with rows or row # Error: Cannot specify columns with rows or row
( (
@ -1058,6 +1067,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["Cannot specify columns with rows or row"], "errors": ["Cannot specify columns with rows or row"],
}, },
[],
), ),
# Error: columns, rows or row is required # Error: columns, rows or row is required
( (
@ -1069,6 +1079,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["columns, rows or row is required"], "errors": ["columns, rows or row is required"],
}, },
[],
), ),
# Error: columns must be a list # Error: columns must be a list
( (
@ -1081,6 +1092,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["columns must be a list"], "errors": ["columns must be a list"],
}, },
[],
), ),
# Error: columns must be a list of objects # Error: columns must be a list of objects
( (
@ -1093,6 +1105,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["columns must be a list of objects"], "errors": ["columns must be a list of objects"],
}, },
[],
), ),
# Error: Column name is required # Error: Column name is required
( (
@ -1105,6 +1118,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["Column name is required"], "errors": ["Column name is required"],
}, },
[],
), ),
# Error: Unsupported column type # Error: Unsupported column type
( (
@ -1117,6 +1131,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["Unsupported column type: bad"], "errors": ["Unsupported column type: bad"],
}, },
[],
), ),
# Error: Duplicate column name # Error: Duplicate column name
( (
@ -1132,6 +1147,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["Duplicate column name: id"], "errors": ["Duplicate column name: id"],
}, },
[],
), ),
# Error: rows must be a list # Error: rows must be a list
( (
@ -1144,6 +1160,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["rows must be a list"], "errors": ["rows must be a list"],
}, },
[],
), ),
# Error: rows must be a list of objects # Error: rows must be a list of objects
( (
@ -1156,6 +1173,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["rows must be a list of objects"], "errors": ["rows must be a list of objects"],
}, },
[],
), ),
# Error: pk must be a string # Error: pk must be a string
( (
@ -1169,6 +1187,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["pk must be a string"], "errors": ["pk must be a string"],
}, },
[],
), ),
# Error: Cannot specify both pk and pks # Error: Cannot specify both pk and pks
( (
@ -1183,6 +1202,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["Cannot specify both pk and pks"], "errors": ["Cannot specify both pk and pks"],
}, },
[],
), ),
# Error: pks must be a list # Error: pks must be a list
( (
@ -1196,12 +1216,14 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["pks must be a list"], "errors": ["pks must be a list"],
}, },
[],
), ),
# Error: pks must be a list of strings # Error: pks must be a list of strings
( (
{"table": "bad", "row": {"id": 1, "name": "Row 1"}, "pks": [1, 2]}, {"table": "bad", "row": {"id": 1, "name": "Row 1"}, "pks": [1, 2]},
400, 400,
{"ok": False, "errors": ["pks must be a list of strings"]}, {"ok": False, "errors": ["pks must be a list of strings"]},
[],
), ),
# Error: ignore and replace are mutually exclusive # Error: ignore and replace are mutually exclusive
( (
@ -1217,6 +1239,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["ignore and replace are mutually exclusive"], "errors": ["ignore and replace are mutually exclusive"],
}, },
[],
), ),
# ignore and replace require row or rows # ignore and replace require row or rows
( (
@ -1230,6 +1253,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["ignore and replace require row or rows"], "errors": ["ignore and replace require row or rows"],
}, },
[],
), ),
# ignore and replace require pk or pks # ignore and replace require pk or pks
( (
@ -1243,6 +1267,7 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["ignore and replace require pk or pks"], "errors": ["ignore and replace require pk or pks"],
}, },
[],
), ),
( (
{ {
@ -1255,10 +1280,14 @@ async def test_drop_table(ds_write, scenario):
"ok": False, "ok": False,
"errors": ["ignore and replace require pk or pks"], "errors": ["ignore and replace require pk or pks"],
}, },
[],
), ),
), ),
) )
async def test_create_table(ds_write, input, expected_status, expected_response): async def test_create_table(
ds_write, input, expected_status, expected_response, expected_events
):
ds_write._tracked_events = []
# Special case for expected status of 403 # Special case for expected status of 403
if expected_status == 403: if expected_status == 403:
token = "bad_token" token = "bad_token"
@ -1272,12 +1301,9 @@ async def test_create_table(ds_write, input, expected_status, expected_response)
assert response.status_code == expected_status assert response.status_code == expected_status
data = response.json() data = response.json()
assert data == expected_response assert data == expected_response
# create-table event # Should have tracked the expected events
if expected_status == 201: events = ds_write._tracked_events
event = last_event(ds_write) assert [e.name for e in events] == expected_events
assert event.name == "create-table"
assert event.actor == {"id": "root", "token": "dstok"}
assert event.schema.startswith("CREATE TABLE ")
@pytest.mark.asyncio @pytest.mark.asyncio
@ -1376,6 +1402,8 @@ async def test_create_table_ignore_replace(ds_write, input, expected_rows_after)
) )
assert first_response.status_code == 201 assert first_response.status_code == 201
ds_write._tracked_events = []
# Try a second time # Try a second time
second_response = await ds_write.client.post( second_response = await ds_write.client.post(
"/data/-/create", "/data/-/create",
@ -1387,6 +1415,10 @@ async def test_create_table_ignore_replace(ds_write, input, expected_rows_after)
rows = await ds_write.client.get("/data/test_insert_replace.json?_shape=array") rows = await ds_write.client.get("/data/test_insert_replace.json?_shape=array")
assert rows.json() == expected_rows_after assert rows.json() == expected_rows_after
# Check it fired the right events
event_names = [e.name for e in ds_write._tracked_events]
assert event_names == ["insert-rows"]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_table_error_if_pk_changed(ds_write): async def test_create_table_error_if_pk_changed(ds_write):
@ -1471,6 +1503,7 @@ async def test_method_not_allowed(ds_write, path):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_uses_alter_by_default_for_new_table(ds_write): async def test_create_uses_alter_by_default_for_new_table(ds_write):
ds_write._tracked_events = []
token = write_token(ds_write) token = write_token(ds_write)
response = await ds_write.client.post( response = await ds_write.client.post(
"/data/-/create", "/data/-/create",
@ -1490,8 +1523,8 @@ async def test_create_uses_alter_by_default_for_new_table(ds_write):
headers=_headers(token), headers=_headers(token),
) )
assert response.status_code == 201 assert response.status_code == 201
event = last_event(ds_write) event_names = [e.name for e in ds_write._tracked_events]
assert event.name == "create-table" assert event_names == ["create-table", "insert-rows"]
@pytest.mark.asyncio @pytest.mark.asyncio
@ -1517,6 +1550,8 @@ async def test_create_using_alter_against_existing_table(
headers=_headers(token), headers=_headers(token),
) )
assert response.status_code == 201 assert response.status_code == 201
ds_write._tracked_events = []
# Now try to insert more rows using /-/create with alter=True # Now try to insert more rows using /-/create with alter=True
response2 = await ds_write.client.post( response2 = await ds_write.client.post(
"/data/-/create", "/data/-/create",
@ -1536,8 +1571,16 @@ async def test_create_using_alter_against_existing_table(
} }
else: else:
assert response2.status_code == 201 assert response2.status_code == 201
event_names = [e.name for e in ds_write._tracked_events]
assert event_names == ["alter-table", "insert-rows"]
# It should have altered the table # It should have altered the table
event = last_event(ds_write) alter_event = ds_write._tracked_events[0]
assert event.name == "alter-table" assert alter_event.name == "alter-table"
assert "extra" not in event.before_schema assert "extra" not in alter_event.before_schema
assert "extra" in event.after_schema assert "extra" in alter_event.after_schema
insert_rows_event = ds_write._tracked_events[1]
assert insert_rows_event.name == "insert-rows"
assert insert_rows_event.num_rows == 1