diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..39a8fc72bc --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,10 @@ +# Code owners file. +# This file controls who is tagged for review for any given pull request. +# +# For syntax help see: +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax + + +# The firestore-dpe team is the default owner for anything not +# explicitly taken by someone else. +* @googleapis/firestore-dpe diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000000..445b4bf82f --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,76 @@ +on: + push: + branches: + - master + pull_request: +name: ci +jobs: + units: + runs-on: ubuntu-latest + strategy: + matrix: + java: [7, 8, 11] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: ${{matrix.java}} + - run: java -version + - run: .kokoro/build.sh + env: + JOB_TYPE: test + - name: coverage + uses: codecov/codecov-action@v1 + with: + name: actions ${{matrix.java}} + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 8 + - run: java -version + - run: .kokoro/build.bat + env: + JOB_TYPE: test + dependencies: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 8 + - run: java -version + - run: .kokoro/dependencies.sh + linkage-monitor: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 8 + - run: java -version + - run: .kokoro/linkage-monitor.sh + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 8 + - run: java -version + - run: .kokoro/build.sh + env: + JOB_TYPE: lint + clirr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 8 + - run: java -version + - run: .kokoro/build.sh + env: + JOB_TYPE: clirr \ No newline at end of file diff --git a/.gitignore b/.gitignore index cc7c273e5a..241f844176 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ target/ .idea/ *.iml __pycache__/ + +.flattened-pom.xml diff --git a/.kokoro/build.bat b/.kokoro/build.bat index 7589b90fcc..05826ad93f 100644 --- a/.kokoro/build.bat +++ b/.kokoro/build.bat @@ -1,3 +1,3 @@ :: See documentation in type-shell-output.bat -"C:\Program Files\Git\bin\bash.exe" github/java-firestore/.kokoro/build.sh +"C:\Program Files\Git\bin\bash.exe" %~dp0build.sh diff --git a/.kokoro/common.sh b/.kokoro/common.sh index a3bbc5f679..a8d0ea04d3 100644 --- a/.kokoro/common.sh +++ b/.kokoro/common.sh @@ -13,18 +13,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -# set -eo pipefail - function retry_with_backoff { attempts_left=$1 sleep_seconds=$2 shift 2 command=$@ + + # store current flag state + flags=$- + + # allow a failures to continue + set +e echo "${command}" ${command} exit_code=$? + # restore "e" flag + if [[ ${flags} =~ e ]] + then set -e + else set +e + fi + if [[ $exit_code == 0 ]] then return 0 @@ -42,3 +52,8 @@ function retry_with_backoff { return $exit_code } + +## Helper functionss +function now() { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n'; } +function msg() { println "$*" >&2; } +function println() { printf '%s\n' "$(now) $*"; } \ No newline at end of file diff --git a/.kokoro/dependencies.sh b/.kokoro/dependencies.sh index 0aade871ce..cf3bb4347e 100755 --- a/.kokoro/dependencies.sh +++ b/.kokoro/dependencies.sh @@ -36,3 +36,51 @@ retry_with_backoff 3 10 \ -Dclirr.skip=true mvn -B dependency:analyze -DfailOnWarning=true + +echo "****************** DEPENDENCY LIST COMPLETENESS CHECK *******************" +## Run dependency list completeness check +function completenessCheck() { + # Output dep list with compile scope generated using the original pom + msg "Generating dependency list using original pom..." + mvn dependency:list -f pom.xml -Dsort=true | grep '\[INFO] .*:.*:.*:.*:.*' | grep -v ':test$' >.org-list.txt + + # Output dep list generated using the flattened pom (test scope deps are ommitted) + msg "Generating dependency list using flattened pom..." + mvn dependency:list -f .flattened-pom.xml -Dsort=true | grep '\[INFO] .*:.*:.*:.*:.*' >.new-list.txt + + # Compare two dependency lists + msg "Comparing dependency lists..." + diff .org-list.txt .new-list.txt >.diff.txt + if [[ $? == 0 ]] + then + msg "Success. No diff!" + else + msg "Diff found. See below: " + msg "You can also check .diff.txt file located in $1." + cat .diff.txt + return 1 + fi +} + +# Allow failures to continue running the script +set +e + +error_count=0 +for path in $(find -name ".flattened-pom.xml") +do + # Check flattened pom in each dir that contains it for completeness + dir=$(dirname "$path") + pushd "$dir" + completenessCheck "$dir" + error_count=$(($error_count + $?)) + popd +done + +if [[ $error_count == 0 ]] +then + msg "All checks passed." + exit 0 +else + msg "Errors found. See log statements above." + exit 1 +fi diff --git a/.kokoro/nightly/integration.cfg b/.kokoro/nightly/integration.cfg index 8bf59c02ed..ba0924a978 100644 --- a/.kokoro/nightly/integration.cfg +++ b/.kokoro/nightly/integration.cfg @@ -6,16 +6,36 @@ env_vars: { value: "gcr.io/cloud-devrel-kokoro-resources/java8" } +env_vars: { + key: "JOB_TYPE" + value: "integration" +} +# TODO: remove this after we've migrated all tests and scripts +env_vars: { + key: "GCLOUD_PROJECT" + value: "gcloud-devel" +} + +env_vars: { + key: "GOOGLE_CLOUD_PROJECT" + value: "gcloud-devel" +} + env_vars: { key: "ENABLE_BUILD_COP" value: "true" } +env_vars: { + key: "GOOGLE_APPLICATION_CREDENTIALS" + value: "keystore/73713_java_it_service_account" +} + before_action { fetch_keystore { keystore_resource { keystore_config_id: 73713 - keyname: "java_it_service_account" + keyname: "java_firestore_it_service_account" } } } diff --git a/.kokoro/nightly/samples.cfg b/.kokoro/nightly/samples.cfg index b4b051cd01..20aabd55de 100644 --- a/.kokoro/nightly/samples.cfg +++ b/.kokoro/nightly/samples.cfg @@ -11,9 +11,15 @@ env_vars: { value: "samples" } +# TODO: remove this after we've migrated all tests and scripts env_vars: { key: "GCLOUD_PROJECT" - value: "gcloud-devel" + value: "java-docs-samples-testing" +} + +env_vars: { + key: "GOOGLE_CLOUD_PROJECT" + value: "java-docs-samples-testing" } env_vars: { diff --git a/.kokoro/presubmit/samples.cfg b/.kokoro/presubmit/samples.cfg index fa7b493d0b..1171aead01 100644 --- a/.kokoro/presubmit/samples.cfg +++ b/.kokoro/presubmit/samples.cfg @@ -11,14 +11,20 @@ env_vars: { value: "samples" } +# TODO: remove this after we've migrated all tests and scripts env_vars: { - key: "GCLOUD_PROJECT" - value: "gcloud-devel" + key: "GCLOUD_PROJECT" + value: "java-docs-samples-testing" } env_vars: { - key: "GOOGLE_APPLICATION_CREDENTIALS" - value: "keystore/73713_java_it_service_account" + key: "GOOGLE_CLOUD_PROJECT" + value: "java-docs-samples-testing" +} + +env_vars: { + key: "GOOGLE_APPLICATION_CREDENTIALS" + value: "keystore/73713_java_it_service_account" } before_action { diff --git a/CHANGELOG.md b/CHANGELOG.md index 19b2f3837b..573f0e59e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ # Changelog +## [1.34.0](https://www.github.com/googleapis/java-firestore/compare/v1.33.0...v1.34.0) (2020-05-29) + + +### Features + +* add support for BigDecimal to CustomClassMapper ([#196](https://www.github.com/googleapis/java-firestore/issues/196)) ([a471f1e](https://www.github.com/googleapis/java-firestore/commit/a471f1eed1e555e95b3d9bcda31ce0277e35a14a)) +* Create CODEOWNERS ([#207](https://www.github.com/googleapis/java-firestore/issues/207)) ([cd19eae](https://www.github.com/googleapis/java-firestore/commit/cd19eae68a4898a53c6c3cc8189eab30545a661d)) + + +### Bug Fixes + +* add RateLimiter ([#230](https://www.github.com/googleapis/java-firestore/issues/230)) ([47d4a11](https://www.github.com/googleapis/java-firestore/commit/47d4a11625d5888d6f31e494923853a08bb8af77)) +* catch null Firestore in system tests ([#215](https://www.github.com/googleapis/java-firestore/issues/215)) ([2a4a7b5](https://www.github.com/googleapis/java-firestore/commit/2a4a7b50d40ff1c165e3d359d5f4eaf929f6ffbc)) +* Fields used in whereIn should be equality filters ([#216](https://www.github.com/googleapis/java-firestore/issues/216)) ([4a62633](https://www.github.com/googleapis/java-firestore/commit/4a626333e5af0d70a4dc4853ed373dcf50ea0f4a)) +* replace usages of transform proto with update_transform ([#213](https://www.github.com/googleapis/java-firestore/issues/213)) ([46a3c51](https://www.github.com/googleapis/java-firestore/commit/46a3c51386b57f20bd65c564e93181e9ce399e2b)) +* support array of references for IN queries ([#211](https://www.github.com/googleapis/java-firestore/issues/211)) ([b376003](https://www.github.com/googleapis/java-firestore/commit/b3760032952529f148065928c3bf13ff73a34edd)) + + +### Dependencies + +* update core dependencies to v1.93.5 ([#229](https://www.github.com/googleapis/java-firestore/issues/229)) ([b078213](https://www.github.com/googleapis/java-firestore/commit/b078213209f3936cfe9c9e2cdea040c1262621d4)) +* update dependency com.google.api:api-common to v1.9.1 ([#228](https://www.github.com/googleapis/java-firestore/issues/228)) ([7e4568d](https://www.github.com/googleapis/java-firestore/commit/7e4568d8b3f0fc6f591640ccc2d646eb2764e572)) +* update dependency com.google.api.grpc:proto-google-common-protos to v1.18.0 ([#204](https://www.github.com/googleapis/java-firestore/issues/204)) ([1e05de4](https://www.github.com/googleapis/java-firestore/commit/1e05de4ecfde055a1c84c2f6dd338604b8580a61)) +* update dependency com.google.cloud:google-cloud-conformance-tests to v0.0.10 ([#197](https://www.github.com/googleapis/java-firestore/issues/197)) ([69372af](https://www.github.com/googleapis/java-firestore/commit/69372af7253564691b291766e2bf4d80e9ecc770)) +* update dependency com.google.guava:guava-bom to v29 ([#180](https://www.github.com/googleapis/java-firestore/issues/180)) ([3c204b4](https://www.github.com/googleapis/java-firestore/commit/3c204b42ddfbe435ac095368d1e695ed282280bd)) +* update dependency io.grpc:grpc-bom to v1.29.0 ([#206](https://www.github.com/googleapis/java-firestore/issues/206)) ([5d8c50f](https://www.github.com/googleapis/java-firestore/commit/5d8c50f105649100abf4fa7a6882bb0469ccbf8f)) +* update dependency org.threeten:threetenbp to v1.4.4 ([#194](https://www.github.com/googleapis/java-firestore/issues/194)) ([c867bd5](https://www.github.com/googleapis/java-firestore/commit/c867bd5772aa4a4710c622546e69fdc0f1ca22b6)) +* update jackson dependencies to v2.11.0 ([#195](https://www.github.com/googleapis/java-firestore/issues/195)) ([5066812](https://www.github.com/googleapis/java-firestore/commit/50668126e99422cc9498b317c9c76a80a8bf7b30)) +* update protobuf.version to v3.12.0 ([#220](https://www.github.com/googleapis/java-firestore/issues/220)) ([2c0b35d](https://www.github.com/googleapis/java-firestore/commit/2c0b35dfc5786b986b5301a00f06177f527496c3)) +* update protobuf.version to v3.12.2 ([#226](https://www.github.com/googleapis/java-firestore/issues/226)) ([2eeea19](https://www.github.com/googleapis/java-firestore/commit/2eeea193d7eb54b1efa92b4d5dd996c170048a73)) + + +### Documentation + +* update README to include code formatting ([#209](https://www.github.com/googleapis/java-firestore/issues/209)) ([04f8b3b](https://www.github.com/googleapis/java-firestore/commit/04f8b3b0f873c2f1988c184de1e5268e0de9053f)) + ## [1.33.0](https://www.github.com/googleapis/java-firestore/compare/v1.32.5...v1.33.0) (2020-04-08) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 085021ddec..7e3c60a735 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,6 +101,15 @@ mvn -Penable-samples clean verify 2. [Activate](#profile-activation) the profile. 3. Define your samples in a normal Maven project in the `samples/` directory +### Code Formatting + +Code in this repo is formatted with [google-java-format] +(https://github.com/google/google-java-format). +To run formatting on your project, you can run: +``` +mvn com.coveo:fmt-maven-plugin:format +``` + ### Profile Activation To include code samples when building and testing the project, enable the diff --git a/README.md b/README.md index 9dc0686b40..d2068fc08f 100644 --- a/README.md +++ b/README.md @@ -35,16 +35,16 @@ If you are using Maven without a BOM, add this to your dependencies. com.google.cloud google-cloud-firestore - 1.33.0 + 1.34.0 ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-firestore:1.33.0' +compile 'com.google.cloud:google-cloud-firestore:1.34.0' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "1.33.0" +libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "1.34.0" ``` [//]: # ({x-version-update-end}) diff --git a/google-cloud-firestore-bom/pom.xml b/google-cloud-firestore-bom/pom.xml index 8d25238d6b..6cba83c989 100644 --- a/google-cloud-firestore-bom/pom.xml +++ b/google-cloud-firestore-bom/pom.xml @@ -3,12 +3,12 @@ 4.0.0 com.google.cloud google-cloud-firestore-bom - 1.33.0 + 1.34.0 pom com.google.cloud google-cloud-shared-config - 0.5.0 + 0.6.0 Google Cloud Firestore BOM @@ -63,37 +63,37 @@ com.google.cloud google-cloud-firestore - 1.33.0 + 1.34.0 com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 com.google.api.grpc proto-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 com.google.api.grpc grpc-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 diff --git a/google-cloud-firestore/EnableAutoValue.txt b/google-cloud-firestore/EnableAutoValue.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/google-cloud-firestore/pom.xml b/google-cloud-firestore/pom.xml index 558e0a9c77..36865f50fb 100644 --- a/google-cloud-firestore/pom.xml +++ b/google-cloud-firestore/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 google-cloud-firestore - 1.33.0 + 1.34.0 jar Google Cloud Firestore https://github.com/googleapis/java-firestore/tree/master @@ -15,7 +15,7 @@ com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 google-cloud-firestore @@ -162,13 +162,13 @@ com.google.cloud google-cloud-conformance-tests - 0.0.9 + 0.0.11 test com.fasterxml.jackson.core jackson-databind - 2.10.3 + 2.11.0 test @@ -215,6 +215,15 @@ + + + + org.codehaus.mojo + flatten-maven-plugin + + + + java9 @@ -225,6 +234,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java index a3c8784b73..6c685767ec 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java @@ -35,6 +35,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -117,11 +118,13 @@ private static Object serialize(T o, ErrorPath path) { } else if (o instanceof Number) { if (o instanceof Long || o instanceof Integer || o instanceof Double || o instanceof Float) { return o; + } else if (o instanceof BigDecimal) { + return String.valueOf(o); } else { throw serializeError( path, String.format( - "Numbers of type %s are not supported, please use an int, long, float or double", + "Numbers of type %s are not supported, please use an int, long, float, double or BigDecimal", o.getClass().getSimpleName())); } } else if (o instanceof String) { @@ -324,6 +327,8 @@ private static T deserializeToPrimitive( return (T) convertDouble(o, context); } else if (Long.class.isAssignableFrom(clazz) || long.class.isAssignableFrom(clazz)) { return (T) convertLong(o, context); + } else if (BigDecimal.class.isAssignableFrom(clazz)) { + return (T) convertBigDecimal(o, context); } else if (Float.class.isAssignableFrom(clazz) || float.class.isAssignableFrom(clazz)) { return (T) (Float) convertDouble(o, context).floatValue(); } else { @@ -462,6 +467,24 @@ private static Double convertDouble(Object o, DeserializeContext context) { } } + private static BigDecimal convertBigDecimal(Object o, DeserializeContext context) { + if (o instanceof Integer) { + return BigDecimal.valueOf(((Integer) o).intValue()); + } else if (o instanceof Long) { + return BigDecimal.valueOf(((Long) o).longValue()); + } else if (o instanceof Double) { + return BigDecimal.valueOf(((Double) o).doubleValue()).abs(); + } else if (o instanceof BigDecimal) { + return (BigDecimal) o; + } else if (o instanceof String) { + return new BigDecimal((String) o); + } else { + throw deserializeError( + context.errorPath, + "Failed to convert a value of type " + o.getClass().getName() + " to BigDecimal"); + } + } + private static Boolean convertBoolean(Object o, DeserializeContext context) { if (o instanceof Boolean) { return (Boolean) o; diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentTransform.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentTransform.java index a90b0ec5ec..d175919eba 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentTransform.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentTransform.java @@ -17,7 +17,7 @@ package com.google.cloud.firestore; import com.google.firestore.v1.DocumentTransform.FieldTransform; -import com.google.firestore.v1.Write; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -30,12 +30,9 @@ */ final class DocumentTransform { - private DocumentReference documentReference; private final SortedMap transforms; // Sorted for testing. - private DocumentTransform( - DocumentReference documentReference, SortedMap transforms) { - this.documentReference = documentReference; + private DocumentTransform(SortedMap transforms) { this.transforms = transforms; } @@ -61,7 +58,7 @@ static DocumentTransform fromFieldPathMap( } } - return new DocumentTransform(documentReference, transforms); + return new DocumentTransform(transforms); } private static SortedMap extractFromMap( @@ -116,11 +113,7 @@ Set getFields() { return Collections.unmodifiableSet(transforms.keySet()); } - Write.Builder toPb() { - Write.Builder write = Write.newBuilder(); - com.google.firestore.v1.DocumentTransform.Builder transform = write.getTransformBuilder(); - transform.addAllFieldTransforms(transforms.values()); - transform.setDocument(documentReference.getName()); - return write; + Collection toPb() { + return transforms.values(); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java index aa5ea84e5f..8f1f0f82de 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java @@ -104,7 +104,7 @@ Value encodeValue() { return encodedValue; } - abstract boolean isEqualsFilter(); + abstract boolean isInequalityFilter(); abstract Filter toProto(); } @@ -117,8 +117,8 @@ private static class UnaryFilter extends FieldFilter { } @Override - boolean isEqualsFilter() { - return true; + boolean isInequalityFilter() { + return false; } Filter toProto() { @@ -148,8 +148,11 @@ private static class ComparisonFilter extends FieldFilter { } @Override - boolean isEqualsFilter() { - return operator.equals(EQUAL); + boolean isInequalityFilter() { + return operator.equals(GREATER_THAN) + || operator.equals(GREATER_THAN_OR_EQUAL) + || operator.equals(LESS_THAN) + || operator.equals(LESS_THAN_OR_EQUAL); } Filter toProto() { @@ -306,7 +309,7 @@ private ImmutableList createImplicitOrderBy() { if (implicitOrders.isEmpty()) { // If no explicit ordering is specified, use the first inequality to define an implicit order. for (FieldFilter fieldFilter : options.getFieldFilters()) { - if (!fieldFilter.isEqualsFilter()) { + if (fieldFilter.isInequalityFilter()) { implicitOrders.add(new FieldOrder(fieldFilter.fieldPath, Direction.ASCENDING)); break; } @@ -407,8 +410,10 @@ private Object convertReference(Object fieldValue) { reference = (DocumentReference) fieldValue; } else { throw new IllegalArgumentException( - "The corresponding value for FieldPath.documentId() must be a String or a " - + "DocumentReference."); + String.format( + "The corresponding value for FieldPath.documentId() must be a String or a " + + "DocumentReference, but was: %s.", + fieldValue.toString())); } if (!basePath.isPrefixOf(reference.getResourcePath())) { @@ -709,7 +714,27 @@ public Query whereIn(@Nonnull FieldPath fieldPath, @Nonnull List extends Objec private Query whereHelper( FieldPath fieldPath, StructuredQuery.FieldFilter.Operator operator, Object value) { if (fieldPath.equals(FieldPath.DOCUMENT_ID)) { - value = this.convertReference(value); + if (operator == ARRAY_CONTAINS || operator == ARRAY_CONTAINS_ANY) { + throw new IllegalArgumentException( + String.format( + "Invalid query. You cannot perform '%s' queries on FieldPath.documentId().", + operator.toString())); + } else if (operator == IN) { + if (!(value instanceof List) || ((List>) value).isEmpty()) { + throw new IllegalArgumentException( + String.format( + "Invalid Query. A non-empty array is required for '%s' filters.", + operator.toString())); + } + List referenceList = new ArrayList<>(); + for (Object arrayValue : (List) value) { + Object convertedValue = this.convertReference(arrayValue); + referenceList.add(convertedValue); + } + value = referenceList; + } else { + value = this.convertReference(value); + } } Builder newOptions = options.toBuilder(); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/RateLimiter.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/RateLimiter.java new file mode 100644 index 0000000000..7e617c546a --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/RateLimiter.java @@ -0,0 +1,138 @@ +/* + * Copyright 2020 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; + +import com.google.common.base.Preconditions; +import java.util.Date; + +/** + * A helper that uses the Token Bucket algorithm to rate limit the number of operations that can be + * made in a second. + * + * Before a given request containing a number of operations can proceed, RateLimiter determines + * doing so stays under the provided rate limits. It can also determine how much time is required + * before a request can be made. + * + * RateLimiter can also implement a gradually increasing rate limit. This is used to enforce the + * 500/50/5 rule. + * + * @see Ramping up + * traffic + */ +class RateLimiter { + private final int initialCapacity; + private final double multiplier; + private final int multiplierMillis; + private final long startTimeMillis; + + private int availableTokens; + private long lastRefillTimeMillis; + + RateLimiter(int initialCapacity, int multiplier, int multiplierMillis) { + this(initialCapacity, multiplier, multiplierMillis, new Date().getTime()); + } + + /** + * @param initialCapacity Initial maximum number of operations per second. + * @param multiplier Rate by which to increase the capacity. + * @param multiplierMillis How often the capacity should increase in milliseconds. + * @param startTimeMillis The starting time in epoch milliseconds that the rate limit is based on. + * Used for testing the limiter. + */ + RateLimiter(int initialCapacity, double multiplier, int multiplierMillis, long startTimeMillis) { + this.initialCapacity = initialCapacity; + this.multiplier = multiplier; + this.multiplierMillis = multiplierMillis; + this.startTimeMillis = startTimeMillis; + + this.availableTokens = initialCapacity; + this.lastRefillTimeMillis = startTimeMillis; + } + + public boolean tryMakeRequest(int numOperations) { + return tryMakeRequest(numOperations, new Date(0).getTime()); + } + + /** + * Tries to make the number of operations. Returns true if the request succeeded and false + * otherwise. + * + * @param requestTimeMillis The time used to calculate the number of available tokens. Used for + * testing the limiter. + */ + public boolean tryMakeRequest(int numOperations, long requestTimeMillis) { + refillTokens(requestTimeMillis); + if (numOperations <= availableTokens) { + availableTokens -= numOperations; + return true; + } + return false; + } + + public long getNextRequestDelayMs(int numOperations) { + return getNextRequestDelayMs(numOperations, new Date().getTime()); + } + + /** + * Returns the number of ms needed to make a request with the provided number of operations. + * Returns 0 if the request can be made with the existing capacity. Returns -1 if the request is + * not possible with the current capacity. + * + * @param requestTimeMillis The time used to calculate the number of available tokens. Used for + * testing the limiter. + */ + public long getNextRequestDelayMs(int numOperations, long requestTimeMillis) { + if (numOperations < availableTokens) { + return 0; + } + + int capacity = calculateCapacity(requestTimeMillis); + if (capacity < numOperations) { + return -1; + } + + int requiredTokens = numOperations - availableTokens; + return (long) Math.ceil((double) (requiredTokens * 1000) / capacity); + } + + /** + * Refills the number of available tokens based on how much time has elapsed since the last time + * the tokens were refilled. + * + * @param requestTimeMillis The time used to calculate the number of available tokens. Used for + * testing the limiter. + */ + private void refillTokens(long requestTimeMillis) { + Preconditions.checkArgument( + requestTimeMillis >= lastRefillTimeMillis, + "Request time should not be before the last token refill time"); + long elapsedTime = requestTimeMillis - lastRefillTimeMillis; + int capacity = calculateCapacity(requestTimeMillis); + int tokensToAdd = (int) ((elapsedTime * capacity) / 1000); + if (tokensToAdd > 0) { + availableTokens = Math.min(capacity, availableTokens + tokensToAdd); + lastRefillTimeMillis = requestTimeMillis; + } + } + + public int calculateCapacity(long requestTimeMillis) { + long millisElapsed = requestTimeMillis - startTimeMillis; + int operationsPerSecond = + (int) (Math.pow(multiplier, (int) (millisElapsed / multiplierMillis)) * initialCapacity); + return operationsPerSecond; + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 599c0ce536..c809160994 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -31,7 +31,6 @@ import io.opencensus.trace.Tracing; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -46,19 +45,13 @@ */ public abstract class UpdateBuilder { - private static class Mutation { - Write.Builder document; - Write.Builder transform; - com.google.firestore.v1.Precondition precondition; - } - final FirestoreImpl firestore; - private final List mutations; + private final List writes; private boolean committed; UpdateBuilder(FirestoreImpl firestore) { this.firestore = firestore; - this.mutations = new ArrayList<>(); + this.writes = new ArrayList<>(); } /** @@ -116,6 +109,7 @@ public T create( private T performCreate( @Nonnull DocumentReference documentReference, @Nonnull Map fields) { + verifyNotCommitted(); DocumentSnapshot documentSnapshot = DocumentSnapshot.fromObject( firestore, documentReference, fields, UserDataConverter.NO_DELETES); @@ -123,27 +117,21 @@ private T performCreate( DocumentTransform.fromFieldPathMap( documentReference, convertToFieldPaths(fields, /* splitOnDots= */ false)); - Mutation mutation = addMutation(); - mutation.precondition = Precondition.exists(false).toPb(); - - if (!documentSnapshot.isEmpty() || documentTransform.isEmpty()) { - mutation.document = documentSnapshot.toPb(); - } + Write.Builder write = documentSnapshot.toPb(); + write.setCurrentDocument(Precondition.exists(false).toPb()); if (!documentTransform.isEmpty()) { - mutation.transform = documentTransform.toPb(); + write.addAllUpdateTransforms(documentTransform.toPb()); } + writes.add(write); + return (T) this; } - /** Adds a new mutation to the batch. */ - private Mutation addMutation() { + private void verifyNotCommitted() { Preconditions.checkState( !committed, "Cannot modify a WriteBatch that has already been committed."); - Mutation mutation = new Mutation(); - mutations.add(mutation); - return mutation; } /** @@ -233,6 +221,7 @@ private T performSet( @Nonnull DocumentReference documentReference, @Nonnull Map fields, @Nonnull SetOptions options) { + verifyNotCommitted(); Map documentData; if (options.getFieldMask() != null) { @@ -248,31 +237,25 @@ private T performSet( DocumentTransform documentTransform = DocumentTransform.fromFieldPathMap(documentReference, documentData); - if (options.isMerge()) { - if (options.getFieldMask() != null) { - List fieldMask = new ArrayList<>(options.getFieldMask()); - fieldMask.removeAll(documentTransform.getFields()); - documentMask = new FieldMask(fieldMask); - } else { - documentMask = FieldMask.fromObject(fields); - } + if (options.getFieldMask() != null) { + List fieldMask = new ArrayList<>(options.getFieldMask()); + fieldMask.removeAll(documentTransform.getFields()); + documentMask = new FieldMask(fieldMask); + } else if (options.isMerge()) { + documentMask = FieldMask.fromObject(fields); } - Mutation mutation = addMutation(); - - boolean hasDocumentData = !documentSnapshot.isEmpty() || !documentMask.isEmpty(); - - if (!options.isMerge()) { - mutation.document = documentSnapshot.toPb(); - } else if (hasDocumentData || documentTransform.isEmpty()) { - mutation.document = documentSnapshot.toPb(); - mutation.document.setUpdateMask(documentMask.toPb()); + Write.Builder write = documentSnapshot.toPb(); + if (!documentTransform.isEmpty()) { + write.addAllUpdateTransforms(documentTransform.toPb()); } - if (!documentTransform.isEmpty()) { - mutation.transform = documentTransform.toPb(); + if (options.isMerge() || options.getFieldMask() != null) { + write.setUpdateMask(documentMask.toPb()); } + writes.add(write); + return (T) this; } @@ -507,6 +490,7 @@ private T performUpdate( @Nonnull DocumentReference documentReference, @Nonnull final Map fields, @Nonnull Precondition precondition) { + verifyNotCommitted(); Preconditions.checkArgument(!fields.isEmpty(), "Data for update() cannot be empty."); Map deconstructedMap = expandObject(fields); @@ -532,17 +516,14 @@ public boolean allowTransform() { fieldPaths.removeAll(documentTransform.getFields()); FieldMask fieldMask = new FieldMask(fieldPaths); - Mutation mutation = addMutation(); - mutation.precondition = precondition.toPb(); - - if (!documentSnapshot.isEmpty() || !fieldMask.isEmpty()) { - mutation.document = documentSnapshot.toPb(); - mutation.document.setUpdateMask(fieldMask.toPb()); - } + Write.Builder write = documentSnapshot.toPb(); + write.setCurrentDocument(precondition.toPb()); + write.setUpdateMask(fieldMask.toPb()); if (!documentTransform.isEmpty()) { - mutation.transform = documentTransform.toPb(); + write.addAllUpdateTransforms(documentTransform.toPb()); } + writes.add(write); return (T) this; } @@ -573,12 +554,13 @@ public T delete(@Nonnull DocumentReference documentReference) { private T performDelete( @Nonnull DocumentReference documentReference, @Nonnull Precondition precondition) { - Mutation mutation = addMutation(); - mutation.document = Write.newBuilder().setDelete(documentReference.getName()); + verifyNotCommitted(); + Write.Builder write = Write.newBuilder().setDelete(documentReference.getName()); if (!precondition.isEmpty()) { - mutation.precondition = precondition.toPb(); + write.setCurrentDocument(precondition.toPb()); } + writes.add(write); return (T) this; } @@ -589,28 +571,13 @@ ApiFuture> commit(@Nullable ByteString transactionId) { .getCurrentSpan() .addAnnotation( "CloudFirestore.Commit", - ImmutableMap.of("numDocuments", AttributeValue.longAttributeValue(mutations.size()))); + ImmutableMap.of("numDocuments", AttributeValue.longAttributeValue(writes.size()))); final CommitRequest.Builder request = CommitRequest.newBuilder(); request.setDatabase(firestore.getDatabaseName()); - for (Mutation mutation : mutations) { - Preconditions.checkState( - mutation.document != null || mutation.transform != null, - "Either a write or transform must be set"); - - if (mutation.precondition != null) { - (mutation.document != null ? mutation.document : mutation.transform) - .setCurrentDocument(mutation.precondition); - } - - if (mutation.document != null) { - request.addWrites(mutation.document); - } - - if (mutation.transform != null) { - request.addWrites(mutation.transform); - } + for (Write.Builder write : writes) { + request.addWrites(write); } if (transactionId != null) { @@ -632,29 +599,8 @@ public List apply(CommitResponse commitResponse) { List result = new ArrayList<>(); - Preconditions.checkState( - request.getWritesCount() == writeResults.size(), - "Expected one write result per operation, but got %s results for %s operations.", - writeResults.size(), - request.getWritesCount()); - - Iterator mutationIterator = mutations.iterator(); - Iterator responseIterator = - writeResults.iterator(); - - while (mutationIterator.hasNext()) { - Mutation mutation = mutationIterator.next(); - - // Don't return both write results for a write that contains a transform, as the fact - // that we have to split one write operation into two distinct write requests is an - // implementation detail. - if (mutation.document != null && mutation.transform != null) { - // The document transform is always sent last and produces the latest update time. - responseIterator.next(); - } - - result.add( - WriteResult.fromProto(responseIterator.next(), commitResponse.getCommitTime())); + for (com.google.firestore.v1.WriteResult writeResult : writeResults) { + result.add(WriteResult.fromProto(writeResult, commitResponse.getCommitTime())); } return result; @@ -665,11 +611,11 @@ public List apply(CommitResponse commitResponse) { /** Checks whether any updates have been queued. */ boolean isEmpty() { - return mutations.isEmpty(); + return writes.isEmpty(); } - /** Get the number of mutations. */ + /** Get the number of writes. */ public int getMutationsSize() { - return mutations.size(); + return writes.size(); } } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java index 7110c270fc..54be303b36 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java @@ -20,7 +20,6 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.ALL_SUPPORTED_TYPES_OBJECT; import static com.google.cloud.firestore.LocalFirestoreHelper.ALL_SUPPORTED_TYPES_PROTO; import static com.google.cloud.firestore.LocalFirestoreHelper.BLOB; -import static com.google.cloud.firestore.LocalFirestoreHelper.CREATE_PRECONDITION; import static com.google.cloud.firestore.LocalFirestoreHelper.DATE; import static com.google.cloud.firestore.LocalFirestoreHelper.DOCUMENT_NAME; import static com.google.cloud.firestore.LocalFirestoreHelper.DOCUMENT_PATH; @@ -28,7 +27,6 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.GEO_POINT; import static com.google.cloud.firestore.LocalFirestoreHelper.NESTED_CLASS_OBJECT; import static com.google.cloud.firestore.LocalFirestoreHelper.SERVER_TIMESTAMP_PROTO; -import static com.google.cloud.firestore.LocalFirestoreHelper.SERVER_TIMESTAMP_TRANSFORM; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_DELETE_COMMIT_RESPONSE; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_MAP; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_OBJECT; @@ -180,19 +178,19 @@ public void doesNotSerializeAdvancedNumberTypes() { pojo.bigIntegerValue = new BigInteger("0"); expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type BigInteger are not supported, please use an int, long, float or double (found in field 'bigIntegerValue')"); + "Could not serialize object. Numbers of type BigInteger are not supported, please use an int, long, float, double or BigDecimal (found in field 'bigIntegerValue')"); pojo = new InvalidPOJO(); pojo.byteValue = 0; expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type Byte are not supported, please use an int, long, float or double (found in field 'byteValue')"); + "Could not serialize object. Numbers of type Byte are not supported, please use an int, long, float, double or BigDecimal (found in field 'byteValue')"); pojo = new InvalidPOJO(); pojo.shortValue = 0; expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type Short are not supported, please use an int, long, float or double (found in field 'shortValue')"); + "Could not serialize object. Numbers of type Short are not supported, please use an int, long, float, double or BigDecimal (found in field 'shortValue')"); for (Map.Entry testCase : expectedErrorMessages.entrySet()) { try { @@ -406,8 +404,8 @@ public void createWithServerTimestamp() throws Exception { CommitRequest create = commit( - transform( - CREATE_PRECONDITION, "foo", serverTimestamp(), "inner.bar", serverTimestamp())); + create(Collections.emptyMap()), + transform("foo", serverTimestamp(), "inner.bar", serverTimestamp())); List commitRequests = commitCapture.getAllValues(); assertCommitEquals(create, commitRequests.get(0)); @@ -424,7 +422,10 @@ public void setWithServerTimestamp() throws Exception { documentReference.set(LocalFirestoreHelper.SERVER_TIMESTAMP_MAP).get(); documentReference.set(LocalFirestoreHelper.SERVER_TIMESTAMP_OBJECT).get(); - CommitRequest set = commit(set(SERVER_TIMESTAMP_PROTO), SERVER_TIMESTAMP_TRANSFORM); + CommitRequest set = + commit( + set(SERVER_TIMESTAMP_PROTO), + transform("foo", serverTimestamp(), "inner.bar", serverTimestamp())); List commitRequests = commitCapture.getAllValues(); assertCommitEquals(set, commitRequests.get(0)); @@ -443,7 +444,7 @@ public void updateWithServerTimestamp() throws Exception { CommitRequest update = commit( update(Collections.emptyMap(), Collections.singletonList("inner")), - SERVER_TIMESTAMP_TRANSFORM); + transform("foo", serverTimestamp(), "inner.bar", serverTimestamp())); assertCommitEquals(update, commitCapture.getValue()); @@ -452,8 +453,11 @@ public void updateWithServerTimestamp() throws Exception { update = commit( - transform( - UPDATE_PRECONDITION, "foo", serverTimestamp(), "inner.bar", serverTimestamp())); + update( + Collections.emptyMap(), + new ArrayList(), + UPDATE_PRECONDITION), + transform("foo", serverTimestamp(), "inner.bar", serverTimestamp())); assertCommitEquals(update, commitCapture.getValue()); } @@ -472,7 +476,10 @@ public void mergeWithServerTimestamps() throws Exception { .set(LocalFirestoreHelper.SERVER_TIMESTAMP_OBJECT, SetOptions.mergeFields("inner.bar")) .get(); - CommitRequest set = commit(transform("inner.bar", serverTimestamp())); + CommitRequest set = + commit( + set(SERVER_TIMESTAMP_PROTO, new ArrayList()), + transform("inner.bar", serverTimestamp())); List commitRequests = commitCapture.getAllValues(); assertCommitEquals(set, commitRequests.get(0)); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java index 0366f7a073..9deea1d745 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java @@ -19,13 +19,13 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_OBJECT; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_PROTO; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_VALUE; -import static com.google.cloud.firestore.LocalFirestoreHelper.UPDATE_PRECONDITION; import static com.google.cloud.firestore.LocalFirestoreHelper.arrayRemove; import static com.google.cloud.firestore.LocalFirestoreHelper.arrayUnion; import static com.google.cloud.firestore.LocalFirestoreHelper.commit; import static com.google.cloud.firestore.LocalFirestoreHelper.commitResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.getAllResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.transform; +import static com.google.cloud.firestore.LocalFirestoreHelper.update; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; @@ -40,6 +40,9 @@ import com.google.firestore.v1.CommitRequest; import com.google.firestore.v1.CommitResponse; import com.google.firestore.v1.ListCollectionIdsRequest; +import com.google.firestore.v1.Value; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import org.junit.Test; @@ -196,7 +199,9 @@ public void arrayUnionWithPojo() throws ExecutionException, InterruptedException doc.update("array", FieldValue.arrayUnion(SINGLE_FIELD_OBJECT)).get(); CommitRequest expectedRequest = - commit(transform(UPDATE_PRECONDITION, "array", arrayUnion(SINGLE_FIELD_VALUE))); + commit( + update(Collections.emptyMap(), new ArrayList()), + transform("array", arrayUnion(SINGLE_FIELD_VALUE))); CommitRequest actualRequest = commitCapture.getValue(); assertEquals(expectedRequest, actualRequest); } @@ -212,7 +217,9 @@ public void arrayRemoveWithPojo() throws ExecutionException, InterruptedExceptio doc.update("array", FieldValue.arrayRemove(SINGLE_FIELD_OBJECT)).get(); CommitRequest expectedRequest = - commit(transform(UPDATE_PRECONDITION, "array", arrayRemove(SINGLE_FIELD_VALUE))); + commit( + update(Collections.emptyMap(), new ArrayList()), + transform("array", arrayRemove(SINGLE_FIELD_VALUE))); CommitRequest actualRequest = commitCapture.getValue(); assertEquals(expectedRequest, actualRequest); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java index 148ce55858..541af70341 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java @@ -38,7 +38,6 @@ import com.google.firestore.v1.DatabaseRootName; import com.google.firestore.v1.Document; import com.google.firestore.v1.DocumentMask; -import com.google.firestore.v1.DocumentTransform; import com.google.firestore.v1.DocumentTransform.FieldTransform; import com.google.firestore.v1.MapValue; import com.google.firestore.v1.Precondition; @@ -112,7 +111,6 @@ public final class LocalFirestoreHelper { public static final Map SERVER_TIMESTAMP_MAP; public static final ServerTimestamp SERVER_TIMESTAMP_OBJECT; public static final Map SERVER_TIMESTAMP_PROTO; - public static final Write SERVER_TIMESTAMP_TRANSFORM; public static final Map ALL_SUPPORTED_TYPES_MAP; public static final AllSupportedTypes ALL_SUPPORTED_TYPES_OBJECT; @@ -131,8 +129,6 @@ public final class LocalFirestoreHelper { public static final GeoPoint GEO_POINT; public static final Blob BLOB; - public static final Precondition CREATE_PRECONDITION; - public static final Precondition UPDATE_PRECONDITION; public static class SingleField { @@ -351,33 +347,21 @@ public static FieldTransform arrayRemove(Value... values) { .build(); } - public static Write transform( + public static List transform( String fieldPath, FieldTransform fieldTransform, Object... fieldPathOrTransform) { - return transform(null, fieldPath, fieldTransform, fieldPathOrTransform); - } - public static Write transform( - @Nullable Precondition precondition, - String fieldPath, - FieldTransform fieldTransform, - Object... fieldPathOrTransform) { - Write.Builder write = Write.newBuilder(); - DocumentTransform.Builder documentTransform = write.getTransformBuilder(); - documentTransform.setDocument(DOCUMENT_NAME); + List transforms = new ArrayList<>(); + FieldTransform.Builder transformBuilder = FieldTransform.newBuilder(); + transformBuilder.setFieldPath(fieldPath).mergeFrom(fieldTransform); - documentTransform.addFieldTransformsBuilder().setFieldPath(fieldPath).mergeFrom(fieldTransform); + transforms.add(transformBuilder.build()); for (int i = 0; i < fieldPathOrTransform.length; i += 2) { String path = (String) fieldPathOrTransform[i]; FieldTransform transform = (FieldTransform) fieldPathOrTransform[i + 1]; - documentTransform.addFieldTransformsBuilder().setFieldPath(path).mergeFrom(transform); - } - - if (precondition != null) { - write.setCurrentDocument(precondition); + transforms.add(FieldTransform.newBuilder().setFieldPath(path).mergeFrom(transform).build()); } - - return write.build(); + return transforms; } public static Write create(Map fields) { @@ -456,6 +440,10 @@ public static CommitRequest commit(Write... writes) { return commit(null, writes); } + public static CommitRequest commit(Write write, List transforms) { + return commit((String) null, write.toBuilder().addAllUpdateTransforms(transforms).build()); + } + public static StructuredQuery filter(StructuredQuery.FieldFilter.Operator operator) { return filter(operator, "foo", "bar"); } @@ -644,9 +632,8 @@ private static CommitRequest sortCommit(CommitRequest commit) { writes.setUpdateMask(DocumentMask.newBuilder().addAllFieldPaths(updateMask)); } - if (writes.getTransform().getFieldTransformsCount() > 0) { - ArrayList transformList = - new ArrayList<>(writes.getTransform().getFieldTransformsList()); + if (!writes.getUpdateTransformsList().isEmpty()) { + ArrayList transformList = new ArrayList<>(writes.getUpdateTransformsList()); Collections.sort( transformList, new Comparator() { @@ -655,7 +642,7 @@ public int compare(FieldTransform t1, FieldTransform t2) { return t1.getFieldPath().compareTo(t2.getFieldPath()); } }); - writes.setTransform(DocumentTransform.newBuilder().addAllFieldTransforms(transformList)); + writes.clearUpdateTransforms().addAllUpdateTransforms(transformList); } } @@ -787,8 +774,6 @@ public boolean equals(Object o) { mapValue.getMapValueBuilder(); SERVER_TIMESTAMP_PROTO = Collections.emptyMap(); SERVER_TIMESTAMP_OBJECT = new ServerTimestamp(); - SERVER_TIMESTAMP_TRANSFORM = - transform("foo", serverTimestamp(), "inner.bar", serverTimestamp()); ALL_SUPPORTED_TYPES_MAP = new HashMap<>(); ALL_SUPPORTED_TYPES_MAP.put("foo", "bar"); @@ -867,8 +852,6 @@ public boolean equals(Object o) { NESTED_CLASS_OBJECT = new NestedClass(); - CREATE_PRECONDITION = Precondition.newBuilder().setExists(false).build(); - UPDATE_PRECONDITION = Precondition.newBuilder().setExists(true).build(); UPDATED_POJO = map("model", (Object) UPDATE_SINGLE_FIELD_OBJECT); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java index 468a7f57a5..a64eb466cf 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java @@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableList; import com.google.firestore.v1.DatabaseRootName; import java.io.Serializable; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -39,6 +40,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; @@ -74,6 +76,31 @@ public double getValue() { } } + private static class BigDecimalBean { + private BigDecimal value; + + public BigDecimal getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BigDecimalBean bean = (BigDecimalBean) o; + return Objects.equals(value, bean.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } + private static class FloatBean { private float value; @@ -1049,6 +1076,41 @@ public void primitiveDeserializeDouble() { } } + @Test + public void primitiveDeserializeBigDecimal() { + BigDecimalBean beanBigdecimal = deserialize("{'value': 123}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(123), beanBigdecimal.value); + + beanBigdecimal = deserialize("{'value': '123'}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(123), beanBigdecimal.value); + + // Int + BigDecimalBean beanInt = deserialize("{'value': 1}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1), beanInt.value); + + // Long + BigDecimalBean beanLong = deserialize("{'value': 1234567890123}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1234567890123L), beanLong.value); + + // Double + BigDecimalBean beanDouble = deserialize("{'value': 1.1}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1.1), beanDouble.value); + + // Boolean + try { + deserialize("{'value': true}", BigDecimalBean.class); + fail("Should throw"); + } catch (RuntimeException e) { // ignore + } + + // String + try { + deserialize("{'value': 'foo'}", BigDecimalBean.class); + fail("Should throw"); + } catch (RuntimeException e) { // ignore + } + } + @Test public void primitiveDeserializeFloat() { FloatBean beanFloat = deserialize("{'value': 1.1}", FloatBean.class); @@ -1513,6 +1575,23 @@ public void serializeLongBean() { assertJson("{'value': 1234567890123}", serialize(bean)); } + @Test + public void serializeBigDecimalBean() { + BigDecimalBean bean = new BigDecimalBean(); + bean.value = BigDecimal.valueOf(1.1); + assertEquals(mapAnyType("value", "1.1"), serialize(bean)); + } + + @Test + public void bigDecimalRoundTrip() { + BigDecimal doubleMaxPlusOne = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.ONE); + BigDecimalBean a = new BigDecimalBean(); + a.value = doubleMaxPlusOne; + Map serialized = (Map) serialize(a); + BigDecimalBean b = convertToCustomClass(serialized, BigDecimalBean.class); + assertEquals(a, b); + } + @Test public void serializeBooleanBean() { BooleanBean bean = new BooleanBean(); @@ -1827,7 +1906,7 @@ public void shortsCantBeSerialized() { final ShortBean bean = new ShortBean(); bean.value = 1; assertExceptionContains( - "Numbers of type Short are not supported, please use an int, long, float or double (found in field 'value')", + "Numbers of type Short are not supported, please use an int, long, float, double or BigDecimal (found in field 'value')", new Runnable() { @Override public void run() { @@ -1841,7 +1920,7 @@ public void bytesCantBeSerialized() { final ByteBean bean = new ByteBean(); bean.value = 1; assertExceptionContains( - "Numbers of type Byte are not supported, please use an int, long, float or double (found in field 'value')", + "Numbers of type Byte are not supported, please use an int, long, float, double or BigDecimal (found in field 'value')", new Runnable() { @Override public void run() { @@ -2478,7 +2557,7 @@ public void serializationFailureIncludesPath() { } catch (RuntimeException e) { assertEquals( "Could not serialize object. Numbers of type Short are not supported, please use an int, " - + "long, float or double (found in field 'value.inner.value.short')", + + "long, float, double or BigDecimal (found in field 'value.inner.value.short')", e.getMessage()); } } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java index fa342778d0..a3315ca6de 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java @@ -40,6 +40,7 @@ import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.cloud.Timestamp; import com.google.cloud.firestore.spi.v1.FirestoreRpc; +import com.google.firestore.v1.ArrayValue; import com.google.firestore.v1.RunQueryRequest; import com.google.firestore.v1.StructuredQuery; import com.google.firestore.v1.StructuredQuery.Direction; @@ -164,7 +165,7 @@ public void limitToLastRequiresAtLeastOneOrderingConstraint() throws Exception { } @Test - public void limitToLastRejectsStream() throws Exception { + public void limitToLastRejectsStream() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -273,6 +274,110 @@ public void withFieldPathFilter() throws Exception { } } + @Test + public void inQueriesWithReferenceArray() throws Exception { + doAnswer(queryResponse()) + .when(firestoreMock) + .streamRequest( + runQuery.capture(), + streamObserverCapture.capture(), + Matchers.any()); + + query + .whereIn( + FieldPath.documentId(), + Arrays.asList("doc", firestoreMock.document("coll/doc"))) + .get() + .get(); + + Value value = + Value.newBuilder() + .setArrayValue( + ArrayValue.newBuilder() + .addValues(reference(DOCUMENT_NAME)) + .addValues(reference(DOCUMENT_NAME)) + .build()) + .build(); + RunQueryRequest expectedRequest = query(filter(Operator.IN, "__name__", value)); + + assertEquals(expectedRequest, runQuery.getValue()); + } + + @Test + public void inQueriesFieldsNotUsedInOrderBy() throws Exception { + doAnswer(queryResponse()) + .when(firestoreMock) + .streamRequest( + runQuery.capture(), + streamObserverCapture.capture(), + Matchers.any()); + + // Field "foo" used in `whereIn` should not appear in implicit orderBys in the resulting query. + query + .whereIn(FieldPath.of("foo"), Arrays.asList("value1", "value2")) + .startAt(SINGLE_FIELD_SNAPSHOT) + .get() + .get(); + + Value value = + Value.newBuilder() + .setArrayValue( + ArrayValue.newBuilder() + .addValues(Value.newBuilder().setStringValue("value1").build()) + .addValues(Value.newBuilder().setStringValue("value2").build()) + .build()) + .build(); + RunQueryRequest expectedRequest = + query( + filter(Operator.IN, "foo", value), + order("__name__", Direction.ASCENDING), + startAt(reference(DOCUMENT_NAME), true)); + + assertEquals(expectedRequest, runQuery.getValue()); + } + + @Test + public void validatesInQueries() { + try { + query.whereIn(FieldPath.documentId(), Arrays.asList("foo", 42)).get(); + fail(); + } catch (IllegalArgumentException e) { + assertEquals( + "The corresponding value for FieldPath.documentId() must be a String or a " + + "DocumentReference, but was: 42.", + e.getMessage()); + } + + try { + query.whereIn(FieldPath.documentId(), Arrays.asList()).get(); + fail(); + } catch (IllegalArgumentException e) { + assertEquals( + "Invalid Query. A non-empty array is required for 'IN' filters.", e.getMessage()); + } + } + + @Test + public void validatesQueryOperatorForFieldPathDocumentId() { + try { + query.whereArrayContains(FieldPath.documentId(), "bar"); + fail(); + } catch (IllegalArgumentException e) { + assertEquals( + "Invalid query. You cannot perform 'ARRAY_CONTAINS' queries on FieldPath.documentId().", + e.getMessage()); + } + + try { + query.whereArrayContainsAny(FieldPath.documentId(), Collections.singletonList("bar")); + fail(); + } catch (IllegalArgumentException e) { + assertEquals( + "Invalid query. You cannot perform 'ARRAY_CONTAINS_ANY' queries on FieldPath.documentId().", + e.getMessage()); + } + } + @Test public void withDocumentIdFilter() throws Exception { doAnswer(queryResponse()) @@ -375,7 +480,7 @@ public void withFieldPathSelect() throws Exception { } @Test - public void withDocumentSnapshotCursor() throws Exception { + public void withDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -396,7 +501,7 @@ public void withDocumentSnapshotCursor() throws Exception { } @Test - public void withDocumentIdAndDocumentSnapshotCursor() throws Exception { + public void withDocumentIdAndDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -417,7 +522,7 @@ public void withDocumentIdAndDocumentSnapshotCursor() throws Exception { } @Test - public void withExtractedDirectionForDocumentSnapshotCursor() throws Exception { + public void withExtractedDirectionForDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -440,7 +545,7 @@ public void withExtractedDirectionForDocumentSnapshotCursor() throws Exception { } @Test - public void withInequalityFilterForDocumentSnapshotCursor() throws Exception { + public void withInequalityFilterForDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -471,7 +576,7 @@ public void withInequalityFilterForDocumentSnapshotCursor() throws Exception { } @Test - public void withEqualityFilterForDocumentSnapshotCursor() throws Exception { + public void withEqualityFilterForDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -516,14 +621,14 @@ public void withStartAt() throws Exception { } @Test - public void withInvalidStartAt() throws Exception { + public void withInvalidStartAt() { try { query.orderBy(FieldPath.documentId()).startAt(42).get(); fail(); } catch (IllegalArgumentException e) { assertEquals( "The corresponding value for FieldPath.documentId() must be a String or a " - + "DocumentReference.", + + "DocumentReference, but was: 42.", e.getMessage()); } @@ -598,7 +703,7 @@ public void withEndAt() throws Exception { } @Test - public void withCollectionGroup() throws Exception { + public void withCollectionGroup() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -635,12 +740,12 @@ public void collectionGroupCannotContainSlashes() { } @Test(expected = IllegalStateException.class) - public void overspecifiedCursor() throws Exception { + public void overspecifiedCursor() { query.orderBy("foo").startAt("foo", "bar", "bar", "foo"); } @Test(expected = IllegalStateException.class) - public void orderByWithCursor() throws Exception { + public void orderByWithCursor() { query.startAt("foo").orderBy("foo"); } @@ -726,7 +831,7 @@ public void onCompleted() { } @Test - public void equalsTest() throws Exception { + public void equalsTest() { assertEquals(query.limit(42).offset(1337), query.offset(1337).limit(42)); assertEquals(query.limit(42).offset(1337).hashCode(), query.offset(1337).limit(42).hashCode()); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/RateLimiterTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/RateLimiterTest.java new file mode 100644 index 0000000000..291f607330 --- /dev/null +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/RateLimiterTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Date; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class RateLimiterTest { + private RateLimiter limiter; + + @Before + public void before() { + limiter = + new RateLimiter( + /* initialCapacity= */ 500, + /* multiplier= */ 1.5, + /* multiplierMillis= */ 5 * 60 * 1000, + /* startTime= */ new Date(0).getTime()); + } + + @Test + public void processRequestsFromCapacity() { + assertTrue(limiter.tryMakeRequest(250, new Date(0).getTime())); + assertTrue(limiter.tryMakeRequest(250, new Date(0).getTime())); + + // Once tokens have been used, further requests should fail. + assertFalse(limiter.tryMakeRequest(1, new Date(0).getTime())); + + // Tokens will only refill up to max capacity. + assertFalse(limiter.tryMakeRequest(501, new Date(1 * 1000).getTime())); + assertTrue(limiter.tryMakeRequest(500, new Date(1 * 1000).getTime())); + + // Tokens will refill incrementally based on number of ms elapsed. + assertFalse(limiter.tryMakeRequest(250, new Date(1 * 1000 + 499).getTime())); + assertTrue(limiter.tryMakeRequest(249, new Date(1 * 1000 + 500).getTime())); + + // Scales with multiplier. + assertFalse(limiter.tryMakeRequest(751, new Date((5 * 60 - 1) * 1000).getTime())); + assertFalse(limiter.tryMakeRequest(751, new Date(5 * 60 * 1000).getTime())); + assertTrue(limiter.tryMakeRequest(750, new Date(5 * 60 * 1000).getTime())); + + // Tokens will never exceed capacity. + assertFalse(limiter.tryMakeRequest(751, new Date((5 * 60 + 3) * 1000).getTime())); + + // Rejects requests made before lastRefillTime. + try { + limiter.tryMakeRequest(751, new Date((5 * 60 + 2) * 1000).getTime()); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Request time should not be before the last token refill time", e.getMessage()); + } + } + + @Test + public void calculatesMsForNextRequest() { + // Should return 0 if there are enough tokens for the request to be made. + long timestamp = new Date(0).getTime(); + assertEquals(0, limiter.getNextRequestDelayMs(500, timestamp)); + + // Should factor in remaining tokens when calculating the time. + assertTrue(limiter.tryMakeRequest(250, timestamp)); + assertEquals(500, limiter.getNextRequestDelayMs(500, timestamp)); + + // Once tokens have been used, should calculate time before next request. + timestamp = new Date(1 * 1000).getTime(); + assertTrue(limiter.tryMakeRequest(500, timestamp)); + assertEquals(200, limiter.getNextRequestDelayMs(100, timestamp)); + assertEquals(500, limiter.getNextRequestDelayMs(250, timestamp)); + assertEquals(1000, limiter.getNextRequestDelayMs(500, timestamp)); + assertEquals(-1, limiter.getNextRequestDelayMs(501, timestamp)); + + // Scales with multiplier. + timestamp = new Date(5 * 60 * 1000).getTime(); + assertTrue(limiter.tryMakeRequest(750, timestamp)); + assertEquals(334, limiter.getNextRequestDelayMs(250, timestamp)); + assertEquals(667, limiter.getNextRequestDelayMs(500, timestamp)); + assertEquals(1000, limiter.getNextRequestDelayMs(750, timestamp)); + assertEquals(-1, limiter.getNextRequestDelayMs(751, timestamp)); + } + + @Test + public void calculatesMaxOperations() { + assertEquals(500, limiter.calculateCapacity(new Date(0).getTime())); + assertEquals(750, limiter.calculateCapacity(new Date(5 * 60 * 1000).getTime())); + assertEquals(1125, limiter.calculateCapacity(new Date(10 * 60 * 1000).getTime())); + assertEquals(1687, limiter.calculateCapacity(new Date(15 * 60 * 1000).getTime())); + assertEquals(738945, limiter.calculateCapacity(new Date(90 * 60 * 1000).getTime())); + } +} diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java index 52b3150b87..f7b3c15dcf 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java @@ -219,7 +219,7 @@ public ApiFuture updateCallback(Transaction transaction) { } @Test - public void rollbackOnCallbackError() throws Exception { + public void rollbackOnCallbackError() { doReturn(beginResponse()) .doReturn(rollbackResponse()) .when(firestoreMock) @@ -250,7 +250,7 @@ public String updateCallback(Transaction transaction) throws Exception { } @Test - public void rollbackOnCallbackErrorAsync() throws Exception { + public void rollbackOnCallbackErrorAsync() { doReturn(beginResponse()) .doReturn(rollbackResponse()) .when(firestoreMock) @@ -281,7 +281,7 @@ public ApiFuture updateCallback(Transaction transaction) { } @Test - public void noRollbackOnBeginFailure() throws Exception { + public void noRollbackOnBeginFailure() { doReturn(ApiFutures.immediateFailedFuture(new Exception("Expected exception"))) .when(firestoreMock) .sendRequest(requestCapture.capture(), Matchers.>any()); @@ -309,7 +309,7 @@ public String updateCallback(Transaction transaction) { } @Test - public void noRollbackOnBeginFailureAsync() throws Exception { + public void noRollbackOnBeginFailureAsync() { doReturn(ApiFutures.immediateFailedFuture(new Exception("Expected exception"))) .when(firestoreMock) .sendRequest(requestCapture.capture(), Matchers.>any()); @@ -337,7 +337,7 @@ public ApiFuture updateCallback(Transaction transaction) { } @Test - public void limitsRetriesWithFailure() throws Exception { + public void limitsRetriesWithFailure() { RequestResponseMap requestResponseMap = new RequestResponseMap() { { diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java index 6eeb1dbd82..6b036e2f80 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java @@ -990,15 +990,15 @@ private ListenResponse docRemove(String docPath) { return response.build(); } - private void send(ListenResponse response) throws InterruptedException { + private void send(ListenResponse response) { streamObserverCapture.getValue().onNext(response); } - private void destroy(Code code) throws InterruptedException { + private void destroy(Code code) { streamObserverCapture.getValue().onError(new StatusException(io.grpc.Status.fromCode(code))); } - private void close() throws InterruptedException { + private void close() { streamObserverCapture.getValue().onCompleted(); } @@ -1006,7 +1006,7 @@ private void close() throws InterruptedException { private Answer newRequestObserver() { return new Answer() { @Override - public Object answer(InvocationOnMock invocationOnMock) throws Throwable { + public Object answer(InvocationOnMock invocationOnMock) { return new ApiStreamObserver() { @Override public void onNext(ListenRequest listenRequest) { diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WriteBatchTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WriteBatchTest.java index ac992db356..3e629324f3 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WriteBatchTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WriteBatchTest.java @@ -215,7 +215,7 @@ public void setDocumentWithFloat() throws Exception { @Test public void omitWriteResultForDocumentTransforms() throws Exception { - doReturn(commitResponse(2, 0)) + doReturn(commitResponse(1, 0)) .when(firestoreMock) .sendRequest( commitCapture.capture(), Matchers.>any()); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java index 69cf5cec81..cb9d69ec16 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java @@ -39,6 +39,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Joiner.MapJoiner; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.Range; @@ -83,6 +84,9 @@ public void before() { @AfterClass public static void afterClass() throws Exception { + Preconditions.checkNotNull( + firestore, + "Error instantiating Firestore. Check that the service account credentials were properly set."); firestore.close(); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java index a0379f061a..f50e346f18 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java @@ -55,6 +55,7 @@ import com.google.cloud.firestore.Transaction.Function; import com.google.cloud.firestore.WriteBatch; import com.google.cloud.firestore.WriteResult; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; @@ -104,6 +105,9 @@ public void before() { @After public void after() throws Exception { + Preconditions.checkNotNull( + firestore, + "Error instantiating Firestore. Check that the service account credentials were properly set."); firestore.close(); } @@ -227,6 +231,15 @@ public void setDocumentWithMerge() throws Exception { assertEquals(resultMap, documentSnapshot.getData()); } + @Test + public void setWithIncrementAndMerge() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("sum", 1L)).get(); + docRef.set(Collections.singletonMap("sum", FieldValue.increment(2)), SetOptions.merge()).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(3L, docSnap.get("sum")); + } + @Test public void mergeDocumentWithServerTimestamp() throws Exception { Map originalMap = LocalFirestoreHelper.map("a", "b"); @@ -1141,6 +1154,21 @@ public void inQueries() throws Exception { assertEquals(asList("a", "c"), querySnapshotToIds(querySnapshot)); } + @Test + public void inQueriesWithDocumentId() throws Exception { + DocumentReference doc1 = setDocument("a", map("count", 1)); + DocumentReference doc2 = setDocument("b", map("count", 2)); + setDocument("c", map("count", 3)); + + QuerySnapshot querySnapshot = + randomColl + .whereIn(FieldPath.documentId(), Arrays.asList(doc1.getId(), doc2)) + .get() + .get(); + + assertEquals(asList("a", "b"), querySnapshotToIds(querySnapshot)); + } + @Test public void arrayContainsAnyQueries() throws Exception { setDocument("a", map("array", asList(42))); diff --git a/grpc-google-cloud-firestore-admin-v1/pom.xml b/grpc-google-cloud-firestore-admin-v1/pom.xml index c188249dce..bdec741da2 100644 --- a/grpc-google-cloud-firestore-admin-v1/pom.xml +++ b/grpc-google-cloud-firestore-admin-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 grpc-google-cloud-firestore-admin-v1 GRPC library for grpc-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -66,6 +66,14 @@ + + + + org.codehaus.mojo + flatten-maven-plugin + + + @@ -77,6 +85,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/grpc-google-cloud-firestore-v1/pom.xml b/grpc-google-cloud-firestore-v1/pom.xml index 9eb92745b8..27a16aed24 100644 --- a/grpc-google-cloud-firestore-v1/pom.xml +++ b/grpc-google-cloud-firestore-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 grpc-google-cloud-firestore-v1 GRPC library for grpc-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -62,6 +62,14 @@ + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -72,6 +80,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/grpc-google-cloud-firestore-v1beta1/pom.xml b/grpc-google-cloud-firestore-v1beta1/pom.xml index 3a0942cfad..3c6ad0498b 100644 --- a/grpc-google-cloud-firestore-v1beta1/pom.xml +++ b/grpc-google-cloud-firestore-v1beta1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 grpc-google-cloud-firestore-v1beta1 GRPC library for grpc-google-cloud-firestore-v1beta1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -62,6 +62,14 @@ + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -72,6 +80,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/pom.xml b/pom.xml index dfefe5ddd8..d710f0100a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-firestore-parent pom - 1.33.0 + 1.34.0 Google Cloud Firestore Parent https://github.com/googleapis/java-firestore @@ -14,7 +14,7 @@ com.google.cloud google-cloud-shared-config - 0.5.0 + 0.6.0 @@ -152,15 +152,15 @@ github google-cloud-firestore-parent https://googleapis.dev/java/google-api-grpc/latest - 1.93.4 - 1.9.0 - 1.17.0 + 1.93.5 + 1.9.1 + 1.18.0 1.56.0 - 1.28.1 - 3.11.4 + 1.29.0 + 3.12.2 4.13 - 28.2-android - 1.4.3 + 29.0-android + 1.4.4 0.24.0 1.10.19 1.3.2 @@ -172,32 +172,32 @@ com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 com.google.api.grpc proto-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 com.google.api.grpc grpc-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 @@ -242,21 +242,11 @@ proto-google-common-protos ${google.common-protos.version} - - com.google.auto.value - auto-value-annotations - ${auto-value-annotation.version} - org.threeten threetenbp ${threeten.version} - - javax.annotation - javax.annotation-api - ${javax.annotations.version} - com.google.code.findbugs jsr305 @@ -285,7 +275,7 @@ com.fasterxml.jackson.core jackson-core - 2.10.3 + 2.11.0 com.google.protobuf @@ -340,89 +330,4 @@ google-cloud-firestore-bom - - - autovalue-java7 - - 1.7 - - - 1.7 - 1.4 - - - - - maven-compiler-plugin - - - - com.google.auto.value - auto-value - ${auto-value.version} - - - - - - - - - - - autovalue-java8 - - [1.8,) - - - 1.7 - ${auto-value.version} - 1.0-rc6 - - - - - maven-compiler-plugin - - - - com.google.auto.value - auto-value - ${auto-value.version} - - - - com.google.auto.service - auto-service-annotations - ${auto-service-annotations.version} - - - - - - - - - diff --git a/proto-google-cloud-firestore-admin-v1/pom.xml b/proto-google-cloud-firestore-admin-v1/pom.xml index ba91af15e3..2d90fd87d7 100644 --- a/proto-google-cloud-firestore-admin-v1/pom.xml +++ b/proto-google-cloud-firestore-admin-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 proto-google-cloud-firestore-admin-v1 PROTO library for proto-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -33,6 +33,14 @@ guava + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -43,6 +51,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/proto-google-cloud-firestore-v1/pom.xml b/proto-google-cloud-firestore-v1/pom.xml index 1c8965b48e..4bd5afde61 100644 --- a/proto-google-cloud-firestore-v1/pom.xml +++ b/proto-google-cloud-firestore-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 proto-google-cloud-firestore-v1 PROTO library for proto-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -33,6 +33,14 @@ guava + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -43,6 +51,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/proto-google-cloud-firestore-v1beta1/pom.xml b/proto-google-cloud-firestore-v1beta1/pom.xml index 7bdf7fbbda..75688760db 100644 --- a/proto-google-cloud-firestore-v1beta1/pom.xml +++ b/proto-google-cloud-firestore-v1beta1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 proto-google-cloud-firestore-v1beta1 PROTO library for proto-google-cloud-firestore-v1beta1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -33,6 +33,14 @@ guava + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -43,6 +51,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 9dac56d376..66332bcfc7 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -29,7 +29,7 @@ com.google.cloud google-cloud-firestore - 1.32.5 + 1.33.0 diff --git a/samples/pom.xml b/samples/pom.xml index e3e7172c5c..a6dc37816f 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -18,7 +18,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index b64c57442d..1bb19341a8 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -28,7 +28,7 @@ com.google.cloud google-cloud-firestore - 1.32.5 + 1.33.0 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 7dad559f25..69dcaed2f1 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -30,7 +30,7 @@ com.google.cloud libraries-bom - 4.4.1 + 5.5.0 pom import diff --git a/synth.metadata b/synth.metadata index 14ed5f1b2e..be495dc067 100644 --- a/synth.metadata +++ b/synth.metadata @@ -1,6 +1,12 @@ { - "updateTime": "2020-03-26T03:27:35.869486Z", "sources": [ + { + "git": { + "name": ".", + "remote": "https://github.com/googleapis/java-firestore.git", + "sha": "495f7f97405fcd2bff4d09e67ddbeb51615ea843" + } + }, { "git": { "name": "googleapis", @@ -13,7 +19,7 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "e36822bfa0acb355502dab391b8ef9c4f30208d8" + "sha": "52638600f387deb98efb5f9c85fec39e82aa9052" } } ], diff --git a/synth.py b/synth.py index 2f2a98d88c..07e0afb3b0 100644 --- a/synth.py +++ b/synth.py @@ -125,7 +125,8 @@ def generate_client(service, version, proto_path=None, bazel_target=None, packag 'README.md', # firestore uses a different project for its integration tests # due to the default project running datastore - '.kokoro/presubmit/integration.cfg' + '.kokoro/presubmit/integration.cfg', + '.kokoro/nightly/integration.cfg' ]) # Mark v1beta1 as deprecated diff --git a/versions.txt b/versions.txt index 7cdcd9ac35..2c3cdc9e56 100644 --- a/versions.txt +++ b/versions.txt @@ -1,12 +1,12 @@ # Format: # module:released-version:current-version -google-cloud-firestore:1.33.0:1.33.0 -google-cloud-firestore-bom:1.33.0:1.33.0 -google-cloud-firestore-parent:1.33.0:1.33.0 -grpc-google-cloud-firestore-admin-v1:1.33.0:1.33.0 -grpc-google-cloud-firestore-v1:1.33.0:1.33.0 -grpc-google-cloud-firestore-v1beta1:0.86.0:0.86.0 -proto-google-cloud-firestore-admin-v1:1.33.0:1.33.0 -proto-google-cloud-firestore-v1:1.33.0:1.33.0 -proto-google-cloud-firestore-v1beta1:0.86.0:0.86.0 +google-cloud-firestore:1.34.0:1.34.0 +google-cloud-firestore-bom:1.34.0:1.34.0 +google-cloud-firestore-parent:1.34.0:1.34.0 +grpc-google-cloud-firestore-admin-v1:1.34.0:1.34.0 +grpc-google-cloud-firestore-v1:1.34.0:1.34.0 +grpc-google-cloud-firestore-v1beta1:0.87.0:0.87.0 +proto-google-cloud-firestore-admin-v1:1.34.0:1.34.0 +proto-google-cloud-firestore-v1:1.34.0:1.34.0 +proto-google-cloud-firestore-v1beta1:0.87.0:0.87.0
Before a given request containing a number of operations can proceed, RateLimiter determines + * doing so stays under the provided rate limits. It can also determine how much time is required + * before a request can be made. + * + *
RateLimiter can also implement a gradually increasing rate limit. This is used to enforce the + * 500/50/5 rule. + * + * @see Ramping up + * traffic + */ +class RateLimiter { + private final int initialCapacity; + private final double multiplier; + private final int multiplierMillis; + private final long startTimeMillis; + + private int availableTokens; + private long lastRefillTimeMillis; + + RateLimiter(int initialCapacity, int multiplier, int multiplierMillis) { + this(initialCapacity, multiplier, multiplierMillis, new Date().getTime()); + } + + /** + * @param initialCapacity Initial maximum number of operations per second. + * @param multiplier Rate by which to increase the capacity. + * @param multiplierMillis How often the capacity should increase in milliseconds. + * @param startTimeMillis The starting time in epoch milliseconds that the rate limit is based on. + * Used for testing the limiter. + */ + RateLimiter(int initialCapacity, double multiplier, int multiplierMillis, long startTimeMillis) { + this.initialCapacity = initialCapacity; + this.multiplier = multiplier; + this.multiplierMillis = multiplierMillis; + this.startTimeMillis = startTimeMillis; + + this.availableTokens = initialCapacity; + this.lastRefillTimeMillis = startTimeMillis; + } + + public boolean tryMakeRequest(int numOperations) { + return tryMakeRequest(numOperations, new Date(0).getTime()); + } + + /** + * Tries to make the number of operations. Returns true if the request succeeded and false + * otherwise. + * + * @param requestTimeMillis The time used to calculate the number of available tokens. Used for + * testing the limiter. + */ + public boolean tryMakeRequest(int numOperations, long requestTimeMillis) { + refillTokens(requestTimeMillis); + if (numOperations <= availableTokens) { + availableTokens -= numOperations; + return true; + } + return false; + } + + public long getNextRequestDelayMs(int numOperations) { + return getNextRequestDelayMs(numOperations, new Date().getTime()); + } + + /** + * Returns the number of ms needed to make a request with the provided number of operations. + * Returns 0 if the request can be made with the existing capacity. Returns -1 if the request is + * not possible with the current capacity. + * + * @param requestTimeMillis The time used to calculate the number of available tokens. Used for + * testing the limiter. + */ + public long getNextRequestDelayMs(int numOperations, long requestTimeMillis) { + if (numOperations < availableTokens) { + return 0; + } + + int capacity = calculateCapacity(requestTimeMillis); + if (capacity < numOperations) { + return -1; + } + + int requiredTokens = numOperations - availableTokens; + return (long) Math.ceil((double) (requiredTokens * 1000) / capacity); + } + + /** + * Refills the number of available tokens based on how much time has elapsed since the last time + * the tokens were refilled. + * + * @param requestTimeMillis The time used to calculate the number of available tokens. Used for + * testing the limiter. + */ + private void refillTokens(long requestTimeMillis) { + Preconditions.checkArgument( + requestTimeMillis >= lastRefillTimeMillis, + "Request time should not be before the last token refill time"); + long elapsedTime = requestTimeMillis - lastRefillTimeMillis; + int capacity = calculateCapacity(requestTimeMillis); + int tokensToAdd = (int) ((elapsedTime * capacity) / 1000); + if (tokensToAdd > 0) { + availableTokens = Math.min(capacity, availableTokens + tokensToAdd); + lastRefillTimeMillis = requestTimeMillis; + } + } + + public int calculateCapacity(long requestTimeMillis) { + long millisElapsed = requestTimeMillis - startTimeMillis; + int operationsPerSecond = + (int) (Math.pow(multiplier, (int) (millisElapsed / multiplierMillis)) * initialCapacity); + return operationsPerSecond; + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 599c0ce536..c809160994 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -31,7 +31,6 @@ import io.opencensus.trace.Tracing; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -46,19 +45,13 @@ */ public abstract class UpdateBuilder { - private static class Mutation { - Write.Builder document; - Write.Builder transform; - com.google.firestore.v1.Precondition precondition; - } - final FirestoreImpl firestore; - private final List mutations; + private final List writes; private boolean committed; UpdateBuilder(FirestoreImpl firestore) { this.firestore = firestore; - this.mutations = new ArrayList<>(); + this.writes = new ArrayList<>(); } /** @@ -116,6 +109,7 @@ public T create( private T performCreate( @Nonnull DocumentReference documentReference, @Nonnull Map fields) { + verifyNotCommitted(); DocumentSnapshot documentSnapshot = DocumentSnapshot.fromObject( firestore, documentReference, fields, UserDataConverter.NO_DELETES); @@ -123,27 +117,21 @@ private T performCreate( DocumentTransform.fromFieldPathMap( documentReference, convertToFieldPaths(fields, /* splitOnDots= */ false)); - Mutation mutation = addMutation(); - mutation.precondition = Precondition.exists(false).toPb(); - - if (!documentSnapshot.isEmpty() || documentTransform.isEmpty()) { - mutation.document = documentSnapshot.toPb(); - } + Write.Builder write = documentSnapshot.toPb(); + write.setCurrentDocument(Precondition.exists(false).toPb()); if (!documentTransform.isEmpty()) { - mutation.transform = documentTransform.toPb(); + write.addAllUpdateTransforms(documentTransform.toPb()); } + writes.add(write); + return (T) this; } - /** Adds a new mutation to the batch. */ - private Mutation addMutation() { + private void verifyNotCommitted() { Preconditions.checkState( !committed, "Cannot modify a WriteBatch that has already been committed."); - Mutation mutation = new Mutation(); - mutations.add(mutation); - return mutation; } /** @@ -233,6 +221,7 @@ private T performSet( @Nonnull DocumentReference documentReference, @Nonnull Map fields, @Nonnull SetOptions options) { + verifyNotCommitted(); Map documentData; if (options.getFieldMask() != null) { @@ -248,31 +237,25 @@ private T performSet( DocumentTransform documentTransform = DocumentTransform.fromFieldPathMap(documentReference, documentData); - if (options.isMerge()) { - if (options.getFieldMask() != null) { - List fieldMask = new ArrayList<>(options.getFieldMask()); - fieldMask.removeAll(documentTransform.getFields()); - documentMask = new FieldMask(fieldMask); - } else { - documentMask = FieldMask.fromObject(fields); - } + if (options.getFieldMask() != null) { + List fieldMask = new ArrayList<>(options.getFieldMask()); + fieldMask.removeAll(documentTransform.getFields()); + documentMask = new FieldMask(fieldMask); + } else if (options.isMerge()) { + documentMask = FieldMask.fromObject(fields); } - Mutation mutation = addMutation(); - - boolean hasDocumentData = !documentSnapshot.isEmpty() || !documentMask.isEmpty(); - - if (!options.isMerge()) { - mutation.document = documentSnapshot.toPb(); - } else if (hasDocumentData || documentTransform.isEmpty()) { - mutation.document = documentSnapshot.toPb(); - mutation.document.setUpdateMask(documentMask.toPb()); + Write.Builder write = documentSnapshot.toPb(); + if (!documentTransform.isEmpty()) { + write.addAllUpdateTransforms(documentTransform.toPb()); } - if (!documentTransform.isEmpty()) { - mutation.transform = documentTransform.toPb(); + if (options.isMerge() || options.getFieldMask() != null) { + write.setUpdateMask(documentMask.toPb()); } + writes.add(write); + return (T) this; } @@ -507,6 +490,7 @@ private T performUpdate( @Nonnull DocumentReference documentReference, @Nonnull final Map fields, @Nonnull Precondition precondition) { + verifyNotCommitted(); Preconditions.checkArgument(!fields.isEmpty(), "Data for update() cannot be empty."); Map deconstructedMap = expandObject(fields); @@ -532,17 +516,14 @@ public boolean allowTransform() { fieldPaths.removeAll(documentTransform.getFields()); FieldMask fieldMask = new FieldMask(fieldPaths); - Mutation mutation = addMutation(); - mutation.precondition = precondition.toPb(); - - if (!documentSnapshot.isEmpty() || !fieldMask.isEmpty()) { - mutation.document = documentSnapshot.toPb(); - mutation.document.setUpdateMask(fieldMask.toPb()); - } + Write.Builder write = documentSnapshot.toPb(); + write.setCurrentDocument(precondition.toPb()); + write.setUpdateMask(fieldMask.toPb()); if (!documentTransform.isEmpty()) { - mutation.transform = documentTransform.toPb(); + write.addAllUpdateTransforms(documentTransform.toPb()); } + writes.add(write); return (T) this; } @@ -573,12 +554,13 @@ public T delete(@Nonnull DocumentReference documentReference) { private T performDelete( @Nonnull DocumentReference documentReference, @Nonnull Precondition precondition) { - Mutation mutation = addMutation(); - mutation.document = Write.newBuilder().setDelete(documentReference.getName()); + verifyNotCommitted(); + Write.Builder write = Write.newBuilder().setDelete(documentReference.getName()); if (!precondition.isEmpty()) { - mutation.precondition = precondition.toPb(); + write.setCurrentDocument(precondition.toPb()); } + writes.add(write); return (T) this; } @@ -589,28 +571,13 @@ ApiFuture> commit(@Nullable ByteString transactionId) { .getCurrentSpan() .addAnnotation( "CloudFirestore.Commit", - ImmutableMap.of("numDocuments", AttributeValue.longAttributeValue(mutations.size()))); + ImmutableMap.of("numDocuments", AttributeValue.longAttributeValue(writes.size()))); final CommitRequest.Builder request = CommitRequest.newBuilder(); request.setDatabase(firestore.getDatabaseName()); - for (Mutation mutation : mutations) { - Preconditions.checkState( - mutation.document != null || mutation.transform != null, - "Either a write or transform must be set"); - - if (mutation.precondition != null) { - (mutation.document != null ? mutation.document : mutation.transform) - .setCurrentDocument(mutation.precondition); - } - - if (mutation.document != null) { - request.addWrites(mutation.document); - } - - if (mutation.transform != null) { - request.addWrites(mutation.transform); - } + for (Write.Builder write : writes) { + request.addWrites(write); } if (transactionId != null) { @@ -632,29 +599,8 @@ public List apply(CommitResponse commitResponse) { List result = new ArrayList<>(); - Preconditions.checkState( - request.getWritesCount() == writeResults.size(), - "Expected one write result per operation, but got %s results for %s operations.", - writeResults.size(), - request.getWritesCount()); - - Iterator mutationIterator = mutations.iterator(); - Iterator responseIterator = - writeResults.iterator(); - - while (mutationIterator.hasNext()) { - Mutation mutation = mutationIterator.next(); - - // Don't return both write results for a write that contains a transform, as the fact - // that we have to split one write operation into two distinct write requests is an - // implementation detail. - if (mutation.document != null && mutation.transform != null) { - // The document transform is always sent last and produces the latest update time. - responseIterator.next(); - } - - result.add( - WriteResult.fromProto(responseIterator.next(), commitResponse.getCommitTime())); + for (com.google.firestore.v1.WriteResult writeResult : writeResults) { + result.add(WriteResult.fromProto(writeResult, commitResponse.getCommitTime())); } return result; @@ -665,11 +611,11 @@ public List apply(CommitResponse commitResponse) { /** Checks whether any updates have been queued. */ boolean isEmpty() { - return mutations.isEmpty(); + return writes.isEmpty(); } - /** Get the number of mutations. */ + /** Get the number of writes. */ public int getMutationsSize() { - return mutations.size(); + return writes.size(); } } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java index 7110c270fc..54be303b36 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java @@ -20,7 +20,6 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.ALL_SUPPORTED_TYPES_OBJECT; import static com.google.cloud.firestore.LocalFirestoreHelper.ALL_SUPPORTED_TYPES_PROTO; import static com.google.cloud.firestore.LocalFirestoreHelper.BLOB; -import static com.google.cloud.firestore.LocalFirestoreHelper.CREATE_PRECONDITION; import static com.google.cloud.firestore.LocalFirestoreHelper.DATE; import static com.google.cloud.firestore.LocalFirestoreHelper.DOCUMENT_NAME; import static com.google.cloud.firestore.LocalFirestoreHelper.DOCUMENT_PATH; @@ -28,7 +27,6 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.GEO_POINT; import static com.google.cloud.firestore.LocalFirestoreHelper.NESTED_CLASS_OBJECT; import static com.google.cloud.firestore.LocalFirestoreHelper.SERVER_TIMESTAMP_PROTO; -import static com.google.cloud.firestore.LocalFirestoreHelper.SERVER_TIMESTAMP_TRANSFORM; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_DELETE_COMMIT_RESPONSE; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_MAP; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_OBJECT; @@ -180,19 +178,19 @@ public void doesNotSerializeAdvancedNumberTypes() { pojo.bigIntegerValue = new BigInteger("0"); expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type BigInteger are not supported, please use an int, long, float or double (found in field 'bigIntegerValue')"); + "Could not serialize object. Numbers of type BigInteger are not supported, please use an int, long, float, double or BigDecimal (found in field 'bigIntegerValue')"); pojo = new InvalidPOJO(); pojo.byteValue = 0; expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type Byte are not supported, please use an int, long, float or double (found in field 'byteValue')"); + "Could not serialize object. Numbers of type Byte are not supported, please use an int, long, float, double or BigDecimal (found in field 'byteValue')"); pojo = new InvalidPOJO(); pojo.shortValue = 0; expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type Short are not supported, please use an int, long, float or double (found in field 'shortValue')"); + "Could not serialize object. Numbers of type Short are not supported, please use an int, long, float, double or BigDecimal (found in field 'shortValue')"); for (Map.Entry testCase : expectedErrorMessages.entrySet()) { try { @@ -406,8 +404,8 @@ public void createWithServerTimestamp() throws Exception { CommitRequest create = commit( - transform( - CREATE_PRECONDITION, "foo", serverTimestamp(), "inner.bar", serverTimestamp())); + create(Collections.emptyMap()), + transform("foo", serverTimestamp(), "inner.bar", serverTimestamp())); List commitRequests = commitCapture.getAllValues(); assertCommitEquals(create, commitRequests.get(0)); @@ -424,7 +422,10 @@ public void setWithServerTimestamp() throws Exception { documentReference.set(LocalFirestoreHelper.SERVER_TIMESTAMP_MAP).get(); documentReference.set(LocalFirestoreHelper.SERVER_TIMESTAMP_OBJECT).get(); - CommitRequest set = commit(set(SERVER_TIMESTAMP_PROTO), SERVER_TIMESTAMP_TRANSFORM); + CommitRequest set = + commit( + set(SERVER_TIMESTAMP_PROTO), + transform("foo", serverTimestamp(), "inner.bar", serverTimestamp())); List commitRequests = commitCapture.getAllValues(); assertCommitEquals(set, commitRequests.get(0)); @@ -443,7 +444,7 @@ public void updateWithServerTimestamp() throws Exception { CommitRequest update = commit( update(Collections.emptyMap(), Collections.singletonList("inner")), - SERVER_TIMESTAMP_TRANSFORM); + transform("foo", serverTimestamp(), "inner.bar", serverTimestamp())); assertCommitEquals(update, commitCapture.getValue()); @@ -452,8 +453,11 @@ public void updateWithServerTimestamp() throws Exception { update = commit( - transform( - UPDATE_PRECONDITION, "foo", serverTimestamp(), "inner.bar", serverTimestamp())); + update( + Collections.emptyMap(), + new ArrayList(), + UPDATE_PRECONDITION), + transform("foo", serverTimestamp(), "inner.bar", serverTimestamp())); assertCommitEquals(update, commitCapture.getValue()); } @@ -472,7 +476,10 @@ public void mergeWithServerTimestamps() throws Exception { .set(LocalFirestoreHelper.SERVER_TIMESTAMP_OBJECT, SetOptions.mergeFields("inner.bar")) .get(); - CommitRequest set = commit(transform("inner.bar", serverTimestamp())); + CommitRequest set = + commit( + set(SERVER_TIMESTAMP_PROTO, new ArrayList()), + transform("inner.bar", serverTimestamp())); List commitRequests = commitCapture.getAllValues(); assertCommitEquals(set, commitRequests.get(0)); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java index 0366f7a073..9deea1d745 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java @@ -19,13 +19,13 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_OBJECT; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_PROTO; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_VALUE; -import static com.google.cloud.firestore.LocalFirestoreHelper.UPDATE_PRECONDITION; import static com.google.cloud.firestore.LocalFirestoreHelper.arrayRemove; import static com.google.cloud.firestore.LocalFirestoreHelper.arrayUnion; import static com.google.cloud.firestore.LocalFirestoreHelper.commit; import static com.google.cloud.firestore.LocalFirestoreHelper.commitResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.getAllResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.transform; +import static com.google.cloud.firestore.LocalFirestoreHelper.update; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; @@ -40,6 +40,9 @@ import com.google.firestore.v1.CommitRequest; import com.google.firestore.v1.CommitResponse; import com.google.firestore.v1.ListCollectionIdsRequest; +import com.google.firestore.v1.Value; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import org.junit.Test; @@ -196,7 +199,9 @@ public void arrayUnionWithPojo() throws ExecutionException, InterruptedException doc.update("array", FieldValue.arrayUnion(SINGLE_FIELD_OBJECT)).get(); CommitRequest expectedRequest = - commit(transform(UPDATE_PRECONDITION, "array", arrayUnion(SINGLE_FIELD_VALUE))); + commit( + update(Collections.emptyMap(), new ArrayList()), + transform("array", arrayUnion(SINGLE_FIELD_VALUE))); CommitRequest actualRequest = commitCapture.getValue(); assertEquals(expectedRequest, actualRequest); } @@ -212,7 +217,9 @@ public void arrayRemoveWithPojo() throws ExecutionException, InterruptedExceptio doc.update("array", FieldValue.arrayRemove(SINGLE_FIELD_OBJECT)).get(); CommitRequest expectedRequest = - commit(transform(UPDATE_PRECONDITION, "array", arrayRemove(SINGLE_FIELD_VALUE))); + commit( + update(Collections.emptyMap(), new ArrayList()), + transform("array", arrayRemove(SINGLE_FIELD_VALUE))); CommitRequest actualRequest = commitCapture.getValue(); assertEquals(expectedRequest, actualRequest); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java index 148ce55858..541af70341 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java @@ -38,7 +38,6 @@ import com.google.firestore.v1.DatabaseRootName; import com.google.firestore.v1.Document; import com.google.firestore.v1.DocumentMask; -import com.google.firestore.v1.DocumentTransform; import com.google.firestore.v1.DocumentTransform.FieldTransform; import com.google.firestore.v1.MapValue; import com.google.firestore.v1.Precondition; @@ -112,7 +111,6 @@ public final class LocalFirestoreHelper { public static final Map SERVER_TIMESTAMP_MAP; public static final ServerTimestamp SERVER_TIMESTAMP_OBJECT; public static final Map SERVER_TIMESTAMP_PROTO; - public static final Write SERVER_TIMESTAMP_TRANSFORM; public static final Map ALL_SUPPORTED_TYPES_MAP; public static final AllSupportedTypes ALL_SUPPORTED_TYPES_OBJECT; @@ -131,8 +129,6 @@ public final class LocalFirestoreHelper { public static final GeoPoint GEO_POINT; public static final Blob BLOB; - public static final Precondition CREATE_PRECONDITION; - public static final Precondition UPDATE_PRECONDITION; public static class SingleField { @@ -351,33 +347,21 @@ public static FieldTransform arrayRemove(Value... values) { .build(); } - public static Write transform( + public static List transform( String fieldPath, FieldTransform fieldTransform, Object... fieldPathOrTransform) { - return transform(null, fieldPath, fieldTransform, fieldPathOrTransform); - } - public static Write transform( - @Nullable Precondition precondition, - String fieldPath, - FieldTransform fieldTransform, - Object... fieldPathOrTransform) { - Write.Builder write = Write.newBuilder(); - DocumentTransform.Builder documentTransform = write.getTransformBuilder(); - documentTransform.setDocument(DOCUMENT_NAME); + List transforms = new ArrayList<>(); + FieldTransform.Builder transformBuilder = FieldTransform.newBuilder(); + transformBuilder.setFieldPath(fieldPath).mergeFrom(fieldTransform); - documentTransform.addFieldTransformsBuilder().setFieldPath(fieldPath).mergeFrom(fieldTransform); + transforms.add(transformBuilder.build()); for (int i = 0; i < fieldPathOrTransform.length; i += 2) { String path = (String) fieldPathOrTransform[i]; FieldTransform transform = (FieldTransform) fieldPathOrTransform[i + 1]; - documentTransform.addFieldTransformsBuilder().setFieldPath(path).mergeFrom(transform); - } - - if (precondition != null) { - write.setCurrentDocument(precondition); + transforms.add(FieldTransform.newBuilder().setFieldPath(path).mergeFrom(transform).build()); } - - return write.build(); + return transforms; } public static Write create(Map fields) { @@ -456,6 +440,10 @@ public static CommitRequest commit(Write... writes) { return commit(null, writes); } + public static CommitRequest commit(Write write, List transforms) { + return commit((String) null, write.toBuilder().addAllUpdateTransforms(transforms).build()); + } + public static StructuredQuery filter(StructuredQuery.FieldFilter.Operator operator) { return filter(operator, "foo", "bar"); } @@ -644,9 +632,8 @@ private static CommitRequest sortCommit(CommitRequest commit) { writes.setUpdateMask(DocumentMask.newBuilder().addAllFieldPaths(updateMask)); } - if (writes.getTransform().getFieldTransformsCount() > 0) { - ArrayList transformList = - new ArrayList<>(writes.getTransform().getFieldTransformsList()); + if (!writes.getUpdateTransformsList().isEmpty()) { + ArrayList transformList = new ArrayList<>(writes.getUpdateTransformsList()); Collections.sort( transformList, new Comparator() { @@ -655,7 +642,7 @@ public int compare(FieldTransform t1, FieldTransform t2) { return t1.getFieldPath().compareTo(t2.getFieldPath()); } }); - writes.setTransform(DocumentTransform.newBuilder().addAllFieldTransforms(transformList)); + writes.clearUpdateTransforms().addAllUpdateTransforms(transformList); } } @@ -787,8 +774,6 @@ public boolean equals(Object o) { mapValue.getMapValueBuilder(); SERVER_TIMESTAMP_PROTO = Collections.emptyMap(); SERVER_TIMESTAMP_OBJECT = new ServerTimestamp(); - SERVER_TIMESTAMP_TRANSFORM = - transform("foo", serverTimestamp(), "inner.bar", serverTimestamp()); ALL_SUPPORTED_TYPES_MAP = new HashMap<>(); ALL_SUPPORTED_TYPES_MAP.put("foo", "bar"); @@ -867,8 +852,6 @@ public boolean equals(Object o) { NESTED_CLASS_OBJECT = new NestedClass(); - CREATE_PRECONDITION = Precondition.newBuilder().setExists(false).build(); - UPDATE_PRECONDITION = Precondition.newBuilder().setExists(true).build(); UPDATED_POJO = map("model", (Object) UPDATE_SINGLE_FIELD_OBJECT); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java index 468a7f57a5..a64eb466cf 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java @@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableList; import com.google.firestore.v1.DatabaseRootName; import java.io.Serializable; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -39,6 +40,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; @@ -74,6 +76,31 @@ public double getValue() { } } + private static class BigDecimalBean { + private BigDecimal value; + + public BigDecimal getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BigDecimalBean bean = (BigDecimalBean) o; + return Objects.equals(value, bean.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } + private static class FloatBean { private float value; @@ -1049,6 +1076,41 @@ public void primitiveDeserializeDouble() { } } + @Test + public void primitiveDeserializeBigDecimal() { + BigDecimalBean beanBigdecimal = deserialize("{'value': 123}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(123), beanBigdecimal.value); + + beanBigdecimal = deserialize("{'value': '123'}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(123), beanBigdecimal.value); + + // Int + BigDecimalBean beanInt = deserialize("{'value': 1}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1), beanInt.value); + + // Long + BigDecimalBean beanLong = deserialize("{'value': 1234567890123}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1234567890123L), beanLong.value); + + // Double + BigDecimalBean beanDouble = deserialize("{'value': 1.1}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1.1), beanDouble.value); + + // Boolean + try { + deserialize("{'value': true}", BigDecimalBean.class); + fail("Should throw"); + } catch (RuntimeException e) { // ignore + } + + // String + try { + deserialize("{'value': 'foo'}", BigDecimalBean.class); + fail("Should throw"); + } catch (RuntimeException e) { // ignore + } + } + @Test public void primitiveDeserializeFloat() { FloatBean beanFloat = deserialize("{'value': 1.1}", FloatBean.class); @@ -1513,6 +1575,23 @@ public void serializeLongBean() { assertJson("{'value': 1234567890123}", serialize(bean)); } + @Test + public void serializeBigDecimalBean() { + BigDecimalBean bean = new BigDecimalBean(); + bean.value = BigDecimal.valueOf(1.1); + assertEquals(mapAnyType("value", "1.1"), serialize(bean)); + } + + @Test + public void bigDecimalRoundTrip() { + BigDecimal doubleMaxPlusOne = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.ONE); + BigDecimalBean a = new BigDecimalBean(); + a.value = doubleMaxPlusOne; + Map serialized = (Map) serialize(a); + BigDecimalBean b = convertToCustomClass(serialized, BigDecimalBean.class); + assertEquals(a, b); + } + @Test public void serializeBooleanBean() { BooleanBean bean = new BooleanBean(); @@ -1827,7 +1906,7 @@ public void shortsCantBeSerialized() { final ShortBean bean = new ShortBean(); bean.value = 1; assertExceptionContains( - "Numbers of type Short are not supported, please use an int, long, float or double (found in field 'value')", + "Numbers of type Short are not supported, please use an int, long, float, double or BigDecimal (found in field 'value')", new Runnable() { @Override public void run() { @@ -1841,7 +1920,7 @@ public void bytesCantBeSerialized() { final ByteBean bean = new ByteBean(); bean.value = 1; assertExceptionContains( - "Numbers of type Byte are not supported, please use an int, long, float or double (found in field 'value')", + "Numbers of type Byte are not supported, please use an int, long, float, double or BigDecimal (found in field 'value')", new Runnable() { @Override public void run() { @@ -2478,7 +2557,7 @@ public void serializationFailureIncludesPath() { } catch (RuntimeException e) { assertEquals( "Could not serialize object. Numbers of type Short are not supported, please use an int, " - + "long, float or double (found in field 'value.inner.value.short')", + + "long, float, double or BigDecimal (found in field 'value.inner.value.short')", e.getMessage()); } } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java index fa342778d0..a3315ca6de 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java @@ -40,6 +40,7 @@ import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.cloud.Timestamp; import com.google.cloud.firestore.spi.v1.FirestoreRpc; +import com.google.firestore.v1.ArrayValue; import com.google.firestore.v1.RunQueryRequest; import com.google.firestore.v1.StructuredQuery; import com.google.firestore.v1.StructuredQuery.Direction; @@ -164,7 +165,7 @@ public void limitToLastRequiresAtLeastOneOrderingConstraint() throws Exception { } @Test - public void limitToLastRejectsStream() throws Exception { + public void limitToLastRejectsStream() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -273,6 +274,110 @@ public void withFieldPathFilter() throws Exception { } } + @Test + public void inQueriesWithReferenceArray() throws Exception { + doAnswer(queryResponse()) + .when(firestoreMock) + .streamRequest( + runQuery.capture(), + streamObserverCapture.capture(), + Matchers.any()); + + query + .whereIn( + FieldPath.documentId(), + Arrays.asList("doc", firestoreMock.document("coll/doc"))) + .get() + .get(); + + Value value = + Value.newBuilder() + .setArrayValue( + ArrayValue.newBuilder() + .addValues(reference(DOCUMENT_NAME)) + .addValues(reference(DOCUMENT_NAME)) + .build()) + .build(); + RunQueryRequest expectedRequest = query(filter(Operator.IN, "__name__", value)); + + assertEquals(expectedRequest, runQuery.getValue()); + } + + @Test + public void inQueriesFieldsNotUsedInOrderBy() throws Exception { + doAnswer(queryResponse()) + .when(firestoreMock) + .streamRequest( + runQuery.capture(), + streamObserverCapture.capture(), + Matchers.any()); + + // Field "foo" used in `whereIn` should not appear in implicit orderBys in the resulting query. + query + .whereIn(FieldPath.of("foo"), Arrays.asList("value1", "value2")) + .startAt(SINGLE_FIELD_SNAPSHOT) + .get() + .get(); + + Value value = + Value.newBuilder() + .setArrayValue( + ArrayValue.newBuilder() + .addValues(Value.newBuilder().setStringValue("value1").build()) + .addValues(Value.newBuilder().setStringValue("value2").build()) + .build()) + .build(); + RunQueryRequest expectedRequest = + query( + filter(Operator.IN, "foo", value), + order("__name__", Direction.ASCENDING), + startAt(reference(DOCUMENT_NAME), true)); + + assertEquals(expectedRequest, runQuery.getValue()); + } + + @Test + public void validatesInQueries() { + try { + query.whereIn(FieldPath.documentId(), Arrays.asList("foo", 42)).get(); + fail(); + } catch (IllegalArgumentException e) { + assertEquals( + "The corresponding value for FieldPath.documentId() must be a String or a " + + "DocumentReference, but was: 42.", + e.getMessage()); + } + + try { + query.whereIn(FieldPath.documentId(), Arrays.asList()).get(); + fail(); + } catch (IllegalArgumentException e) { + assertEquals( + "Invalid Query. A non-empty array is required for 'IN' filters.", e.getMessage()); + } + } + + @Test + public void validatesQueryOperatorForFieldPathDocumentId() { + try { + query.whereArrayContains(FieldPath.documentId(), "bar"); + fail(); + } catch (IllegalArgumentException e) { + assertEquals( + "Invalid query. You cannot perform 'ARRAY_CONTAINS' queries on FieldPath.documentId().", + e.getMessage()); + } + + try { + query.whereArrayContainsAny(FieldPath.documentId(), Collections.singletonList("bar")); + fail(); + } catch (IllegalArgumentException e) { + assertEquals( + "Invalid query. You cannot perform 'ARRAY_CONTAINS_ANY' queries on FieldPath.documentId().", + e.getMessage()); + } + } + @Test public void withDocumentIdFilter() throws Exception { doAnswer(queryResponse()) @@ -375,7 +480,7 @@ public void withFieldPathSelect() throws Exception { } @Test - public void withDocumentSnapshotCursor() throws Exception { + public void withDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -396,7 +501,7 @@ public void withDocumentSnapshotCursor() throws Exception { } @Test - public void withDocumentIdAndDocumentSnapshotCursor() throws Exception { + public void withDocumentIdAndDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -417,7 +522,7 @@ public void withDocumentIdAndDocumentSnapshotCursor() throws Exception { } @Test - public void withExtractedDirectionForDocumentSnapshotCursor() throws Exception { + public void withExtractedDirectionForDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -440,7 +545,7 @@ public void withExtractedDirectionForDocumentSnapshotCursor() throws Exception { } @Test - public void withInequalityFilterForDocumentSnapshotCursor() throws Exception { + public void withInequalityFilterForDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -471,7 +576,7 @@ public void withInequalityFilterForDocumentSnapshotCursor() throws Exception { } @Test - public void withEqualityFilterForDocumentSnapshotCursor() throws Exception { + public void withEqualityFilterForDocumentSnapshotCursor() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -516,14 +621,14 @@ public void withStartAt() throws Exception { } @Test - public void withInvalidStartAt() throws Exception { + public void withInvalidStartAt() { try { query.orderBy(FieldPath.documentId()).startAt(42).get(); fail(); } catch (IllegalArgumentException e) { assertEquals( "The corresponding value for FieldPath.documentId() must be a String or a " - + "DocumentReference.", + + "DocumentReference, but was: 42.", e.getMessage()); } @@ -598,7 +703,7 @@ public void withEndAt() throws Exception { } @Test - public void withCollectionGroup() throws Exception { + public void withCollectionGroup() { doAnswer(queryResponse()) .when(firestoreMock) .streamRequest( @@ -635,12 +740,12 @@ public void collectionGroupCannotContainSlashes() { } @Test(expected = IllegalStateException.class) - public void overspecifiedCursor() throws Exception { + public void overspecifiedCursor() { query.orderBy("foo").startAt("foo", "bar", "bar", "foo"); } @Test(expected = IllegalStateException.class) - public void orderByWithCursor() throws Exception { + public void orderByWithCursor() { query.startAt("foo").orderBy("foo"); } @@ -726,7 +831,7 @@ public void onCompleted() { } @Test - public void equalsTest() throws Exception { + public void equalsTest() { assertEquals(query.limit(42).offset(1337), query.offset(1337).limit(42)); assertEquals(query.limit(42).offset(1337).hashCode(), query.offset(1337).limit(42).hashCode()); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/RateLimiterTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/RateLimiterTest.java new file mode 100644 index 0000000000..291f607330 --- /dev/null +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/RateLimiterTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Date; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class RateLimiterTest { + private RateLimiter limiter; + + @Before + public void before() { + limiter = + new RateLimiter( + /* initialCapacity= */ 500, + /* multiplier= */ 1.5, + /* multiplierMillis= */ 5 * 60 * 1000, + /* startTime= */ new Date(0).getTime()); + } + + @Test + public void processRequestsFromCapacity() { + assertTrue(limiter.tryMakeRequest(250, new Date(0).getTime())); + assertTrue(limiter.tryMakeRequest(250, new Date(0).getTime())); + + // Once tokens have been used, further requests should fail. + assertFalse(limiter.tryMakeRequest(1, new Date(0).getTime())); + + // Tokens will only refill up to max capacity. + assertFalse(limiter.tryMakeRequest(501, new Date(1 * 1000).getTime())); + assertTrue(limiter.tryMakeRequest(500, new Date(1 * 1000).getTime())); + + // Tokens will refill incrementally based on number of ms elapsed. + assertFalse(limiter.tryMakeRequest(250, new Date(1 * 1000 + 499).getTime())); + assertTrue(limiter.tryMakeRequest(249, new Date(1 * 1000 + 500).getTime())); + + // Scales with multiplier. + assertFalse(limiter.tryMakeRequest(751, new Date((5 * 60 - 1) * 1000).getTime())); + assertFalse(limiter.tryMakeRequest(751, new Date(5 * 60 * 1000).getTime())); + assertTrue(limiter.tryMakeRequest(750, new Date(5 * 60 * 1000).getTime())); + + // Tokens will never exceed capacity. + assertFalse(limiter.tryMakeRequest(751, new Date((5 * 60 + 3) * 1000).getTime())); + + // Rejects requests made before lastRefillTime. + try { + limiter.tryMakeRequest(751, new Date((5 * 60 + 2) * 1000).getTime()); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Request time should not be before the last token refill time", e.getMessage()); + } + } + + @Test + public void calculatesMsForNextRequest() { + // Should return 0 if there are enough tokens for the request to be made. + long timestamp = new Date(0).getTime(); + assertEquals(0, limiter.getNextRequestDelayMs(500, timestamp)); + + // Should factor in remaining tokens when calculating the time. + assertTrue(limiter.tryMakeRequest(250, timestamp)); + assertEquals(500, limiter.getNextRequestDelayMs(500, timestamp)); + + // Once tokens have been used, should calculate time before next request. + timestamp = new Date(1 * 1000).getTime(); + assertTrue(limiter.tryMakeRequest(500, timestamp)); + assertEquals(200, limiter.getNextRequestDelayMs(100, timestamp)); + assertEquals(500, limiter.getNextRequestDelayMs(250, timestamp)); + assertEquals(1000, limiter.getNextRequestDelayMs(500, timestamp)); + assertEquals(-1, limiter.getNextRequestDelayMs(501, timestamp)); + + // Scales with multiplier. + timestamp = new Date(5 * 60 * 1000).getTime(); + assertTrue(limiter.tryMakeRequest(750, timestamp)); + assertEquals(334, limiter.getNextRequestDelayMs(250, timestamp)); + assertEquals(667, limiter.getNextRequestDelayMs(500, timestamp)); + assertEquals(1000, limiter.getNextRequestDelayMs(750, timestamp)); + assertEquals(-1, limiter.getNextRequestDelayMs(751, timestamp)); + } + + @Test + public void calculatesMaxOperations() { + assertEquals(500, limiter.calculateCapacity(new Date(0).getTime())); + assertEquals(750, limiter.calculateCapacity(new Date(5 * 60 * 1000).getTime())); + assertEquals(1125, limiter.calculateCapacity(new Date(10 * 60 * 1000).getTime())); + assertEquals(1687, limiter.calculateCapacity(new Date(15 * 60 * 1000).getTime())); + assertEquals(738945, limiter.calculateCapacity(new Date(90 * 60 * 1000).getTime())); + } +} diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java index 52b3150b87..f7b3c15dcf 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java @@ -219,7 +219,7 @@ public ApiFuture updateCallback(Transaction transaction) { } @Test - public void rollbackOnCallbackError() throws Exception { + public void rollbackOnCallbackError() { doReturn(beginResponse()) .doReturn(rollbackResponse()) .when(firestoreMock) @@ -250,7 +250,7 @@ public String updateCallback(Transaction transaction) throws Exception { } @Test - public void rollbackOnCallbackErrorAsync() throws Exception { + public void rollbackOnCallbackErrorAsync() { doReturn(beginResponse()) .doReturn(rollbackResponse()) .when(firestoreMock) @@ -281,7 +281,7 @@ public ApiFuture updateCallback(Transaction transaction) { } @Test - public void noRollbackOnBeginFailure() throws Exception { + public void noRollbackOnBeginFailure() { doReturn(ApiFutures.immediateFailedFuture(new Exception("Expected exception"))) .when(firestoreMock) .sendRequest(requestCapture.capture(), Matchers.>any()); @@ -309,7 +309,7 @@ public String updateCallback(Transaction transaction) { } @Test - public void noRollbackOnBeginFailureAsync() throws Exception { + public void noRollbackOnBeginFailureAsync() { doReturn(ApiFutures.immediateFailedFuture(new Exception("Expected exception"))) .when(firestoreMock) .sendRequest(requestCapture.capture(), Matchers.>any()); @@ -337,7 +337,7 @@ public ApiFuture updateCallback(Transaction transaction) { } @Test - public void limitsRetriesWithFailure() throws Exception { + public void limitsRetriesWithFailure() { RequestResponseMap requestResponseMap = new RequestResponseMap() { { diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java index 6eeb1dbd82..6b036e2f80 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java @@ -990,15 +990,15 @@ private ListenResponse docRemove(String docPath) { return response.build(); } - private void send(ListenResponse response) throws InterruptedException { + private void send(ListenResponse response) { streamObserverCapture.getValue().onNext(response); } - private void destroy(Code code) throws InterruptedException { + private void destroy(Code code) { streamObserverCapture.getValue().onError(new StatusException(io.grpc.Status.fromCode(code))); } - private void close() throws InterruptedException { + private void close() { streamObserverCapture.getValue().onCompleted(); } @@ -1006,7 +1006,7 @@ private void close() throws InterruptedException { private Answer newRequestObserver() { return new Answer() { @Override - public Object answer(InvocationOnMock invocationOnMock) throws Throwable { + public Object answer(InvocationOnMock invocationOnMock) { return new ApiStreamObserver() { @Override public void onNext(ListenRequest listenRequest) { diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WriteBatchTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WriteBatchTest.java index ac992db356..3e629324f3 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WriteBatchTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WriteBatchTest.java @@ -215,7 +215,7 @@ public void setDocumentWithFloat() throws Exception { @Test public void omitWriteResultForDocumentTransforms() throws Exception { - doReturn(commitResponse(2, 0)) + doReturn(commitResponse(1, 0)) .when(firestoreMock) .sendRequest( commitCapture.capture(), Matchers.>any()); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java index 69cf5cec81..cb9d69ec16 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryWatchTest.java @@ -39,6 +39,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Joiner.MapJoiner; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.Range; @@ -83,6 +84,9 @@ public void before() { @AfterClass public static void afterClass() throws Exception { + Preconditions.checkNotNull( + firestore, + "Error instantiating Firestore. Check that the service account credentials were properly set."); firestore.close(); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java index a0379f061a..f50e346f18 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java @@ -55,6 +55,7 @@ import com.google.cloud.firestore.Transaction.Function; import com.google.cloud.firestore.WriteBatch; import com.google.cloud.firestore.WriteResult; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; @@ -104,6 +105,9 @@ public void before() { @After public void after() throws Exception { + Preconditions.checkNotNull( + firestore, + "Error instantiating Firestore. Check that the service account credentials were properly set."); firestore.close(); } @@ -227,6 +231,15 @@ public void setDocumentWithMerge() throws Exception { assertEquals(resultMap, documentSnapshot.getData()); } + @Test + public void setWithIncrementAndMerge() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("sum", 1L)).get(); + docRef.set(Collections.singletonMap("sum", FieldValue.increment(2)), SetOptions.merge()).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(3L, docSnap.get("sum")); + } + @Test public void mergeDocumentWithServerTimestamp() throws Exception { Map originalMap = LocalFirestoreHelper.map("a", "b"); @@ -1141,6 +1154,21 @@ public void inQueries() throws Exception { assertEquals(asList("a", "c"), querySnapshotToIds(querySnapshot)); } + @Test + public void inQueriesWithDocumentId() throws Exception { + DocumentReference doc1 = setDocument("a", map("count", 1)); + DocumentReference doc2 = setDocument("b", map("count", 2)); + setDocument("c", map("count", 3)); + + QuerySnapshot querySnapshot = + randomColl + .whereIn(FieldPath.documentId(), Arrays.asList(doc1.getId(), doc2)) + .get() + .get(); + + assertEquals(asList("a", "b"), querySnapshotToIds(querySnapshot)); + } + @Test public void arrayContainsAnyQueries() throws Exception { setDocument("a", map("array", asList(42))); diff --git a/grpc-google-cloud-firestore-admin-v1/pom.xml b/grpc-google-cloud-firestore-admin-v1/pom.xml index c188249dce..bdec741da2 100644 --- a/grpc-google-cloud-firestore-admin-v1/pom.xml +++ b/grpc-google-cloud-firestore-admin-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 grpc-google-cloud-firestore-admin-v1 GRPC library for grpc-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -66,6 +66,14 @@ + + + + org.codehaus.mojo + flatten-maven-plugin + + + @@ -77,6 +85,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/grpc-google-cloud-firestore-v1/pom.xml b/grpc-google-cloud-firestore-v1/pom.xml index 9eb92745b8..27a16aed24 100644 --- a/grpc-google-cloud-firestore-v1/pom.xml +++ b/grpc-google-cloud-firestore-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 grpc-google-cloud-firestore-v1 GRPC library for grpc-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -62,6 +62,14 @@ + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -72,6 +80,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/grpc-google-cloud-firestore-v1beta1/pom.xml b/grpc-google-cloud-firestore-v1beta1/pom.xml index 3a0942cfad..3c6ad0498b 100644 --- a/grpc-google-cloud-firestore-v1beta1/pom.xml +++ b/grpc-google-cloud-firestore-v1beta1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 grpc-google-cloud-firestore-v1beta1 GRPC library for grpc-google-cloud-firestore-v1beta1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -62,6 +62,14 @@ + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -72,6 +80,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/pom.xml b/pom.xml index dfefe5ddd8..d710f0100a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-firestore-parent pom - 1.33.0 + 1.34.0 Google Cloud Firestore Parent https://github.com/googleapis/java-firestore @@ -14,7 +14,7 @@ com.google.cloud google-cloud-shared-config - 0.5.0 + 0.6.0 @@ -152,15 +152,15 @@ github google-cloud-firestore-parent https://googleapis.dev/java/google-api-grpc/latest - 1.93.4 - 1.9.0 - 1.17.0 + 1.93.5 + 1.9.1 + 1.18.0 1.56.0 - 1.28.1 - 3.11.4 + 1.29.0 + 3.12.2 4.13 - 28.2-android - 1.4.3 + 29.0-android + 1.4.4 0.24.0 1.10.19 1.3.2 @@ -172,32 +172,32 @@ com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 com.google.api.grpc proto-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 com.google.api.grpc grpc-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 @@ -242,21 +242,11 @@ proto-google-common-protos ${google.common-protos.version} - - com.google.auto.value - auto-value-annotations - ${auto-value-annotation.version} - org.threeten threetenbp ${threeten.version} - - javax.annotation - javax.annotation-api - ${javax.annotations.version} - com.google.code.findbugs jsr305 @@ -285,7 +275,7 @@ com.fasterxml.jackson.core jackson-core - 2.10.3 + 2.11.0 com.google.protobuf @@ -340,89 +330,4 @@ google-cloud-firestore-bom - - - autovalue-java7 - - 1.7 - - - 1.7 - 1.4 - - - - - maven-compiler-plugin - - - - com.google.auto.value - auto-value - ${auto-value.version} - - - - - - - - - - - autovalue-java8 - - [1.8,) - - - 1.7 - ${auto-value.version} - 1.0-rc6 - - - - - maven-compiler-plugin - - - - com.google.auto.value - auto-value - ${auto-value.version} - - - - com.google.auto.service - auto-service-annotations - ${auto-service-annotations.version} - - - - - - - - - diff --git a/proto-google-cloud-firestore-admin-v1/pom.xml b/proto-google-cloud-firestore-admin-v1/pom.xml index ba91af15e3..2d90fd87d7 100644 --- a/proto-google-cloud-firestore-admin-v1/pom.xml +++ b/proto-google-cloud-firestore-admin-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 1.33.0 + 1.34.0 proto-google-cloud-firestore-admin-v1 PROTO library for proto-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -33,6 +33,14 @@ guava + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -43,6 +51,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/proto-google-cloud-firestore-v1/pom.xml b/proto-google-cloud-firestore-v1/pom.xml index 1c8965b48e..4bd5afde61 100644 --- a/proto-google-cloud-firestore-v1/pom.xml +++ b/proto-google-cloud-firestore-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 1.33.0 + 1.34.0 proto-google-cloud-firestore-v1 PROTO library for proto-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -33,6 +33,14 @@ guava + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -43,6 +51,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/proto-google-cloud-firestore-v1beta1/pom.xml b/proto-google-cloud-firestore-v1beta1/pom.xml index 7bdf7fbbda..75688760db 100644 --- a/proto-google-cloud-firestore-v1beta1/pom.xml +++ b/proto-google-cloud-firestore-v1beta1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-v1beta1 - 0.86.0 + 0.87.0 proto-google-cloud-firestore-v1beta1 PROTO library for proto-google-cloud-firestore-v1beta1 com.google.cloud google-cloud-firestore-parent - 1.33.0 + 1.34.0 @@ -33,6 +33,14 @@ guava + + + + org.codehaus.mojo + flatten-maven-plugin + + + java9 @@ -43,6 +51,7 @@ javax.annotation javax.annotation-api + ${javax.annotations.version} diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 9dac56d376..66332bcfc7 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -29,7 +29,7 @@ com.google.cloud google-cloud-firestore - 1.32.5 + 1.33.0 diff --git a/samples/pom.xml b/samples/pom.xml index e3e7172c5c..a6dc37816f 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -18,7 +18,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index b64c57442d..1bb19341a8 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -28,7 +28,7 @@ com.google.cloud google-cloud-firestore - 1.32.5 + 1.33.0 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 7dad559f25..69dcaed2f1 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.0.15 + 1.0.17 @@ -30,7 +30,7 @@ com.google.cloud libraries-bom - 4.4.1 + 5.5.0 pom import diff --git a/synth.metadata b/synth.metadata index 14ed5f1b2e..be495dc067 100644 --- a/synth.metadata +++ b/synth.metadata @@ -1,6 +1,12 @@ { - "updateTime": "2020-03-26T03:27:35.869486Z", "sources": [ + { + "git": { + "name": ".", + "remote": "https://github.com/googleapis/java-firestore.git", + "sha": "495f7f97405fcd2bff4d09e67ddbeb51615ea843" + } + }, { "git": { "name": "googleapis", @@ -13,7 +19,7 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "e36822bfa0acb355502dab391b8ef9c4f30208d8" + "sha": "52638600f387deb98efb5f9c85fec39e82aa9052" } } ], diff --git a/synth.py b/synth.py index 2f2a98d88c..07e0afb3b0 100644 --- a/synth.py +++ b/synth.py @@ -125,7 +125,8 @@ def generate_client(service, version, proto_path=None, bazel_target=None, packag 'README.md', # firestore uses a different project for its integration tests # due to the default project running datastore - '.kokoro/presubmit/integration.cfg' + '.kokoro/presubmit/integration.cfg', + '.kokoro/nightly/integration.cfg' ]) # Mark v1beta1 as deprecated diff --git a/versions.txt b/versions.txt index 7cdcd9ac35..2c3cdc9e56 100644 --- a/versions.txt +++ b/versions.txt @@ -1,12 +1,12 @@ # Format: # module:released-version:current-version -google-cloud-firestore:1.33.0:1.33.0 -google-cloud-firestore-bom:1.33.0:1.33.0 -google-cloud-firestore-parent:1.33.0:1.33.0 -grpc-google-cloud-firestore-admin-v1:1.33.0:1.33.0 -grpc-google-cloud-firestore-v1:1.33.0:1.33.0 -grpc-google-cloud-firestore-v1beta1:0.86.0:0.86.0 -proto-google-cloud-firestore-admin-v1:1.33.0:1.33.0 -proto-google-cloud-firestore-v1:1.33.0:1.33.0 -proto-google-cloud-firestore-v1beta1:0.86.0:0.86.0 +google-cloud-firestore:1.34.0:1.34.0 +google-cloud-firestore-bom:1.34.0:1.34.0 +google-cloud-firestore-parent:1.34.0:1.34.0 +grpc-google-cloud-firestore-admin-v1:1.34.0:1.34.0 +grpc-google-cloud-firestore-v1:1.34.0:1.34.0 +grpc-google-cloud-firestore-v1beta1:0.87.0:0.87.0 +proto-google-cloud-firestore-admin-v1:1.34.0:1.34.0 +proto-google-cloud-firestore-v1:1.34.0:1.34.0 +proto-google-cloud-firestore-v1beta1:0.87.0:0.87.0