@@ -5,6 +5,7 @@ use alloc::format;
55use alloc:: string:: String ;
66use alloc:: vec:: Vec ;
77use core:: ffi:: c_int;
8+ use core:: fmt:: Write ;
89use streaming_iterator:: StreamingIterator ;
910
1011use sqlite:: { Connection , Context , ResultCode , Value } ;
@@ -53,6 +54,9 @@ fn powersync_view_sql_impl(
5354 if include_metadata {
5455 column_names_quoted. push ( quote_identifier ( "_metadata" ) ) ;
5556 column_values. push ( String :: from ( "NULL" ) ) ;
57+
58+ column_names_quoted. push ( quote_identifier ( "_deleted" ) ) ;
59+ column_values. push ( String :: from ( "NULL" ) ) ;
5660 }
5761
5862 let view_statement = format ! (
@@ -115,7 +119,7 @@ fn powersync_trigger_delete_sql_impl(
115119 } ;
116120
117121 return if !local_only && !insert_only {
118- let trigger = format ! (
122+ let mut trigger = format ! (
119123 "\
120124 CREATE TRIGGER {trigger_name}
121125INSTEAD OF DELETE ON {quoted_name}
@@ -125,10 +129,31 @@ DELETE FROM {internal_name} WHERE id = OLD.id;
125129INSERT INTO powersync_crud_(data) VALUES(json_object('op', 'DELETE', 'type', {type_string}, 'id', OLD.id {old_fragment}));
126130INSERT OR IGNORE INTO ps_updated_rows(row_type, row_id) VALUES({type_string}, OLD.id);
127131INSERT OR REPLACE INTO ps_buckets(name, last_op, target_op) VALUES('$local', 0, {MAX_OP_ID});
128- END" ,
132+ END;"
129133 ) ;
134+
135+ // The DELETE statement can't include metadata for the delete operation, so we create
136+ // another trigger to delete with a fake UPDATE syntax.
137+ if table_info. flags . include_metadata ( ) {
138+ let trigger_name = quote_identifier_prefixed ( "ps_view_delete2_" , view_name) ;
139+ write ! ( & mut trigger, "\
140+ CREATE TRIGGER {trigger_name}
141+ INSTEAD OF UPDATE ON {quoted_name}
142+ FOR EACH ROW
143+ WHEN NEW._deleted IS TRUE
144+ BEGIN
145+ DELETE FROM {internal_name} WHERE id = NEW.id;
146+ INSERT INTO powersync_crud_(data) VALUES(json_object('op', 'DELETE', 'type', {type_string}, 'id', NEW.id {old_fragment}, 'metadata', NEW._metadata));
147+ INSERT OR IGNORE INTO ps_updated_rows(row_type, row_id) VALUES({type_string}, NEW.id);
148+ INSERT OR REPLACE INTO ps_buckets(name, last_op, target_op) VALUES('$local', 0, {MAX_OP_ID});
149+ END;"
150+ ) . expect ( "writing to string should be infallible" ) ;
151+ }
152+
130153 Ok ( trigger)
131154 } else if local_only {
155+ debug_assert ! ( !table_info. flags. include_metadata( ) ) ;
156+
132157 let trigger = format ! (
133158 "\
134159 CREATE TRIGGER {trigger_name}
@@ -285,10 +310,18 @@ fn powersync_trigger_update_sql_impl(
285310 } ;
286311
287312 return if !local_only && !insert_only {
313+ // If we're supposed to include metadata, we support UPDATE ... SET _deleted = TRUE with
314+ // another trigger (because there's no way to attach data to DELETE statements otherwise).
315+ let when = if table_info. flags . include_metadata ( ) {
316+ "WHEN NEW._deleted IS NOT TRUE"
317+ } else {
318+ ""
319+ } ;
320+
288321 let trigger = format ! ( "\
289322 CREATE TRIGGER {trigger_name}
290323INSTEAD OF UPDATE ON {quoted_name}
291- FOR EACH ROW
324+ FOR EACH ROW {when}
292325BEGIN
293326 SELECT CASE
294327 WHEN (OLD.id != NEW.id)
@@ -303,6 +336,8 @@ BEGIN
303336END" , type_string, json_fragment_old, json_fragment_new, old_fragment, metadata_fragment) ;
304337 Ok ( trigger)
305338 } else if local_only {
339+ debug_assert ! ( !table_info. flags. include_metadata( ) ) ;
340+
306341 let trigger = format ! (
307342 "\
308343 CREATE TRIGGER {trigger_name}
0 commit comments