插入

说明

insert() 是一个数据操纵语言 (DML) 阶段,用于向 Cloud Firestore 添加新文档。此阶段可以附加到查询末尾,以将该查询的结果插入到集合中。

insert() 阶段可以采用不同的参数来确定目标集合和文档 ID:

  • insert(collectionName, documentIdExpression):将文档插入到指定集合中,并使用求值表达式来确定文档 ID。
  • insert(collectionName):将文档插入到指定集合中,并自动生成文档 ID。
  • insert():插入文档,要求输入文档中包含 __name__ 字段,以确定目标集合和文档 ID。

示例

插入静态文档

以下示例将新文档插入 users 集合中,并使用输入中的 id 字段来定义文档 ID:

Node.js
```js const results = await db.pipeline() .literals([ { foo: "bar", id: "doc1" }, { foo: "baz", id: "doc2" } ]) .insert({ collection: "users", documentId: field("id") }) .execute(); ```
Java
```java Pipeline pipeline = db.pipeline() .literals( Map.of("foo", "bar", "id", "doc1"), Map.of("foo", "baz", "id", "doc2")) .insert(new Insert().withCollection("users").withDocumentId(field("id"))); pipeline.execute(); ```

行为

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

已评估的文档 ID 表达式

以下示例使用字符串串联表达式 stringConcat(...) 计算新的文档 ID,并在显式事务中将结果插入 new_users:

Node.js
```js const results = await db.runTransaction(async (transaction) => { return transaction.execute( db.pipeline() .collection("users") .distinct(field("name")) .insert({ collection: "new_users", documentId: stringConcat(constant("new_"), field("name")), }) ); }); ```
Java
```java Pipeline pipeline = db.pipeline() .collection("users") .distinct(field("name")) .insert(new Insert() .withCollection("new_users") .withDocumentId(stringConcat(constant("new_"), field("name")))); db.runTransaction(transaction -> { transaction.execute(pipeline); return null; }); ```

文档引用 (__name__)

以下示例使用分配给 __name__ 字段的文档引用来指定目标集合和文档 ID:

Node.js
```js const results = await db.pipeline() .literals([ { __name__: db.doc("users/doc1"), foo: "bar" }, { __name__: db.doc("users/doc2"), foo: "baz" } ]) .insert() .execute(); ```
Java
```java Pipeline pipeline = db.pipeline() .literals( Map.of("__name__", db.document("users/doc1"), "foo", "bar"), Map.of("__name__", db.document("users/doc2"), "foo", "baz")) .insert(new Insert()); pipeline.execute(); ```

自动生成的 ID

以下示例会将新文档插入 users 集合中,并允许 Cloud Firestore 自动生成文档 ID:

Node.js
```js const results = await db.pipeline() .literals([ { foo: "bar" }, { foo: "baz" } ]) .insert({ collection: "users" }) .execute(); ```
Java
```java Pipeline pipeline = db.pipeline() .literals( Map.of("foo", "bar"), Map.of("foo", "baz")) .insert(new Insert().withCollection("users")); pipeline.execute(); ```

插入到子集合中

insert() 阶段可用于使用完整路径将文档直接插入到子集合中。

Node.js
```js const results = await db.pipeline() .literals([ { title: "post1" }, { title: "post2" } ]) .insert({ collection: "users/u1/posts" }) .execute(); ```
Java
```java Pipeline pipeline = db.pipeline() .literals( Map.of("title", "post1"), Map.of("title", "post2")) .insert(new Insert().withCollection("users/u1/posts")); pipeline.execute(); ```

插入到其他集合

insert() 阶段可以从一个集合中读取数据,应用转换,并在事务中写入另一个集合:

Node.js
```js const results = await db.runTransaction(async (transaction) => { return transaction.execute( db.pipeline() .collection("source_coll") .where(field("active").equal(true)) .addFields(constant("val").as("new_field")) .insert({ collection: "target_coll" }) ); }); ```
Java
```java Pipeline pipeline = db.pipeline() .collection("source_coll") .where(field("active").equal(true)) .addFields(constant("val").as("new_field")) .insert(new Insert().withCollection("target_coll")); db.runTransaction(transaction -> { transaction.execute(pipeline); return null; }); ```

从“高级查询”(搜索和查找最近的地点)中插入

insert() 阶段可与 search 或 findNearest 等高级查询操作结合使用,以将匹配的文档写入输出集合。

Node.js
```js const results = await db.runTransaction(async (transaction) => { return transaction.execute( db.pipeline() .collection("products") .search({ query: "shoes" }) .insert({ collection: "matched_products" }) ); }); ```
Java
```java Pipeline pipeline = db.pipeline() .collection("products") .search(searchOptionsBuilder().query(documentMatches("shoes"))) .insert(new Insert().withCollection("matched_products")); db.runTransaction(transaction -> { transaction.execute(pipeline); return null; }); ```

回答

insert() 阶段始终会发出一个文档(如 { documents_modified: 2 }),用于描述插入了多少个文档。

最终阶段

insert() 阶段必须位于流水线的末尾,不能再提供其他阶段。

需要事务

insert() 阶段需要事务。它必须在活跃的读写事务中运行,或者在启动新事务时将 atomic (Node.js) 或 auto_commit 选项设置为 true。

已存在

如果要插入的任何文档已存在于目标位置,insert() 阶段将失败并显示 ALREADY_EXISTS 错误。

回答

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

{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)); ```