@@ -6,6 +6,8 @@ use pyo3::types::{PyList, PyTuple};
6
6
use std:: cell:: RefCell ;
7
7
use std:: sync:: Arc ;
8
8
9
+ const LEGACY_TRANSACTION_CONTROL : i32 = -1 ;
10
+
9
11
fn to_py_err ( error : libsql_core:: errors:: Error ) -> PyErr {
10
12
let msg = match error {
11
13
libsql:: Error :: SqliteFailure ( _, err) => err,
@@ -19,6 +21,7 @@ fn is_remote_path(path: &str) -> bool {
19
21
}
20
22
21
23
#[ pyfunction]
24
+ #[ cfg( not( Py_3_12 ) ) ]
22
25
#[ pyo3( signature = ( database, isolation_level="DEFERRED" . to_string( ) , check_same_thread=true , uri=false , sync_url=None , sync_interval=None , auth_token="" , encryption_key=None ) ) ]
23
26
fn connect (
24
27
py : Python < ' _ > ,
@@ -30,6 +33,69 @@ fn connect(
30
33
sync_interval : Option < f64 > ,
31
34
auth_token : & str ,
32
35
encryption_key : Option < String > ,
36
+ ) -> PyResult < Connection > {
37
+ let conn = _connect_core (
38
+ py,
39
+ database,
40
+ isolation_level,
41
+ check_same_thread,
42
+ uri,
43
+ sync_url,
44
+ sync_interval,
45
+ auth_token,
46
+ encryption_key,
47
+ ) ?;
48
+ Ok ( conn)
49
+ }
50
+
51
+ #[ pyfunction]
52
+ #[ cfg( Py_3_12 ) ]
53
+ #[ pyo3( signature = ( database, isolation_level="DEFERRED" . to_string( ) , check_same_thread=true , uri=false , sync_url=None , sync_interval=None , auth_token="" , encryption_key=None , autocommit = LEGACY_TRANSACTION_CONTROL ) ) ]
54
+ fn connect (
55
+ py : Python < ' _ > ,
56
+ database : String ,
57
+ isolation_level : Option < String > ,
58
+ check_same_thread : bool ,
59
+ uri : bool ,
60
+ sync_url : Option < String > ,
61
+ sync_interval : Option < f64 > ,
62
+ auth_token : & str ,
63
+ encryption_key : Option < String > ,
64
+ autocommit : i32 ,
65
+ ) -> PyResult < Connection > {
66
+ let mut conn = _connect_core (
67
+ py,
68
+ database,
69
+ isolation_level. clone ( ) ,
70
+ check_same_thread,
71
+ uri,
72
+ sync_url,
73
+ sync_interval,
74
+ auth_token,
75
+ encryption_key,
76
+ ) ?;
77
+
78
+ conn. autocommit =
79
+ if autocommit == LEGACY_TRANSACTION_CONTROL || autocommit == 1 || autocommit == 0 {
80
+ autocommit
81
+ } else {
82
+ return Err ( PyValueError :: new_err (
83
+ "autocommit must be True, False, or sqlite3.LEGACY_TRANSACTION_CONTROL" ,
84
+ ) ) ;
85
+ } ;
86
+ Ok ( conn)
87
+ }
88
+
89
+ fn _connect_core (
90
+ py : Python < ' _ > ,
91
+ database : String ,
92
+ isolation_level : Option < String > ,
93
+ check_same_thread : bool ,
94
+ uri : bool ,
95
+ sync_url : Option < String > ,
96
+ sync_interval : Option < f64 > ,
97
+ auth_token : & str ,
98
+ encryption_key : Option < String > ,
33
99
) -> PyResult < Connection > {
34
100
let ver = env ! ( "CARGO_PKG_VERSION" ) ;
35
101
let ver = format ! ( "libsql-python-rpc-{ver}" ) ;
@@ -74,7 +140,8 @@ fn connect(
74
140
}
75
141
}
76
142
} ;
77
- let autocommit = isolation_level. is_none ( ) ;
143
+
144
+ let autocommit = isolation_level. is_none ( ) as i32 ;
78
145
let conn = db. connect ( ) . map_err ( to_py_err) ?;
79
146
Ok ( Connection {
80
147
db,
@@ -121,7 +188,7 @@ pub struct Connection {
121
188
conn : Arc < ConnectionGuard > ,
122
189
rt : tokio:: runtime:: Runtime ,
123
190
isolation_level : Option < String > ,
124
- autocommit : bool ,
191
+ autocommit : i32 ,
125
192
}
126
193
127
194
// SAFETY: The libsql crate guarantees that `Connection` is thread-safe.
@@ -138,6 +205,7 @@ impl Connection {
138
205
rows : RefCell :: new ( None ) ,
139
206
rowcount : RefCell :: new ( 0 ) ,
140
207
autocommit : self . autocommit ,
208
+ isolation_level : self . isolation_level . clone ( ) ,
141
209
done : RefCell :: new ( false ) ,
142
210
} )
143
211
}
@@ -218,8 +286,30 @@ impl Connection {
218
286
219
287
#[ getter]
220
288
fn in_transaction ( self_ : PyRef < ' _ , Self > ) -> PyResult < bool > {
289
+ #[ cfg( Py_3_12 ) ]
290
+ {
291
+ return Ok ( !self_. conn . is_autocommit ( ) || self_. autocommit == 0 ) ;
292
+ }
221
293
Ok ( !self_. conn . is_autocommit ( ) )
222
294
}
295
+
296
+ #[ getter]
297
+ #[ cfg( Py_3_12 ) ]
298
+ fn get_autocommit ( self_ : PyRef < ' _ , Self > ) -> PyResult < i32 > {
299
+ Ok ( self_. autocommit )
300
+ }
301
+
302
+ #[ setter]
303
+ #[ cfg( Py_3_12 ) ]
304
+ fn set_autocommit ( mut self_ : PyRefMut < ' _ , Self > , autocommit : i32 ) -> PyResult < ( ) > {
305
+ if autocommit != LEGACY_TRANSACTION_CONTROL && autocommit != 1 && autocommit != 0 {
306
+ return Err ( PyValueError :: new_err (
307
+ "autocommit must be True, False, or sqlite3.LEGACY_TRANSACTION_CONTROL" ,
308
+ ) ) ;
309
+ }
310
+ self_. autocommit = autocommit;
311
+ Ok ( ( ) )
312
+ }
223
313
}
224
314
225
315
#[ pyclass]
@@ -232,7 +322,8 @@ pub struct Cursor {
232
322
rows : RefCell < Option < libsql_core:: Rows > > ,
233
323
rowcount : RefCell < i64 > ,
234
324
done : RefCell < bool > ,
235
- autocommit : bool ,
325
+ isolation_level : Option < String > ,
326
+ autocommit : i32 ,
236
327
}
237
328
238
329
// SAFETY: The libsql crate guarantees that `Connection` is thread-safe.
@@ -265,12 +356,13 @@ impl Cursor {
265
356
Ok ( self_)
266
357
}
267
358
268
- fn executescript < ' a > ( self_ : PyRef < ' a , Self > , script : String ) -> PyResult < pyo3:: PyRef < ' a , Self > > {
359
+ fn executescript < ' a > (
360
+ self_ : PyRef < ' a , Self > ,
361
+ script : String ,
362
+ ) -> PyResult < pyo3:: PyRef < ' a , Self > > {
269
363
self_
270
364
. rt
271
- . block_on ( async {
272
- self_. conn . execute_batch ( & script) . await
273
- } )
365
+ . block_on ( async { self_. conn . execute_batch ( & script) . await } )
274
366
. map_err ( to_py_err) ?;
275
367
Ok ( self_)
276
368
}
@@ -403,7 +495,8 @@ async fn begin_transaction(conn: &libsql_core::Connection) -> PyResult<()> {
403
495
404
496
async fn execute ( cursor : & Cursor , sql : String , parameters : Option < & PyTuple > ) -> PyResult < ( ) > {
405
497
let stmt_is_dml = stmt_is_dml ( & sql) ;
406
- if !cursor. autocommit && stmt_is_dml && cursor. conn . is_autocommit ( ) {
498
+ let autocommit = determine_autocommit ( cursor) ;
499
+ if !autocommit && stmt_is_dml && cursor. conn . is_autocommit ( ) {
407
500
begin_transaction ( & cursor. conn ) . await ?;
408
501
}
409
502
let params = match parameters {
@@ -440,6 +533,21 @@ async fn execute(cursor: &Cursor, sql: String, parameters: Option<&PyTuple>) ->
440
533
Ok ( ( ) )
441
534
}
442
535
536
+ fn determine_autocommit ( cursor : & Cursor ) -> bool {
537
+ #[ cfg( Py_3_12 ) ]
538
+ {
539
+ match cursor. autocommit {
540
+ LEGACY_TRANSACTION_CONTROL => cursor. isolation_level . is_none ( ) ,
541
+ _ => cursor. autocommit != 0 ,
542
+ }
543
+ }
544
+
545
+ #[ cfg( not( Py_3_12 ) ) ]
546
+ {
547
+ cursor. isolation_level . is_none ( )
548
+ }
549
+ }
550
+
443
551
fn stmt_is_dml ( sql : & str ) -> bool {
444
552
let sql = sql. trim ( ) ;
445
553
let sql = sql. to_uppercase ( ) ;
@@ -473,6 +581,7 @@ create_exception!(libsql_experimental, Error, pyo3::exceptions::PyException);
473
581
#[ pymodule]
474
582
fn libsql_experimental ( py : Python , m : & PyModule ) -> PyResult < ( ) > {
475
583
let _ = tracing_subscriber:: fmt:: try_init ( ) ;
584
+ m. add ( "LEGACY_TRANSACTION_CONTROL" , LEGACY_TRANSACTION_CONTROL ) ?;
476
585
m. add ( "paramstyle" , "qmark" ) ?;
477
586
m. add ( "sqlite_version_info" , ( 3 , 42 , 0 ) ) ?;
478
587
m. add ( "Error" , py. get_type :: < Error > ( ) ) ?;
0 commit comments