更新(输出阶段)

说明

update(...) 是一个数据操纵语言 (DML) 阶段,用于更新 Firestore 中的现有文档。此阶段可以附加到查询末尾,以更新上一阶段引用的所有文档中的字段。

示例

回填架构更改

例如,以下操作会将数据模型更改回填到集合组中的所有文档。流水线会向 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)

行为

以下部分介绍了 update(...) 阶段的执行行为、响应和限制。

最终阶段

所有数据操纵语言 (DML) 阶段都必须位于流水线的末尾,不能再附加其他阶段。

__name__ 必需

进入此阶段的文档必须包含 __name__ 字段,以标识要更新的文档。如果任何文档不存在,则操作会失败。 大多数输入阶段(例如 collection(...)、collection_group(...)、database(...) 和 documents(...))默认包含 __name__ 字段。

可选字段转换

您可以选择提供 transformations,以便在写入文档之前应用。这些操作与在最终输出阶段之前添加 add_fields(...) 完全相同,并且表达式会在上一个文档的上下文中运行。

通过聚合子查询进行更新

update() 阶段可与聚合子查询和标量函数结合使用,以根据动态计算的值更新文档,例如确定用户是否获得了最高分。此模式必须在用户事务中执行。

Node.js
```js const results = await db.runTransaction(async (transaction) => { return transaction.execute( db.pipeline() .collection("users") .where( field("score").equal( scalar( db.pipeline() .collection("scores") .aggregate({ accumulators: [max(field("points")).as("max_points")] }) .select(field("max_points")) ) ) ) .update([constant(true).as("is_top_scorer")]) ); }); ```
Java
```java Pipeline pipeline = db.pipeline() .collection("users") .where( eq( field("score"), scalar( db.pipeline() .collection("scores") .aggregate(max(field("points")).as("max_points")) .select(field("max_points"))))) .update(new Update(constant(true).as("is_top_scorer"))); db.runTransaction(transaction -> { transaction.execute(pipeline); return null; }); ```

响应

响应包含已修改文档数量的摘要。 例如,以下响应确认流水线修改了三个文档:

{documents_modified: 3L}

限制

  • 现在可以在事务中运行 DML 阶段。在大多数情况下,insert() 和 upsert() 阶段需要在事务中运行。

  • 在新事务中执行具有 DML 阶段的流水线时,您可以使用 atomic 选项在事务中自动运行,并在流水线成功执行后提交。

  • 在事务中运行时,其他阶段(例如 where、addFields、union、findNearest 和 aggregate 子查询)没有任何限制。事务性 DML 操作受 Firestore 企业版事务限制的约束。由于事务不允许在首次写入后进行读取,因此您无法在单个事务中多次调用 DML 流水线,也无法在 DML 流水线执行后读取文档。在 DML 写入后尝试执行读取操作会失败,并显示 FAILED_PRECONDITION 错误:Cannot have reads after the first write in a Firestore transaction.

  • 在事务之外运行时:

    • 每个文档都是独立更新的,并且跨文档的写入操作不具有原子性。流水线会在遇到第一个错误时停止,从而导致部分成功。
    • 不支持高级多查询阶段(aggregate、distinct、unnest、find_nearest、union、联接和子查询)。
  • 在 Firebase 安全规则下,每批文档都会重新评估规则并执行查找。如果规则使用 get() 或 exists(),则在写入大约 200 个文档后,会达到全局 20 次查找限制,并且操作会失败,并显示 PERMISSION_DENIED。请使用自定义声明,而不是 get() / exists() 查找,以避免此限制。

  • 如果 DML 阶段之前的阶段生成了多个具有相同 __name__ 的文档,或计算结果为相同的目标文档 ID,则每个变更都会按顺序处理:

    • 对于 upsert(...) 和 update(...):针对同一文档的多个变更遵循“最后一次变更生效”语义,即最后处理的变更决定文档的最终状态。
    • 对于 delete(...):第一次尝试会删除文档,而对同一文档的后续尝试则不执行任何操作。
    • 对于 insert(...):第一次尝试会创建文档,而后续针对同一文档 ID 的尝试会失败,并显示 ALREADY_EXISTS 错误。

事务性执行

DML 操作可以作为读写事务的一部分执行,方法是向流水线执行调用提供显式事务,或使用 atomic 选项。

Firestore 事务要求所有读取操作在任何写入操作之前执行。由于 DML 流水线会执行写入操作,因此在同一事务中运行 DML 流水线后,您无法执行读取操作(例如文档查找或查询读取)。例如,您不能在单个事务中多次调用 DML 流水线。在 DML 流水线执行后尝试读取失败,并显示 FAILED_PRECONDITION 错误:Cannot have reads after the first write in a Firestore transaction.

使用显式事务

Node.js
```js // Execute a pipeline with DML stage within an explicit transaction const pipeline = db.pipeline() .collection("users") .where(field("active").equal(false)) .addFields(constant("inactive").as("status")) .update(); await db.runTransaction(async (transaction) => { return transaction.execute(pipeline); }); ```
Java
```java // Execute a pipeline with DML stage within an explicit transaction Pipeline pipeline = db.pipeline() .collection("users") .where(field("active").equal(false)) .addFields(constant("inactive").as("status")) .update(); db.runTransaction(transaction -> { transaction.execute(pipeline); return null; }); ```

使用 atomic 选项

Node.js
```js // 执行流水线 const results = await db.pipeline() .collection("users") .where(field("active").equal(false)) .addFields(constant("inactive").as("status")) .update() .execute({ atomic: true }); ```
Java
```java // Execute a pipeline passing atomic option to execute() Pipeline pipeline = db.pipeline() .collection("users") .where(field("active").equal(false)) .addFields(constant("inactive").as("status")) .update(); pipeline.execute(new PipelineExecuteOptions().withAtomic(true)); ```