Pipeline オペレーションを使用してデータを変更する

update(...) と delete(...) のデータ操作言語(DML)ステージを使用して、ドキュメントをクエリし、データを削除または変更できるデータ パイプラインを構築します。

エディションの要件

このページで説明するオペレーションには、Firestore Enterprise エディションが必要です。

始める前に

Pipeline オペレーションを使用してデータベースにクエリを実行する方法を理解している必要があります。

ドキュメントを更新する

update(...) DML ステージを使用して、ドキュメントをクエリし、データを追加または変更できるデータ パイプラインを構築します。

すべての DML ステージは、パイプラインの最後に配置する必要があります。このステージに入るドキュメントには、更新するドキュメントを識別するための __name__ フィールドが含まれている必要があります。更新しようとしたドキュメントが存在しない場合、オペレーションは失敗します。

たとえば、次のオペレーションでは、データモデルの変更をコレクション グループ内のすべてのドキュメントにバックフィルします。パイプラインは、users コレクション グループ内のすべてのドキュメントに preferences.color フィールドを追加します。

Node.js
const snapshot = await db.pipeline()
   .collectionGroup("users")
   .where(not(exists(field("preferences.color"))))
   .addFields(constant(null).as("preferences.color"))
   .removeFields("color")
   .update()
   .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Constant, Field, Not

snapshot = (
    client.pipeline()
    .collection_group("users")
    .where(Not(Field.of("preferences.color").exists()))
    .add_fields(Constant.of(None).as_("preferences.color"))
    .remove_fields("color")
    .update()
    .execute()
)
Java
Pipeline.Snapshot snapshot = firestore.pipeline()
   .collectionGroup("users")
   .where(not(exists(field("preferences.color"))))
   .addFields(constant((String) null).as("preferences.color"))
   .removeFields("color")
   .update()
   .execute().get();
Go
snapshot := client.Pipeline().
	CollectionGroup("users").
	Where(firestore.Not(firestore.FieldExists(firestore.FieldOf("preferences.color")))).
	AddFields(firestore.Selectables(
		firestore.ConstantOfNull().As("preferences.color"),
	)).
	RemoveFields(firestore.Fields("color")).
	Update().
	Execute(ctx)

ドキュメントを削除する

delete(...) DML ステージを使用して、ドキュメントをクエリしてデータを削除できるデータ パイプラインを構築します。誤って一括削除されないように、delete(...) で終わるパイプラインには少なくとも 1 つの where(...) ステージを含める必要があります。すべての DML ステージは、パイプラインの最後に配置する必要があります。

たとえば、次のパイプラインは、address.users が USA に設定され、__create_time__ が 10 日未満のすべての users ドキュメントを削除します。

Node.js
const pipeline = db.pipeline()
  .collectionGroup("users")
  .where(field("address.country").equal("USA"))
  .where(field("__create_time__").timestampAdd("day", 10).lessThan(currentTimestamp()))
  .delete();
await pipeline.execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import CurrentTimestamp, Field

snapshot = (
    client.pipeline()
    .collection_group("users")
    .where(Field.of("address.country").equal("USA"))
    .where(
        Field.of("__create_time__")
        .timestamp_add("day", 10)
        .less_than(CurrentTimestamp())
    )
    .delete()
    .execute()
)
Java
Pipeline.Snapshot deleteResults = firestore.pipeline()
  .collectionGroup("users")
  .where(field("address.country").equal("USA"))
  .where(field("__create_time__").add(constant(10)).lessThan(currentTimestamp()))
  .delete()
  .execute().get();
Go
snapshot := client.Pipeline().
	CollectionGroup("users").
	Where(firestore.FieldOf("address.country").Equal("USA")).
	Where(firestore.FieldOf("__create_time__").Add(firestore.ConstantOf(10)).LessThan(firestore.CurrentTimestamp())).
	Delete().
	Execute(ctx)

実行モードと整合性

DML ステージは、明示的なトランザクション内またはトランザクション外で実行できます。

トランザクション実行

トランザクション DML は、ドキュメント間でアトミックな all-or-nothing の整合性を提供し、パイプライン クエリ ステージの完全なセットをサポートします。そのため、厳格な整合性の保証が必要なワークロード(部分的な更新が許容されないワークロード)や、複雑なクエリステージに依存するワークロードに最適です。ただし、変更がトランザクション サイズの上限内に収まる必要があります。

明示的なトランザクション内で DML パイプラインを実行すると(runTransaction / transaction.execute(pipeline) または atomic: true オプションを使用)、次の動作が発生します。

  • すべてのミューテーションは、まとめて commit されるか、失敗時に完全にロールバックされます。
  • DML ステージ(where、add_fields、union、find_nearest、aggregate サブクエリを含む)の前に、すべてのパイプライン クエリ ステージがサポートされます。
  • トランザクションでは、すべての読み取りオペレーションを書き込みオペレーションの前に実行する必要があります。DML パイプラインは書き込みを実行するため、同じトランザクションで DML パイプラインを実行した後に読み取りを実行することはできません。たとえば、1 つのトランザクションで DML パイプラインを複数回呼び出すことはできません。また、DML パイプラインの実行後にドキュメントを読み取ることもできません。DML パイプラインの実行後に読み取りを実行しようとすると、FAILED_PRECONDITION エラー(Cannot have reads after the first write in a Firestore transaction.)が発生します。
  • このオペレーションには、Firestore Enterprise エディションのトランザクション上限が適用されます。

非トランザクション実行

非トランザクション DML は、トランザクション サイズの制限に達することなく、多数のドキュメントにわたる一括変更を行うように設計されています。オペレーション全体ではなくドキュメント レベルでロックすることで、ドキュメントを大規模に更新しながら、他の同時オペレーションを続行できます。

DML ステージが明示的なトランザクションの外部で実行される場合:

  • 独立した更新と部分的な成功: 各ドキュメントは互いに独立して更新され、オペレーションはドキュメント間でアトミックではありません。パイプラインは最初のエラーで停止し、以前に変更されたドキュメントはデータベースに書き込まれたままになります。これにより、部分的な成功が発生する可能性があります。
  • ステージのサポート:
    • トランザクション外の DML の前に、高度なマルチクエリ ステージ(aggregate(...)、distinct(...)、unnest(...)、find_nearest(...)、union(...)、結合、サブクエリなど)はサポートされていません。パイプラインにこれらのステージが必要な場合は、トランザクションを使用します。
  • セキュリティ ルールの 20 ルックアップ制限: ドキュメントの各バッチは独自のトランザクションで実行され、セキュリティ ルールを再評価します。セキュリティ ルールで get() または exists() ルックアップを使用している場合、各バッチはルックアップを実行し、約 200 個のドキュメントを書き込んだ後にリクエストあたりのルックアップの上限である 20 回に達し、PERMISSION_DENIED で失敗します。この制限を回避するには、get() または exists() のルックアップではなく、カスタム クレームを使用します。

制限事項

DML ステージには次の制限事項があります。

  • DML ステージは、.execute() を呼び出す前に、パイプライン定義の最後のステージにする必要があります。
  • トランザクションの外部で実行すると、各ドキュメントは互いに独立して更新され、部分的な成功が発生する可能性があります。
  • get() または exists() ルックアップを含むセキュリティ ルールの対象となる非トランザクション DML オペレーションは、20 ルックアップの上限により、約 200 ドキュメントに制限されます。
  • トランザクション DML オペレーションは、Firestore Enterprise エディションのトランザクション上限によって制限されます。Firestore トランザクションでは、最初の書き込み後に読み取りを行うことができないため、1 つのトランザクション内で DML パイプラインを複数回呼び出すことはできません。また、DML パイプラインの実行後にドキュメントを読み取ることもできません。DML 書き込み後に読み取りを実行しようとすると、FAILED_PRECONDITION エラーで失敗します。 Cannot have reads after the first write in a Firestore transaction.
  • DML ステージの前のステージで、同じ __name__ を持つ複数のドキュメントが生成されるか、同じ宛先ドキュメント ID に評価される場合、各ミューテーションは順番に処理されます。
    • upsert(...) と update(...) の場合: 同じドキュメントをターゲットとする複数のミューテーションは、「最後のミューテーションが優先される」セマンティクスに従います。つまり、最後に処理されたミューテーションによってドキュメントの最終状態が決まります。
    • delete(...) の場合: 最初の試行でドキュメントが削除され、同じドキュメントに対する後続の試行は何も行われません。
    • insert(...) の場合: 最初の試行でドキュメントが作成され、同じドキュメント ID をターゲットとする後続の試行は ALREADY_EXISTS エラーで失敗します。