mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Fire insert-rows on /db/-/create if rows were inserted, refs #2260
This commit is contained in:
parent
244f3ff83a
commit
3a999a85fb
2 changed files with 69 additions and 15 deletions
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue