mirror of https://github.com/AMT-Cheif/drift.git
Support excluded in upserts on the Dart API
This commit is contained in:
parent
9fffebbae6
commit
90e179643c
|
@ -1,3 +1,7 @@
|
||||||
|
## 4.6.0-dev
|
||||||
|
|
||||||
|
- Add `DoUpdate.withExcluded` to refer to the excluded row in an upsert clause.
|
||||||
|
|
||||||
## 4.5.0
|
## 4.5.0
|
||||||
|
|
||||||
- Add `moorRuntimeOptions.debugPrint` option to control which `print` method is used by moor.
|
- Add `moorRuntimeOptions.debugPrint` option to control which `print` method is used by moor.
|
||||||
|
|
|
@ -5,6 +5,15 @@ part of 'dsl.dart';
|
||||||
abstract class Column<T> extends Expression<T> {
|
abstract class Column<T> extends Expression<T> {
|
||||||
@override
|
@override
|
||||||
final Precedence precedence = Precedence.primary;
|
final Precedence precedence = Precedence.primary;
|
||||||
|
|
||||||
|
/// The (unescaped) name of this column.
|
||||||
|
///
|
||||||
|
/// Use [escapedName] to access a name that's escaped in double quotes if
|
||||||
|
/// needed.
|
||||||
|
String get name;
|
||||||
|
|
||||||
|
/// [name], but escaped if it's an sql keyword.
|
||||||
|
String get escapedName => escapeIfNeeded(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A column that stores int values.
|
/// A column that stores int values.
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:typed_data' show Uint8List;
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:meta/meta_meta.dart';
|
import 'package:meta/meta_meta.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
|
import 'package:moor/sqlite_keywords.dart';
|
||||||
|
|
||||||
part 'columns.dart';
|
part 'columns.dart';
|
||||||
part 'database.dart';
|
part 'database.dart';
|
||||||
|
|
|
@ -7,10 +7,7 @@ const VerificationResult _invalidNull = VerificationResult.failure(
|
||||||
/// Implementation for a [Column] declared on a table.
|
/// Implementation for a [Column] declared on a table.
|
||||||
class GeneratedColumn<T> extends Column<T> {
|
class GeneratedColumn<T> extends Column<T> {
|
||||||
/// The sql name of this column.
|
/// The sql name of this column.
|
||||||
final String $name;
|
final String $name; // todo: Remove, replace with `name`
|
||||||
|
|
||||||
/// [$name], but escaped if it's an sql keyword.
|
|
||||||
String get escapedName => escapeIfNeeded($name);
|
|
||||||
|
|
||||||
/// The name of the table that contains this column
|
/// The name of the table that contains this column
|
||||||
final String tableName;
|
final String tableName;
|
||||||
|
@ -55,6 +52,9 @@ class GeneratedColumn<T> extends Column<T> {
|
||||||
bool get hasAutoIncrement =>
|
bool get hasAutoIncrement =>
|
||||||
_defaultConstraints?.contains('AUTOINCREMENT') == true;
|
_defaultConstraints?.contains('AUTOINCREMENT') == true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => $name;
|
||||||
|
|
||||||
/// Used by generated code.
|
/// Used by generated code.
|
||||||
GeneratedColumn(
|
GeneratedColumn(
|
||||||
this.$name,
|
this.$name,
|
||||||
|
|
|
@ -149,7 +149,10 @@ class InsertStatement<T extends Table, D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeDoUpdate(DoUpdate<T, D> onConflict) {
|
void writeDoUpdate(DoUpdate<T, D> onConflict) {
|
||||||
final upsertInsertable = onConflict._createInsertable(table.asDslTable);
|
if (onConflict._usesExcludedTable) {
|
||||||
|
ctx.hasMultipleTables = true;
|
||||||
|
}
|
||||||
|
final upsertInsertable = onConflict._createInsertable(table);
|
||||||
|
|
||||||
if (!identical(entry, upsertInsertable)) {
|
if (!identical(entry, upsertInsertable)) {
|
||||||
// We run a ON CONFLICT DO UPDATE, so make sure upsertInsertable is
|
// We run a ON CONFLICT DO UPDATE, so make sure upsertInsertable is
|
||||||
|
@ -176,7 +179,9 @@ class InsertStatement<T extends Table, D> {
|
||||||
for (final target in conflictTarget) {
|
for (final target in conflictTarget) {
|
||||||
if (!first) ctx.buffer.write(', ');
|
if (!first) ctx.buffer.write(', ');
|
||||||
|
|
||||||
target.writeInto(ctx);
|
// Writing the escaped name directly because it should not have a table
|
||||||
|
// name in front of it.
|
||||||
|
ctx.buffer.write(target.escapedName);
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +300,8 @@ abstract class UpsertClause<T extends Table, D> {}
|
||||||
///
|
///
|
||||||
/// For an example, see [InsertStatement.insert].
|
/// For an example, see [InsertStatement.insert].
|
||||||
class DoUpdate<T extends Table, D> extends UpsertClause<T, D> {
|
class DoUpdate<T extends Table, D> extends UpsertClause<T, D> {
|
||||||
final Insertable<D> Function(T old) _creator;
|
final Insertable<D> Function(T old, T excluded) _creator;
|
||||||
|
final bool _usesExcludedTable;
|
||||||
|
|
||||||
/// An optional list of columns to serve as an "conflict target", which
|
/// An optional list of columns to serve as an "conflict target", which
|
||||||
/// specifies the uniqueness constraint that will trigger the upsert.
|
/// specifies the uniqueness constraint that will trigger the upsert.
|
||||||
|
@ -303,12 +309,32 @@ class DoUpdate<T extends Table, D> extends UpsertClause<T, D> {
|
||||||
/// By default, the primary key of the table will be used.
|
/// By default, the primary key of the table will be used.
|
||||||
final List<Column>? target;
|
final List<Column>? target;
|
||||||
|
|
||||||
|
/// Creates a `DO UPDATE` clause.
|
||||||
|
///
|
||||||
|
/// The [update] function will be used to construct an [Insertable] used to
|
||||||
|
/// update an old row that prevented an insert.
|
||||||
|
/// If you need to refer to both the old row and the row that would have
|
||||||
|
/// been inserted, use [DoUpdate.withExcluded].
|
||||||
|
///
|
||||||
/// For an example, see [InsertStatement.insert].
|
/// For an example, see [InsertStatement.insert].
|
||||||
DoUpdate(Insertable<D> Function(T old) update, {this.target})
|
DoUpdate(Insertable<D> Function(T old) update, {this.target})
|
||||||
: _creator = update;
|
: _creator = ((old, _) => update(old)),
|
||||||
|
_usesExcludedTable = false;
|
||||||
|
|
||||||
Insertable<D> _createInsertable(T table) {
|
/// Creates a `DO UPDATE` clause.
|
||||||
return _creator(table);
|
///
|
||||||
|
/// The [update] function will be used to construct an [Insertable] used to
|
||||||
|
/// update an old row that prevented an insert.
|
||||||
|
/// It can refer to the values from the old row in the first parameter and
|
||||||
|
/// to columns in the row that couldn't be inserted with the `excluded`
|
||||||
|
/// parameter.
|
||||||
|
///
|
||||||
|
/// For an example, see [InsertStatement.insert].
|
||||||
|
DoUpdate.withExcluded(this._creator, {this.target})
|
||||||
|
: _usesExcludedTable = true;
|
||||||
|
|
||||||
|
Insertable<D> _createInsertable(TableInfo<T, D> table) {
|
||||||
|
return _creator(table.asDslTable, table.createAlias('excluded').asDslTable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: moor
|
name: moor
|
||||||
description: Moor is a safe and reactive persistence library for Dart applications
|
description: Moor is a safe and reactive persistence library for Dart applications
|
||||||
version: 4.5.0
|
version: 4.6.0-dev
|
||||||
repository: https://github.com/simolus3/moor
|
repository: https://github.com/simolus3/moor
|
||||||
homepage: https://moor.simonbinder.eu/
|
homepage: https://moor.simonbinder.eu/
|
||||||
issue_tracker: https://github.com/simolus3/moor/issues
|
issue_tracker: https://github.com/simolus3/moor/issues
|
||||||
|
|
|
@ -266,6 +266,24 @@ void main() {
|
||||||
expect(id, 3);
|
expect(id, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can access excluded row in upsert', () async {
|
||||||
|
await db.into(db.todosTable).insert(
|
||||||
|
TodosTableCompanion.insert(content: 'content'),
|
||||||
|
onConflict: DoUpdate.withExcluded(
|
||||||
|
(old, excluded) => TodosTableCompanion.custom(
|
||||||
|
content: old.content + excluded.content,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
verify(executor.runInsert(
|
||||||
|
'INSERT INTO todos (content) VALUES (?) '
|
||||||
|
'ON CONFLICT(id) DO UPDATE '
|
||||||
|
'SET content = todos.content || excluded.content',
|
||||||
|
['content'],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
test('applies implicit type converter', () async {
|
test('applies implicit type converter', () async {
|
||||||
await db.into(db.categories).insert(CategoriesCompanion.insert(
|
await db.into(db.categories).insert(CategoriesCompanion.insert(
|
||||||
description: 'description',
|
description: 'description',
|
||||||
|
|
|
@ -26,6 +26,27 @@ void main() {
|
||||||
expect(row.description, 'changed description');
|
expect(row.description, 'changed description');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('insert with DoUpdate and excluded row', () async {
|
||||||
|
await db.into(db.categories).insert(
|
||||||
|
CategoriesCompanion.insert(description: 'original description'));
|
||||||
|
|
||||||
|
var row = await db.select(db.categories).getSingle();
|
||||||
|
|
||||||
|
await db.into(db.categories).insert(
|
||||||
|
CategoriesCompanion(
|
||||||
|
id: Value(row.id),
|
||||||
|
description: const Value('new description'),
|
||||||
|
),
|
||||||
|
onConflict: DoUpdate.withExcluded(
|
||||||
|
(old, excluded) => CategoriesCompanion.custom(
|
||||||
|
description:
|
||||||
|
old.description + const Constant(' ') + excluded.description),
|
||||||
|
));
|
||||||
|
|
||||||
|
row = await db.select(db.categories).getSingle();
|
||||||
|
expect(row.description, 'original description new description');
|
||||||
|
});
|
||||||
|
|
||||||
test('returning', () async {
|
test('returning', () async {
|
||||||
final entry = await db.into(db.categories).insertReturning(
|
final entry = await db.into(db.categories).insertReturning(
|
||||||
CategoriesCompanion.insert(description: 'Description'));
|
CategoriesCompanion.insert(description: 'Description'));
|
||||||
|
|
Loading…
Reference in New Issue