@@ -49,14 +49,20 @@ struct ActiveCrudTransaction {
49
49
}
50
50
51
51
enum CrudTransactionMode {
52
- Manual {
53
- stmt : ManagedStmt ,
54
- } ,
55
- Simple {
56
- stmt : ManagedStmt ,
57
- set_updated_rows : ManagedStmt ,
58
- update_local_bucket : ManagedStmt ,
59
- } ,
52
+ Manual ( ManualCrudTransactionMode ) ,
53
+ Simple ( SimpleCrudTransactionMode ) ,
54
+ }
55
+
56
+ #[ derive( Default ) ]
57
+ struct ManualCrudTransactionMode {
58
+ stmt : Option < ManagedStmt > ,
59
+ }
60
+
61
+ #[ derive( Default ) ]
62
+ struct SimpleCrudTransactionMode {
63
+ stmt : Option < ManagedStmt > ,
64
+ set_updated_rows : Option < ManagedStmt > ,
65
+ had_writes : bool ,
60
66
}
61
67
62
68
impl VirtualTable {
@@ -73,31 +79,29 @@ impl VirtualTable {
73
79
}
74
80
}
75
81
76
- fn handle_insert ( & self , args : & [ * mut sqlite:: value ] ) -> Result < ( ) , SQLiteError > {
82
+ fn handle_insert ( & mut self , args : & [ * mut sqlite:: value ] ) -> Result < ( ) , SQLiteError > {
77
83
let current_tx = self
78
84
. current_tx
79
- . as_ref ( )
85
+ . as_mut ( )
80
86
. ok_or_else ( || SQLiteError ( ResultCode :: MISUSE , Some ( String :: from ( "No tx_id" ) ) ) ) ?;
87
+ let db = self . db ;
81
88
82
- match & current_tx. mode {
83
- CrudTransactionMode :: Manual { stmt } => {
89
+ match & mut current_tx. mode {
90
+ CrudTransactionMode :: Manual ( manual ) => {
84
91
// Columns are (data TEXT, options INT HIDDEN)
85
92
let data = args[ 0 ] . text ( ) ;
86
93
let flags = match args[ 1 ] . value_type ( ) {
87
94
sqlite_nostd:: ColumnType :: Null => TableInfoFlags :: default ( ) ,
88
95
_ => TableInfoFlags ( args[ 1 ] . int ( ) as u32 ) ,
89
96
} ;
90
97
98
+ let stmt = manual. raw_crud_statement ( db) ?;
91
99
stmt. bind_int64 ( 1 , current_tx. tx_id ) ?;
92
100
stmt. bind_text ( 2 , data, sqlite:: Destructor :: STATIC ) ?;
93
101
stmt. bind_int ( 3 , flags. 0 as i32 ) ?;
94
102
stmt. exec ( ) ?;
95
103
}
96
- CrudTransactionMode :: Simple {
97
- stmt,
98
- set_updated_rows,
99
- update_local_bucket,
100
- } => {
104
+ CrudTransactionMode :: Simple ( simple) => {
101
105
// Columns are (op TEXT, id TEXT, type TEXT, data TEXT, old_values TEXT, metadata TEXT, options INT HIDDEN)
102
106
let flags = match args[ 6 ] . value_type ( ) {
103
107
sqlite_nostd:: ColumnType :: Null => TableInfoFlags :: default ( ) ,
@@ -133,6 +137,7 @@ impl VirtualTable {
133
137
134
138
// First, we insert into ps_crud like the manual vtab would too. We have to create
135
139
// the JSON out of the individual components for that.
140
+ let stmt = simple. raw_crud_statement ( db) ?;
136
141
stmt. bind_int64 ( 1 , current_tx. tx_id ) ?;
137
142
138
143
let serialized = serde_json:: to_string ( & CrudEntry {
@@ -151,10 +156,11 @@ impl VirtualTable {
151
156
stmt. exec ( ) ?;
152
157
153
158
// However, we also set ps_updated_rows and update the $local bucket
159
+ let set_updated_rows = simple. set_updated_rows_statement ( db) ?;
154
160
set_updated_rows. bind_text ( 1 , row_type, sqlite:: Destructor :: STATIC ) ?;
155
161
set_updated_rows. bind_text ( 2 , id, sqlite:: Destructor :: STATIC ) ?;
156
162
set_updated_rows. exec ( ) ?;
157
- update_local_bucket . exec ( ) ?;
163
+ simple . record_local_write ( db ) ?;
158
164
}
159
165
}
160
166
@@ -176,39 +182,78 @@ impl VirtualTable {
176
182
self . current_tx = Some ( ActiveCrudTransaction {
177
183
tx_id,
178
184
mode : if self . is_simple {
179
- CrudTransactionMode :: Simple {
180
- // language=SQLite
181
- stmt : db. prepare_v3 ( "INSERT INTO ps_crud(tx_id, data) VALUES (?, ?)" , 0 ) ?,
182
- // language=SQLite
183
- set_updated_rows : db. prepare_v3 (
184
- "INSERT OR IGNORE INTO ps_updated_rows(row_type, row_id) VALUES(?, ?)" ,
185
- 0 ,
186
- ) ?,
187
- update_local_bucket : db. prepare_v3 ( formatcp ! ( "INSERT OR REPLACE INTO ps_buckets(name, last_op, target_op) VALUES('$local', 0, {MAX_OP_ID})" ) , 0 ) ?,
188
- }
185
+ CrudTransactionMode :: Simple ( Default :: default ( ) )
189
186
} else {
190
- const SQL : & str = formatcp ! (
191
- "\
187
+ CrudTransactionMode :: Manual ( Default :: default ( ) )
188
+ } ,
189
+ } ) ;
190
+
191
+ Ok ( ( ) )
192
+ }
193
+
194
+ fn end_transaction ( & mut self ) {
195
+ self . current_tx = None ;
196
+ }
197
+ }
198
+
199
+ impl ManualCrudTransactionMode {
200
+ fn raw_crud_statement ( & mut self , db : * mut sqlite:: sqlite3 ) -> Result < & ManagedStmt , ResultCode > {
201
+ prepare_lazy ( & mut self . stmt , || {
202
+ const SQL : & str = formatcp ! (
203
+ "\
192
204
WITH insertion (tx_id, data) AS (VALUES (?1, ?2))
193
205
INSERT INTO ps_crud(tx_id, data)
194
206
SELECT * FROM insertion WHERE (NOT (?3 & {})) OR data->>'op' != 'PATCH' OR data->'data' != '{{}}';
195
207
" ,
196
- TableInfoFlags :: IGNORE_EMPTY_UPDATE
197
- ) ;
208
+ TableInfoFlags :: IGNORE_EMPTY_UPDATE
209
+ ) ;
198
210
199
- let insert_statement = db. prepare_v3 ( SQL , 0 ) ?;
200
- CrudTransactionMode :: Manual {
201
- stmt : insert_statement,
202
- }
203
- } ,
204
- } ) ;
211
+ db. prepare_v3 ( SQL , 0 )
212
+ } )
213
+ }
214
+ }
215
+
216
+ impl SimpleCrudTransactionMode {
217
+ fn raw_crud_statement ( & mut self , db : * mut sqlite:: sqlite3 ) -> Result < & ManagedStmt , ResultCode > {
218
+ prepare_lazy ( & mut self . stmt , || {
219
+ // language=SQLite
220
+ db. prepare_v3 ( "INSERT INTO ps_crud(tx_id, data) VALUES (?, ?)" , 0 )
221
+ } )
222
+ }
223
+
224
+ fn set_updated_rows_statement (
225
+ & mut self ,
226
+ db : * mut sqlite:: sqlite3 ,
227
+ ) -> Result < & ManagedStmt , ResultCode > {
228
+ prepare_lazy ( & mut self . set_updated_rows , || {
229
+ // language=SQLite
230
+ db. prepare_v3 (
231
+ "INSERT OR IGNORE INTO ps_updated_rows(row_type, row_id) VALUES(?, ?)" ,
232
+ 0 ,
233
+ )
234
+ } )
235
+ }
236
+
237
+ fn record_local_write ( & mut self , db : * mut sqlite:: sqlite3 ) -> Result < ( ) , ResultCode > {
238
+ if !self . had_writes {
239
+ db. exec_safe ( formatcp ! ( "INSERT OR REPLACE INTO ps_buckets(name, last_op, target_op) VALUES('$local', 0, {MAX_OP_ID})" ) ) ?;
240
+ self . had_writes = true ;
241
+ }
205
242
206
243
Ok ( ( ) )
207
244
}
245
+ }
208
246
209
- fn end_transaction ( & mut self ) {
210
- self . current_tx = None ;
247
+ /// A variant of `Option.get_or_insert` that handles insertions returning errors.
248
+ fn prepare_lazy (
249
+ stmt : & mut Option < ManagedStmt > ,
250
+ prepare : impl FnOnce ( ) -> Result < ManagedStmt , ResultCode > ,
251
+ ) -> Result < & ManagedStmt , ResultCode > {
252
+ if let None = stmt {
253
+ * stmt = Some ( prepare ( ) ?) ;
211
254
}
255
+
256
+ return Ok ( unsafe { stmt. as_ref ( ) . unwrap_unchecked ( ) } ) ;
212
257
}
213
258
214
259
extern "C" fn connect (
@@ -295,7 +340,7 @@ extern "C" fn update(
295
340
ResultCode :: MISUSE as c_int
296
341
} else if rowid. value_type ( ) == sqlite:: ColumnType :: Null {
297
342
// INSERT
298
- let tab = unsafe { & * ( vtab. cast :: < VirtualTable > ( ) ) } ;
343
+ let tab = unsafe { & mut * ( vtab. cast :: < VirtualTable > ( ) ) } ;
299
344
let result = tab. handle_insert ( & args[ 2 ..] ) ;
300
345
vtab_result ( vtab, result)
301
346
} else {
0 commit comments