1+ import 'crud.dart' ;
12import 'schema_logic.dart' ;
23
34/// The schema used by the database.
@@ -26,8 +27,30 @@ class Schema {
2627 }
2728}
2829
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+
2950/// A single table in the schema.
3051class Table {
52+ static const _maxNumberOfColumns = 1999 ;
53+
3154 /// The synced table name, matching sync rules.
3255 final String name;
3356
@@ -37,20 +60,34 @@ class Table {
3760 /// List of indexes.
3861 final List <Index > indexes;
3962
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.
4174 final bool localOnly;
4275
4376 /// Whether this is an insert-only table.
4477 final bool insertOnly;
4578
79+ /// Whether an `UPDATE` statement that doesn't change any values should be
80+ /// ignored when creating CRUD entries.
81+ final bool ignoreEmptyUpdates;
82+
4683 /// Override the name for the view
4784 final String ? _viewNameOverride;
4885
4986 /// powersync-sqlite-core limits the number of columns
5087 /// per table to 1999, due to internal SQLite limits.
5188 ///
5289 /// In earlier versions this was limited to 63.
53- final int maxNumberOfColumns = 1999 ;
90+ final int maxNumberOfColumns = _maxNumberOfColumns ;
5491
5592 /// Internal use only.
5693 ///
@@ -66,9 +103,16 @@ class Table {
66103 /// Create a synced table.
67104 ///
68105 /// 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 ,
72116 _viewNameOverride = viewName;
73117
74118 /// Create a table that only exists locally.
@@ -78,6 +122,9 @@ class Table {
78122 {this .indexes = const [], String ? viewName})
79123 : localOnly = true ,
80124 insertOnly = false ,
125+ trackMetadata = false ,
126+ trackPreviousValues = null ,
127+ ignoreEmptyUpdates = false ,
81128 _viewNameOverride = viewName;
82129
83130 /// Create a table that only supports inserts.
@@ -88,8 +135,14 @@ class Table {
88135 ///
89136 /// SELECT queries on the table will always return 0 rows.
90137 ///
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 ,
93146 insertOnly = true ,
94147 indexes = const [],
95148 _viewNameOverride = viewName;
@@ -106,9 +159,9 @@ class Table {
106159
107160 /// Check that there are no issues in the table definition.
108161 void validate () {
109- if (columns.length > maxNumberOfColumns ) {
162+ if (columns.length > _maxNumberOfColumns ) {
110163 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" );
112165 }
113166
114167 if (invalidSqliteCharacters.hasMatch (name)) {
@@ -121,6 +174,14 @@ class Table {
121174 "Invalid characters in view name: $_viewNameOverride " );
122175 }
123176
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+
124185 Set <String > columnNames = {"id" };
125186 for (var column in columns) {
126187 if (column.name == 'id' ) {
@@ -168,7 +229,13 @@ class Table {
168229 'local_only' : localOnly,
169230 'insert_only' : insertOnly,
170231 '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+ },
172239 };
173240}
174241
0 commit comments