In sqlite3, if you set uri=True, you can pass query param arguments to the underlying database, including options like cache=shared and mode=memory, which are useful for unit testing (you can create an in-memory DB which stays alive between multiple connections).
For example, without the query params, :memory: databases are completely separate:
import sqlite3
db1 = sqlite3.connect(":memory:")
db1.execute("CREATE TABLE Example(id INT)")
db1.execute("SELECT * FROM Example")
db2 = sqlite3.connect(":memory:")
db2.execute("SELECT * FROM Example") # sqlite3.OperationalError: no such table: Example
But if we pass cache=shared, they're shared:
import sqlite3
db1 = sqlite3.connect("file::memory:?cache=shared", uri=True)
db1.execute("CREATE TABLE Example(id INT)")
db1.execute("SELECT * FROM Example")
db2 = sqlite3.connect("file::memory:?cache=shared", uri=True)
db2.execute("SELECT * FROM Example") # no error
mode=memory lets you create named memory DB's so you can share them by name instead of having a single shared namespace.
The problem
The problem is that databases handles query params itself and doesn't pass them to the underlying connection:
import asyncio
import sqlite3
from databases import Database
async def example():
_ = sqlite3.connect("file::memory:?cache=shared", uri=True) # Hold a reference open since SQLite deletes the shared DB when ths last reference closes
db1 = Database("sqlite:///file::memory:?cache=shared", uri=True)
await db1.execute("CREATE TABLE Example(id INT)")
await db1.execute("SELECT * FROM Example")
db2 = Database("sqlite:///file::memory:?cache=shared", uri=True)
await db2.execute("SELECT * FROM Example") # sqlite3.OperationalError: no such table: Example
asyncio.run(example())
Proposed solutions
I messed around with this locally and come up with this solution:
... which basically just passes options through, but it doesn't seem to match how other backends work (with kwargs and URL query params being treated identically).
If you wanted to make options and kwargs interchangeable, you could do something like:
function_kwarg_names = {"timeout", "detect_types", "isolation_level", "factory", "check_same_thread", "cached_statements", "uri"}
self._function_options = {key: value for (key, value) in options.items() if key in function_kwargs}
self._uri_options = {key: value for (key, value) in options.items() if key not in function_kwargs}
....
self._database = url.database + "?" + urlencode(self._uri_options)
I can make a PR if that would be helpful but I'm not really sure how to approach this.
Background
In sqlite3, if you set
uri=True
, you can pass query param arguments to the underlying database, including options likecache=shared
andmode=memory
, which are useful for unit testing (you can create an in-memory DB which stays alive between multiple connections).For example, without the query params,
:memory:
databases are completely separate:But if we pass
cache=shared
, they're shared:mode=memory
lets you create named memory DB's so you can share them by name instead of having a single shared namespace.The problem
The problem is that
databases
handles query params itself and doesn't pass them to the underlying connection:Proposed solutions
I messed around with this locally and come up with this solution:
... which basically just passes options through, but it doesn't seem to match how other backends work (with kwargs and URL query params being treated identically).
If you wanted to make options and kwargs interchangeable, you could do something like:
I can make a PR if that would be helpful but I'm not really sure how to approach this.
This would provide a workaround to fix #75