Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .agents/skills/verify-local-changes/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
name: Verify Local Changes
description: Verifies local Java SDK changes.
---

# Verify Local Changes

This skill documents how to verify local code changes for the Java Firestore SDK. This should be run **every time** you complete a fix or feature and are prepared to push a pull request.

## Prerequisites

Ensure you have Maven installed and are in the `java-firestore` directory before running commands.

---

## Step 0: Format the Code

Run the formatter to ensure formatting checks pass:

```bash
mvn com.spotify.fmt:fmt-maven-plugin:format
```

---

## Step 1: Unit Testing (Isolated then Suite)

1. **Identify modified unit tests** in your changes.
2. **Run specific units only** to test isolated logic regressions:
```bash
mvn test -Dtest=MyUnitTest#testMethod
```
3. **Run the entire unit test suite** that contains those modified tests if the isolated unit tests pass:
```bash
mvn test -Dtest=MyUnitTest
```

---

## Step 2: Integration Testing (Isolated then Suite)

### 💡 Integration Test Nuances (from `ITBaseTest.java`)

When running integration tests, configure your execution using properties or environment variables:

- **`FIRESTORE_EDITION`**:
- `standard` (Default)
- `enterprise`
- *Note*: **Pipelines can only be run against `enterprise` editions**, while standard Queries run on both.
- **`FIRESTORE_NAMED_DATABASE`**:
- Enterprise editions usually require a named database (often `enterprise`). Adjust this flag if pointing to specific instances.
- **`FIRESTORE_TARGET_BACKEND`**:
- `PROD` (Default)
- `QA` (points to standard sandboxes)
- `NIGHTLY` (points to `test-firestore.sandbox.googleapis.com:443`)
- `EMULATOR` (points to `localhost:8080`)

1. **Identify modified integration tests** (usually Starting in `IT`).
2. **Run specific integration tests only** (isolated checks run quicker):
```bash
mvn verify -Penable-integration-tests -DFIRESTORE_EDITION=enterprise -DFIRESTORE_NAMED_DATABASE=enterprise -Dtest=ITTest#testMethod -Dclirr.skip=true -Denforcer.skip=true -fae
```
3. **Run the entire integration test suite** for the modified class if isolation tests pass:
```bash
mvn verify -Penable-integration-tests -DFIRESTORE_EDITION=enterprise -DFIRESTORE_NAMED_DATABASE=enterprise -Dtest=ITTest -Dclirr.skip=true -Denforcer.skip=true -fae
```



---

## Step 3: Full Suite Regressions

Run the full integration regression suite once you are confident subsets pass:

```bash
mvn verify -Penable-integration-tests -DFIRESTORE_EDITION=enterprise -DFIRESTORE_NAMED_DATABASE=enterprise -Dclirr.skip=true -Denforcer.skip=true -fae
```

---

> [!TIP]
> Use `-Dclirr.skip=true -Denforcer.skip=true` to speed up iterations where appropriate without leaking compliance checks.

---

## 🛠️ Troubleshooting & Source of Truth

If you run into issues executing tests with the commands above, **consult the Kokoro configuration files** as the ultimate source of truth:

- **Presubmit configurations**: See `.kokoro/presubmit/integration.cfg` (or `integration-named-db.cfg`)
- **Nightly configurations**: See `.kokoro/nightly/integration.cfg`
- **Build shell scripts**: See `.kokoro/build.sh`

These files define the exact environment variables (e.g., specific endpoints or endpoints overrides) the CI server uses!
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@
import com.google.cloud.firestore.pipeline.stages.AddFields;
import com.google.cloud.firestore.pipeline.stages.Aggregate;
import com.google.cloud.firestore.pipeline.stages.AggregateOptions;
import com.google.cloud.firestore.pipeline.stages.Delete;
import com.google.cloud.firestore.pipeline.stages.DeleteOptions;
import com.google.cloud.firestore.pipeline.stages.Distinct;
import com.google.cloud.firestore.pipeline.stages.FindNearest;
import com.google.cloud.firestore.pipeline.stages.FindNearestOptions;
import com.google.cloud.firestore.pipeline.stages.Insert;
import com.google.cloud.firestore.pipeline.stages.InsertOptions;
import com.google.cloud.firestore.pipeline.stages.Limit;
import com.google.cloud.firestore.pipeline.stages.Offset;
import com.google.cloud.firestore.pipeline.stages.PipelineExecuteOptions;
Expand All @@ -58,6 +62,10 @@
import com.google.cloud.firestore.pipeline.stages.Union;
import com.google.cloud.firestore.pipeline.stages.Unnest;
import com.google.cloud.firestore.pipeline.stages.UnnestOptions;
import com.google.cloud.firestore.pipeline.stages.Update;
import com.google.cloud.firestore.pipeline.stages.UpdateOptions;
import com.google.cloud.firestore.pipeline.stages.Upsert;
import com.google.cloud.firestore.pipeline.stages.UpsertOptions;
import com.google.cloud.firestore.pipeline.stages.Where;
import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext;
import com.google.cloud.firestore.telemetry.TelemetryConstants;
Expand Down Expand Up @@ -995,6 +1003,138 @@ public Pipeline unnest(Selectable field, UnnestOptions options) {
return append(new Unnest(field, options));
}

/**
* Performs a delete operation on documents from previous stages.
*
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@BetaApi
public Pipeline delete() {
return append(new Delete());
}

/**
* Performs a delete operation on documents from previous stages.
*
* @param target The collection to delete from.
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@BetaApi
public Pipeline delete(CollectionReference target) {
return append(Delete.withCollection(target));
}

/**
* Performs a delete operation on documents from previous stages.
*
* @param deleteStage The {@code Delete} stage to append.
* @param options The {@code DeleteOptions} to apply to the stage.
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@InternalApi
public Pipeline delete(Delete deleteStage, DeleteOptions options) {
return append(deleteStage.withOptions(options));
}

/**
* Performs an upsert operation using documents from previous stages.
*
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@BetaApi
public Pipeline upsert() {
return append(new Upsert());
}

/**
* Performs an upsert operation using documents from previous stages.
*
* @param target The collection to upsert to.
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@BetaApi
public Pipeline upsert(CollectionReference target) {
return append(Upsert.withCollection(target));
}

/**
* Performs an upsert operation using documents from previous stages.
*
* @param upsertStage The {@code Upsert} stage to append.
* @param options The {@code UpsertOptions} to apply to the stage.
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@InternalApi
public Pipeline upsert(Upsert upsertStage, UpsertOptions options) {
return append(upsertStage.withOptions(options));
}

/**
* Performs an update operation using documents from previous stages.
*
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@BetaApi
public Pipeline update() {
return append(new Update());
}

/**
* Performs an update operation using documents from previous stages.
*
* @param transformations The transformations to apply.
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@BetaApi
public Pipeline update(Selectable... transformations) {
return append(new Update().withTransformations(transformations));
}

/**
* Performs an update operation using documents from previous stages.
*
* @param update The {@code Update} stage to append.
* @param options The {@code UpdateOptions} to apply to the stage.
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@InternalApi
public Pipeline update(Update updateStage, UpdateOptions options) {
return append(updateStage.withOptions(options));
}

/**
* Performs an insert operation using documents from previous stages.
*
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@BetaApi
public Pipeline insert() {
return append(new Insert());
}

/**
* Performs an insert operation using documents from previous stages.
*
* @param target The collection to insert to.
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@BetaApi
public Pipeline insert(CollectionReference target) {
return append(Insert.withCollection(target));
}

/**
* Performs an insert operation using documents from previous stages.
*
* @param insertStage The {@code Insert} stage to append.
* @param options The {@code InsertOptions} to apply to the stage.
* @return A new {@code Pipeline} object with this stage appended to the stage list.
*/
@InternalApi
public Pipeline insert(Insert insertStage, InsertOptions options) {
return append(insertStage.withOptions(options));
}

/**
* Adds a generic stage to the pipeline.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

/** Defines the conflict resolution options for an Upsert pipeline stage. */
public enum ConflictResolution {
OVERWRITE("OVERWRITE"),
MERGE("MERGE"),
FAIL("FAIL"),
KEEP("KEEP");

private final String value;

ConflictResolution(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.cloud.firestore.CollectionReference;
import com.google.cloud.firestore.PipelineUtils;
import com.google.firestore.v1.Value;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;

@InternalApi
public final class Delete extends Stage {

@Nullable private final String path;

private Delete(@Nullable String path, InternalOptions options) {
super("delete", options);
this.path = path;
}

@BetaApi
public Delete() {
this(null, InternalOptions.EMPTY);
}

@BetaApi
public static Delete withCollection(CollectionReference target) {
String path = target.getPath();
return new Delete(path.startsWith("/") ? path : "/" + path, InternalOptions.EMPTY);
}

@InternalApi
public Delete withOptions(DeleteOptions options) {
return new Delete(path, this.options.adding(options));
}

@InternalApi
public Delete withReturns(DeleteReturn returns) {
return new Delete(
path, this.options.with("returns", PipelineUtils.encodeValue(returns.getValue())));
}

@Override
Iterable<Value> toStageArgs() {
List<Value> args = new ArrayList<>();
if (path != null) {
args.add(Value.newBuilder().setReferenceValue(path).build());
}
return args;
}
}
Loading
Loading