Skip to main content

書き込み(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();
}
caution

更新や削除の際にwhere句を明示的に追加しないと、その文はテーブル内のすべての行に影響します!

Entries, companions... なぜこんなものが必要なのか?

最初の更新では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の新しいバージョンの機能で、競合する行がすでに存在する場合、insertupdateのように動作させることができます。

これにより、プライマリキーがデータの一部である場合に、既存の行を作成したり上書きしたりすることができます:

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))),
);
}
独自の制約とコンフリクトターゲット

insertOnConflictUpdateonConflict: DoUpdateは、SQLのDO UPDATEアップさーとを使用します。 このため、いわゆる「コンフリクトターゲット」、一意性違反をチェックするカラムのセットを提供する必要があります。 デフォルトでは、Driftはテーブルのプライマリキーをコンフリクトターゲットとして使用します。 ほとんどの場合はこれでうまくいきますが、いくつかのカラムにカスタムUNIQUE制約を設定している場合は、DartのDoUpdatetargetパラメータを使用してそれらのカラムを含める必要があります。

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デバイスでは使用出来ない可能性があることに注意してください。 NativeDatabasesqlite3_flutter_libsにはAndroidの最新のSQLiteが含まれているので、upsertをサポートしたい場合はこれを使うことを検討してください。

また、upsertが行われた場合、返されるrowidは正確でない可能性があることに注意してください。

戻り値

insertReturningを使用すると、行やcompanionを挿入し、挿入した行をすぐに取得することができます。 返された行には、生成されたすべてのデフォルト値とインクリメントされたidが含まれています。

note

これは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によって返される行にも設定されます。