Migrating from sqlite3¶
A side-by-side reference for switching from the stdlib sqlite3 module
to cysqlite. For the design rationale, see SQLite Notes.
Connection¶
stdlib |
cysqlite |
|---|---|
db = sqlite3.connect('app.db')
|
db = cysqlite.connect('app.db')
|
db = sqlite3.connect('app.db')
db.execute('PRAGMA journal_mode=WAL')
|
db = cysqlite.connect(
'app.db',
journal_mode='wal')
|
Transactions¶
The stdlib opens transactions implicitly by string-matching DML
statements. cysqlite operates in pure autocommit and uses an explicit
context manager. atomic() uses a real transaction at the outer
level and a savepoint when nested — no manual SAVEPOINT needed.
stdlib |
cysqlite |
|---|---|
db.execute(
'INSERT INTO t VALUES (?)', (1,))
db.commit()
|
with db.atomic():
db.execute(
'INSERT INTO t VALUES (?)', (1,))
|
with db:
db.execute(...)
|
with db.atomic():
db.execute(...)
|
db.execute('SAVEPOINT sp')
try:
...
db.execute('RELEASE SAVEPOINT sp')
except Exception:
db.execute('ROLLBACK TO SAVEPOINT sp')
raise
|
with db.atomic(): # nests freely
with db.atomic():
...
|
Row factories¶
cysqlite’s Row adds attribute access and .as_dict() on top of
the stdlib’s index/name/keys interface. Note that name lookup is
case-sensitive in cysqlite, case-insensitive in stdlib.
stdlib |
cysqlite |
|---|---|
db.row_factory = sqlite3.Row
row = db.execute(...).fetchone()
row[0]; row['name']; row.keys()
|
db.row_factory = cysqlite.Row
row = db.execute(...).fetchone()
row[0]; row['name']; row.name
row.as_dict()
|
def dict_factory(cur, row):
cols = [d[0]
for d in cur.description]
return dict(zip(cols, row))
db.row_factory = dict_factory
|
db.row_factory = cysqlite.dict_factory
|
Adapters and converters¶
stdlib registers globally; cysqlite registers per connection. There is
no detect_types flag — converters always consult the declared
column type.
stdlib |
cysqlite |
|---|---|
sqlite3.register_adapter(dict, json.dumps)
sqlite3.register_converter('JSON',
json.loads)
db = sqlite3.connect(
'x.db',
detect_types=sqlite3.PARSE_DECLTYPES)
|
db = cysqlite.connect('x.db')
db.register_adapter(dict, json.dumps)
db.register_converter('JSON', json.loads)
|
executescript¶
stdlib implicitly commits any open transaction first; cysqlite does not. Wrap explicitly if you want script-as-atomic-unit.
stdlib |
cysqlite |
|---|---|
db.executescript(schema_sql)
|
with db.atomic():
db.executescript(schema_sql)
|
Error handling¶
cysqlite splits IntegrityError and OperationalError along the
extended error code, so you can catch the specific constraint kind
that fired (e.g. duplicate PRIMARY KEY raises
PrimaryKeyIntegrityError, not UniqueIntegrityError).
stdlib |
cysqlite |
|---|---|
try:
db.execute(...)
except sqlite3.IntegrityError as e:
if e.sqlite_errorname == \
'SQLITE_CONSTRAINT_UNIQUE':
...
|
from cysqlite import \
UniqueIntegrityError
try:
db.execute(...)
except UniqueIntegrityError:
...
|
Available subclasses:
IntegrityError:UniqueIntegrityError,NotNullIntegrityError,ForeignKeyIntegrityError,CheckIntegrityError,PrimaryKeyIntegrityErrorOperationalError:DiskFullError,ReadOnlyError,DatabaseLockedError,AuthorizationError
Exception messages include the failing SQL and both error codes:
UniqueIntegrityError: error executing query:
[INSERT INTO users(email) VALUES (?)]
UNIQUE constraint failed: users.email (code=19, ext=2067)
User-defined functions¶
cysqlite takes the callable first and the name as an optional keyword argument.
stdlib |
cysqlite |
|---|---|
db.create_function(
'doubler', 1, lambda x: x * 2,
deterministic=True)
|
db.create_function(
lambda x: x * 2,
name='doubler', nargs=1,
deterministic=True)
|
db.create_aggregate('avg2', 1, Avg)
|
db.create_aggregate(
Avg, name='avg2', nargs=1)
|
BLOBs¶
stdlib |
cysqlite |
|---|---|
with db.blobopen(
't', 'data', rowid) as blob:
blob.read(100)
|
with db.blob_open(
't', 'data', rowid) as blob:
blob.read(100)
blob.readline() # full RawIOBase
blob.reopen(other_rowid)
|
Behaviors that change in your tests¶
commit()androllback()raiseOperationalErrorwhen no transaction is open. Wrap writes inwith db.atomic():and you won’t call them directly.executescriptruns inside any open transaction rather than implicitly committing. Wrap withatomic()if you want it atomic.cysqlite.Rowis case-sensitive on string lookup; stdlib’s is case-insensitive.Anywhere you parsed an
IntegrityErrormessage, catch the exact subclass instead.
What you gain¶
atomic for nested transactions without manual savepoints.
Five
IntegrityErrorand fourOperationalErrorsubclasses; failing SQL embedded in error messages.Built-in
dict_factory(),rank_bm25(),rank_lucene(),levenshtein_dist(),damerau_levenshtein_dist(),median.Virtual tables via
TableFunction.Blobas a fullio.RawIOBase.cysqlite.aiofor asyncio.cysqlite.utils.Poolfor read/write separation under WAL.cysqlite.utils.slow_query_log()for one-line slow-query logging.pragma(..., permanent=True)for pragmas that survive reconnects.