diff --git a/datasette/views/database.py b/datasette/views/database.py index a420bb5c..9e46ec3e 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -701,7 +701,7 @@ async def _table_columns(datasette, database_name): class TableCreateView(BaseView): name = "table-create" - _valid_keys = {"table", "rows", "row", "columns", "pk"} + _valid_keys = {"table", "rows", "row", "columns", "pk", "pks"} _supported_column_types = { "text", "integer", @@ -785,18 +785,28 @@ class TableCreateView(BaseView): return _error(["rows must be a list of objects"]) pk = data.get("pk") + pks = data.get("pks") + + if pk and pks: + return _error(["Cannot specify both pk and pks"]) if pk: if not isinstance(pk, str): return _error(["pk must be a string"]) + if pks: + if not isinstance(pks, list): + return _error(["pks must be a list"]) + for pk in pks: + if not isinstance(pk, str): + return _error(["pks must be a list of strings"]) def create_table(conn): table = sqlite_utils.Database(conn)[table_name] if rows: - table.insert_all(rows, pk=pk) + table.insert_all(rows, pk=pks or pk) else: table.create( {c["name"]: c["type"] for c in columns}, - pk=pk, + pk=pks or pk, ) return table.schema diff --git a/docs/json_api.rst b/docs/json_api.rst index 06a1d65d..058c9f63 100644 --- a/docs/json_api.rst +++ b/docs/json_api.rst @@ -666,6 +666,8 @@ The JSON here describes the table that will be created: If you set this to ``id`` without including an ``id`` column in the list of ``columns``, Datasette will create an integer ID column for you. +* ``pks`` can be used instead of ``pk`` to create a compound primary key. It should be a JSON list of column names to use in that primary key. + If the table is successfully created this will return a ``201`` status code and the following response: .. code-block:: json diff --git a/tests/test_api_write.py b/tests/test_api_write.py index 0479a2dd..51fd5124 100644 --- a/tests/test_api_write.py +++ b/tests/test_api_write.py @@ -643,6 +643,27 @@ async def test_drop_table(ds_write, scenario): "row_count": 1, }, ), + # Create table with compound primary key + ( + { + "table": "five", + "row": {"type": "article", "key": 123, "title": "Article 1"}, + "pks": ["type", "key"], + }, + 201, + { + "ok": True, + "database": "data", + "table": "five", + "table_url": "http://localhost/data/five", + "table_api_url": "http://localhost/data/five.json", + "schema": ( + "CREATE TABLE [five] (\n [type] TEXT,\n [key] INTEGER,\n" + " [title] TEXT,\n PRIMARY KEY ([type], [key])\n)" + ), + "row_count": 1, + }, + ), # Error: Table is required ( { @@ -799,6 +820,39 @@ async def test_drop_table(ds_write, scenario): "errors": ["pk must be a string"], }, ), + # Error: Cannot specify both pk and pks + ( + { + "table": "bad", + "row": {"id": 1, "name": "Row 1"}, + "pk": "id", + "pks": ["id", "name"], + }, + 400, + { + "ok": False, + "errors": ["Cannot specify both pk and pks"], + }, + ), + # Error: pks must be a list + ( + { + "table": "bad", + "row": {"id": 1, "name": "Row 1"}, + "pks": "id", + }, + 400, + { + "ok": False, + "errors": ["pks must be a list"], + }, + ), + # Error: pks must be a list of strings + ( + {"table": "bad", "row": {"id": 1, "name": "Row 1"}, "pks": [1, 2]}, + 400, + {"ok": False, "errors": ["pks must be a list of strings"]}, + ), ), ) async def test_create_table(ds_write, input, expected_status, expected_response):