/
update.dart
161 lines (136 loc) · 5.75 KB
/
update.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
part of '../query_builder.dart';
/// Represents an `UPDATE` statement in sql.
class UpdateStatement<T extends Table, D> extends Query<T, D>
with SingleTableQueryMixin<T, D> {
/// Used internally by drift, construct an update statement
UpdateStatement(super.database, TableInfo<T, D> super.table);
late Map<String, Expression> _updatedFields;
@override
void writeStartPart(GenerationContext ctx) {
// TODO support the OR (ROLLBACK / ABORT / REPLACE / FAIL / IGNORE...) thing
ctx.buffer.write('UPDATE ${table.tableWithAlias} SET ');
var first = true;
_updatedFields.forEach((columnName, variable) {
if (!first) {
ctx.buffer.write(', ');
} else {
first = false;
}
ctx.buffer
..write(ctx.identifier(columnName))
..write(' = ');
variable.writeInto(ctx);
});
}
Future<int> _performQuery() async {
final ctx = constructQuery();
final rows = await database.withCurrentExecutor((e) async {
return await e.runUpdate(ctx.sql, ctx.boundVariables);
});
if (rows > 0) {
database.notifyUpdates(
{TableUpdate.onTable(_sourceTable, kind: UpdateKind.update)});
}
return rows;
}
/// Writes all non-null fields from [entity] into the columns of all rows
/// that match the [where] clause. Warning: That also means that, when you're
/// not setting a where clause explicitly, this method will update all rows in
/// the [table].
///
/// The fields that are null on the [entity] object will not be changed by
/// this operation, they will be ignored.
///
/// When [dontExecute] is true (defaults to false), the query will __NOT__ be
/// run, but all the validations are still in place. This is mainly used
/// internally by drift.
///
/// Returns the amount of rows that have been affected by this operation.
///
/// See also: [replace], which does not require [where] statements and
/// supports setting fields back to null.
Future<int> write(Insertable<D> entity, {bool dontExecute = false}) async {
_sourceTable.validateIntegrity(entity).throwIfInvalid(entity);
_updatedFields = entity.toColumns(true);
if (_updatedFields.isEmpty) {
// nothing to update, we're done
return Future.value(0);
}
if (dontExecute) return -1;
return await _performQuery();
}
/// Applies the updates from [entity] to all rows matching the applied `where`
/// clause and returns affected rows _after the update_.
///
/// For more details on writing entries, see [write].
/// Note that this requires sqlite 3.35 or later.
Future<List<D>> writeReturning(Insertable<D> entity) async {
writeReturningClause = true;
await write(entity, dontExecute: true);
final ctx = constructQuery();
final rows = await database.withCurrentExecutor((e) {
return e.runSelect(ctx.sql, ctx.boundVariables);
});
if (rows.isNotEmpty) {
database.notifyUpdates(
{TableUpdate.onTable(_sourceTable, kind: UpdateKind.update)});
}
return rows.mapAsyncAndAwait(table.map);
}
/// Replaces the old version of [entity] that is stored in the database with
/// the fields of the [entity] provided here. This implicitly applies a
/// [where] clause to rows with the same primary key as [entity], so that only
/// the row representing outdated data will be replaced.
///
/// If [entity] has absent values (set to null on the [DataClass] or
/// explicitly to absent on the [UpdateCompanion]), and a default value for
/// the field exists, that default value will be used. Otherwise, the field
/// will be reset to null. This behavior is different to [write], which simply
/// ignores such fields without changing them in the database.
///
/// When [dontExecute] is true (defaults to false), the query will __NOT__ be
/// run, but all the validations are still in place. This is mainly used
/// internally by drift.
///
/// Returns true if a row was affected by this operation.
///
/// See also:
/// - [write], which doesn't apply a [where] statement itself and ignores
/// null values in the entity.
/// - [InsertStatement.insert] with the `orReplace` parameter, which behaves
/// similar to this method but creates a new row if none exists.
Future<bool> replace(Insertable<D> entity, {bool dontExecute = false}) async {
// We don't turn nulls to absent values here (as opposed to a regular
// update, where only non-null fields will be written).
final columns = entity.toColumns(false);
_sourceTable
.validateIntegrity(entity, isInserting: true)
.throwIfInvalid(entity);
assert(
whereExpr == null,
'When using replace on an update statement, you may not use where(...)'
'as well. The where clause will be determined automatically');
whereSamePrimaryKey(entity);
// copying to work around type issues - Map<String, Variable> extends
// Map<String, Expression> but crashes when adding anything that is not
// a Variable.
_updatedFields = columns is Map<String, Variable>
? Map<String, Expression>.of(columns)
: columns;
final primaryKeys = _sourceTable.$primaryKey.map((c) => c.$name);
// entityToSql doesn't include absent values, so we might have to apply the
// default value here
for (final column in table.$columns) {
// if a default value exists and no value is set, apply the default
if (column.defaultValue != null &&
!_updatedFields.containsKey(column.$name)) {
_updatedFields[column.$name] = column.defaultValue!;
}
}
// Don't update the primary key
_updatedFields.removeWhere((key, _) => primaryKeys.contains(key));
if (dontExecute) return false;
final updatedRows = await _performQuery();
return updatedRows != 0;
}
}