Skip to content

Commit 11bc071

Browse files
dlarocquewu-hui
andauthored
feat(firestore): DML (#14703)
Co-authored-by: Hui Wu <wuandy@google.com>
1 parent e83c768 commit 11bc071

10 files changed

Lines changed: 1397 additions & 30 deletions

File tree

‎java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Pipeline.java‎

Lines changed: 133 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.google.cloud.firestore.pipeline.stages.Distinct;
4848
import com.google.cloud.firestore.pipeline.stages.FindNearest;
4949
import com.google.cloud.firestore.pipeline.stages.FindNearestOptions;
50+
import com.google.cloud.firestore.pipeline.stages.Insert;
5051
import com.google.cloud.firestore.pipeline.stages.Limit;
5152
import com.google.cloud.firestore.pipeline.stages.Offset;
5253
import com.google.cloud.firestore.pipeline.stages.PipelineExecuteOptions;
@@ -63,6 +64,7 @@
6364
import com.google.cloud.firestore.pipeline.stages.Unnest;
6465
import com.google.cloud.firestore.pipeline.stages.UnnestOptions;
6566
import com.google.cloud.firestore.pipeline.stages.Update;
67+
import com.google.cloud.firestore.pipeline.stages.Upsert;
6668
import com.google.cloud.firestore.pipeline.stages.Where;
6769
import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext;
6870
import com.google.cloud.firestore.telemetry.TelemetryConstants;
@@ -76,6 +78,7 @@
7678
import com.google.firestore.v1.ExecutePipelineRequest;
7779
import com.google.firestore.v1.ExecutePipelineResponse;
7880
import com.google.firestore.v1.StructuredPipeline;
81+
import com.google.firestore.v1.TransactionOptions;
7982
import com.google.firestore.v1.Value;
8083
import com.google.protobuf.ByteString;
8184
import 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() {

‎java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineSource.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ public Pipeline documents(String... docs) {
152152
/**
153153
* Creates a new {@link Pipeline} that operates on a static set of documents represented as Maps.
154154
*
155+
* <p>Each map becomes one input document. Field values can be constants or {@link
156+
* com.google.cloud.firestore.pipeline.expressions.Expression}s, including expressions nested
157+
* inside map or list values. {@link FieldValue} sentinels are not supported; use an expression
158+
* such as {@code currentTimestamp()} instead. To give a document a path, set its {@code __name__}
159+
* field to a {@link DocumentReference}. Executing a pipeline with no documents fails with {@code
160+
* INVALID_ARGUMENT}.
161+
*
155162
* <p>Example:
156163
*
157164
* <pre>{@code

‎java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Delete.java‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.google.cloud.firestore.pipeline.stages;
1818

19-
import com.google.api.core.BetaApi;
2019
import com.google.api.core.InternalApi;
2120
import com.google.firestore.v1.Value;
2221
import java.util.ArrayList;
@@ -27,7 +26,6 @@ private Delete(InternalOptions options) {
2726
super("delete", options);
2827
}
2928

30-
@BetaApi
3129
public Delete() {
3230
this(InternalOptions.EMPTY);
3331
}

0 commit comments

Comments
 (0)