@@ -5,6 +5,7 @@ use alloc::string::String;
5
5
use core:: ffi:: { c_char, c_int, c_void} ;
6
6
use core:: slice;
7
7
8
+ use const_format:: formatcp;
8
9
use sqlite:: { Connection , ResultCode , Value } ;
9
10
use sqlite_nostd as sqlite;
10
11
use sqlite_nostd:: ManagedStmt ;
@@ -15,7 +16,7 @@ use crate::ext::SafeManagedStmt;
15
16
use crate :: vtab_util:: * ;
16
17
17
18
// Structure:
18
- // CREATE TABLE powersync_crud_(data TEXT);
19
+ // CREATE TABLE powersync_crud_(data TEXT, options INT HIDDEN );
19
20
//
20
21
// This is a insert-only virtual table. It generates transaction ids in ps_tx, and inserts data in
21
22
// ps_crud(tx_id, data).
@@ -29,9 +30,13 @@ struct VirtualTable {
29
30
base : sqlite:: vtab ,
30
31
db : * mut sqlite:: sqlite3 ,
31
32
current_tx : Option < i64 > ,
32
- insert_statement : Option < ManagedStmt >
33
+ insert_statement : Option < ManagedStmt > ,
33
34
}
34
35
36
+ #[ repr( transparent) ]
37
+ #[ derive( Clone , Copy ) ]
38
+ pub struct PowerSyncCrudFlags ( pub u32 ) ;
39
+
35
40
extern "C" fn connect (
36
41
db : * mut sqlite:: sqlite3 ,
37
42
_aux : * mut c_void ,
@@ -40,8 +45,10 @@ extern "C" fn connect(
40
45
vtab : * mut * mut sqlite:: vtab ,
41
46
_err : * mut * mut c_char ,
42
47
) -> 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
+ ) {
45
52
return rc as c_int ;
46
53
}
47
54
@@ -54,7 +61,7 @@ extern "C" fn connect(
54
61
} ,
55
62
db,
56
63
current_tx : None ,
57
- insert_statement : None
64
+ insert_statement : None ,
58
65
} ) ) ;
59
66
* vtab = tab. cast :: < sqlite:: vtab > ( ) ;
60
67
let _ = sqlite:: vtab_config ( db, 0 ) ;
@@ -69,15 +76,22 @@ extern "C" fn disconnect(vtab: *mut sqlite::vtab) -> c_int {
69
76
ResultCode :: OK as c_int
70
77
}
71
78
72
-
73
79
fn begin_impl ( tab : & mut VirtualTable ) -> Result < ( ) , SQLiteError > {
74
80
let db = tab. db ;
75
81
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 ) ?;
77
90
tab. insert_statement = Some ( insert_statement) ;
78
91
79
92
// 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" ) ?;
81
95
if statement. step ( ) ? == ResultCode :: ROW {
82
96
let tx_id = statement. column_int64 ( 0 ) ? - 1 ;
83
97
tab. current_tx = Some ( tx_id) ;
@@ -110,22 +124,31 @@ extern "C" fn rollback(vtab: *mut sqlite::vtab) -> c_int {
110
124
}
111
125
112
126
fn 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 > {
114
131
let tab = unsafe { & mut * ( vtab. cast :: < VirtualTable > ( ) ) } ;
115
132
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
+ ) ) ;
117
137
}
118
138
let current_tx = tab. current_tx . unwrap ( ) ;
119
139
// 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 ) ) ?;
121
144
statement. bind_int64 ( 1 , current_tx) ?;
122
145
statement. bind_text ( 2 , data, sqlite:: Destructor :: STATIC ) ?;
146
+ statement. bind_int ( 3 , flags. 0 as i32 ) ?;
123
147
statement. exec ( ) ?;
124
148
125
149
Ok ( ( ) )
126
150
}
127
151
128
-
129
152
extern "C" fn update (
130
153
vtab : * mut sqlite:: vtab ,
131
154
argc : c_int ,
@@ -142,7 +165,13 @@ extern "C" fn update(
142
165
} else if rowid. value_type ( ) == sqlite:: ColumnType :: Null {
143
166
// INSERT
144
167
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) ;
146
175
vtab_result ( vtab, result)
147
176
} else {
148
177
// UPDATE - not supported
@@ -185,3 +214,26 @@ pub fn register(db: *mut sqlite::sqlite3) -> Result<(), ResultCode> {
185
214
186
215
Ok ( ( ) )
187
216
}
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