@@ -218,9 +218,130 @@ void main() {
218218 isEmpty,
219219 );
220220 });
221+
222+ group ('triggers for raw tables' , () {
223+ const createUsers =
224+ 'CREATE TABLE users (id TEXT, email TEXT, email_verified INTEGER);' ;
225+
226+ const testCases = < _RawTableTestCase > [
227+ // Default options
228+ _RawTableTestCase (
229+ createTable: createUsers,
230+ tableOptions: {'table_name' : 'users' },
231+ insert: '''
232+ CREATE TRIGGER "test_insert" AFTER INSERT ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
233+ INSERT INTO powersync_crud(op,id,type,data) VALUES ('PUT', NEW.id, 'sync_type', json(powersync_diff('{}', json_object('email', powersync_strip_subtype(NEW."email"), 'email_verified', powersync_strip_subtype(NEW."email_verified")))));
234+ END''' ,
235+ update: '''
236+ CREATE TRIGGER "test_update" AFTER UPDATE ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
237+ SELECT CASE WHEN (OLD.id != NEW.id) THEN RAISE (FAIL, 'Cannot update id') END;
238+ INSERT INTO powersync_crud(op,id,type,data,options) VALUES ('PATCH', NEW.id, 'sync_type', json(powersync_diff(json_object('email', powersync_strip_subtype(OLD."email"), 'email_verified', powersync_strip_subtype(OLD."email_verified")), json_object('email', powersync_strip_subtype(NEW."email"), 'email_verified', powersync_strip_subtype(NEW."email_verified")))), 0);
239+ END''' ,
240+ delete: '''
241+ CREATE TRIGGER "test_delete" AFTER DELETE ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
242+ INSERT INTO powersync_crud(op,id,type) VALUES ('DELETE', OLD.id, 'sync_type');
243+ END''' ,
244+ ),
245+ // Insert-only
246+ _RawTableTestCase (
247+ createTable: createUsers,
248+ tableOptions: {
249+ 'table_name' : 'users' ,
250+ 'insert_only' : true ,
251+ },
252+ insert: '''
253+ CREATE TRIGGER "test_insert" AFTER INSERT ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
254+ INSERT INTO powersync_crud_(data) VALUES(json_object('op', 'PUT', 'type', 'sync_type', 'id', NEW.id, 'data', json(powersync_diff('{}', json_object('email', powersync_strip_subtype(NEW."email"), 'email_verified', powersync_strip_subtype(NEW."email_verified"))))));END''' ,
255+ update: '''
256+ CREATE TRIGGER "test_update" AFTER UPDATE ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
257+ SELECT RAISE(FAIL, 'Unexpected update on insert-only table');
258+ END''' ,
259+ delete: '''
260+ CREATE TRIGGER "test_delete" AFTER DELETE ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
261+ SELECT RAISE(FAIL, 'Unexpected update on insert-only table');
262+ END''' ,
263+ ),
264+ // Tracking old values
265+ _RawTableTestCase (
266+ createTable: createUsers,
267+ tableOptions: {
268+ 'table_name' : 'users' ,
269+ 'include_old' : true ,
270+ },
271+ insert: '''
272+ CREATE TRIGGER "test_insert" AFTER INSERT ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
273+ INSERT INTO powersync_crud(op,id,type,data) VALUES ('PUT', NEW.id, 'sync_type', json(powersync_diff('{}', json_object('email', powersync_strip_subtype(NEW."email"), 'email_verified', powersync_strip_subtype(NEW."email_verified")))));
274+ END''' ,
275+ update: '''
276+ CREATE TRIGGER "test_update" AFTER UPDATE ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
277+ SELECT CASE WHEN (OLD.id != NEW.id) THEN RAISE (FAIL, 'Cannot update id') END;
278+ INSERT INTO powersync_crud(op,id,type,data,old_values,options) VALUES ('PATCH', NEW.id, 'sync_type', json(powersync_diff(json_object('email', powersync_strip_subtype(OLD."email"), 'email_verified', powersync_strip_subtype(OLD."email_verified")), json_object('email', powersync_strip_subtype(NEW."email"), 'email_verified', powersync_strip_subtype(NEW."email_verified")))), json_object('email', powersync_strip_subtype(OLD."email"), 'email_verified', powersync_strip_subtype(OLD."email_verified")), 0);
279+ END''' ,
280+ delete: '''
281+ CREATE TRIGGER "test_delete" AFTER DELETE ON "users" FOR EACH ROW WHEN NOT powersync_in_sync_operation() BEGIN
282+ INSERT INTO powersync_crud(op,id,type,old_values) VALUES ('DELETE', OLD.id, 'sync_type', json_object('email', powersync_strip_subtype(OLD."email"), 'email_verified', powersync_strip_subtype(OLD."email_verified")));
283+ END''' ,
284+ ),
285+ ];
286+
287+ for (final (i, testCase) in testCases.indexed) {
288+ test ('#$i ' , () => testCase.testWith (db));
289+ }
290+ });
221291 });
222292}
223293
294+ final class _RawTableTestCase {
295+ final String createTable;
296+ final Map <String , Object ?> tableOptions;
297+ final String insert, update, delete;
298+
299+ const _RawTableTestCase ({
300+ required this .createTable,
301+ required this .tableOptions,
302+ required this .insert,
303+ required this .update,
304+ required this .delete,
305+ });
306+
307+ void testWith (CommonDatabase db) {
308+ db.execute (createTable);
309+ db.execute ('''
310+ SELECT
311+ powersync_create_raw_table_crud_trigger(?1, 'test_insert', 'INSERT'),
312+ powersync_create_raw_table_crud_trigger(?1, 'test_update', 'UPDATE'),
313+ powersync_create_raw_table_crud_trigger(?1, 'test_delete', 'DELETE')
314+ ''' , [
315+ json.encode ({
316+ 'name' : 'sync_type' ,
317+ 'put' : {
318+ 'sql' : 'unused' ,
319+ 'params' : [],
320+ },
321+ 'delete' : {
322+ 'sql' : 'unused' ,
323+ 'params' : [],
324+ },
325+ ...tableOptions,
326+ })
327+ ]);
328+
329+ final foundTriggers =
330+ db.select ("SELECT name, sql FROM sqlite_schema WHERE type = 'trigger'" );
331+
332+ // Uncomment to help update expectations
333+ // for (final row in foundTriggers) {
334+ // print(row['sql']);
335+ // }
336+
337+ expect (foundTriggers, [
338+ {'name' : 'test_insert' , 'sql' : insert},
339+ {'name' : 'test_update' , 'sql' : update},
340+ {'name' : 'test_delete' , 'sql' : delete},
341+ ]);
342+ }
343+ }
344+
224345final schema = {
225346 "tables" : [
226347 {
0 commit comments