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
|
## 1.2.0
|
||||||
- Blob data type
|
- Blob data type
|
||||||
|
- `insertOrReplace` method for insert statements
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
- Transactions
|
- Transactions
|
||||||
|
|
|
@ -18,6 +18,13 @@ class Category {
|
||||||
description: stringType.mapFromDatabaseResponse(data['description']),
|
description: stringType.mapFromDatabaseResponse(data['description']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'description': description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Category copyWith({int id, String description}) => Category(
|
Category copyWith({int id, String description}) => Category(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
|
@ -99,6 +106,15 @@ class Recipe {
|
||||||
category: intType.mapFromDatabaseResponse(data['category']),
|
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 copyWith({int id, String title, String instructions, int category}) =>
|
||||||
Recipe(
|
Recipe(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
|
@ -207,6 +223,14 @@ class Ingredient {
|
||||||
caloriesPer100g: intType.mapFromDatabaseResponse(data['calories']),
|
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(
|
Ingredient copyWith({int id, String name, int caloriesPer100g}) => Ingredient(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
|
@ -303,6 +327,14 @@ class IngredientInRecipe {
|
||||||
amountInGrams: intType.mapFromDatabaseResponse(data['amount']),
|
amountInGrams: intType.mapFromDatabaseResponse(data['amount']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'recipe': recipe,
|
||||||
|
'ingredient': ingredient,
|
||||||
|
'amountInGrams': amountInGrams,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
IngredientInRecipe copyWith(
|
IngredientInRecipe copyWith(
|
||||||
{int recipe, int ingredient, int amountInGrams}) =>
|
{int recipe, int ingredient, int amountInGrams}) =>
|
||||||
IngredientInRecipe(
|
IngredientInRecipe(
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:moor/src/runtime/components/component.dart';
|
import 'package:moor/src/runtime/components/component.dart';
|
||||||
|
import 'update.dart';
|
||||||
|
|
||||||
class InsertStatement<DataClass> {
|
class InsertStatement<DataClass> {
|
||||||
@protected
|
@protected
|
||||||
|
@ -10,8 +11,17 @@ class InsertStatement<DataClass> {
|
||||||
@protected
|
@protected
|
||||||
final TableInfo<dynamic, DataClass> table;
|
final TableInfo<dynamic, DataClass> table;
|
||||||
|
|
||||||
|
bool _orReplace = false;
|
||||||
|
|
||||||
InsertStatement(this.database, this.table);
|
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 {
|
Future<void> insert(DataClass entity) async {
|
||||||
if (!table.validateIntegrity(entity, true)) {
|
if (!table.validateIntegrity(entity, true)) {
|
||||||
throw InvalidDataException(
|
throw InvalidDataException(
|
||||||
|
@ -23,7 +33,9 @@ class InsertStatement<DataClass> {
|
||||||
|
|
||||||
final ctx = GenerationContext(database);
|
final ctx = GenerationContext(database);
|
||||||
ctx.buffer
|
ctx.buffer
|
||||||
..write('INSERT INTO ')
|
..write('INSERT ')
|
||||||
|
..write(_orReplace ? 'OR REPLACE ' : '')
|
||||||
|
..write('INTO ')
|
||||||
..write(table.$tableName)
|
..write(table.$tableName)
|
||||||
..write(' (')
|
..write(' (')
|
||||||
..write(map.keys.join(', '))
|
..write(map.keys.join(', '))
|
||||||
|
@ -50,4 +62,16 @@ class InsertStatement<DataClass> {
|
||||||
|
|
||||||
// TODO insert multiple values
|
// 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
|
/// Writes all non-null fields from [entity] into the columns of all rows
|
||||||
/// that match the set [where] and limit constraints. Warning: That also
|
/// that match the [where] clause. Warning: That also means that, when you're
|
||||||
/// means that, when you're not setting a where or limit expression
|
/// not setting a where clause explicitly, this method will update all rows in
|
||||||
/// explicitly, this method will update all rows in the specific table.
|
/// the [table].
|
||||||
///
|
///
|
||||||
/// The fields that are null on the [entity] object will not be changed by
|
/// 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.
|
/// Returns the amount of rows that have been affected by this operation.
|
||||||
///
|
///
|
||||||
/// See also: [replace], which does not require [where] statements and
|
/// 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 {
|
Future<int> write(D entity) async {
|
||||||
if (!table.validateIntegrity(entity, false)) {
|
if (!table.validateIntegrity(entity, false)) {
|
||||||
throw InvalidDataException(
|
throw InvalidDataException(
|
||||||
|
@ -81,6 +81,12 @@ class UpdateStatement<T, D> extends Query<T, D> {
|
||||||
/// null fields.
|
/// null fields.
|
||||||
///
|
///
|
||||||
/// Returns true if a row was affected by this operation.
|
/// 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 {
|
Future<bool> replace(D entity) async {
|
||||||
// We set isInserting to true here although we're in an update. This is
|
// 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
|
// 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']),
|
category: intType.mapFromDatabaseResponse(data['category']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'title': title,
|
||||||
|
'content': content,
|
||||||
|
'targetDate': targetDate,
|
||||||
|
'category': category,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
TodoEntry copyWith(
|
TodoEntry copyWith(
|
||||||
{int id,
|
{int id,
|
||||||
String title,
|
String title,
|
||||||
|
@ -154,6 +164,13 @@ class Category {
|
||||||
description: stringType.mapFromDatabaseResponse(data['`desc`']),
|
description: stringType.mapFromDatabaseResponse(data['`desc`']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'description': description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Category copyWith({int id, String description}) => Category(
|
Category copyWith({int id, String description}) => Category(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
|
@ -238,6 +255,15 @@ class User {
|
||||||
uint8ListType.mapFromDatabaseResponse(data['profile_picture']),
|
uint8ListType.mapFromDatabaseResponse(data['profile_picture']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'isAwesome': isAwesome,
|
||||||
|
'profilePicture': profilePicture,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
User copyWith(
|
User copyWith(
|
||||||
{int id, String name, bool isAwesome, Uint8List profilePicture}) =>
|
{int id, String name, bool isAwesome, Uint8List profilePicture}) =>
|
||||||
User(
|
User(
|
||||||
|
@ -344,6 +370,13 @@ class SharedTodo {
|
||||||
user: intType.mapFromDatabaseResponse(data['user']),
|
user: intType.mapFromDatabaseResponse(data['user']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'todo': todo,
|
||||||
|
'user': user,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
SharedTodo copyWith({int todo, int user}) => SharedTodo(
|
SharedTodo copyWith({int todo, int user}) => SharedTodo(
|
||||||
todo: todo ?? this.todo,
|
todo: todo ?? this.todo,
|
||||||
user: user ?? this.user,
|
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
|
// The length of a title must be between 4 and 16 chars
|
||||||
|
|
||||||
expect(() async {
|
expect(() async {
|
||||||
await db.into(db.todosTable).insert(TodoEntry(title: 'lol'));
|
await db.update(db.todosTable).write(TodoEntry(title: 'lol'));
|
||||||
}, throwsA(const TypeMatcher<InvalidDataException>()));
|
}, throwsA(const TypeMatcher<InvalidDataException>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue