1
+ import 'crud.dart' ;
1
2
import 'schema_logic.dart' ;
2
3
3
4
/// The schema used by the database.
@@ -26,8 +27,30 @@ class Schema {
26
27
}
27
28
}
28
29
30
+ /// Options to include old values in [CrudEntry] for update statements.
31
+ ///
32
+ /// These options are enabled by passing them to a non-local [Table]
33
+ /// constructor.
34
+ final class TrackPreviousValuesOptions {
35
+ /// A filter of column names for which updates should be tracked.
36
+ ///
37
+ /// When set to a non-null value, columns not included in this list will not
38
+ /// appear in [CrudEntry.previousValues] . By default, all columns are
39
+ /// included.
40
+ final List <String >? columnFilter;
41
+
42
+ /// Whether to only include old values when they were changed by an update,
43
+ /// instead of always including all old values.
44
+ final bool onlyWhenChanged;
45
+
46
+ const TrackPreviousValuesOptions (
47
+ {this .columnFilter, this .onlyWhenChanged = false });
48
+ }
49
+
29
50
/// A single table in the schema.
30
51
class Table {
52
+ static const _maxNumberOfColumns = 1999 ;
53
+
31
54
/// The synced table name, matching sync rules.
32
55
final String name;
33
56
@@ -37,20 +60,34 @@ class Table {
37
60
/// List of indexes.
38
61
final List <Index > indexes;
39
62
40
- /// Whether the table only exists only.
63
+ /// Whether to add a hidden `_metadata` column that will be enabled for
64
+ /// updates to attach custom information about writes that will be reported
65
+ /// through [CrudEntry.metadata] .
66
+ final bool trackMetadata;
67
+
68
+ /// Whether to track old values of columns for [CrudEntry.previousValues] .
69
+ ///
70
+ /// See [TrackPreviousValuesOptions] for details.
71
+ final TrackPreviousValuesOptions ? trackPreviousValues;
72
+
73
+ /// Whether the table only exists locally.
41
74
final bool localOnly;
42
75
43
76
/// Whether this is an insert-only table.
44
77
final bool insertOnly;
45
78
79
+ /// Whether an `UPDATE` statement that doesn't change any values should be
80
+ /// ignored when creating CRUD entries.
81
+ final bool ignoreEmptyUpdates;
82
+
46
83
/// Override the name for the view
47
84
final String ? _viewNameOverride;
48
85
49
86
/// powersync-sqlite-core limits the number of columns
50
87
/// per table to 1999, due to internal SQLite limits.
51
88
///
52
89
/// In earlier versions this was limited to 63.
53
- final int maxNumberOfColumns = 1999 ;
90
+ final int maxNumberOfColumns = _maxNumberOfColumns ;
54
91
55
92
/// Internal use only.
56
93
///
@@ -66,9 +103,16 @@ class Table {
66
103
/// Create a synced table.
67
104
///
68
105
/// Local changes are recorded, and remote changes are synced to the local table.
69
- const Table (this .name, this .columns,
70
- {this .indexes = const [], String ? viewName, this .localOnly = false })
71
- : insertOnly = false ,
106
+ const Table (
107
+ this .name,
108
+ this .columns, {
109
+ this .indexes = const [],
110
+ String ? viewName,
111
+ this .localOnly = false ,
112
+ this .ignoreEmptyUpdates = false ,
113
+ this .trackMetadata = false ,
114
+ this .trackPreviousValues,
115
+ }) : insertOnly = false ,
72
116
_viewNameOverride = viewName;
73
117
74
118
/// Create a table that only exists locally.
@@ -78,6 +122,9 @@ class Table {
78
122
{this .indexes = const [], String ? viewName})
79
123
: localOnly = true ,
80
124
insertOnly = false ,
125
+ trackMetadata = false ,
126
+ trackPreviousValues = null ,
127
+ ignoreEmptyUpdates = false ,
81
128
_viewNameOverride = viewName;
82
129
83
130
/// Create a table that only supports inserts.
@@ -88,8 +135,14 @@ class Table {
88
135
///
89
136
/// SELECT queries on the table will always return 0 rows.
90
137
///
91
- const Table .insertOnly (this .name, this .columns, {String ? viewName})
92
- : localOnly = false ,
138
+ const Table .insertOnly (
139
+ this .name,
140
+ this .columns, {
141
+ String ? viewName,
142
+ this .ignoreEmptyUpdates = false ,
143
+ this .trackMetadata = false ,
144
+ this .trackPreviousValues,
145
+ }) : localOnly = false ,
93
146
insertOnly = true ,
94
147
indexes = const [],
95
148
_viewNameOverride = viewName;
@@ -106,9 +159,9 @@ class Table {
106
159
107
160
/// Check that there are no issues in the table definition.
108
161
void validate () {
109
- if (columns.length > maxNumberOfColumns ) {
162
+ if (columns.length > _maxNumberOfColumns ) {
110
163
throw AssertionError (
111
- "Table $name has more than $maxNumberOfColumns columns, which is not supported" );
164
+ "Table $name has more than $_maxNumberOfColumns columns, which is not supported" );
112
165
}
113
166
114
167
if (invalidSqliteCharacters.hasMatch (name)) {
@@ -121,6 +174,14 @@ class Table {
121
174
"Invalid characters in view name: $_viewNameOverride " );
122
175
}
123
176
177
+ if (trackMetadata && localOnly) {
178
+ throw AssertionError ("Local-only tables can't track metadata" );
179
+ }
180
+
181
+ if (trackPreviousValues != null && localOnly) {
182
+ throw AssertionError ("Local-only tables can't track old values" );
183
+ }
184
+
124
185
Set <String > columnNames = {"id" };
125
186
for (var column in columns) {
126
187
if (column.name == 'id' ) {
@@ -168,7 +229,13 @@ class Table {
168
229
'local_only' : localOnly,
169
230
'insert_only' : insertOnly,
170
231
'columns' : columns,
171
- 'indexes' : indexes.map ((e) => e.toJson (this )).toList (growable: false )
232
+ 'indexes' : indexes.map ((e) => e.toJson (this )).toList (growable: false ),
233
+ 'ignore_empty_update' : ignoreEmptyUpdates,
234
+ 'include_metadata' : trackMetadata,
235
+ if (trackPreviousValues case final trackPreviousValues? ) ...{
236
+ 'include_old' : trackPreviousValues.columnFilter ?? true ,
237
+ 'include_old_only_when_changed' : trackPreviousValues.onlyWhenChanged,
238
+ },
172
239
};
173
240
}
174
241
0 commit comments