mirror of https://github.com/AMT-Cheif/drift.git
Implement insertOrReplace method for insert statements
This commit is contained in:
parent
e7cb0cb2e5
commit
e7ece27528
|
@ -1,5 +1,6 @@
|
|||
## 1.2.0
|
||||
- Blob data type
|
||||
- `insertOrReplace` method for insert statements
|
||||
|
||||
## 1.1.0
|
||||
- Transactions
|
||||
|
|
|
@ -18,6 +18,13 @@ class Category {
|
|||
description: stringType.mapFromDatabaseResponse(data['description']),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'description': description,
|
||||
};
|
||||
}
|
||||
|
||||
Category copyWith({int id, String description}) => Category(
|
||||
id: id ?? this.id,
|
||||
description: description ?? this.description,
|
||||
|
@ -99,6 +106,15 @@ class Recipe {
|
|||
category: intType.mapFromDatabaseResponse(data['category']),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'instructions': instructions,
|
||||
'category': category,
|
||||
};
|
||||
}
|
||||
|
||||
Recipe copyWith({int id, String title, String instructions, int category}) =>
|
||||
Recipe(
|
||||
id: id ?? this.id,
|
||||
|
@ -207,6 +223,14 @@ class Ingredient {
|
|||
caloriesPer100g: intType.mapFromDatabaseResponse(data['calories']),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'caloriesPer100g': caloriesPer100g,
|
||||
};
|
||||
}
|
||||
|
||||
Ingredient copyWith({int id, String name, int caloriesPer100g}) => Ingredient(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
|
@ -303,6 +327,14 @@ class IngredientInRecipe {
|
|||
amountInGrams: intType.mapFromDatabaseResponse(data['amount']),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'recipe': recipe,
|
||||
'ingredient': ingredient,
|
||||
'amountInGrams': amountInGrams,
|
||||
};
|
||||
}
|
||||
|
||||
IngredientInRecipe copyWith(
|
||||
{int recipe, int ingredient, int amountInGrams}) =>
|
||||
IngredientInRecipe(
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||
import 'package:meta/meta.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:moor/src/runtime/components/component.dart';
|
||||
import 'update.dart';
|
||||
|
||||
class InsertStatement<DataClass> {
|
||||
@protected
|
||||
|
@ -10,8 +11,17 @@ class InsertStatement<DataClass> {
|
|||
@protected
|
||||
final TableInfo<dynamic, DataClass> table;
|
||||
|
||||
bool _orReplace = false;
|
||||
|
||||
InsertStatement(this.database, this.table);
|
||||
|
||||
/// Inserts a row constructed from the fields in [entity].
|
||||
///
|
||||
/// All fields in the entity that don't have a default value or auto-increment
|
||||
/// must be set and non-null. Otherwise, an [InvalidDataException] will be
|
||||
/// thrown. An insert will also fail if another row with the same primary key
|
||||
/// or unique constraints already exists. If you want to override data in that
|
||||
/// case, use [insertOrReplace] instead.
|
||||
Future<void> insert(DataClass entity) async {
|
||||
if (!table.validateIntegrity(entity, true)) {
|
||||
throw InvalidDataException(
|
||||
|
@ -23,7 +33,9 @@ class InsertStatement<DataClass> {
|
|||
|
||||
final ctx = GenerationContext(database);
|
||||
ctx.buffer
|
||||
..write('INSERT INTO ')
|
||||
..write('INSERT ')
|
||||
..write(_orReplace ? 'OR REPLACE ' : '')
|
||||
..write('INTO ')
|
||||
..write(table.$tableName)
|
||||
..write(' (')
|
||||
..write(map.keys.join(', '))
|
||||
|
@ -50,4 +62,16 @@ class InsertStatement<DataClass> {
|
|||
|
||||
// TODO insert multiple values
|
||||
|
||||
/// Updates the row with the same primary key in the database or creates one
|
||||
/// if it doesn't exist.
|
||||
///
|
||||
/// Behaves similar to [UpdateStatement.replace], meaning that all fields from
|
||||
/// [entity] will be written to override rows with the same primary key, which
|
||||
/// includes setting columns with null values back to null.
|
||||
///
|
||||
/// However, if no such row exists, a new row will be written instead.
|
||||
Future<void> insertOrReplace(DataClass entity) async {
|
||||
_orReplace = true;
|
||||
await insert(entity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,17 +43,17 @@ class UpdateStatement<T, D> extends Query<T, D> {
|
|||
}
|
||||
|
||||
/// Writes all non-null fields from [entity] into the columns of all rows
|
||||
/// that match the set [where] and limit constraints. Warning: That also
|
||||
/// means that, when you're not setting a where or limit expression
|
||||
/// explicitly, this method will update all rows in the specific table.
|
||||
/// 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.
|
||||
/// this operation, they will be ignored.
|
||||
///
|
||||
/// 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 to null.
|
||||
/// supports setting fields back to null.
|
||||
Future<int> write(D entity) async {
|
||||
if (!table.validateIntegrity(entity, false)) {
|
||||
throw InvalidDataException(
|
||||
|
@ -81,6 +81,12 @@ class UpdateStatement<T, D> extends Query<T, D> {
|
|||
/// null fields.
|
||||
///
|
||||
/// 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.insertOrReplace], which behaves similar to this method
|
||||
/// but creates a new row if none exists.
|
||||
Future<bool> replace(D entity) async {
|
||||
// We set isInserting to true here although we're in an update. This is
|
||||
// because all the fields from the entity will be written (as opposed to a
|
||||
|
|
|
@ -26,6 +26,16 @@ class TodoEntry {
|
|||
category: intType.mapFromDatabaseResponse(data['category']),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'content': content,
|
||||
'targetDate': targetDate,
|
||||
'category': category,
|
||||
};
|
||||
}
|
||||
|
||||
TodoEntry copyWith(
|
||||
{int id,
|
||||
String title,
|
||||
|
@ -154,6 +164,13 @@ class Category {
|
|||
description: stringType.mapFromDatabaseResponse(data['`desc`']),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'description': description,
|
||||
};
|
||||
}
|
||||
|
||||
Category copyWith({int id, String description}) => Category(
|
||||
id: id ?? this.id,
|
||||
description: description ?? this.description,
|
||||
|
@ -238,6 +255,15 @@ class User {
|
|||
uint8ListType.mapFromDatabaseResponse(data['profile_picture']),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'isAwesome': isAwesome,
|
||||
'profilePicture': profilePicture,
|
||||
};
|
||||
}
|
||||
|
||||
User copyWith(
|
||||
{int id, String name, bool isAwesome, Uint8List profilePicture}) =>
|
||||
User(
|
||||
|
@ -344,6 +370,13 @@ class SharedTodo {
|
|||
user: intType.mapFromDatabaseResponse(data['user']),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'todo': todo,
|
||||
'user': user,
|
||||
};
|
||||
}
|
||||
|
||||
SharedTodo copyWith({int todo, int user}) => SharedTodo(
|
||||
todo: todo ?? this.todo,
|
||||
user: user ?? this.user,
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import 'package:moor/moor.dart';
|
||||
import 'package:test_api/test_api.dart';
|
||||
|
||||
import 'data/tables/todos.dart';
|
||||
import 'data/utils/mocks.dart';
|
||||
|
||||
void main() {
|
||||
TodoDb db;
|
||||
MockExecutor executor;
|
||||
MockStreamQueries streamQueries;
|
||||
|
||||
setUp(() {
|
||||
executor = MockExecutor();
|
||||
streamQueries = MockStreamQueries();
|
||||
db = TodoDb(executor)..streamQueries = streamQueries;
|
||||
});
|
||||
|
||||
test('generates insert statements', () async {
|
||||
await db.into(db.todosTable).insert(TodoEntry(
|
||||
content: 'Implement insert statements',
|
||||
));
|
||||
|
||||
verify(executor.runInsert('INSERT INTO todos (content) VALUES (?)',
|
||||
['Implement insert statements']));
|
||||
});
|
||||
|
||||
test('generates insert or replace statements', () async {
|
||||
await db.into(db.todosTable).insertOrReplace(TodoEntry(
|
||||
id: 113,
|
||||
content: 'Done',
|
||||
));
|
||||
|
||||
verify(executor.runInsert(
|
||||
'INSERT OR REPLACE INTO todos (id, content) VALUES (?, ?)',
|
||||
[113, 'Done']));
|
||||
});
|
||||
|
||||
test('notifies stream queries on inserts', () async {
|
||||
await db.into(db.users).insert(User(
|
||||
name: 'User McUserface',
|
||||
isAwesome: true,
|
||||
profilePicture: Uint8List(0),
|
||||
));
|
||||
|
||||
verify(streamQueries.handleTableUpdates({'users'}));
|
||||
});
|
||||
|
||||
test('enforces data integrety', () {
|
||||
expect(
|
||||
db.into(db.todosTable).insert(
|
||||
TodoEntry(
|
||||
content: null, // not declared as nullable in table definition
|
||||
),
|
||||
),
|
||||
throwsA(const TypeMatcher<InvalidDataException>()),
|
||||
);
|
||||
});
|
||||
}
|
|
@ -55,7 +55,7 @@ void main() {
|
|||
// The length of a title must be between 4 and 16 chars
|
||||
|
||||
expect(() async {
|
||||
await db.into(db.todosTable).insert(TodoEntry(title: 'lol'));
|
||||
await db.update(db.todosTable).write(TodoEntry(title: 'lol'));
|
||||
}, throwsA(const TypeMatcher<InvalidDataException>()));
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue