12
12
sqlite3_open = sq3 .func ("i" , "sqlite3_open" , "sp" )
13
13
# int sqlite3_config(int, ...);
14
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" )
15
17
# int sqlite3_close_v2(sqlite3*);
16
18
sqlite3_close = sq3 .func ("i" , "sqlite3_close_v2" , "p" )
17
19
# int sqlite3_prepare(
57
59
58
60
SQLITE_CONFIG_URI = 17
59
61
62
+ # For compatibility with CPython sqlite3 driver
63
+ LEGACY_TRANSACTION_CONTROL = - 1
64
+
60
65
61
66
class Error (Exception ):
62
67
pass
@@ -71,86 +76,138 @@ def get_ptr_size():
71
76
return uctypes .sizeof ({"ptr" : (0 | uctypes .PTR , uctypes .PTR )})
72
77
73
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
+
74
101
class Connections :
75
- def __init__ (self , h ):
76
- 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" )
77
120
78
121
def cursor (self ):
79
- return Cursor (self .h )
122
+ return Cursor (self .db , self . isolation_level , self . autocommit )
80
123
81
124
def close (self ):
82
- if self .h :
83
- s = sqlite3_close (self .h )
84
- check_error (self .h , s )
85
- self .h = None
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
86
132
87
133
88
134
class Cursor :
89
- def __init__ (self , h ):
90
- self .h = h
91
- 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 )
92
145
93
146
def execute (self , sql , params = None ):
94
- if self .stmnt :
147
+ if self .stmt :
95
148
# If there is an existing statement, finalize that to free it
96
- res = sqlite3_finalize (self .stmnt )
97
- check_error (self .h , res )
149
+ res = sqlite3_finalize (self .stmt )
150
+ check_error (self .db , res )
98
151
99
152
if params :
100
- params = [quote (v ) for v in params ]
153
+ params = [self . __quote (v ) for v in params ]
101
154
sql = sql % tuple (params )
102
155
103
- stmnt_ptr = bytes (get_ptr_size ())
104
- res = sqlite3_prepare (self .h , sql , - 1 , stmnt_ptr , None )
105
- check_error (self .h , res )
106
- self .stmnt = int .from_bytes (stmnt_ptr , sys .byteorder )
107
- self .num_cols = sqlite3_column_count (self .stmnt )
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 )
108
163
109
164
if not self .num_cols :
110
165
v = self .fetchone ()
111
166
# If it's not select, actually execute it here
112
167
# num_cols == 0 for statements which don't return data (=> modify it)
113
168
assert v is None
114
- self .lastrowid = sqlite3_last_insert_rowid (self .h )
169
+ self .lastrowid = sqlite3_last_insert_rowid (self .db )
115
170
116
171
def close (self ):
117
- if self .stmnt :
118
- s = sqlite3_finalize (self .stmnt )
119
- check_error (self .h , s )
120
- self .stmnt = None
172
+ if self .stmt :
173
+ res = sqlite3_finalize (self .stmt )
174
+ check_error (self .db , res )
175
+ self .stmt = None
121
176
122
- def make_row (self ):
177
+ def __make_row (self ):
123
178
res = []
124
179
for i in range (self .num_cols ):
125
- t = sqlite3_column_type (self .stmnt , i )
180
+ t = sqlite3_column_type (self .stmt , i )
126
181
if t == SQLITE_INTEGER :
127
- res .append (sqlite3_column_int (self .stmnt , i ))
182
+ res .append (sqlite3_column_int (self .stmt , i ))
128
183
elif t == SQLITE_FLOAT :
129
- res .append (sqlite3_column_double (self .stmnt , i ))
184
+ res .append (sqlite3_column_double (self .stmt , i ))
130
185
elif t == SQLITE_TEXT :
131
- res .append (sqlite3_column_text (self .stmnt , i ))
186
+ res .append (sqlite3_column_text (self .stmt , i ))
132
187
else :
133
188
raise NotImplementedError
134
189
return tuple (res )
135
190
136
191
def fetchone (self ):
137
- res = sqlite3_step (self .stmnt )
192
+ res = sqlite3_step (self .stmt )
138
193
if res == SQLITE_DONE :
139
194
return None
140
195
if res == SQLITE_ROW :
141
- return self .make_row ()
142
- check_error (self .h , res )
196
+ return self .__make_row ()
197
+ check_error (self .db , res )
198
+
143
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" )
144
203
145
- def connect (fname , uri = False ):
146
204
sqlite3_config (SQLITE_CONFIG_URI , int (uri ))
147
205
148
206
sqlite_ptr = bytes (get_ptr_size ())
149
207
sqlite3_open (fname , sqlite_ptr )
150
- return Connections ( int .from_bytes (sqlite_ptr , sys .byteorder ) )
208
+ db = int .from_bytes (sqlite_ptr , sys .byteorder )
151
209
210
+ if autocommit == False :
211
+ __exec_stmt (db , "BEGIN" )
152
212
153
- def quote (val ):
154
- if isinstance (val , str ):
155
- return "'%s'" % val
156
- return str (val )
213
+ return Connections (db , isolation_level , autocommit )
0 commit comments