db.execute_write_script() and db.execute_write_many(), closes #1570

Refs #1555
pull/1589/head
Simon Willison 2021-12-18 10:57:22 -08:00
rodzic 2e4ba71b53
commit 5cadc24489
4 zmienionych plików z 58 dodań i 23 usunięć

Wyświetl plik

@ -94,22 +94,33 @@ class Database:
f"file:{self.path}{qs}", uri=True, check_same_thread=False f"file:{self.path}{qs}", uri=True, check_same_thread=False
) )
async def execute_write(self, sql, params=None, executescript=False, block=False): async def execute_write(self, sql, params=None, block=False):
assert not (
executescript and params
), "Cannot use params with executescript=True"
def _inner(conn): def _inner(conn):
with conn: with conn:
if executescript:
return conn.executescript(sql)
else:
return conn.execute(sql, params or []) return conn.execute(sql, params or [])
with trace("sql", database=self.name, sql=sql.strip(), params=params): with trace("sql", database=self.name, sql=sql.strip(), params=params):
results = await self.execute_write_fn(_inner, block=block) results = await self.execute_write_fn(_inner, block=block)
return results return results
async def execute_write_script(self, sql, block=False):
def _inner(conn):
with conn:
return conn.executescript(sql)
with trace("sql", database=self.name, sql=sql.strip(), executescript=True):
results = await self.execute_write_fn(_inner, block=block)
return results
async def execute_write_many(self, sql, params_seq, block=False):
def _inner(conn):
with conn:
return conn.executemany(sql, params_seq)
with trace("sql", database=self.name, sql=sql.strip(), executemany=True):
results = await self.execute_write_fn(_inner, block=block)
return results
async def execute_write_fn(self, fn, block=False): async def execute_write_fn(self, fn, block=False):
task_id = uuid.uuid5(uuid.NAMESPACE_DNS, "datasette.io") task_id = uuid.uuid5(uuid.NAMESPACE_DNS, "datasette.io")
if self._write_queue is None: if self._write_queue is None:

Wyświetl plik

@ -61,7 +61,7 @@ async def init_internal_db(db):
); );
""" """
).strip() ).strip()
await db.execute_write(create_tables_sql, block=True, executescript=True) await db.execute_write_script(create_tables_sql, block=True)
async def populate_schema_tables(internal_db, db): async def populate_schema_tables(internal_db, db):

Wyświetl plik

@ -663,8 +663,8 @@ Example usage:
.. _database_execute_write: .. _database_execute_write:
await db.execute_write(sql, params=None, executescript=False, block=False) await db.execute_write(sql, params=None, 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. 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,7 +676,27 @@ 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 ``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() <https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executescript>`__ method. This allows multiple SQL statements to be separated by semicolons, but cannot be used with the ``params=`` option. .. _database_execute_write_script:
await db.execute_write_script(sql, block=False)
-----------------------------------------------
Like ``execute_write()`` but can be used to send multiple SQL statements in a single string separated by semicolons, using the ``sqlite3`` `conn.executescript() <https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executescript>`__ method.
.. _database_execute_write_many:
await db.execute_write_many(sql, params_seq, block=False)
---------------------------------------------------------
Like ``execute_write()`` but uses the ``sqlite3`` `conn.executemany() <https://docs.python.org/3/library/sqlite3.html#sqlite3.Cursor.executemany>`__ method. This will efficiently execute the same SQL statement against each of the parameters in the ``params_seq`` iterator, for example:
.. code-block:: python
await db.execute_write_many(
"insert into characters (id, name) values (?, ?)",
[(1, "Melanie"), (2, "Selma"), (2, "Viktor")],
block=True,
)
.. _database_execute_write_fn: .. _database_execute_write_fn:

Wyświetl plik

@ -397,10 +397,9 @@ async def test_execute_write_block_false(db):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_execute_write_executescript(db): async def test_execute_write_script(db):
await db.execute_write( await db.execute_write_script(
"create table foo (id integer primary key); create table bar (id integer primary key); ", "create table foo (id integer primary key); create table bar (id integer primary key); ",
executescript=True,
block=True, block=True,
) )
table_names = await db.table_names() table_names = await db.table_names()
@ -408,13 +407,18 @@ async def test_execute_write_executescript(db):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_execute_write_executescript_not_allowed_with_params(db): async def test_execute_write_many(db):
with pytest.raises(AssertionError): await db.execute_write_script(
await db.execute_write( "create table foomany (id integer primary key)",
"update roadside_attractions set name = ? where pk = ?", block=True,
["Mystery!", 1],
executescript=True,
) )
await db.execute_write_many(
"insert into foomany (id) values (?)",
[(1,), (10,), (100,)],
block=True,
)
result = await db.execute("select * from foomany")
assert [r[0] for r in result.rows] == [1, 10, 100]
@pytest.mark.asyncio @pytest.mark.asyncio