@@ -49,14 +49,20 @@ struct ActiveCrudTransaction {
4949}
5050
5151enum 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 ,
6066}
6167
6268impl VirtualTable {
@@ -73,31 +79,29 @@ impl VirtualTable {
7379 }
7480 }
7581
76- fn handle_insert ( & self , args : & [ * mut sqlite:: value ] ) -> Result < ( ) , SQLiteError > {
82+ fn handle_insert ( & mut self , args : & [ * mut sqlite:: value ] ) -> Result < ( ) , SQLiteError > {
7783 let current_tx = self
7884 . current_tx
79- . as_ref ( )
85+ . as_mut ( )
8086 . ok_or_else ( || SQLiteError ( ResultCode :: MISUSE , Some ( String :: from ( "No tx_id" ) ) ) ) ?;
87+ let db = self . db ;
8188
82- match & current_tx. mode {
83- CrudTransactionMode :: Manual { stmt } => {
89+ match & mut current_tx. mode {
90+ CrudTransactionMode :: Manual ( manual ) => {
8491 // Columns are (data TEXT, options INT HIDDEN)
8592 let data = args[ 0 ] . text ( ) ;
8693 let flags = match args[ 1 ] . value_type ( ) {
8794 sqlite_nostd:: ColumnType :: Null => TableInfoFlags :: default ( ) ,
8895 _ => TableInfoFlags ( args[ 1 ] . int ( ) as u32 ) ,
8996 } ;
9097
98+ let stmt = manual. raw_crud_statement ( db) ?;
9199 stmt. bind_int64 ( 1 , current_tx. tx_id ) ?;
92100 stmt. bind_text ( 2 , data, sqlite:: Destructor :: STATIC ) ?;
93101 stmt. bind_int ( 3 , flags. 0 as i32 ) ?;
94102 stmt. exec ( ) ?;
95103 }
96- CrudTransactionMode :: Simple {
97- stmt,
98- set_updated_rows,
99- update_local_bucket,
100- } => {
104+ CrudTransactionMode :: Simple ( simple) => {
101105 // Columns are (op TEXT, id TEXT, type TEXT, data TEXT, old_values TEXT, metadata TEXT, options INT HIDDEN)
102106 let flags = match args[ 6 ] . value_type ( ) {
103107 sqlite_nostd:: ColumnType :: Null => TableInfoFlags :: default ( ) ,
@@ -133,6 +137,7 @@ impl VirtualTable {
133137
134138 // First, we insert into ps_crud like the manual vtab would too. We have to create
135139 // the JSON out of the individual components for that.
140+ let stmt = simple. raw_crud_statement ( db) ?;
136141 stmt. bind_int64 ( 1 , current_tx. tx_id ) ?;
137142
138143 let serialized = serde_json:: to_string ( & CrudEntry {
@@ -151,10 +156,11 @@ impl VirtualTable {
151156 stmt. exec ( ) ?;
152157
153158 // However, we also set ps_updated_rows and update the $local bucket
159+ let set_updated_rows = simple. set_updated_rows_statement ( db) ?;
154160 set_updated_rows. bind_text ( 1 , row_type, sqlite:: Destructor :: STATIC ) ?;
155161 set_updated_rows. bind_text ( 2 , id, sqlite:: Destructor :: STATIC ) ?;
156162 set_updated_rows. exec ( ) ?;
157- update_local_bucket . exec ( ) ?;
163+ simple . record_local_write ( db ) ?;
158164 }
159165 }
160166
@@ -176,39 +182,78 @@ impl VirtualTable {
176182 self . current_tx = Some ( ActiveCrudTransaction {
177183 tx_id,
178184 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 ( ) )
189186 } 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+ "\
192204 WITH insertion (tx_id, data) AS (VALUES (?1, ?2))
193205INSERT INTO ps_crud(tx_id, data)
194206SELECT * FROM insertion WHERE (NOT (?3 & {})) OR data->>'op' != 'PATCH' OR data->'data' != '{{}}';
195207 " ,
196- TableInfoFlags :: IGNORE_EMPTY_UPDATE
197- ) ;
208+ TableInfoFlags :: IGNORE_EMPTY_UPDATE
209+ ) ;
198210
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+ }
205242
206243 Ok ( ( ) )
207244 }
245+ }
208246
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 ( ) ?) ;
211254 }
255+
256+ return Ok ( unsafe { stmt. as_ref ( ) . unwrap_unchecked ( ) } ) ;
212257}
213258
214259extern "C" fn connect (
@@ -295,7 +340,7 @@ extern "C" fn update(
295340 ResultCode :: MISUSE as c_int
296341 } else if rowid. value_type ( ) == sqlite:: ColumnType :: Null {
297342 // INSERT
298- let tab = unsafe { & * ( vtab. cast :: < VirtualTable > ( ) ) } ;
343+ let tab = unsafe { & mut * ( vtab. cast :: < VirtualTable > ( ) ) } ;
299344 let result = tab. handle_insert ( & args[ 2 ..] ) ;
300345 vtab_result ( vtab, result)
301346 } else {
0 commit comments