4747import com .google .cloud .firestore .pipeline .stages .Distinct ;
4848import com .google .cloud .firestore .pipeline .stages .FindNearest ;
4949import com .google .cloud .firestore .pipeline .stages .FindNearestOptions ;
50+ import com .google .cloud .firestore .pipeline .stages .Insert ;
5051import com .google .cloud .firestore .pipeline .stages .Limit ;
5152import com .google .cloud .firestore .pipeline .stages .Offset ;
5253import com .google .cloud .firestore .pipeline .stages .PipelineExecuteOptions ;
6364import com .google .cloud .firestore .pipeline .stages .Unnest ;
6465import com .google .cloud .firestore .pipeline .stages .UnnestOptions ;
6566import com .google .cloud .firestore .pipeline .stages .Update ;
67+ import com .google .cloud .firestore .pipeline .stages .Upsert ;
6668import com .google .cloud .firestore .pipeline .stages .Where ;
6769import com .google .cloud .firestore .telemetry .MetricsUtil .MetricsContext ;
6870import com .google .cloud .firestore .telemetry .TelemetryConstants ;
7678import com .google .firestore .v1 .ExecutePipelineRequest ;
7779import com .google .firestore .v1 .ExecutePipelineResponse ;
7880import com .google .firestore .v1 .StructuredPipeline ;
81+ import com .google .firestore .v1 .TransactionOptions ;
7982import com .google .firestore .v1 .Value ;
8083import com .google .protobuf .ByteString ;
8184import java .util .ArrayList ;
@@ -1198,6 +1201,9 @@ public Pipeline unnest(Selectable field, UnnestOptions options) {
11981201 /**
11991202 * Performs a delete operation on documents from previous stages.
12001203 *
1204+ * <p>Each document produced by the previous stages is deleted from the database. The result
1205+ * contains a single document with a {@code documents_modified} field.
1206+ *
12011207 * <p>Example:
12021208 *
12031209 * <pre>{@code
@@ -1212,16 +1218,20 @@ public Pipeline unnest(Selectable field, UnnestOptions options) {
12121218 *
12131219 * @return A new {@code Pipeline} object with this stage appended to the stage list.
12141220 */
1215- @ BetaApi
12161221 public Pipeline delete () {
12171222 return append (new Delete ());
12181223 }
12191224
12201225 /**
12211226 * Performs an update operation using documents from previous stages.
12221227 *
1223- * <p>This method updates the documents in place based on the data flowing through the pipeline.
1224- * To specify transformations, use {@link #update(Selectable...)}.
1228+ * <p>This method updates existing documents based on the data flowing through the pipeline. To
1229+ * specify transformations, use {@link #update(Selectable...)}.
1230+ *
1231+ * <p>Each document is written to the path given by its {@code __name__} field. The stored
1232+ * document is replaced by the pipeline document, so fields removed by earlier stages (for
1233+ * example, with {@link #removeFields(String, String...)}) are also removed from the stored
1234+ * document. This stage does not create documents: target documents that don't exist are skipped.
12251235 *
12261236 * <p>Example 1: Update a collection's schema by adding a new field and removing an old one.
12271237 *
@@ -1235,7 +1245,7 @@ public Pipeline delete() {
12351245 * .get();
12361246 * }</pre>
12371247 *
1238- * <p>Example 2: Update documents in place with data from literals.
1248+ * <p>Example 2: Update documents with data from literals.
12391249 *
12401250 * <pre>{@code
12411251 * Map<String, Object> updateData = new HashMap<>();
@@ -1251,7 +1261,6 @@ public Pipeline delete() {
12511261 *
12521262 * @return A new {@code Pipeline} object with this stage appended to the stage list.
12531263 */
1254- @ BetaApi
12551264 public Pipeline update () {
12561265 return append (new Update ());
12571266 }
@@ -1275,7 +1284,6 @@ public Pipeline update() {
12751284 * @param transformedFields The transformations to apply.
12761285 * @return A new {@code Pipeline} object with this stage appended to the stage list.
12771286 */
1278- @ BetaApi
12791287 public Pipeline update (Selectable ... transformedFields ) {
12801288 return append (new Update ().withTransformedFields (transformedFields ));
12811289 }
@@ -1301,14 +1309,104 @@ public Pipeline update(Selectable... transformedFields) {
13011309 * @param update The {@code Update} stage to append.
13021310 * @return A new {@code Pipeline} object with this stage appended to the stage list.
13031311 */
1304- @ BetaApi
13051312 public Pipeline update (Update update ) {
13061313 return append (update );
13071314 }
13081315
13091316 /**
1310- * Performs an insert operation using documents from previous stages. Adds a generic stage to the
1311- * pipeline.
1317+ * Inserts each document produced by the previous stages at its own path, given by its {@code
1318+ * __name__} field.
1319+ *
1320+ * <p>This is equivalent to {@code insert(new Insert())}. A document without a {@code __name__}
1321+ * field fails with {@code INVALID_ARGUMENT}.
1322+ *
1323+ * <p>The insert fails with {@code ALREADY_EXISTS} if a target document already exists.
1324+ *
1325+ * @return A new {@code Pipeline} object with this stage appended to the stage list.
1326+ */
1327+ public Pipeline insert () {
1328+ return append (new Insert ());
1329+ }
1330+
1331+ /**
1332+ * Inserts each document produced by the previous stages as a new document, using a configured
1333+ * {@link Insert} stage.
1334+ *
1335+ * <p>The insert fails with {@code ALREADY_EXISTS} if a target document already exists. See {@link
1336+ * Insert} for how the target path of each document is determined.
1337+ *
1338+ * <p>Example:
1339+ *
1340+ * <pre>{@code
1341+ * Map<String, Object> book = new HashMap<>();
1342+ * book.put("title", "Dune");
1343+ *
1344+ * firestore.pipeline()
1345+ * .literals(book)
1346+ * .insert(new Insert().withCollection("books"))
1347+ * .execute(new PipelineExecuteOptions().withAtomic(true))
1348+ * .get();
1349+ * }</pre>
1350+ *
1351+ * @param insert The {@code Insert} stage to append.
1352+ * @return A new {@code Pipeline} object with this stage appended to the stage list.
1353+ */
1354+ public Pipeline insert (Insert insert ) {
1355+ return append (insert );
1356+ }
1357+
1358+ /**
1359+ * Writes each document produced by the previous stages, using a configured {@link Upsert} stage.
1360+ * The target document is created if it doesn't exist and replaced if it does; stored fields are
1361+ * not merged.
1362+ *
1363+ * <p>See {@link Upsert} for how the target path of each document is determined.
1364+ *
1365+ * <p>Example:
1366+ *
1367+ * <pre>{@code
1368+ * firestore.pipeline()
1369+ * .collection("books")
1370+ * .where(field("title").equal("Dune"))
1371+ * .upsert(new Upsert().withCollection("archive"))
1372+ * .execute()
1373+ * .get();
1374+ * }</pre>
1375+ *
1376+ * @param upsert The {@code Upsert} stage to append.
1377+ * @return A new {@code Pipeline} object with this stage appended to the stage list.
1378+ */
1379+ public Pipeline upsert (Upsert upsert ) {
1380+ return append (upsert );
1381+ }
1382+
1383+ /**
1384+ * Writes each document produced by the previous stages to the path given by its {@code __name__}
1385+ * field, after adding the given fields. The document is created if it doesn't exist and replaced
1386+ * if it does; stored fields are not merged. A document without a {@code __name__} field fails
1387+ * with {@code INVALID_ARGUMENT}.
1388+ *
1389+ * <p>Example:
1390+ *
1391+ * <pre>{@code
1392+ * firestore.pipeline()
1393+ * .collection("books")
1394+ * .where(field("rating").greaterThan(4))
1395+ * .upsert(constant("Featured").as("status"))
1396+ * .execute()
1397+ * .get();
1398+ * }</pre>
1399+ *
1400+ * @param additionalFields Fields to add to each input document before it is written. A field with
1401+ * the same name as an existing input field overwrites that field.
1402+ * @return A new {@code Pipeline} object with this stage appended to the stage list.
1403+ */
1404+ public Pipeline upsert (Selectable ... additionalFields ) {
1405+ return append (new Upsert (additionalFields ));
1406+ }
1407+
1408+ /**
1409+ * Adds a generic stage to the pipeline.
13121410 *
13131411 * <p>This method provides a flexible way to extend the pipeline's functionality by adding custom
13141412 * stages. Each generic stage is defined by a unique `name` and a set of `params` that control its
@@ -1509,31 +1607,50 @@ public void onError(Throwable t) {
15091607 }
15101608 }
15111609
1512- void executeInternal (
1610+ ExecutePipelineRequest toExecutePipelineRequest (
15131611 @ Nonnull PipelineExecuteOptions options ,
15141612 @ Nullable final ByteString transactionId ,
1515- @ Nullable com .google .protobuf .Timestamp readTime ,
1516- PipelineResultObserver observer ,
1517- MetricsContext metricsContext ) {
1613+ @ Nullable com .google .protobuf .Timestamp readTime ) {
1614+ java .util .Map <String , com .google .firestore .v1 .Value > optionsMap =
1615+ new java .util .HashMap <>(StageUtils .toMap (options ));
1616+ optionsMap .remove ("atomic" );
1617+
15181618 ExecutePipelineRequest .Builder request =
15191619 ExecutePipelineRequest .newBuilder ()
1520- .setDatabase (rpcContext .getDatabaseName ())
1620+ .setDatabase (rpcContext != null ? rpcContext .getDatabaseName () : "" )
15211621 .setStructuredPipeline (
15221622 StructuredPipeline .newBuilder ()
15231623 .setPipeline (toProto ())
1524- .putAllOptions (StageUtils . toMap ( options ) )
1624+ .putAllOptions (optionsMap )
15251625 .build ());
15261626
15271627 if (transactionId != null ) {
15281628 request .setTransaction (transactionId );
1629+ } else if (options .isAtomic ()) {
1630+ request .setNewTransaction (
1631+ TransactionOptions .newBuilder ()
1632+ .setReadWrite (TransactionOptions .ReadWrite .getDefaultInstance ())
1633+ .build ());
1634+ request .setAutoCommitTransaction (true );
15291635 }
15301636
15311637 if (readTime != null ) {
15321638 request .setReadTime (readTime );
15331639 }
15341640
1641+ return request .build ();
1642+ }
1643+
1644+ void executeInternal (
1645+ @ Nonnull PipelineExecuteOptions options ,
1646+ @ Nullable final ByteString transactionId ,
1647+ @ Nullable com .google .protobuf .Timestamp readTime ,
1648+ PipelineResultObserver observer ,
1649+ MetricsContext metricsContext ) {
1650+ ExecutePipelineRequest request = toExecutePipelineRequest (options , transactionId , readTime );
1651+
15351652 pipelineInternalStream (
1536- request . build () ,
1653+ request ,
15371654 new PipelineResultObserver () {
15381655 @ Override
15391656 public void onCompleted () {
0 commit comments