diff --git a/datasette/database.py b/datasette/database.py index 468e9360..350c4e9c 100644 --- a/datasette/database.py +++ b/datasette/database.py @@ -94,10 +94,14 @@ class Database: f"file:{self.path}{qs}", uri=True, check_same_thread=False ) - async def execute_write(self, sql, params=None, block=False): + async def execute_write(self, sql, params=None, executescript=False, block=False): + assert not (executescript and params), "Cannot use params with executescript=True" def _inner(conn): with conn: - return conn.execute(sql, params or []) + if executescript: + return conn.executescript(sql) + else: + return conn.execute(sql, params or []) with trace("sql", database=self.name, sql=sql.strip(), params=params): results = await self.execute_write_fn(_inner, block=block) diff --git a/docs/internals.rst b/docs/internals.rst index 8788b26a..d40e679b 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -663,8 +663,8 @@ Example usage: .. _database_execute_write: -await db.execute_write(sql, params=None, block=False) ------------------------------------------------------ +await db.execute_write(sql, params=None, executescript=False, block=False) +-------------------------------------------------------------------------- SQLite only allows one database connection to write at a time. Datasette handles this for you by maintaining a queue of writes to be executed against a given database. Plugins can submit write operations to this queue and they will be executed in the order in which they are received. @@ -676,6 +676,8 @@ By default queries are considered to be "fire and forget" - they will be added t If you pass ``block=True`` this behaviour changes: the method will block until the write operation has completed, and the return value will be the return from calling ``conn.execute(...)`` using the underlying ``sqlite3`` Python library. +If you pass ``executescript=True`` your SQL will be executed using the ``sqlite3`` `conn.executescript() `__ method. This allows multiple SQL statements to be separated by semicolons, but cannot be used with the ``params=`` option. + .. _database_execute_write_fn: await db.execute_write_fn(fn, block=False) diff --git a/tests/test_internals_database.py b/tests/test_internals_database.py index 609caabf..0a5c01a3 100644 --- a/tests/test_internals_database.py +++ b/tests/test_internals_database.py @@ -396,6 +396,27 @@ async def test_execute_write_block_false(db): assert "Mystery!" == rows.rows[0][0] +@pytest.mark.asyncio +async def test_execute_write_executescript(db): + await db.execute_write( + "create table foo (id integer primary key); create table bar (id integer primary key); ", + executescript=True, + block=True + ) + table_names = await db.table_names() + assert {"foo", "bar"}.issubset(table_names) + + +@pytest.mark.asyncio +async def test_execute_write_executescript_not_allowed_with_params(db): + with pytest.raises(AssertionError): + await db.execute_write( + "update roadside_attractions set name = ? where pk = ?", + ["Mystery!", 1], + executescript=True + ) + + @pytest.mark.asyncio async def test_execute_write_has_correctly_prepared_connection(db): # The sleep() function is only available if ds._prepare_connection() was called