1
1
#![ allow( dead_code) ]
2
2
3
+ use crate :: local:: rows:: BatchedRows ;
3
4
use crate :: params:: Params ;
4
- use crate :: errors;
5
+ use crate :: { connection :: BatchRows , errors} ;
5
6
6
7
use super :: { Database , Error , Result , Rows , RowsFuture , Statement , Transaction } ;
7
8
@@ -136,21 +137,93 @@ impl Connection {
136
137
///
137
138
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
138
139
/// or if the underlying SQLite call fails.
139
- pub fn execute_batch < S > ( & self , sql : S ) -> Result < ( ) >
140
+ pub fn execute_batch < S > ( & self , sql : S ) -> Result < BatchRows >
140
141
where
141
142
S : Into < String > ,
142
143
{
143
144
let sql = sql. into ( ) ;
144
145
let mut sql = sql. as_str ( ) ;
145
146
147
+ let mut batch_rows = Vec :: new ( ) ;
148
+
146
149
while !sql. is_empty ( ) {
147
150
let stmt = self . prepare ( sql) ?;
148
151
149
- if !stmt. inner . raw_stmt . is_null ( ) {
150
- stmt. step ( ) ?;
151
- }
152
+ let tail = if !stmt. inner . raw_stmt . is_null ( ) {
153
+ let returned_rows = stmt. step ( ) ?;
152
154
153
- let tail = stmt. tail ( ) ;
155
+ let tail = stmt. tail ( ) ;
156
+
157
+ // Check if there are rows to be extracted, we must do this upfront due to the lazy
158
+ // nature of sqlite and our somewhat hacked batch command.
159
+ if returned_rows {
160
+ // Extract columns
161
+ let cols = stmt
162
+ . columns ( )
163
+ . iter ( )
164
+ . enumerate ( )
165
+ . map ( |( i, c) | {
166
+ use crate :: value:: ValueType ;
167
+
168
+ let val = stmt. inner . column_type ( i as i32 ) ;
169
+ let t = match val {
170
+ libsql_sys:: ffi:: SQLITE_INTEGER => ValueType :: Integer ,
171
+ libsql_sys:: ffi:: SQLITE_FLOAT => ValueType :: Real ,
172
+ libsql_sys:: ffi:: SQLITE_BLOB => ValueType :: Blob ,
173
+ libsql_sys:: ffi:: SQLITE_TEXT => ValueType :: Text ,
174
+ libsql_sys:: ffi:: SQLITE_NULL => ValueType :: Null ,
175
+ _ => unreachable ! ( "unknown column type {} at index {}" , val, i) ,
176
+ } ;
177
+
178
+ ( c. name . to_string ( ) , t)
179
+ } )
180
+ . collect :: < Vec < _ > > ( ) ;
181
+
182
+ let mut rows = Vec :: new ( ) ;
183
+
184
+ // If returned rows we must extract the rows available right away instead of
185
+ // using the `Rows` type we have already. This is due to the step api once its
186
+ // returned SQLITE_ROWS we must extract them before we call step again.
187
+ {
188
+ let row = crate :: local:: Row { stmt : stmt. clone ( ) } ;
189
+
190
+ let mut values = Vec :: with_capacity ( cols. len ( ) ) ;
191
+
192
+ for i in 0 ..cols. len ( ) {
193
+ let value = row. get_value ( i as i32 ) ?;
194
+
195
+ values. push ( value) ;
196
+ }
197
+
198
+ rows. push ( values) ;
199
+ }
200
+
201
+ // Now we can use the normal rows type to extract any n+1 rows
202
+ let rows_sys = Rows :: new ( stmt) ;
203
+
204
+ while let Some ( row) = rows_sys. next ( ) ? {
205
+ let mut values = Vec :: with_capacity ( cols. len ( ) ) ;
206
+
207
+ for i in 0 ..cols. len ( ) {
208
+ let value = row. get_value ( i as i32 ) ?;
209
+
210
+ values. push ( value) ;
211
+ }
212
+
213
+ rows. push ( values) ;
214
+ }
215
+
216
+ rows. len ( ) ;
217
+
218
+ batch_rows. push ( Some ( crate :: Rows :: new ( BatchedRows :: new ( cols, rows) ) ) ) ;
219
+ } else {
220
+ batch_rows. push ( None ) ;
221
+ }
222
+
223
+ tail
224
+ } else {
225
+ stmt. tail ( )
226
+ } ;
154
227
155
228
if tail == 0 || tail >= sql. len ( ) {
156
229
break ;
@@ -159,12 +232,12 @@ impl Connection {
159
232
sql = & sql[ tail..] ;
160
233
}
161
234
162
- Ok ( ( ) )
235
+ Ok ( BatchRows :: new ( batch_rows ) )
163
236
}
164
237
165
238
fn execute_transactional_batch_inner < S > ( & self , sql : S ) -> Result < ( ) >
166
- where
167
- S : Into < String > ,
239
+ where
240
+ S : Into < String > ,
168
241
{
169
242
let sql = sql. into ( ) ;
170
243
let mut sql = sql. as_str ( ) ;
@@ -177,13 +250,16 @@ impl Connection {
177
250
} else {
178
251
& sql[ ..tail]
179
252
} ;
180
- let prefix_count = stmt_sql
181
- . chars ( )
182
- . take_while ( |c| c. is_whitespace ( ) )
183
- . count ( ) ;
253
+ let prefix_count = stmt_sql. chars ( ) . take_while ( |c| c. is_whitespace ( ) ) . count ( ) ;
184
254
let stmt_sql = & stmt_sql[ prefix_count..] ;
185
- if stmt_sql. starts_with ( "BEGIN" ) || stmt_sql. starts_with ( "COMMIT" ) || stmt_sql. starts_with ( "ROLLBACK" ) || stmt_sql. starts_with ( "END" ) {
186
- return Err ( Error :: TransactionalBatchError ( "Transactions forbidden inside transactional batch" . to_string ( ) ) ) ;
255
+ if stmt_sql. starts_with ( "BEGIN" )
256
+ || stmt_sql. starts_with ( "COMMIT" )
257
+ || stmt_sql. starts_with ( "ROLLBACK" )
258
+ || stmt_sql. starts_with ( "END" )
259
+ {
260
+ return Err ( Error :: TransactionalBatchError (
261
+ "Transactions forbidden inside transactional batch" . to_string ( ) ,
262
+ ) ) ;
187
263
}
188
264
189
265
if !stmt. inner . raw_stmt . is_null ( ) {
@@ -299,35 +375,53 @@ impl Connection {
299
375
pub fn enable_load_extension ( & self , onoff : bool ) -> Result < ( ) > {
300
376
// SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION configration verb accepts 2 additional parameters: an on/off flag and a pointer to an c_int where new state of the parameter will be written (or NULL if reporting back the setting is not needed)
301
377
// See: https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension
302
- let err = unsafe { ffi:: sqlite3_db_config ( self . raw , ffi:: SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION , onoff as i32 , std:: ptr:: null :: < c_int > ( ) ) } ;
378
+ let err = unsafe {
379
+ ffi:: sqlite3_db_config (
380
+ self . raw ,
381
+ ffi:: SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION ,
382
+ onoff as i32 ,
383
+ std:: ptr:: null :: < c_int > ( ) ,
384
+ )
385
+ } ;
303
386
match err {
304
387
ffi:: SQLITE_OK => Ok ( ( ) ) ,
305
- _ => Err ( errors:: Error :: SqliteFailure ( err, errors:: error_from_code ( err) ) ) ,
388
+ _ => Err ( errors:: Error :: SqliteFailure (
389
+ err,
390
+ errors:: error_from_code ( err) ,
391
+ ) ) ,
306
392
}
307
393
}
308
394
309
- pub fn load_extension (
310
- & self ,
311
- dylib_path : & Path ,
312
- entry_point : Option < & str > ,
313
- ) -> Result < ( ) > {
395
+ pub fn load_extension ( & self , dylib_path : & Path , entry_point : Option < & str > ) -> Result < ( ) > {
314
396
let mut raw_err_msg: * mut std:: ffi:: c_char = std:: ptr:: null_mut ( ) ;
315
397
let dylib_path = match dylib_path. to_str ( ) {
316
- Some ( dylib_path) => {
317
- std :: ffi :: CString :: new ( dylib_path ) . unwrap ( )
318
- } ,
319
- None => return Err ( crate :: Error :: Misuse ( format ! (
320
- "dylib path is not a valid utf8 string"
321
- ) ) ) ,
398
+ Some ( dylib_path) => std :: ffi :: CString :: new ( dylib_path ) . unwrap ( ) ,
399
+ None => {
400
+ return Err ( crate :: Error :: Misuse ( format ! (
401
+ "dylib path is not a valid utf8 string"
402
+ ) ) )
403
+ }
322
404
} ;
323
405
let err = match entry_point {
324
406
Some ( entry_point) => {
325
407
let entry_point = std:: ffi:: CString :: new ( entry_point) . unwrap ( ) ;
326
- unsafe { ffi:: sqlite3_load_extension ( self . raw , dylib_path. as_ptr ( ) , entry_point. as_ptr ( ) , & mut raw_err_msg) }
327
- }
328
- None => {
329
- unsafe { ffi:: sqlite3_load_extension ( self . raw , dylib_path. as_ptr ( ) , std:: ptr:: null ( ) , & mut raw_err_msg) }
408
+ unsafe {
409
+ ffi:: sqlite3_load_extension (
410
+ self . raw ,
411
+ dylib_path. as_ptr ( ) ,
412
+ entry_point. as_ptr ( ) ,
413
+ & mut raw_err_msg,
414
+ )
415
+ }
330
416
}
417
+ None => unsafe {
418
+ ffi:: sqlite3_load_extension (
419
+ self . raw ,
420
+ dylib_path. as_ptr ( ) ,
421
+ std:: ptr:: null ( ) ,
422
+ & mut raw_err_msg,
423
+ )
424
+ } ,
331
425
} ;
332
426
match err {
333
427
ffi:: SQLITE_OK => Ok ( ( ) ) ,
0 commit comments