書き込み(update, insert, delete)
更新と削除
生成されたクラスを使用して、任意の行の個々のフィールドを更新できます:
Future moveImportantTasksIntoCategory(Category target) {
// 更新の際には、生成されたクラスの`Companion`バージョンを使用します。
// これにより、フィールドが`Value`タイプでラップされ、`Value.absent()`を使用して存在しないことを設定できます。
// これにより、`SET category = NULL(category: Value(null))`とcategoryを全く更新しないこと
// (Value.absent())との違いを区別することができます。
return (update(todos)
..where((t) => t.title.like('%Important%'))
).write(TodosCompanion(
category: Value(target.id),
),
);
}
Future updateTodo(Todo entry) {
// replaceを使用すると、プライマリキーとしてマークされていないエントリのすべてのフィールドが更新されます。
// また、同じプライマリキーを持つエントリのみが更新されることも保証します。ここでは、これはエントリと同じ
// idを持つ行が、エントリのタイトル、コンテンツ、カテゴリを反映するように更新されることを意味します。
// そのwhere句は自動的に設定されるため、whereと一緒に使用することはできません。
return update(todos).replace(entry);
}
Future feelingLazy() {
// 最も古い9つのタスクを削除する
return (delete(todos)..where((t) => t.id.isSmallerThanValue(10))).go();
}
更新や削除の際にwhere句を明示的に追加しないと、その文はテーブル内のすべての行に影響します!
最初の更新ではTodo
を渡す代わりにTodosCompanion
を使用したことにお気づきでしょうか。
Driftは、すべてのデータを含む完全な行を保持するためにTodo
クラス(テーブルのデータクラスとも呼ばれる)を生成します。
部分的なデータについては、companionを使用することをおすすめします。上の例では、category
カラムだけを設定しているので、companionを使用しています。
なぜそれが必要なのでしょうか?
フィールドがnull
に設定されていた場合、データベースでそのカラムをnull
に戻す必要があるのか、そのままにしておく必要があるのかが分かりません。
companionのフィールドは特別なValue.absent()
ステートを持っており、これを明示します。
companionには、挿入用の特別なコンストラクタも用意されています。デフォルト値を持たず、nullにできないカラムはすべて、コンストラクタで@required
とマークされます。
これにより、どのフィールドを設定すればよいかわかるので、companionを挿入に使いやすくなります。
挿入
テーブルに有効なオブジェクトを簡単に挿入することができます。 いくつかの値は存在しない可能性があるので(明示的に設定する必要のないデフォルト値など)、ここでもcompanionバージョンを使用します。
// 生成したidを返す
Future<int> addTodo(TodosCompanion entry) {
return into(todos).insert(entry);
}
生成される行クラスはすべて、オブジェクトの生成に使用できるコンストラクタを持ちます:
addTodo(
TodosCompanion(
title: Value('Important task'),
content: Value('Refactor persistence code'),
),
);
カラムがnullableであるか、デフォルト値(auto-incrementsを含む)を持つ場合、そのフィールドは省略可能です。
その他のフィールドはすべてNULL
でない値で設定しなければなりません。そうでない場合insert
メソッドhが例外をスローします。
バッチを使用すれば、複数のinsert
文を効率的に実行できます。そのためには、バッチ内でinsertAll
メソッドを使用します:
Future<void> insertMultipleEntries() async{
await batch((batch) {
// batch内の関数はawaitする必要はありません、
// ただし、後でbatch全体をawaitしてください
batch.insertAll(todos, [
TodosCompanion.insert(
title: 'First entry',
content: 'My content',
),
TodosCompanion.insert(
title: 'Another entry',
content: 'More content',
// columns that aren't required for inserts are still wrapped in a Value:
category: Value(3),
),
// ...
]);
});
}
すべての更新がアトミックに行われるという意味では、batchはトランザクションに似ていますが、同じSQL文を2度準備することを避けるために、さらなる最適化が可能です。 そのため、一括挿入や更新操作に適しています。
Upserts
upsertsはSQLite3の新しいバージョンの機能で、競合する行がすでに存在する場合、insert
をupdate
のように動作させることができます。
これにより、プライマリキーがデータの一部である場合に、既存の行を作成したり上書きしたりすることができます:
class Users extends Table {
TextColumn get email => text()();
TextColumn get name => text()();
Set<Column> get primaryKey => {email};
}
Future<int> createOrUpdateUser(User user) {
return into(users).insertOnConflictUpdate(user);
}
すでに存在する電子メールアドレスを指定して、createOrUpdateUser()
を呼び出すと、そのユーザの名前が更新されます。
そうでない場合は、新しいユーザーがデータベースに挿入されます。
挿入はより高度なクエリでも実行できます。例えば、辞書を作成していて、ある単語に何回遭遇したかを記録したいとします。 そのためのテーブルは次のようになります。
class Words extends Table {
TextColumn get word => text()();
IntColumn get usages => integer().withDefault(const Constant(1))();
Set<Column> get primaryKey => {word};
}
カスタムアップデートを使うことで、新しい単語を挿入したり、すでに存在する単語の使用回数カウンターをインクリメントできます:
Future<void> trackWord(String word) {
return into(words).insert(
WordsCompanion.insert(word: word),
onConflict: DoUpdate(
(old) => WordsCompanion.custom(usages: old.usages + Constant(1))),
);
}
insertOnConflictUpdate
とonConflict: DoUpdate
は、SQLのDO UPDATE
アップさーとを使用します。
このため、いわゆる「コンフリクトターゲット」、一意性違反をチェックするカラムのセットを提供する必要があります。
デフォルトでは、Driftはテーブルのプライマリキーをコンフリクトターゲットとして使用します。
ほとんどの場合はこれでうまくいきますが、いくつかのカラムにカスタムUNIQUE
制約を設定している場合は、DartのDoUpdate
でtarget
パラメータを使用してそれらのカラムを含める必要があります。
class MatchResults extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get teamA => text()();
TextColumn get teamB => text()();
BoolColumn get teamAWon => boolean()();
List<Set<Column<Object>>>? get uniqueKeys => [
{teamA, teamB}
];
}
Future<void> insertMatch(String teamA, String teamB, bool teamAWon) {
final data = MatchResultsCompanion.insert(
teamA: teamA, teamB: teamB, teamAWon: teamAWon);
return into(matches).insert(data,
onConflict:
DoUpdate((old) => data, target: [matches.teamA, matches.teamB]));
}
これにはかなり新しいSQLite3のバージョン(3.24.0)が必要で、drift_sqflite
を使用する 場合、古いAndroidデバイスでは使用出来ない可能性があることに注意してください。
NativeDatabase
とsqlite3_flutter_libs
にはAndroidの最新のSQLiteが含まれているので、upsert
をサポートしたい場合はこれを使うことを検討してください。
また、upsert
が行われた場合、返されるrowidは正確でない可能性があることに注意してください。
戻り値
insertReturning
を使用すると、行やcompanionを挿入し、挿入した行をすぐに取得することができます。
返された行には、生成されたすべてのデフォルト値とインクリメントされたidが含まれています。
これはSQLite3バージョン3.35で追加されたRETURNING
構文を使用しますが、ほとんどのオペレーティングシステムではデフォルトでは使用できません。
このメソッドを使用する場合は、最新のSQLite3バージョンが利用可能であることを確認してください。これはsqlite3_flutter_libs
の場合です。
例えば、スタートガイドのテーブルを使った次のスニペットを考えます:
final row = await into(todos).insertReturning(TodosCompanion.insert(
title: 'A todo entry',
content: 'A description',
));
返される行には適切なid
が設定されています。テーブルがCURRENT_TIME
のような動的な値を含みます。
さらなるデフォルト値を持つ場合、それらはinsertReturning
によって返される行にも設定されます。