Skip to content

Commit 09b91e0

Browse files
committed
Track old values for delete
1 parent cfcadb8 commit 09b91e0

File tree

2 files changed

+138
-65
lines changed

2 files changed

+138
-65
lines changed

crates/core/src/views.rs

+34-11
Original file line numberDiff line numberDiff line change
@@ -89,31 +89,54 @@ fn powersync_trigger_delete_sql_impl(
8989
let trigger_name = quote_identifier_prefixed("ps_view_delete_", view_name);
9090
let type_string = quote_string(name);
9191

92+
let db = ctx.db_handle();
93+
let old_fragment: Cow<'static, str> = match table_info.diff_include_old {
94+
Some(include_old) => {
95+
let mut columns = ColumnNameAndTypeStatement::new(db, table)?;
96+
97+
let json = match include_old {
98+
DiffIncludeOld::OnlyForColumns { columns } => {
99+
let mut iterator = columns.iter();
100+
let mut columns =
101+
streaming_iterator::from_fn(|| -> Option<Result<&str, ResultCode>> {
102+
Some(Ok(iterator.next()?.as_str()))
103+
});
104+
105+
json_object_fragment("OLD", &mut columns)
106+
}
107+
DiffIncludeOld::ForAllColumns => {
108+
json_object_fragment("OLD", &mut columns.names_iter())
109+
}
110+
}?;
111+
112+
format!(", 'old', {json}").into()
113+
}
114+
None => "".into(),
115+
};
116+
92117
return if !local_only && !insert_only {
93118
let trigger = format!(
94119
"\
95-
CREATE TRIGGER {:}
96-
INSTEAD OF DELETE ON {:}
120+
CREATE TRIGGER {trigger_name}
121+
INSTEAD OF DELETE ON {quoted_name}
97122
FOR EACH ROW
98123
BEGIN
99-
DELETE FROM {:} WHERE id = OLD.id;
100-
INSERT INTO powersync_crud_(data) VALUES(json_object('op', 'DELETE', 'type', {:}, 'id', OLD.id));
101-
INSERT OR IGNORE INTO ps_updated_rows(row_type, row_id) VALUES({:}, OLD.id);
102-
INSERT OR REPLACE INTO ps_buckets(name, last_op, target_op) VALUES('$local', 0, {:});
124+
DELETE FROM {internal_name} WHERE id = OLD.id;
125+
INSERT INTO powersync_crud_(data) VALUES(json_object('op', 'DELETE', 'type', {type_string}, 'id', OLD.id {old_fragment}));
126+
INSERT OR IGNORE INTO ps_updated_rows(row_type, row_id) VALUES({type_string}, OLD.id);
127+
INSERT OR REPLACE INTO ps_buckets(name, last_op, target_op) VALUES('$local', 0, {MAX_OP_ID});
103128
END",
104-
trigger_name, quoted_name, internal_name, type_string, type_string, MAX_OP_ID
105129
);
106130
Ok(trigger)
107131
} else if local_only {
108132
let trigger = format!(
109133
"\
110-
CREATE TRIGGER {:}
111-
INSTEAD OF DELETE ON {:}
134+
CREATE TRIGGER {trigger_name}
135+
INSTEAD OF DELETE ON {quoted_name}
112136
FOR EACH ROW
113137
BEGIN
114-
DELETE FROM {:} WHERE id = OLD.id;
138+
DELETE FROM {internal_name} WHERE id = OLD.id;
115139
END",
116-
trigger_name, quoted_name, internal_name
117140
);
118141
Ok(trigger)
119142
} else if insert_only {

dart/test/crud_test.dart

+104-54
Original file line numberDiff line numberDiff line change
@@ -247,76 +247,126 @@ void main() {
247247
'select powersync_replace_schema(?)', [jsonEncode(tableSchema)]);
248248
}
249249

250-
void insertThenUpdate() {
251-
db
252-
..execute('insert into test (id, name, name2) values (?, ?, ?)',
253-
['id', 'name', 'name2'])
254-
..execute('delete from ps_crud')
255-
..execute('update test set name = name || ?', ['.']);
256-
}
250+
group('for updates', () {
251+
void insertThenUpdate() {
252+
db
253+
..execute('insert into test (id, name, name2) values (?, ?, ?)',
254+
['id', 'name', 'name2'])
255+
..execute('delete from ps_crud')
256+
..execute('update test set name = name || ?', ['.']);
257+
}
258+
259+
test('is not tracked by default', () {
260+
createTable();
261+
insertThenUpdate();
262+
263+
final [row] = db.select('select data from ps_crud');
264+
expect(jsonDecode(row[0] as String), isNot(contains('old')));
265+
});
257266

258-
test('are not tracked by default', () {
259-
createTable();
260-
insertThenUpdate();
267+
test('can be disabled', () {
268+
createTable({'include_old': false});
269+
insertThenUpdate();
261270

262-
final [row] = db.select('select data from ps_crud');
263-
expect(jsonDecode(row[0] as String), isNot(contains('old')));
264-
});
271+
final [row] = db.select('select data from ps_crud');
272+
expect(jsonDecode(row[0] as String), isNot(contains('old')));
273+
});
265274

266-
test('can be disabled', () {
267-
createTable({'include_old': false});
268-
insertThenUpdate();
275+
test('can be enabled for all columns', () {
276+
createTable({'include_old': true});
277+
insertThenUpdate();
269278

270-
final [row] = db.select('select data from ps_crud');
271-
expect(jsonDecode(row[0] as String), isNot(contains('old')));
272-
});
279+
final [row] = db.select('select data from ps_crud');
280+
final op = jsonDecode(row[0] as String);
281+
expect(op['data'], {'name': 'name.'});
282+
expect(op['old'], {'name': 'name', 'name2': 'name2'});
283+
});
273284

274-
test('can be enabled for all columns', () {
275-
createTable({'include_old': true});
276-
insertThenUpdate();
285+
test('can be enabled for some columns', () {
286+
createTable({
287+
'include_old': ['name']
288+
});
289+
insertThenUpdate();
277290

278-
final [row] = db.select('select data from ps_crud');
279-
final op = jsonDecode(row[0] as String);
280-
expect(op['data'], {'name': 'name.'});
281-
expect(op['old'], {'name': 'name', 'name2': 'name2'});
282-
});
291+
final [row] = db.select('select data from ps_crud');
292+
final op = jsonDecode(row[0] as String);
293+
expect(op['data'], {'name': 'name.'});
294+
expect(op['old'], {'name': 'name'});
295+
});
283296

284-
test('can be enabled for some columns', () {
285-
createTable({
286-
'include_old': ['name']
297+
test('can track changed values only', () {
298+
createTable({
299+
'include_old': true,
300+
'include_old_only_when_changed': true,
301+
});
302+
insertThenUpdate();
303+
304+
final [row] = db.select('select data from ps_crud');
305+
final op = jsonDecode(row[0] as String);
306+
expect(op['data'], {'name': 'name.'});
307+
expect(op['old'], {'name': 'name'});
287308
});
288-
insertThenUpdate();
289309

290-
final [row] = db.select('select data from ps_crud');
291-
final op = jsonDecode(row[0] as String);
292-
expect(op['data'], {'name': 'name.'});
293-
expect(op['old'], {'name': 'name'});
310+
test('combined column filter and only tracking changes', () {
311+
createTable({
312+
'include_old': ['name2'],
313+
'include_old_only_when_changed': true,
314+
});
315+
insertThenUpdate();
316+
317+
final [row] = db.select('select data from ps_crud');
318+
final op = jsonDecode(row[0] as String);
319+
expect(op['data'], {'name': 'name.'});
320+
expect(op['old'], {});
321+
});
294322
});
295323

296-
test('can track changed values only', () {
297-
createTable({
298-
'include_old': true,
299-
'include_old_only_when_changed': true,
324+
group('for deletes', () {
325+
void insertThenDelete() {
326+
db
327+
..execute('insert into test (id, name, name2) values (?, ?, ?)',
328+
['id', 'name', 'name2'])
329+
..execute('delete from ps_crud')
330+
..execute('delete from test');
331+
}
332+
333+
test('is not tracked by default', () {
334+
createTable();
335+
insertThenDelete();
336+
337+
final [row] = db.select('select data from ps_crud');
338+
expect(jsonDecode(row[0] as String), isNot(contains('old')));
300339
});
301-
insertThenUpdate();
302340

303-
final [row] = db.select('select data from ps_crud');
304-
final op = jsonDecode(row[0] as String);
305-
expect(op['data'], {'name': 'name.'});
306-
expect(op['old'], {'name': 'name'});
307-
});
341+
test('can be disabled', () {
342+
createTable({'include_old': false});
343+
insertThenDelete();
308344

309-
test('combined column filter and only tracking changes', () {
310-
createTable({
311-
'include_old': ['name2'],
312-
'include_old_only_when_changed': true,
345+
final [row] = db.select('select data from ps_crud');
346+
expect(jsonDecode(row[0] as String), isNot(contains('old')));
313347
});
314-
insertThenUpdate();
315348

316-
final [row] = db.select('select data from ps_crud');
317-
final op = jsonDecode(row[0] as String);
318-
expect(op['data'], {'name': 'name.'});
319-
expect(op['old'], {});
349+
test('can be enabled for all columns', () {
350+
createTable({'include_old': true});
351+
insertThenDelete();
352+
353+
final [row] = db.select('select data from ps_crud');
354+
final op = jsonDecode(row[0] as String);
355+
expect(op['data'], null);
356+
expect(op['old'], {'name': 'name', 'name2': 'name2'});
357+
});
358+
359+
test('can be enabled for some columns', () {
360+
createTable({
361+
'include_old': ['name']
362+
});
363+
insertThenDelete();
364+
365+
final [row] = db.select('select data from ps_crud');
366+
final op = jsonDecode(row[0] as String);
367+
expect(op['data'], null);
368+
expect(op['old'], {'name': 'name'});
369+
});
320370
});
321371
});
322372
});

0 commit comments

Comments
 (0)