Bei Verwendung von SQLite aus Python ist sqlite3 standardmäßig in Python 2.5 und höher verfügbar.
Es bietet eine SQL-Schnittstelle, die der DB-API 2.0-Spezifikation entspricht und unter dem Namen pysqlite entwickelt wurde.
sqlite3 http://docs.python.jp/2/library/sqlite3.html
Diese Bibliothek verfügt jedoch nicht über alle Funktionen von SQLite. Dies entspricht den DB-API-Spezifikationen.
APSW ist eine Bibliothek, mit der Sie die SQLite-API in Python vollständig nutzen können. APSW funktioniert mit Python 2.x und Python 3.x.
https://github.com/rogerbinns/apsw http://rogerbinns.github.io/apsw/
Die Installation ist mit pip oder easy_install nicht möglich.
** Update 2019.08.03 ** Derzeit kann es auch unten installiert werden.
pip install --user https://github.com/rogerbinns/apsw/releases/download/3.28.0-r1/apsw-3.28.0-r1.zip --global-option=fetch --global-option=--version --global-option=3.28.0 --global-option=--all --global-option=build --global-option=--enable-all-extensions
Die neuesten Informationen finden Sie weiter unten. https://rogerbinns.github.io/apsw/download.html#easy-install-pip-pypi
Laden Sie die entsprechende Binärdatei aus dem Folgenden herunter und führen Sie das Installationsprogramm aus.
http://rogerbinns.github.io/apsw/download.html#source-and-binaries
Wenn Sie über Visual Studio verfügen, können Sie genau wie UNIX aus dem Quellcode erstellen. (Mit mingw32 konnte ich es erstellen, aber es funktionierte nicht, da zur Laufzeit keine DLL vorhanden war.)
Laden Sie den Quellcode herunter und führen Sie den folgenden Befehl aus.
python setup.py fetch --all build --enable-all-extensions install
Details zum Build finden Sie auf der folgenden Seite. http://rogerbinns.github.io/apsw/build.html
APSW und Pysqlite bieten Zugriff auf SQLite aus radikal unterschiedlichen Richtungen.
APSW umschließt nur Version 3 von SQLite und bietet eine Möglichkeit, auf alle APIs zuzugreifen.
pysqlite verhält sich wie jede andere Datenbank, um einen DBAPI-kompatiblen Wrapper bereitzustellen. Daher werden einige SQLite-Funktionen ausgeblendet.
Im Folgenden finden Sie einige der Vorteile und erweiterten Funktionen von APSW.
APSW stellt die neueste Version von SQLite zur Verfügung. Wenn Features zu SQLite hinzugefügt oder geändert werden, folgt APSW auch diesen Features.
Virtuelle Tabelle ist eine in SQLite 3.3.7 eingeführte Funktion.
Virtuelle Tabellen unterscheiden sich in Bezug auf SQL-Anweisungen nicht von anderen Tabellen und Ansichten. Hinter den Kulissen lösen Abfragen und Schreibvorgänge in Virtual Table Rückrufmethoden aus, anstatt Datenbankdateien zu lesen und zu schreiben.
Das folgende Beispiel zeigt die Bearbeitung von Daten in einem zweidimensionalen Array mit SQL.
# -*- coding: utf-8 -*-
import os, sys, time
import apsw
connection=apsw.Connection(":memory:")
cursor=connection.cursor()
###
### Virtual tables
###
#
data = [
[1, 'test1', 'categoryA'],
[2, 'test2', 'categoryA'],
[3, 'test3', 'categoryA'],
[4, 'test4', 'categoryB'],
[5, 'test5', 'categoryB'],
[6, 'test6', 'categoryB'],
[7, 'test7', 'categoryB'],
[8, 'test8', 'categoryC'],
[9, 'test9', 'categoryC'],
[10, 'test10', 'categoryC']
]
counter = len(data)
# This gets registered with the Connection
class Source:
def Create(self, db, modulename, dbname, tablename, *args):
columns = ['rowid', 'name', 'category']
schema="create table foo("+','.join(["'%s'" % (x,) for x in columns[1:]])+")"
return schema,Table(columns,data)
Connect=Create
# Represents a table
class Table:
def __init__(self, columns, data):
self.columns=columns
self.data=data
def BestIndex(self, *args):
return None
def Open(self):
return Cursor(self)
def Disconnect(self):
pass
def UpdateChangeRow(self, row, newrowid, fields):
for d in data:
if(d[0] == row):
d[0] = newrowid
d[1] = fields[0]
d[2] = fields[1]
def UpdateDeleteRow(self, row):
for i in range(len(data)):
if(data[i][0] == row):
del data[i]
return
def UpdateInsertRow(self, rowid, fields):
global counter
counter = counter + 1
data.append([counter, fields[0], fields[1]])
return counter
Destroy=Disconnect
# Represents a cursor
class Cursor:
def __init__(self, table):
self.table=table
def Filter(self, *args):
self.pos=0
def Eof(self):
return self.pos>=len(self.table.data)
def Rowid(self):
return self.table.data[self.pos][0]
def Column(self, col):
return self.table.data[self.pos][1+col]
def Next(self):
self.pos+=1
def Close(self):
pass
connection.createmodule("source", Source())
cursor.execute("create virtual table test using source()")
ret = cursor.execute("select * from test where category = 'categoryB'")
for row in ret:
print row[0], row[1]
print ('update -----------------')
cursor.execute("update test set category='categoryB' where name='test1'")
ret = cursor.execute("select * from test where category = 'categoryB'")
for row in ret:
print row[0], row[1]
print ('delete -----------------')
cursor.execute("delete from test where name='test4'")
ret = cursor.execute("select * from test")
for row in ret:
print row[0], row[1]
print ('insert ----------------')
cursor.execute("insert into test values('xxxx','yyyy')")
ret = cursor.execute("select * from test")
for row in ret:
print row[0], row[1]
Auf diese Weise können Sie mithilfe von VirutalTable beliebige Daten mit SQL bearbeiten. Im offiziellen Beispiel gibt es ein Beispiel, das die Dateien im Verzeichnis mit SQL auswählt. http://apidoc.apsw.googlecode.com/hg/example.html
Weitere Informationen zu VirtualTable finden Sie im Folgenden. http://rogerbinns.github.io/apsw/vtable.html
Sie können VFS verwenden, das die Schnittstelle zwischen dem Kern und den zugrunde liegenden Betriebssystemen von SQLite definiert.
Mit APSW können Sie die VFS-Funktionalität nutzen und das Standard-VFS erben und erweitern. Im folgenden Beispiel wird beispielsweise VFS verwendet, um eine SQLite-Datei zu verschleiern.
# -*- coding: utf-8 -*-
import os, sys, time
import apsw
###Dieses Beispiel ist ein Auszug aus dem Folgenden
### http://apidoc.apsw.googlecode.com/hg/example.html
###Verschleiern Sie die Datenbank mit VFS
###XOR alle Bytes des Schemas mit 0xa5.
###Diese Methode wird von MAPI und SQL Server verwendet
###
def encryptme(data):
if not data: return data
return "".join([chr(ord(x)^0xa5) for x in data])
# ""Die Vererbung von der Basis von ist das Standard-VFS
class ObfuscatedVFS(apsw.VFS):
def __init__(self, vfsname="obfu", basevfs=""):
self.vfsname=vfsname
self.basevfs=basevfs
apsw.VFS.__init__(self, self.vfsname, self.basevfs)
#Ich möchte meine eigene Datei implementieren, aber ich möchte sie auch erben
def xOpen(self, name, flags):
# We can look at uri parameters
if isinstance(name, apsw.URIFilename):
print "fast is", name.uri_parameter("fast")
print "level is", name.uri_int("level", 3)
print "warp is", name.uri_boolean("warp", False)
print "notpresent is", name.uri_parameter("notpresent")
return ObfuscatedVFSFile(self.basevfs, name, flags)
#Überschreiben Sie xRead und xWrite, um Kryptoroutinen zu implementieren
class ObfuscatedVFSFile(apsw.VFSFile):
def __init__(self, inheritfromvfsname, filename, flags):
apsw.VFSFile.__init__(self, inheritfromvfsname, filename, flags)
def xRead(self, amount, offset):
return encryptme(super(ObfuscatedVFSFile, self).xRead(amount, offset))
def xWrite(self, data, offset):
super(ObfuscatedVFSFile, self).xWrite(encryptme(data), offset)
# To register the VFS we just instantiate it
obfuvfs=ObfuscatedVFS()
# Lets see what vfs are now available?
print apsw.vfsnames()
# Make an obfuscated db, passing in some URI parameters
obfudb=apsw.Connection("file:myobfudb?fast=speed&level=7&warp=on",
flags=apsw.SQLITE_OPEN_READWRITE | apsw.SQLITE_OPEN_CREATE | apsw.SQLITE_OPEN_URI,
vfs=obfuvfs.vfsname)
# Check it works
obfudb.cursor().execute("create table foo(x,y); insert into foo values(1,2)")
#Überprüfen Sie den Inhalt der tatsächlichen Disc
print `open("myobfudb", "rb").read()[:20]`
# '\xf6\xf4\xe9\xcc\xd1\xc0\x85\xc3\xca\xd7\xc8\xc4\xd1\x85\x96\xa5\xa1\xa5\xa4\xa4'
print `encryptme(open("myobfudb", "rb").read()[:20])`
# 'SQLite format 3\x00\x04\x00\x01\x01'
# Tidy up
obfudb.close()
os.remove("myobfudb")
Siehe unten für Details. http://rogerbinns.github.io/apsw/vfs.html
Blob ist ein SQLite-Datentyp, der eine Folge von Bytes darstellt. Es ist ein Byte der Größe Null oder größer.
Sie können diesen Blob mit APSW lesen und schreiben. Das Verwendungsbeispiel ist unten dargestellt.
# -*- coding: utf-8 -*-
import os, sys, time
import apsw
import os
connection=apsw.Connection("blob.sqlite")
cursor=connection.cursor()
###
### Blob I/O
### http://apidoc.apsw.googlecode.com/hg/example.html
cursor.execute("create table blobby(x,y)")
# Add a blob we will fill in later
cursor.execute("insert into blobby values(1,zeroblob(10000))")
# Or as a binding
cursor.execute("insert into blobby values(2,?)", (apsw.zeroblob(20000),))
# Open a blob for writing. We need to know the rowid
rowid=cursor.execute("select ROWID from blobby where x=1").next()[0]
blob=connection.blobopen("main", "blobby", "y", rowid, 1) # 1 is for read/write
blob.write("hello world")
blob.seek(100)
blob.write("hello world, again")
blob.close()
Weitere Informationen finden Sie im Folgenden. http://rogerbinns.github.io/apsw/blob.html
APSW kann die Sicherung verwenden, um eine verbundene Datenbank in einer anderen verbundenen Datenbank zu sichern.
# -*- coding: utf-8 -*-
import os, sys, time
import apsw
import os
connection=apsw.Connection("src.sqlite")
cursor=connection.cursor()
#Erstellen Sie eine Sicherungsquelle
cursor.execute("create table test(x,y)")
cursor.execute("insert into test values(1,'TEST1')")
cursor.execute("insert into test values(2,'TEST2')")
cursor.execute("insert into test values(3,'TEST3')")
cursor.execute("insert into test values(4,'TEST4')")
cursor.execute("insert into test values(5,'TEST5')")
#Backup
memcon=apsw.Connection("backup.sqlite")
with memcon.backup("main", connection, "main") as backup:
backup.step() # copy whole database in one go
for row in memcon.cursor().execute("select * from test"):
print row[0], row[1]
pass
Weitere Informationen finden Sie im Folgenden. http://rogerbinns.github.io/apsw/backup.html
Sie können Verbindungen und Cursor über Threads hinweg gemeinsam nutzen. Für pysqlite müssen Connection und Cursor im selben Thread verwendet werden.
Pysqlite Beispiel
# -*- coding: utf-8 -*-
import threading
import sqlite3
def func(t):
return 1 + t
class TestThread(threading.Thread):
def __init__(self, conn):
threading.Thread.__init__(self)
self.conn = conn
def run(self):
self.conn.create_function("func", 1, func)
cur = self.conn.cursor()
ret = cur.execute("select func(3)")
for row in ret:
print(row[0])
conn = sqlite3.connect(":memory:")
th = TestThread(conn)
th.start()
th.join()
Da pysqlite keine Cross-Thread-Operationen zulässt, treten die folgenden Ausnahmen auf.
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python27\lib\threading.py", line 810, in __bootstrap_inner
self.run()
File "test_thread.py", line 14, in run
self.conn.create_function("func", 1, func)
ProgrammingError: SQLite objects created in a thread can only be used in that sa
me thread.The object was created in thread id 19540 and this is thread id 4652
Im Fall von APSW wird keine Ausnahme für ähnliche Threads ausgelöst.
Spreizgewinde mit APSW
# -*- coding: utf-8 -*-
import threading
import apsw
def func(t):
return 1 + t
class TestThread(threading.Thread):
def __init__(self, conn):
threading.Thread.__init__(self)
self.conn = conn
def run(self):
self.conn.createscalarfunction("func", func, 1)
cur = self.conn.cursor()
ret = cur.execute("select func(3)")
for row in ret:
print(row[0])
conn = apsw.Connection(":memory:")
th = TestThread(conn)
th.start()
th.join()
Wenn Sie die exklusive Verarbeitung jedoch nicht sorgfältig behandeln, führt dies zu einem Absturz oder einer Deadlock.
Mit APSW können Sie verschachtelte Transaktionen mithilfe des Kontextmanagers von Connection verwenden. In pysqlite kann jeweils nur eine Transaktion verwendet werden, und eine Verschachtelung ist nicht möglich.
Der in dieser verschachtelten Transaktion verwendete SavePoint wurde in SQLite 3.6.8 hinzugefügt. Dies ist einer der Vorteile von APSW, mit dem Sie SQLite aktuell verwenden können.
Das Folgende ist ein Beispiel für verschachtelte Transaktionen.
# -*- coding: utf-8 -*-
import os, sys, time
import apsw
import os
connection=apsw.Connection(":memory:")
connection.cursor().execute("create table test(x primary key,y)")
with connection: #Starten Sie eine Transaktion mit mit. Rollback mit Ausnahmen, sonst Commit
connection.cursor().execute("insert into test values(1,'TEST1')")
try: #Starten Sie SAVE POINT mit mit. Rollback mit Ausnahmen, sonst Commit
with connection:
connection.cursor().execute("insert into test values(2,'TEST2')")
connection.cursor().execute("insert into test values(3,'TEST3')")
except Exception, ex:
print (ex)
print ('rollback 1')
try:
with connection: #Die folgende SQL wird nicht mit einem Fehler aufgezeichnet
connection.cursor().execute("insert into test values(4,'TEST4')")
connection.cursor().execute("insert into test values(4,'Error')")
except Exception, ex:
print (ex)
print ('rollback 2')
try:
with connection:
connection.cursor().execute("insert into test values(5,'TEST5')")
connection.cursor().execute("insert into test values(6,'TEST6')")
except Exception, ex:
print (ex)
print ('rollback 3')
for row in connection.cursor().execute("select * from test"):
print row[0], row[1]
** Informationen zum Verbindungskontext ** http://rogerbinns.github.io/apsw/connection.html#apsw.Connection.enter
** Informationen zu verschachtelten SQLite-Transaktionen ** https://sqlite.org/lang_savepoint.html
In APSW können mehrere Befehle ausgeführt werden, indem sie durch ein Semikolon getrennt werden.
# -*- coding: utf-8 -*-
import apsw
con=apsw.Connection(":memory:")
cur=con.cursor()
for row in cur.execute("create table foo(x,y,z);insert into foo values (?,?,?);"
"insert into foo values(?,?,?);select * from foo;drop table foo;"
"create table bar(x,y);insert into bar values(?,?);"
"insert into bar values(?,?);select * from bar;",
(1,2,3,4,5,6,7,8,9,10)):
print row
In APSW ist SQL, das Daten wie SELECT zurückgibt, in Cursor.executemany () verfügbar.
# -*- coding: utf-8 -*-
import apsw
con=apsw.Connection(":memory:")
cur=con.cursor()
cur.execute("create table foo(x);")
cur.executemany("insert into foo (x) values(?)", ( [1], [2], [3] ) )
# You can also use it for statements that return data
for row in cur.executemany("select * from foo where x=?", ( [1], [2], [3] ) ):
print row
In pysqlite kann executemany () in SQL einschließlich SELECT nicht verwendet werden. http://stackoverflow.com/questions/14142554/sqlite3-python-executemany-select
APSW gibt eine leicht nachvollziehbare Ausnahme aus, z. B. wenn innerhalb einer benutzerdefinierten Funktion ein Fehler auftritt.
Lassen Sie uns unten den Unterschied überprüfen, wenn eine Ausnahme in einer benutzerdefinierten Funktion ausgelöst wird.
Pysqlite-Ausnahme
import sqlite3
def badfunc(t):
return 1/0
#sqlite3.enable_callback_tracebacks(True)
con = sqlite3.connect(":memory:")
con.create_function("badfunc", 1, badfunc)
cur = con.cursor()
cur.execute("select badfunc(3)")
Wenn enable_callback_tracebacks False ist (Standard), wird der folgende Fehler angezeigt:
Traceback (most recent call last):
File "test_fnc1.py", line 9, in <module>
cur.execute("select badfunc(3)")
sqlite3.OperationalError: user-defined function raised exception
Wenn enable_callback_tracebacks True ist, tritt der folgende Fehler auf.
Traceback (most recent call last):
File "test_fnc1.py", line 3, in badfunc
return 1/0
ZeroDivisionError: integer division or modulo by zero
Traceback (most recent call last):
File "test_fnc1.py", line 9, in <module>
cur.execute("select badfunc(3)")
sqlite3.OperationalError: user-defined function raised exception
Wenn enable_callback_tracebacks False ist, werden die Ausnahmen in der benutzerdefinierten Funktion gequetscht, und selbst wenn dies auf True gesetzt ist, ist die Art und Weise, wie Traceback angezeigt wird, nicht intuitiv.
Schauen wir uns auf der anderen Seite die Ausnahme von APSW an.
Ausnahme in APSW
def badfunc(t):
return 1/0
import apsw
con = apsw.Connection(":memory:")
con.createscalarfunction("badfunc", badfunc, 1)
cur = con.cursor()
cur.execute("select badfunc(3)")
APSW gibt Traceback aus, das intuitiv wie folgt leicht zu verstehen ist.
Traceback (most recent call last):
File "test_fnc2.py", line 9, in <module>
cur.execute("select badfunc(3)")
File "c:\apsw\src\connection.c", line 2021, in user-defined-scalar-badfunc
File "test_fnc2.py", line 2, in badfunc
return 1/0
ZeroDivisionError: integer division or modulo by zero
APSW Trace verfolgt problemlos die SQL-Ausführung, ohne Ihren Code zu ändern, und bietet einen zusammenfassenden Bericht.
APSW Trace befindet sich im Ordner tools im folgenden Quellcode. http://rogerbinns.github.io/apsw/download.html#source-and-binaries
** Ausführungsmethode **
$ python /path/to/apswtrace.py [apswtrace options] yourscript.py [your options]
** Ausführungsbeispiel ** Das folgende Beispiel zeigt das Anfordern eines Berichts des Skripts, das unter "Verwenden verschachtelter Transaktionen" verwendet wird.
C:\dev\python\apsw>python apswtrace.py --sql --rows --timestamps --thread test_n
est.py
290e5c0 0.002 1734 OPEN: "" win32 READWRITE|CREATE
292aad8 0.009 1734 CURSORFROM: 290e5c0 DB: ""
292aad8 0.010 1734 SQL: create table test(x primary key,y)
290e5c0 0.012 1734 SQL: SAVEPOINT "_apsw-0"
292aad8 0.013 1734 CURSORFROM: 290e5c0 DB: ""
292aad8 0.015 1734 SQL: insert into test values(1,'TEST1')
290e5c0 0.016 1734 SQL: SAVEPOINT "_apsw-1"
292aad8 0.018 1734 CURSORFROM: 290e5c0 DB: ""
292aad8 0.019 1734 SQL: insert into test values(2,'TEST2')
292aad8 0.021 1734 CURSORFROM: 290e5c0 DB: ""
292aad8 0.022 1734 SQL: insert into test values(3,'TEST3')
290e5c0 0.023 1734 SQL: RELEASE SAVEPOINT "_apsw-1"
290e5c0 0.025 1734 SQL: SAVEPOINT "_apsw-1"
292ab10 0.026 1734 CURSORFROM: 290e5c0 DB: ""
292ab10 0.028 1734 SQL: insert into test values(4,'TEST4')
292ab10 0.029 1734 CURSORFROM: 290e5c0 DB: ""
292ab10 0.031 1734 SQL: insert into test values(4,'Error')
290e5c0 0.032 1734 SQL: ROLLBACK TO SAVEPOINT "_apsw-1"
290e5c0 0.034 1734 SQL: RELEASE SAVEPOINT "_apsw-1"
ConstraintError: UNIQUE constraint failed: test.x
rollback 2
290e5c0 0.038 1734 SQL: SAVEPOINT "_apsw-1"
292ab48 0.040 1734 CURSORFROM: 290e5c0 DB: ""
292ab48 0.041 1734 SQL: insert into test values(5,'TEST5')
292ab48 0.043 1734 CURSORFROM: 290e5c0 DB: ""
292ab48 0.044 1734 SQL: insert into test values(6,'TEST6')
290e5c0 0.046 1734 SQL: RELEASE SAVEPOINT "_apsw-1"
290e5c0 0.047 1734 SQL: RELEASE SAVEPOINT "_apsw-0"
292acd0 0.049 1734 CURSORFROM: 290e5c0 DB: ""
292acd0 0.050 1734 SQL: select * from test
292acd0 0.052 1734 ROW: (1, "TEST1")
1 TEST1
292acd0 0.056 1734 ROW: (2, "TEST2")
2 TEST2
292acd0 0.059 1734 ROW: (3, "TEST3")
3 TEST3
292acd0 0.062 1734 ROW: (5, "TEST5")
5 TEST5
292acd0 0.066 1734 ROW: (6, "TEST6")
6 TEST6
APSW TRACE SUMMARY REPORT
Program run time 0.072 seconds
Total connections 1
Total cursors 9
Number of threads used for queries 1
Total queries 18
Number of distinct queries 14
Number of rows returned 5
Time spent processing queries 0.017 seconds
MOST POPULAR QUERIES
3 SAVEPOINT "_apsw-1"
3 RELEASE SAVEPOINT "_apsw-1"
1 select * from test
1 insert into test values(6,'TEST6')
1 insert into test values(5,'TEST5')
1 insert into test values(4,'TEST4')
1 insert into test values(4,'Error')
1 insert into test values(3,'TEST3')
1 insert into test values(2,'TEST2')
1 insert into test values(1,'TEST1')
1 create table test(x primary key,y)
1 SAVEPOINT "_apsw-0"
1 ROLLBACK TO SAVEPOINT "_apsw-1"
1 RELEASE SAVEPOINT "_apsw-0"
LONGEST RUNNING - AGGREGATE
1 0.017 select * from test
3 0.000 SAVEPOINT "_apsw-1"
3 0.000 RELEASE SAVEPOINT "_apsw-1"
1 0.000 insert into test values(6,'TEST6')
1 0.000 insert into test values(5,'TEST5')
1 0.000 insert into test values(4,'TEST4')
1 0.000 insert into test values(4,'Error')
1 0.000 insert into test values(3,'TEST3')
1 0.000 insert into test values(2,'TEST2')
1 0.000 insert into test values(1,'TEST1')
1 0.000 create table test(x primary key,y)
1 0.000 SAVEPOINT "_apsw-0"
1 0.000 ROLLBACK TO SAVEPOINT "_apsw-1"
1 0.000 RELEASE SAVEPOINT "_apsw-0"
LONGEST RUNNING - INDIVIDUAL
0.017 select * from test
0.000 insert into test values(6,'TEST6')
0.000 insert into test values(5,'TEST5')
0.000 insert into test values(4,'TEST4')
0.000 insert into test values(4,'Error')
0.000 insert into test values(3,'TEST3')
0.000 insert into test values(2,'TEST2')
0.000 insert into test values(1,'TEST1')
0.000 create table test(x primary key,y)
0.000 SAVEPOINT "_apsw-1"
0.000 SAVEPOINT "_apsw-1"
0.000 SAVEPOINT "_apsw-1"
0.000 SAVEPOINT "_apsw-0"
0.000 ROLLBACK TO SAVEPOINT "_apsw-1"
0.000 RELEASE SAVEPOINT "_apsw-1"
C:\dev\python\apsw>
Weitere Einzelheiten entnehmen Sie bitte dem Folgenden. http://rogerbinns.github.io/apsw/execution.html#apswtrace
Die folgenden Tests zeigen, dass APSW schneller als Pysqlite ist. http://rogerbinns.github.io/apsw/benchmarking.html
APSW 3.8.7.3-r1 documentation » pysqlite differences http://rogerbinns.github.io/apsw/pysqlite.html#pysqlitediffs