1
1
import sys
2
2
import ffilib
3
+ import uctypes
3
4
4
5
5
6
sq3 = ffilib .open ("libsqlite3" )
6
7
8
+ # int sqlite3_open(
9
+ # const char *filename, /* Database filename (UTF-8) */
10
+ # sqlite3 **ppDb /* OUT: SQLite db handle */
11
+ # );
7
12
sqlite3_open = sq3 .func ("i" , "sqlite3_open" , "sp" )
8
- # int sqlite3_close(sqlite3*);
9
- sqlite3_close = sq3 .func ("i" , "sqlite3_close" , "p" )
13
+ # int sqlite3_config(int, ...);
14
+ sqlite3_config = sq3 .func ("i" , "sqlite3_config" , "ii" )
15
+ # int sqlite3_get_autocommit(sqlite3*);
16
+ sqlite3_get_autocommit = sq3 .func ("i" , "sqlite3_get_autocommit" , "p" )
17
+ # int sqlite3_close_v2(sqlite3*);
18
+ sqlite3_close = sq3 .func ("i" , "sqlite3_close_v2" , "p" )
10
19
# int sqlite3_prepare(
11
20
# sqlite3 *db, /* Database handle */
12
21
# const char *zSql, /* SQL statement, UTF-8 encoded */
13
22
# int nByte, /* Maximum length of zSql in bytes. */
14
23
# sqlite3_stmt **ppStmt, /* OUT: Statement handle */
15
24
# const char **pzTail /* OUT: Pointer to unused portion of zSql */
16
25
# );
17
- sqlite3_prepare = sq3 .func ("i" , "sqlite3_prepare " , "psipp" )
26
+ sqlite3_prepare = sq3 .func ("i" , "sqlite3_prepare_v2 " , "psipp" )
18
27
# int sqlite3_finalize(sqlite3_stmt *pStmt);
19
28
sqlite3_finalize = sq3 .func ("i" , "sqlite3_finalize" , "p" )
20
29
# int sqlite3_step(sqlite3_stmt*);
23
32
sqlite3_column_count = sq3 .func ("i" , "sqlite3_column_count" , "p" )
24
33
# int sqlite3_column_type(sqlite3_stmt*, int iCol);
25
34
sqlite3_column_type = sq3 .func ("i" , "sqlite3_column_type" , "pi" )
35
+ # int sqlite3_column_int(sqlite3_stmt*, int iCol);
26
36
sqlite3_column_int = sq3 .func ("i" , "sqlite3_column_int" , "pi" )
27
- # using "d" return type gives wrong results
37
+ # double sqlite3_column_double(sqlite3_stmt*, int iCol);
28
38
sqlite3_column_double = sq3 .func ("d" , "sqlite3_column_double" , "pi" )
39
+ # const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
29
40
sqlite3_column_text = sq3 .func ("s" , "sqlite3_column_text" , "pi" )
30
41
# sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
31
- # TODO: should return long int
32
- sqlite3_last_insert_rowid = sq3 .func ("i" , "sqlite3_last_insert_rowid" , "p" )
42
+ sqlite3_last_insert_rowid = sq3 .func ("l" , "sqlite3_last_insert_rowid" , "p" )
33
43
# const char *sqlite3_errmsg(sqlite3*);
34
44
sqlite3_errmsg = sq3 .func ("s" , "sqlite3_errmsg" , "p" )
35
45
36
- # Too recent
37
- ##const char *sqlite3_errstr(int);
38
- # sqlite3_errstr = sq3.func("s", "sqlite3_errstr", "i")
39
-
40
46
41
47
SQLITE_OK = 0
42
48
SQLITE_ERROR = 1
51
57
SQLITE_BLOB = 4
52
58
SQLITE_NULL = 5
53
59
60
+ SQLITE_CONFIG_URI = 17
61
+
62
+ # For compatibility with CPython sqlite3 driver
63
+ LEGACY_TRANSACTION_CONTROL = - 1
64
+
54
65
55
66
class Error (Exception ):
56
67
pass
@@ -61,79 +72,142 @@ def check_error(db, s):
61
72
raise Error (s , sqlite3_errmsg (db ))
62
73
63
74
75
+ def get_ptr_size ():
76
+ return uctypes .sizeof ({"ptr" : (0 | uctypes .PTR , uctypes .PTR )})
77
+
78
+
79
+ def __prepare_stmt (db , sql ):
80
+ # Prepares a statement
81
+ stmt_ptr = bytes (get_ptr_size ())
82
+ res = sqlite3_prepare (db , sql , - 1 , stmt_ptr , None )
83
+ check_error (db , res )
84
+ return int .from_bytes (stmt_ptr , sys .byteorder )
85
+
86
+ def __exec_stmt (db , sql ):
87
+ # Prepares, executes, and finalizes a statement
88
+ stmt = __prepare_stmt (db , sql )
89
+ sqlite3_step (stmt )
90
+ res = sqlite3_finalize (stmt )
91
+ check_error (db , res )
92
+
93
+ def __is_dml (sql ):
94
+ # Checks if a sql query is a DML, as these get a BEGIN in LEGACY_TRANSACTION_CONTROL
95
+ for dml in ["INSERT" , "DELETE" , "UPDATE" , "MERGE" ]:
96
+ if dml in sql .upper ():
97
+ return True
98
+ return False
99
+
100
+
64
101
class Connections :
65
- def __init__ (self , h ):
66
- self .h = h
102
+ def __init__ (self , db , isolation_level , autocommit ):
103
+ self .db = db
104
+ self .isolation_level = isolation_level
105
+ self .autocommit = autocommit
106
+
107
+ def commit (self ):
108
+ if self .autocommit == LEGACY_TRANSACTION_CONTROL and not sqlite3_get_autocommit (self .db ):
109
+ __exec_stmt (self .db , "COMMIT" )
110
+ elif self .autocommit == False :
111
+ __exec_stmt (self .db , "COMMIT" )
112
+ __exec_stmt (self .db , "BEGIN" )
113
+
114
+ def rollback (self ):
115
+ if self .autocommit == LEGACY_TRANSACTION_CONTROL and not sqlite3_get_autocommit (self .db ):
116
+ __exec_stmt (self .db , "ROLLBACK" )
117
+ elif self .autocommit == False :
118
+ __exec_stmt (self .db , "ROLLBACK" )
119
+ __exec_stmt (self .db , "BEGIN" )
67
120
68
121
def cursor (self ):
69
- return Cursor (self .h )
122
+ return Cursor (self .db , self . isolation_level , self . autocommit )
70
123
71
124
def close (self ):
72
- s = sqlite3_close (self .h )
73
- check_error (self .h , s )
125
+ if self .db :
126
+ if self .autocommit == False and not sqlite3_get_autocommit (self .db ):
127
+ __exec_stmt (self .db , "ROLLBACK" )
128
+
129
+ res = sqlite3_close (self .db )
130
+ check_error (self .db , res )
131
+ self .db = None
74
132
75
133
76
134
class Cursor :
77
- def __init__ (self , h ):
78
- self .h = h
79
- self .stmnt = None
135
+ def __init__ (self , db , isolation_level , autocommit ):
136
+ self .db = db
137
+ self .isolation_level = isolation_level
138
+ self .autocommit = autocommit
139
+ self .stmt = None
140
+
141
+ def __quote (val ):
142
+ if isinstance (val , str ):
143
+ return "'%s'" % val
144
+ return str (val )
80
145
81
146
def execute (self , sql , params = None ):
147
+ if self .stmt :
148
+ # If there is an existing statement, finalize that to free it
149
+ res = sqlite3_finalize (self .stmt )
150
+ check_error (self .db , res )
151
+
82
152
if params :
83
- params = [quote (v ) for v in params ]
153
+ params = [self . __quote (v ) for v in params ]
84
154
sql = sql % tuple (params )
85
- print (sql )
86
- b = bytearray (4 )
87
- s = sqlite3_prepare (self .h , sql , - 1 , b , None )
88
- check_error (self .h , s )
89
- self .stmnt = int .from_bytes (b , sys .byteorder )
90
- # print("stmnt", self.stmnt)
91
- self .num_cols = sqlite3_column_count (self .stmnt )
92
- # print("num_cols", self.num_cols)
93
- # If it's not select, actually execute it here
94
- # num_cols == 0 for statements which don't return data (=> modify it)
155
+
156
+ if __is_dml (sql ) and self .autocommit == LEGACY_TRANSACTION_CONTROL and sqlite3_get_autocommit (self .db ):
157
+ # For compatibility with CPython, add functionality for their default transaction
158
+ # behavior. Changing autocommit from LEGACY_TRANSACTION_CONTROL will remove this
159
+ __exec_stmt (self .db , "BEGIN " + self .isolation_level )
160
+
161
+ self .stmt = __prepare_stmt (self .db , sql )
162
+ self .num_cols = sqlite3_column_count (self .stmt )
163
+
95
164
if not self .num_cols :
96
165
v = self .fetchone ()
166
+ # If it's not select, actually execute it here
167
+ # num_cols == 0 for statements which don't return data (=> modify it)
97
168
assert v is None
98
- self .lastrowid = sqlite3_last_insert_rowid (self .h )
169
+ self .lastrowid = sqlite3_last_insert_rowid (self .db )
99
170
100
171
def close (self ):
101
- s = sqlite3_finalize (self .stmnt )
102
- check_error (self .h , s )
172
+ if self .stmt :
173
+ res = sqlite3_finalize (self .stmt )
174
+ check_error (self .db , res )
175
+ self .stmt = None
103
176
104
- def make_row (self ):
177
+ def __make_row (self ):
105
178
res = []
106
179
for i in range (self .num_cols ):
107
- t = sqlite3_column_type (self .stmnt , i )
108
- # print("type", t)
180
+ t = sqlite3_column_type (self .stmt , i )
109
181
if t == SQLITE_INTEGER :
110
- res .append (sqlite3_column_int (self .stmnt , i ))
182
+ res .append (sqlite3_column_int (self .stmt , i ))
111
183
elif t == SQLITE_FLOAT :
112
- res .append (sqlite3_column_double (self .stmnt , i ))
184
+ res .append (sqlite3_column_double (self .stmt , i ))
113
185
elif t == SQLITE_TEXT :
114
- res .append (sqlite3_column_text (self .stmnt , i ))
186
+ res .append (sqlite3_column_text (self .stmt , i ))
115
187
else :
116
188
raise NotImplementedError
117
189
return tuple (res )
118
190
119
191
def fetchone (self ):
120
- res = sqlite3_step (self .stmnt )
121
- # print("step:", res)
192
+ res = sqlite3_step (self .stmt )
122
193
if res == SQLITE_DONE :
123
194
return None
124
195
if res == SQLITE_ROW :
125
- return self .make_row ()
126
- check_error (self .h , res )
196
+ return self .__make_row ()
197
+ check_error (self .db , res )
198
+
199
+
200
+ def connect (fname , uri = False , isolation_level = "" , autocommit = LEGACY_TRANSACTION_CONTROL ):
201
+ if isolation_level not in [None , "" , "DEFERRED" , "IMMEDIATE" , "EXCLUSIVE" ]:
202
+ raise Error ("Invalid option for isolation level" )
127
203
204
+ sqlite3_config (SQLITE_CONFIG_URI , int (uri ))
128
205
129
- def connect (fname ):
130
- b = bytearray (4 )
131
- sqlite3_open (fname , b )
132
- h = int .from_bytes (b , sys .byteorder )
133
- return Connections (h )
206
+ sqlite_ptr = bytes (get_ptr_size ())
207
+ sqlite3_open (fname , sqlite_ptr )
208
+ db = int .from_bytes (sqlite_ptr , sys .byteorder )
134
209
210
+ if autocommit == False :
211
+ __exec_stmt (db , "BEGIN" )
135
212
136
- def quote (val ):
137
- if isinstance (val , str ):
138
- return "'%s'" % val
139
- return str (val )
213
+ return Connections (db , isolation_level , autocommit )
0 commit comments