Skip to content

database.execute_write_fn() should default to running in a transaction #2277

@simonw

Description

@simonw

The three functions execute_write and execute_write_script and execute_write_many all use a transaction automatically - the with conn: bits:

async def execute_write(self, sql, params=None, block=True):
def _inner(conn):
with conn:
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)
return results
async def execute_write_script(self, sql, block=True):
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=True):
def _inner(conn):
count = 0
def count_params(params):
nonlocal count
for param in params:
count += 1
yield param
with conn:
return conn.executemany(sql, count_params(params_seq)), count
with trace(
"sql", database=self.name, sql=sql.strip(), executemany=True
) as kwargs:
results, count = await self.execute_write_fn(_inner, block=block)
kwargs["count"] = count
return results

execute_write_fn() does not do that. It leaves the function to implement its own transaction handling:

async def execute_write_fn(self, fn, block=True):
if self.ds.executor is None:
# non-threaded mode
if self._write_connection is None:
self._write_connection = self.connect(write=True)
self.ds._prepare_connection(self._write_connection, self.name)
return fn(self._write_connection)
else:
return await self._send_to_write_thread(fn, block)

try:
result = task.fn(conn)
except Exception as e:
sys.stderr.write("{}\n".format(e))
sys.stderr.flush()
result = e

Every time I forget to do my own with conn: in that function I end up with a "database locked" error cropping up! It's an annoying mistake. Here for example:

I'm going to change the execute_write_fn() method to include a transaction=True argument - defaults to True - which causes it to wrap the function in a transaction for you.

You can call transaction=False to opt out of that and handle transactions manually instead.

This would technically be a breaking change which is why I want to get it in before 1.0.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions