@@ -5,6 +5,7 @@ use alloc::string::String;
55use core:: ffi:: { c_char, c_int, c_void} ;
66use core:: slice;
77
8+ use const_format:: formatcp;
89use sqlite:: { Connection , ResultCode , Value } ;
910use sqlite_nostd as sqlite;
1011use sqlite_nostd:: ManagedStmt ;
@@ -15,7 +16,7 @@ use crate::ext::SafeManagedStmt;
1516use crate :: vtab_util:: * ;
1617
1718// Structure:
18- // CREATE TABLE powersync_crud_(data TEXT);
19+ // CREATE TABLE powersync_crud_(data TEXT, options INT HIDDEN );
1920//
2021// This is a insert-only virtual table. It generates transaction ids in ps_tx, and inserts data in
2122// ps_crud(tx_id, data).
@@ -29,9 +30,13 @@ struct VirtualTable {
2930 base : sqlite:: vtab ,
3031 db : * mut sqlite:: sqlite3 ,
3132 current_tx : Option < i64 > ,
32- insert_statement : Option < ManagedStmt >
33+ insert_statement : Option < ManagedStmt > ,
3334}
3435
36+ #[ repr( transparent) ]
37+ #[ derive( Clone , Copy ) ]
38+ pub struct PowerSyncCrudFlags ( pub u32 ) ;
39+
3540extern "C" fn connect (
3641 db : * mut sqlite:: sqlite3 ,
3742 _aux : * mut c_void ,
@@ -40,8 +45,10 @@ extern "C" fn connect(
4045 vtab : * mut * mut sqlite:: vtab ,
4146 _err : * mut * mut c_char ,
4247) -> c_int {
43- if let Err ( rc) = sqlite:: declare_vtab ( db, "CREATE TABLE powersync_crud_(data TEXT);" )
44- {
48+ if let Err ( rc) = sqlite:: declare_vtab (
49+ db,
50+ "CREATE TABLE powersync_crud_(data TEXT, options INT HIDDEN);" ,
51+ ) {
4552 return rc as c_int ;
4653 }
4754
@@ -54,7 +61,7 @@ extern "C" fn connect(
5461 } ,
5562 db,
5663 current_tx : None ,
57- insert_statement : None
64+ insert_statement : None ,
5865 } ) ) ;
5966 * vtab = tab. cast :: < sqlite:: vtab > ( ) ;
6067 let _ = sqlite:: vtab_config ( db, 0 ) ;
@@ -69,15 +76,22 @@ extern "C" fn disconnect(vtab: *mut sqlite::vtab) -> c_int {
6976 ResultCode :: OK as c_int
7077}
7178
72-
7379fn begin_impl ( tab : & mut VirtualTable ) -> Result < ( ) , SQLiteError > {
7480 let db = tab. db ;
7581
76- let insert_statement = db. prepare_v3 ( "INSERT INTO ps_crud(tx_id, data) VALUES (?1, ?2)" , 0 ) ?;
82+ const SQL : & str = formatcp ! ( "\
83+ WITH insertion (tx_id, data) AS (VALUES (?1, ?2))
84+ INSERT INTO ps_crud(tx_id, data)
85+ SELECT * FROM insertion WHERE (?3 & {}) OR data->>'op' != 'PATCH' OR EXISTS (SELECT 1 FROM json_each(data->'data'));
86+ " , PowerSyncCrudFlags :: FLAG_INCLUDE_EMPTY_UPDATE ) ;
87+
88+ // language=SQLite
89+ let insert_statement = db. prepare_v3 ( SQL , 0 ) ?;
7790 tab. insert_statement = Some ( insert_statement) ;
7891
7992 // language=SQLite
80- let statement = db. prepare_v2 ( "UPDATE ps_tx SET next_tx = next_tx + 1 WHERE id = 1 RETURNING next_tx" ) ?;
93+ let statement =
94+ db. prepare_v2 ( "UPDATE ps_tx SET next_tx = next_tx + 1 WHERE id = 1 RETURNING next_tx" ) ?;
8195 if statement. step ( ) ? == ResultCode :: ROW {
8296 let tx_id = statement. column_int64 ( 0 ) ? - 1 ;
8397 tab. current_tx = Some ( tx_id) ;
@@ -110,22 +124,31 @@ extern "C" fn rollback(vtab: *mut sqlite::vtab) -> c_int {
110124}
111125
112126fn insert_operation (
113- vtab : * mut sqlite:: vtab , data : & str ) -> Result < ( ) , SQLiteError > {
127+ vtab : * mut sqlite:: vtab ,
128+ data : & str ,
129+ flags : PowerSyncCrudFlags ,
130+ ) -> Result < ( ) , SQLiteError > {
114131 let tab = unsafe { & mut * ( vtab. cast :: < VirtualTable > ( ) ) } ;
115132 if tab. current_tx . is_none ( ) {
116- return Err ( SQLiteError ( ResultCode :: MISUSE , Some ( String :: from ( "No tx_id" ) ) ) ) ;
133+ return Err ( SQLiteError (
134+ ResultCode :: MISUSE ,
135+ Some ( String :: from ( "No tx_id" ) ) ,
136+ ) ) ;
117137 }
118138 let current_tx = tab. current_tx . unwrap ( ) ;
119139 // language=SQLite
120- let statement = tab. insert_statement . as_ref ( ) . ok_or ( SQLiteError :: from ( NULL ) ) ?;
140+ let statement = tab
141+ . insert_statement
142+ . as_ref ( )
143+ . ok_or ( SQLiteError :: from ( NULL ) ) ?;
121144 statement. bind_int64 ( 1 , current_tx) ?;
122145 statement. bind_text ( 2 , data, sqlite:: Destructor :: STATIC ) ?;
146+ statement. bind_int ( 3 , flags. 0 as i32 ) ?;
123147 statement. exec ( ) ?;
124148
125149 Ok ( ( ) )
126150}
127151
128-
129152extern "C" fn update (
130153 vtab : * mut sqlite:: vtab ,
131154 argc : c_int ,
@@ -142,7 +165,13 @@ extern "C" fn update(
142165 } else if rowid. value_type ( ) == sqlite:: ColumnType :: Null {
143166 // INSERT
144167 let data = args[ 2 ] . text ( ) ;
145- let result = insert_operation ( vtab, data) ;
168+ let flags = match args[ 3 ] . value_type ( ) {
169+ // We don't ignore empty updates by default.
170+ sqlite_nostd:: ColumnType :: Null => PowerSyncCrudFlags :: default ( ) ,
171+ _ => PowerSyncCrudFlags ( args[ 3 ] . int ( ) as u32 ) ,
172+ } ;
173+
174+ let result = insert_operation ( vtab, data, flags) ;
146175 vtab_result ( vtab, result)
147176 } else {
148177 // UPDATE - not supported
@@ -185,3 +214,26 @@ pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
185214
186215 Ok ( ( ) )
187216}
217+
218+ impl PowerSyncCrudFlags {
219+ pub const FLAG_INCLUDE_EMPTY_UPDATE : u32 = 1 << 0 ;
220+
221+ pub fn set_include_empty_update ( & mut self , value : bool ) {
222+ if value {
223+ self . 0 |= Self :: FLAG_INCLUDE_EMPTY_UPDATE ;
224+ } else {
225+ self . 0 &= !Self :: FLAG_INCLUDE_EMPTY_UPDATE ;
226+ }
227+ }
228+
229+ pub fn has_include_empty_update ( self ) -> bool {
230+ self . 0 & Self :: FLAG_INCLUDE_EMPTY_UPDATE != 0
231+ }
232+ }
233+
234+ impl Default for PowerSyncCrudFlags {
235+ fn default ( ) -> Self {
236+ // For backwards-compatibility, we include empty updates by default.
237+ return Self ( Self :: FLAG_INCLUDE_EMPTY_UPDATE ) ;
238+ }
239+ }
0 commit comments