1+ import 'crud.dart' ;
12import 'schema_logic.dart' ;
23
34/// The schema used by the database.
@@ -26,8 +27,27 @@ class Schema {
2627 }
2728}
2829
30+ /// Options to include old values in [CrudEntry] for update statements.
31+ ///
32+ /// This options are enabled by passing it to a non-local [Table] constructor.
33+ final class IncludeOldOptions {
34+ /// A filter of column names for which updates should be tracked.
35+ ///
36+ /// When set to a non-null value, olumns not included in this list will not
37+ /// appear in [CrudEntry.oldData] . By default, all columns are included.
38+ final List <String >? columnFilter;
39+
40+ /// Whether to only include old values when they were changed by an update,
41+ /// instead of always including all old values.
42+ final bool onlyWhenChanged;
43+
44+ const IncludeOldOptions ({this .columnFilter, this .onlyWhenChanged = false });
45+ }
46+
2947/// A single table in the schema.
3048class Table {
49+ static const _maxNumberOfColumns = 1999 ;
50+
3151 /// The synced table name, matching sync rules.
3252 final String name;
3353
@@ -37,20 +57,34 @@ class Table {
3757 /// List of indexes.
3858 final List <Index > indexes;
3959
40- /// Whether the table only exists only.
60+ /// Whether to add a hidden `_metadata` column that will be enabled for
61+ /// updates to attach custom information about writes that will be reported
62+ /// through [CrudEntry.metadata] .
63+ final bool includeMetadata;
64+
65+ /// Whether to track old values of columns for [CrudEntry.oldData] .
66+ ///
67+ /// See [IncludeOldOptions] for details.
68+ final IncludeOldOptions ? includeOld;
69+
70+ /// Whether the table only exists locally.
4171 final bool localOnly;
4272
4373 /// Whether this is an insert-only table.
4474 final bool insertOnly;
4575
76+ /// Whether an `UPDATE` statement that doesn't change any values should be
77+ /// ignored when creating CRUD entries.
78+ final bool ignoreEmptyUpdate;
79+
4680 /// Override the name for the view
4781 final String ? _viewNameOverride;
4882
4983 /// powersync-sqlite-core limits the number of columns
5084 /// per table to 1999, due to internal SQLite limits.
5185 ///
5286 /// In earlier versions this was limited to 63.
53- final int maxNumberOfColumns = 1999 ;
87+ final int maxNumberOfColumns = _maxNumberOfColumns ;
5488
5589 /// Internal use only.
5690 ///
@@ -66,9 +100,16 @@ class Table {
66100 /// Create a synced table.
67101 ///
68102 /// 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 ,
103+ const Table (
104+ this .name,
105+ this .columns, {
106+ this .indexes = const [],
107+ String ? viewName,
108+ this .localOnly = false ,
109+ this .ignoreEmptyUpdate = false ,
110+ this .includeMetadata = false ,
111+ this .includeOld,
112+ }) : insertOnly = false ,
72113 _viewNameOverride = viewName;
73114
74115 /// Create a table that only exists locally.
@@ -78,6 +119,9 @@ class Table {
78119 {this .indexes = const [], String ? viewName})
79120 : localOnly = true ,
80121 insertOnly = false ,
122+ includeMetadata = false ,
123+ includeOld = null ,
124+ ignoreEmptyUpdate = false ,
81125 _viewNameOverride = viewName;
82126
83127 /// Create a table that only supports inserts.
@@ -88,8 +132,14 @@ class Table {
88132 ///
89133 /// SELECT queries on the table will always return 0 rows.
90134 ///
91- const Table .insertOnly (this .name, this .columns, {String ? viewName})
92- : localOnly = false ,
135+ const Table .insertOnly (
136+ this .name,
137+ this .columns, {
138+ String ? viewName,
139+ this .ignoreEmptyUpdate = false ,
140+ this .includeMetadata = false ,
141+ this .includeOld,
142+ }) : localOnly = false ,
93143 insertOnly = true ,
94144 indexes = const [],
95145 _viewNameOverride = viewName;
@@ -106,9 +156,9 @@ class Table {
106156
107157 /// Check that there are no issues in the table definition.
108158 void validate () {
109- if (columns.length > maxNumberOfColumns ) {
159+ if (columns.length > _maxNumberOfColumns ) {
110160 throw AssertionError (
111- "Table $name has more than $maxNumberOfColumns columns, which is not supported" );
161+ "Table $name has more than $_maxNumberOfColumns columns, which is not supported" );
112162 }
113163
114164 if (invalidSqliteCharacters.hasMatch (name)) {
@@ -121,6 +171,14 @@ class Table {
121171 "Invalid characters in view name: $_viewNameOverride " );
122172 }
123173
174+ if (includeMetadata && localOnly) {
175+ throw AssertionError ("Local-only tables can't track metadata" );
176+ }
177+
178+ if (includeOld != null && localOnly) {
179+ throw AssertionError ("Local-only tables can't track old values" );
180+ }
181+
124182 Set <String > columnNames = {"id" };
125183 for (var column in columns) {
126184 if (column.name == 'id' ) {
@@ -168,7 +226,13 @@ class Table {
168226 'local_only' : localOnly,
169227 'insert_only' : insertOnly,
170228 'columns' : columns,
171- 'indexes' : indexes.map ((e) => e.toJson (this )).toList (growable: false )
229+ 'indexes' : indexes.map ((e) => e.toJson (this )).toList (growable: false ),
230+ 'ignore_empty_update' : ignoreEmptyUpdate,
231+ 'include_metadata' : includeMetadata,
232+ if (includeOld case final includeOld? ) ...{
233+ 'include_old' : includeOld.columnFilter ?? true ,
234+ 'include_old_only_when_changed' : includeOld.onlyWhenChanged,
235+ },
172236 };
173237}
174238
0 commit comments