/
data_class.dart
267 lines (227 loc) · 8.75 KB
/
data_class.dart
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:meta/meta.dart';
/// Common interface for objects which can be inserted or updated into a
/// database.
/// [D] is the associated data class.
@optionalTypeArgs
abstract class Insertable<D> {
/// Constant constructor so the classes that extend this can be const.
const Insertable();
/// Converts this object into a map of column names to expressions to insert
/// or update.
///
/// Note that the keys in the map are the raw column names, they're not
/// escaped.
///
/// The [nullToAbsent] can be used on [DataClass]es to control whether null
/// fields should be set to a null constant in sql or absent from the map.
/// Other implementations ignore that [nullToAbsent], it mainly exists for
/// legacy reasons.
Map<String, Expression> toColumns(bool nullToAbsent);
}
/// A common supertype for all data classes generated by drift. Data classes are
/// immutable structures that represent a single row in a database table.
abstract class DataClass {
/// Constant constructor so that generated data classes can be constant.
const DataClass();
/// Converts this object into a representation that can be encoded with
/// [json]. The [serializer] can be used to configure how individual values
/// will be encoded. By default, [DriftRuntimeOptions.defaultSerializer] will
/// be used. See [ValueSerializer.defaults] for details.
Map<String, dynamic> toJson({ValueSerializer? serializer});
/// Converts this object into a json representation. The [serializer] can be
/// used to configure how individual values will be encoded. By default,
/// [DriftRuntimeOptions.defaultSerializer] will be used. See
/// [ValueSerializer.defaults] for details.
String toJsonString({ValueSerializer? serializer}) {
return json.encode(toJson(serializer: serializer));
}
/// Used internally be generated code
@protected
static dynamic parseJson(String jsonString) {
return json.decode(jsonString);
}
}
/// An update companion for a [DataClass] which is used to write data into a
/// database using [InsertStatement.insert] or [UpdateStatement.write].
///
/// [D] is the associated data class for this companion.
///
/// See also:
/// - the explanation in the changelog for 1.5
/// - https://github.com/simolus3/drift/issues/25
abstract class UpdateCompanion<D> implements Insertable<D> {
/// Constant constructor so that generated companion classes can be constant.
const UpdateCompanion();
static const _mapEquality = MapEquality<dynamic, dynamic>();
@override
int get hashCode {
return _mapEquality.hash(toColumns(false));
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! UpdateCompanion<D>) return false;
return _mapEquality.equals(other.toColumns(false), toColumns(false));
}
}
/// An [Insertable] implementation based on raw column expressions.
///
/// Mostly used in generated code.
class RawValuesInsertable<D> implements Insertable<D> {
/// A map from column names to a value that should be inserted or updated.
///
/// See also:
/// - [toColumns], which returns [data] in a [RawValuesInsertable]
final Map<String, Expression> data;
/// Creates a [RawValuesInsertable] based on the [data] to insert or update.
const RawValuesInsertable(this.data);
@override
Map<String, Expression> toColumns(bool nullToAbsent) => data;
@override
String toString() {
return 'RawValuesInsertable($data)';
}
}
/// A wrapper around arbitrary data [T] to indicate presence or absence
/// explicitly.
///
/// [Value]s are commonly used in companions to distringuish between `null` and
/// absent values.
/// For instance, consider a table with a nullable column with a non-nullable
/// default value:
///
/// ```sql
/// CREATE TABLE orders (
/// priority INT DEFAULT 1 -- may be null if there's no assigned priority
/// );
///
/// For inserts in Dart, there are three different scenarios for the `priority`
/// column:
///
/// - It may be set to `null`, overriding the default value
/// - It may be absent, meaning that the default value should be used
/// - It may be set to an `int` to override the default value
/// ```
///
/// As you can see, a simple `int?` does not provide enough information to
/// distinguish between the three cases. A `null` value could mean that the
/// column is absent, or that it should explicitly be set to `null`.
/// For this reason, drift introduces the [Value] wrapper to make the
/// distinction explicit.
class Value<T> {
/// Whether this [Value] wrapper contains a present [value] that should be
/// inserted or updated.
final bool present;
final T? _value;
/// If this value is [present], contains the value to update or insert.
T get value => _value as T;
/// Create a (present) value by wrapping the [value] provided.
const Value(T value)
: _value = value,
present = true;
/// Create an absent value that will not be written into the database, the
/// default value or null will be used instead.
const Value.absent()
: _value = null,
present = false;
/// Create a value that is absent if [value] is `null` and [present] if it's
/// not.
///
/// The functionality is equiavalent to the following:
/// `x != null ? Value(x) : Value.absent()`.
///
/// This constructor should only be used when [T] is not nullable. If [T] were
/// nullable, there wouldn't be a clear interpretation for a `null` [value].
/// See the overall documentation on [Value] for details.
@Deprecated('Use Value.absentIfNull instead')
const Value.ofNullable(T? value)
: assert(
value != null || null is! T,
"Value.ofNullable(null) can't be used for a nullable T, since the "
'null value could be both absent and present.',
),
_value = value,
present = value != null;
/// Create a value that is absent if [value] is `null` and [present] if it's
/// not.
///
/// The functionality is equiavalent to the following:
/// `x != null ? Value(x) : Value.absent()`.
const Value.absentIfNull(T? value)
: _value = value,
present = value != null;
@override
String toString() => present ? 'Value($value)' : 'Value.absent()';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Value && present == other.present && _value == other._value;
@override
int get hashCode => present.hashCode ^ _value.hashCode;
}
/// Serializer responsible for mapping atomic types from and to json.
abstract class ValueSerializer {
/// Constant super-constructor to allow constant child classes.
const ValueSerializer();
/// The builtin default serializer.
///
/// This serializer won't transform numbers or strings. Date times will be
/// encoded as a unix-timestamp.
///
/// To override the default serializer drift uses, you can change the
/// [DriftRuntimeOptions.defaultSerializer] field.
///
/// The [serializeDateTimeValuesAsString] option (which defaults to `false`)
/// describes whether [DateTime] values should be serialized to a unix
/// timestamp ([DateTime.millisecondsSinceEpoch]) or a string
/// ([DateTime.toIso8601String]).
/// In either case, date time values can be _deserialized_ from both formats.
const factory ValueSerializer.defaults(
{bool serializeDateTimeValuesAsString}) = _DefaultValueSerializer;
/// Converts the [value] to something that can be passed to
/// [JsonCodec.encode].
dynamic toJson<T>(T value);
/// Inverse of [toJson]: Converts a value obtained from [JsonCodec.decode]
/// into a value that can be hold by data classes.
T fromJson<T>(dynamic json);
}
class _DefaultValueSerializer extends ValueSerializer {
final bool serializeDateTimeValuesAsString;
const _DefaultValueSerializer({this.serializeDateTimeValuesAsString = false});
@override
T fromJson<T>(dynamic json) {
if (json == null) {
return null as T;
}
final typeList = <T>[];
if (typeList is List<DateTime?>) {
if (json is int) {
return DateTime.fromMillisecondsSinceEpoch(json) as T;
} else {
return DateTime.parse(json.toString()) as T;
}
}
if (typeList is List<double?> && json is int) {
return json.toDouble() as T;
}
// blobs are encoded as a regular json array, so we manually convert that to
// a Uint8List
if (typeList is List<Uint8List?> && json is! Uint8List) {
final asList = (json as List).cast<int>();
return Uint8List.fromList(asList) as T;
}
return json as T;
}
@override
dynamic toJson<T>(T value) {
if (value is DateTime) {
return serializeDateTimeValuesAsString
? value.toIso8601String()
: value.millisecondsSinceEpoch;
}
return value;
}
}