-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathTableOptions.swift
More file actions
167 lines (150 loc) · 6.22 KB
/
TableOptions.swift
File metadata and controls
167 lines (150 loc) · 6.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
public protocol TableOptionsProtocol: Sendable {
///
/// Whether the table only exists locally.
///
var localOnly: Bool { get }
///
/// Whether this is an insert-only table.
///
var insertOnly: Bool { get }
/// Whether to add a hidden `_metadata` column that will ne abled for updates to
/// attach custom information about writes.
///
/// When the `_metadata` column is written to for inserts or updates, its value will not be
/// part of ``CrudEntry/opData``. Instead, it is reported as ``CrudEntry/metadata``,
/// allowing ``PowerSyncBackendConnector``s to handle these updates specially.
var trackMetadata: Bool { get }
/// When set to a non-`nil` value, track old values of columns for ``CrudEntry/previousValues``.
///
/// See ``TrackPreviousValuesOptions`` for details
var trackPreviousValues: TrackPreviousValuesOptions? { get }
/// Whether an `UPDATE` statement that doesn't change any values should be ignored entirely when
/// creating CRUD entries.
///
/// This is disabled by default, meaning that an `UPDATE` on a row that doesn't change values would
/// create a ``CrudEntry`` with an empty ``CrudEntry/opData`` and ``UpdateType/patch``.
var ignoreEmptyUpdates: Bool { get }
}
public struct TableOptions: TableOptionsProtocol {
///
/// Whether the table only exists locally.
///
public let localOnly: Bool
///
/// Whether this is an insert-only table.
///
public let insertOnly: Bool
/// Whether to add a hidden `_metadata` column that will ne abled for updates to
/// attach custom information about writes.
///
/// When the `_metadata` column is written to for inserts or updates, its value will not be
/// part of ``CrudEntry/opData``. Instead, it is reported as ``CrudEntry/metadata``,
/// allowing ``PowerSyncBackendConnector``s to handle these updates specially.
public let trackMetadata: Bool
/// When set to a non-`nil` value, track old values of columns for ``CrudEntry/previousValues``.
///
/// See ``TrackPreviousValuesOptions`` for details
public let trackPreviousValues: TrackPreviousValuesOptions?
/// Whether an `UPDATE` statement that doesn't change any values should be ignored entirely when
/// creating CRUD entries.
///
/// This is disabled by default, meaning that an `UPDATE` on a row that doesn't change values would
/// create a ``CrudEntry`` with an empty ``CrudEntry/opData`` and ``UpdateType/patch``.
public let ignoreEmptyUpdates: Bool
public init(
localOnly: Bool = false,
insertOnly: Bool = false,
trackMetadata: Bool = false,
trackPreviousValues: TrackPreviousValuesOptions? = nil,
ignoreEmptyUpdates: Bool = false
) {
self.localOnly = localOnly
self.insertOnly = insertOnly
self.trackMetadata = trackMetadata
self.trackPreviousValues = trackPreviousValues
self.ignoreEmptyUpdates = ignoreEmptyUpdates
}
internal func validate(tableName: String) throws(TableError) {
if localOnly {
if trackPreviousValues != nil {
throw TableError.trackPreviousForLocalTable(tableName: tableName)
}
if trackMetadata {
throw TableError.metadataForLocalTable(tableName: tableName)
}
}
}
internal func serializeTo<T: CodingKey>(_ container: KeyedEncodingContainer<TableOptionsCodingKeys<T>>) throws {
var container = container
try container.encode(localOnly, forKey: .localOnly)
try container.encode(insertOnly, forKey: .insertOnly)
try container.encode(trackMetadata, forKey: .includeMetadata)
try container.encode(ignoreEmptyUpdates, forKey: .ignoreEmptyUpdate)
try trackPreviousValues?.serializeTo(container)
}
}
/// Options to include old values in ``CrudEntry/previousValues`` for update statements.
///
/// These options are enabled by passing them to a non-local ``Table`` constructor.
public struct TrackPreviousValuesOptions: Sendable {
/// A filter of column names for which updates should be tracked.
///
/// When set to a non-`nil` value, columns not included in this list will not appear in
/// ``CrudEntry/previousValues``. By default, all columns are included.
public let columnFilter: [String]?
/// Whether to only include old values when they were changed by an update, instead of always including
/// all old values.
public let onlyWhenChanged: Bool
public init(columnFilter: [String]? = nil, onlyWhenChanged: Bool = false) {
self.columnFilter = columnFilter
self.onlyWhenChanged = onlyWhenChanged
}
internal func serializeTo<T: CodingKey>(_ container: KeyedEncodingContainer<TableOptionsCodingKeys<T>>) throws {
var container = container
if let columnFilter {
try container.encode(columnFilter, forKey: .diffIncludeOld)
} else {
try container.encode(true, forKey: .diffIncludeOld)
}
try container.encode(onlyWhenChanged, forKey: .includeOldOnlyWhenChanged)
}
}
/// Coding keys for table options (which are always embedded into another outer object.
internal enum TableOptionsCodingKeys<T: CodingKey>: CodingKey {
case outer(T)
case diffIncludeOld
case localOnly
case insertOnly
case includeMetadata
case includeOldOnlyWhenChanged
case ignoreEmptyUpdate
// We don't use these for decoding, so we can return nil here.
init?(stringValue: String) {
return nil
}
init?(intValue: Int) {
return nil
}
var stringValue: String {
switch self {
case .outer(let field):
return field.stringValue
case .diffIncludeOld:
return "include_old"
case .localOnly:
return "local_only"
case .insertOnly:
return "insert_only"
case .includeMetadata:
return "include_metadata"
case .includeOldOnlyWhenChanged:
return "include_old_only_when_changed"
case .ignoreEmptyUpdate:
return "ignore_empty_update"
}
}
// We'll only encode into string-keyed dictionaries (JSON objects).
var intValue: Int? {
nil
}
}