@@ -6,6 +6,8 @@ use pyo3::types::{PyList, PyTuple};
66use std:: cell:: RefCell ;
77use std:: sync:: Arc ;
88
9+ const LEGACY_TRANSACTION_CONTROL : i32 = -1 ;
10+
911fn to_py_err ( error : libsql_core:: errors:: Error ) -> PyErr {
1012 let msg = match error {
1113 libsql:: Error :: SqliteFailure ( _, err) => err,
@@ -19,6 +21,7 @@ fn is_remote_path(path: &str) -> bool {
1921}
2022
2123#[ pyfunction]
24+ #[ cfg( not( Py_3_12 ) ) ]
2225#[ 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 ) ) ]
2326fn connect (
2427 py : Python < ' _ > ,
@@ -30,6 +33,69 @@ fn connect(
3033 sync_interval : Option < f64 > ,
3134 auth_token : & str ,
3235 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 > ,
3399) -> PyResult < Connection > {
34100 let ver = env ! ( "CARGO_PKG_VERSION" ) ;
35101 let ver = format ! ( "libsql-python-rpc-{ver}" ) ;
@@ -74,7 +140,8 @@ fn connect(
74140 }
75141 }
76142 } ;
77- let autocommit = isolation_level. is_none ( ) ;
143+
144+ let autocommit = isolation_level. is_none ( ) as i32 ;
78145 let conn = db. connect ( ) . map_err ( to_py_err) ?;
79146 Ok ( Connection {
80147 db,
@@ -121,7 +188,7 @@ pub struct Connection {
121188 conn : Arc < ConnectionGuard > ,
122189 rt : tokio:: runtime:: Runtime ,
123190 isolation_level : Option < String > ,
124- autocommit : bool ,
191+ autocommit : i32 ,
125192}
126193
127194// SAFETY: The libsql crate guarantees that `Connection` is thread-safe.
@@ -138,6 +205,7 @@ impl Connection {
138205 rows : RefCell :: new ( None ) ,
139206 rowcount : RefCell :: new ( 0 ) ,
140207 autocommit : self . autocommit ,
208+ isolation_level : self . isolation_level . clone ( ) ,
141209 done : RefCell :: new ( false ) ,
142210 } )
143211 }
@@ -218,8 +286,30 @@ impl Connection {
218286
219287 #[ getter]
220288 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+ }
221293 Ok ( !self_. conn . is_autocommit ( ) )
222294 }
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+ }
223313}
224314
225315#[ pyclass]
@@ -232,7 +322,8 @@ pub struct Cursor {
232322 rows : RefCell < Option < libsql_core:: Rows > > ,
233323 rowcount : RefCell < i64 > ,
234324 done : RefCell < bool > ,
235- autocommit : bool ,
325+ isolation_level : Option < String > ,
326+ autocommit : i32 ,
236327}
237328
238329// SAFETY: The libsql crate guarantees that `Connection` is thread-safe.
@@ -265,12 +356,13 @@ impl Cursor {
265356 Ok ( self_)
266357 }
267358
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 > > {
269363 self_
270364 . rt
271- . block_on ( async {
272- self_. conn . execute_batch ( & script) . await
273- } )
365+ . block_on ( async { self_. conn . execute_batch ( & script) . await } )
274366 . map_err ( to_py_err) ?;
275367 Ok ( self_)
276368 }
@@ -403,7 +495,8 @@ async fn begin_transaction(conn: &libsql_core::Connection) -> PyResult<()> {
403495
404496async fn execute ( cursor : & Cursor , sql : String , parameters : Option < & PyTuple > ) -> PyResult < ( ) > {
405497 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 ( ) {
407500 begin_transaction ( & cursor. conn ) . await ?;
408501 }
409502 let params = match parameters {
@@ -440,6 +533,21 @@ async fn execute(cursor: &Cursor, sql: String, parameters: Option<&PyTuple>) ->
440533 Ok ( ( ) )
441534}
442535
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+
443551fn stmt_is_dml ( sql : & str ) -> bool {
444552 let sql = sql. trim ( ) ;
445553 let sql = sql. to_uppercase ( ) ;
@@ -473,6 +581,7 @@ create_exception!(libsql_experimental, Error, pyo3::exceptions::PyException);
473581#[ pymodule]
474582fn libsql_experimental ( py : Python , m : & PyModule ) -> PyResult < ( ) > {
475583 let _ = tracing_subscriber:: fmt:: try_init ( ) ;
584+ m. add ( "LEGACY_TRANSACTION_CONTROL" , LEGACY_TRANSACTION_CONTROL ) ?;
476585 m. add ( "paramstyle" , "qmark" ) ?;
477586 m. add ( "sqlite_version_info" , ( 3 , 42 , 0 ) ) ?;
478587 m. add ( "Error" , py. get_type :: < Error > ( ) ) ?;
0 commit comments