diff --git a/.github/workflows/hermetic_library_generation.yaml b/.github/workflows/hermetic_library_generation.yaml index 7838b82ee3..f611b78e29 100644 --- a/.github/workflows/hermetic_library_generation.yaml +++ b/.github/workflows/hermetic_library_generation.yaml @@ -37,7 +37,7 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.CLOUD_JAVA_BOT_TOKEN }} - - uses: googleapis/sdk-platform-java/.github/scripts@v2.60.0 + - uses: googleapis/sdk-platform-java/.github/scripts@v2.60.1 if: env.SHOULD_RUN == 'true' with: base_ref: ${{ github.base_ref }} diff --git a/.github/workflows/unmanaged-dependency-check.yaml b/.github/workflows/unmanaged-dependency-check.yaml index f2ddd24228..689f4bb4a0 100644 --- a/.github/workflows/unmanaged-dependency-check.yaml +++ b/.github/workflows/unmanaged-dependency-check.yaml @@ -14,6 +14,6 @@ jobs: shell: bash run: .kokoro/build.sh - name: Unmanaged dependency check - uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.50.0 + uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.50.1 with: bom-path: google-cloud-firestore-bom/pom.xml diff --git a/.kokoro/presubmit/graalvm-native-a.cfg b/.kokoro/presubmit/graalvm-native-a.cfg index 475d1271a9..ee61380cf8 100644 --- a/.kokoro/presubmit/graalvm-native-a.cfg +++ b/.kokoro/presubmit/graalvm-native-a.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.50.0" # {x-version-update:google-cloud-shared-dependencies:current} + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.50.1" # {x-version-update:google-cloud-shared-dependencies:current} } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native-b.cfg b/.kokoro/presubmit/graalvm-native-b.cfg index 0ec61ea39d..526f79dfa1 100644 --- a/.kokoro/presubmit/graalvm-native-b.cfg +++ b/.kokoro/presubmit/graalvm-native-b.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.50.0" # {x-version-update:google-cloud-shared-dependencies:current} + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.50.1" # {x-version-update:google-cloud-shared-dependencies:current} } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native-c.cfg b/.kokoro/presubmit/graalvm-native-c.cfg index 963711caeb..bbfc279a87 100644 --- a/.kokoro/presubmit/graalvm-native-c.cfg +++ b/.kokoro/presubmit/graalvm-native-c.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_c:3.50.0" # {x-version-update:google-cloud-shared-dependencies:current} + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_c:3.50.1" # {x-version-update:google-cloud-shared-dependencies:current} } env_vars: { diff --git a/CHANGELOG.md b/CHANGELOG.md index e2d40ff7ef..f04c145d69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [3.31.9](https://github.com/googleapis/java-firestore/compare/v3.31.8...v3.31.9) (2025-07-11) + + +### Bug Fixes + +* Improve performance of the UTF-8 string comparison logic. ([#2182](https://github.com/googleapis/java-firestore/issues/2182)) ([869a381](https://github.com/googleapis/java-firestore/commit/869a3814511f9a6e15641b3279bb871867886940)) + + +### Dependencies + +* Update dependency com.diffplug.spotless:spotless-maven-plugin to v2.45.0 ([#2184](https://github.com/googleapis/java-firestore/issues/2184)) ([88097cb](https://github.com/googleapis/java-firestore/commit/88097cbe8878b45897c89e102fa82e1975d2d74b)) +* Update dependency com.google.cloud:sdk-platform-java-config to v3.50.1 ([#2186](https://github.com/googleapis/java-firestore/issues/2186)) ([466e597](https://github.com/googleapis/java-firestore/commit/466e597c282533c0e500b57d27f386ef9ea99e5e)) +* Update dependency org.apache.commons:commons-lang3 to v3.18.0 ([#2187](https://github.com/googleapis/java-firestore/issues/2187)) ([1f3c20b](https://github.com/googleapis/java-firestore/commit/1f3c20b1923c3ae000359be91e7e39458c36ce2e)) +* Update googleapis/sdk-platform-java action to v2.60.0 ([#2176](https://github.com/googleapis/java-firestore/issues/2176)) ([8283a08](https://github.com/googleapis/java-firestore/commit/8283a08b1749e06864b50ab1813c13e9eb61c087)) +* Update googleapis/sdk-platform-java action to v2.60.1 ([#2181](https://github.com/googleapis/java-firestore/issues/2181)) ([be9daba](https://github.com/googleapis/java-firestore/commit/be9dabab8467a5aa3986d34f393d597d670af029)) + + +### Documentation + +* Add an example to the detailed comment in compareUtf8Strings() ([#2183](https://github.com/googleapis/java-firestore/issues/2183)) ([e16b158](https://github.com/googleapis/java-firestore/commit/e16b15853d6545c3c1476086c3bc88cb46991718)) + ## [3.31.8](https://github.com/googleapis/java-firestore/compare/v3.31.7...v3.31.8) (2025-07-02) diff --git a/README.md b/README.md index 9ebefdf62c..9e98fb6131 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-firestore - 3.31.7 + 3.31.8 ``` @@ -56,13 +56,13 @@ implementation 'com.google.cloud:google-cloud-firestore' If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-firestore:3.31.8' +implementation 'com.google.cloud:google-cloud-firestore:3.31.9' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.31.8" +libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.31.9" ``` ## Authentication @@ -219,7 +219,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-firestore/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-firestore.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-firestore/3.31.8 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-firestore/3.31.9 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/generation_config.yaml b/generation_config.yaml index 572090409e..0ce8be713d 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,5 +1,5 @@ gapic_generator_version: 2.59.0 -googleapis_commitish: 63161c080e7eb3dd1d4fe76db1fad108b6ac9a27 +googleapis_commitish: e0ccc57ed01b0057222f865de5765b7e453cfd81 libraries_bom_version: 26.61.0 libraries: - api_shortname: firestore diff --git a/google-cloud-firestore-admin/pom.xml b/google-cloud-firestore-admin/pom.xml index 0432ba1215..b843fd0ee5 100644 --- a/google-cloud-firestore-admin/pom.xml +++ b/google-cloud-firestore-admin/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-admin - 3.31.8 + 3.31.9 jar Google Cloud Firestore Admin Client https://github.com/googleapis/java-firestore @@ -14,7 +14,7 @@ com.google.cloud google-cloud-firestore-parent - 3.31.8 + 3.31.9 diff --git a/google-cloud-firestore-bom/pom.xml b/google-cloud-firestore-bom/pom.xml index 42647ce2b7..6990e2839b 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 - 3.31.8 + 3.31.9 pom com.google.cloud sdk-platform-java-config - 3.50.0 + 3.50.1 Google Cloud Firestore BOM @@ -52,37 +52,37 @@ com.google.cloud google-cloud-firestore - 3.31.8 + 3.31.9 com.google.cloud google-cloud-firestore-admin - 3.31.8 + 3.31.9 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 3.31.8 + 3.31.9 com.google.api.grpc grpc-google-cloud-firestore-v1 - 3.31.8 + 3.31.9 com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 3.31.8 + 3.31.9 com.google.api.grpc proto-google-cloud-firestore-v1 - 3.31.8 + 3.31.9 com.google.cloud proto-google-cloud-firestore-bundle-v1 - 3.31.8 + 3.31.9 diff --git a/google-cloud-firestore/pom.xml b/google-cloud-firestore/pom.xml index 1606ee730e..a57b746de9 100644 --- a/google-cloud-firestore/pom.xml +++ b/google-cloud-firestore/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-firestore - 3.31.8 + 3.31.9 jar Google Cloud Firestore https://github.com/googleapis/java-firestore @@ -12,7 +12,7 @@ com.google.cloud google-cloud-firestore-parent - 3.31.8 + 3.31.9 google-cloud-firestore @@ -199,7 +199,7 @@ org.apache.commons commons-lang3 - 3.17.0 + 3.18.0 test @@ -291,7 +291,7 @@ com.diffplug.spotless spotless-maven-plugin - 2.44.5 + 2.45.0 diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Order.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Order.java index 7f37d8e05f..c382140227 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Order.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Order.java @@ -16,6 +16,8 @@ package com.google.cloud.firestore; +import static java.lang.Character.isSurrogate; + import com.google.firestore.v1.MapValue; import com.google.firestore.v1.Value; import com.google.firestore.v1.Value.ValueTypeCase; @@ -136,46 +138,62 @@ public int compare(@Nonnull Value left, @Nonnull Value right) { /** Compare strings in UTF-8 encoded byte order */ public static int compareUtf8Strings(String left, String right) { - int i = 0; - while (i < left.length() && i < right.length()) { - int leftCodePoint = left.codePointAt(i); - int rightCodePoint = right.codePointAt(i); - - if (leftCodePoint != rightCodePoint) { - if (leftCodePoint < 128 && rightCodePoint < 128) { - // ASCII comparison - return Integer.compare(leftCodePoint, rightCodePoint); - } else { - // UTF-8 encode the character at index i for byte comparison. - ByteString leftBytes = ByteString.copyFromUtf8(getUtf8SafeBytes(left, i)); - ByteString rightBytes = ByteString.copyFromUtf8(getUtf8SafeBytes(right, i)); - int comp = compareByteStrings(leftBytes, rightBytes); - if (comp != 0) { - return comp; - } else { - // EXTREMELY RARE CASE: Code points differ, but their UTF-8 byte representations are - // identical. This can happen with malformed input (invalid surrogate pairs), where - // Java's encoding leads to unexpected byte sequences. Meanwhile, any invalid surrogate - // inputs get converted to "?" by protocol buffer while round tripping, so we almost - // never receive invalid strings from backend. - // Fallback to code point comparison for graceful handling. - return Integer.compare(leftCodePoint, rightCodePoint); - } - } + // noinspection StringEquality + if (left == right) { + return 0; + } + + // Find the first differing character (a.k.a. "UTF-16 code unit") in the two strings and, + // if found, use that character to determine the relative ordering of the two strings as a + // whole. Comparing UTF-16 strings in UTF-8 byte order can be done simply and efficiently by + // comparing the UTF-16 code units (chars). This serendipitously works because of the way UTF-8 + // and UTF-16 happen to represent Unicode code points. + // + // After finding the first pair of differing characters, there are two cases: + // + // Case 1: Both characters are non-surrogates (code points less than or equal to 0xFFFF) or + // both are surrogates from a surrogate pair (that collectively represent code points greater + // than 0xFFFF). In this case their numeric order as UTF-16 code units is the same as the + // lexicographical order of their corresponding UTF-8 byte sequences. A direct comparison is + // sufficient. + // + // Case 2: One character is a surrogate and the other is not. In this case the surrogate- + // containing string is always ordered after the non-surrogate. This is because surrogates are + // used to represent code points greater than 0xFFFF which have 4-byte UTF-8 representations + // and are lexicographically greater than the 1, 2, or 3-byte representations of code points + // less than or equal to 0xFFFF. + // + // An example of why Case 2 is required is comparing the following two Unicode code points: + // + // |-----------------------|------------|---------------------|-----------------| + // | Name | Code Point | UTF-8 Encoding | UTF-16 Encoding | + // |-----------------------|------------|---------------------|-----------------| + // | Replacement Character | U+FFFD | 0xEF 0xBF 0xBD | 0xFFFD | + // | Grinning Face | U+1F600 | 0xF0 0x9F 0x98 0x80 | 0xD83D 0xDE00 | + // |-----------------------|------------|---------------------|-----------------| + // + // A lexicographical comparison of the UTF-8 encodings of these code points would order + // "Replacement Character" _before_ "Grinning Face" because 0xEF is less than 0xF0. However, a + // direct comparison of the UTF-16 code units, as would be done in case 1, would erroneously + // produce the _opposite_ ordering, because 0xFFFD is _greater than_ 0xD83D. As it turns out, + // this relative ordering holds for all comparisons of UTF-16 code points requiring a surrogate + // pair with those that do not. + final int length = Math.min(left.length(), right.length()); + for (int i = 0; i < length; i++) { + final char leftChar = left.charAt(i); + final char rightChar = right.charAt(i); + if (leftChar != rightChar) { + return (isSurrogate(leftChar) == isSurrogate(rightChar)) + ? Character.compare(leftChar, rightChar) + : isSurrogate(leftChar) ? 1 : -1; } - // Increment by 2 for surrogate pairs, 1 otherwise. - i += Character.charCount(leftCodePoint); } - // Compare lengths if all characters are equal + // Use the lengths of the strings to determine the overall comparison result since either the + // strings were equal or one is a prefix of the other. return Integer.compare(left.length(), right.length()); } - private static String getUtf8SafeBytes(String str, int index) { - int firstCodePoint = str.codePointAt(index); - return str.substring(index, index + Character.charCount(firstCodePoint)); - } - private int compareBlobs(Value left, Value right) { ByteString leftBytes = left.getBytesValue(); ByteString rightBytes = right.getBytesValue(); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/OrderTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/OrderTest.java index 076cbf2db3..65e19d745f 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/OrderTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/OrderTest.java @@ -218,7 +218,7 @@ public void compareUtf8StringsShouldReturnCorrectValue() { ByteString b2 = ByteString.copyFromUtf8(s2); int expected = compareByteStrings(b1, b2); - if (actual == expected) { + if (Integer.signum(actual) == Integer.signum(expected)) { passCount++; } else { errors.add( @@ -227,9 +227,12 @@ public void compareUtf8StringsShouldReturnCorrectValue() { + "\", s2=\"" + s2 + "\") returned " + + signName(actual) + + " (" + actual + + ")" + ", but expected " - + expected + + signName(expected) + " (i=" + i + ", s1.length=" @@ -252,6 +255,16 @@ public void compareUtf8StringsShouldReturnCorrectValue() { } } + private static String signName(int value) { + if (value < 0) { + return "a negative value"; + } else if (value > 0) { + return "a positive value"; + } else { + return "0"; + } + } + private static class StringPairGenerator { private final StringGenerator stringGenerator; diff --git a/grpc-google-cloud-firestore-admin-v1/pom.xml b/grpc-google-cloud-firestore-admin-v1/pom.xml index 7c1033b8a2..2e7df92f12 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 - 3.31.8 + 3.31.9 grpc-google-cloud-firestore-admin-v1 GRPC library for grpc-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 3.31.8 + 3.31.9 diff --git a/grpc-google-cloud-firestore-v1/pom.xml b/grpc-google-cloud-firestore-v1/pom.xml index d14fa962dd..e99a7593d2 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 - 3.31.8 + 3.31.9 grpc-google-cloud-firestore-v1 GRPC library for grpc-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 3.31.8 + 3.31.9 diff --git a/pom.xml b/pom.xml index 304086c490..8487ece924 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-firestore-parent pom - 3.31.8 + 3.31.9 Google Cloud Firestore Parent https://github.com/googleapis/java-firestore @@ -14,7 +14,7 @@ com.google.cloud sdk-platform-java-config - 3.50.0 + 3.50.1 @@ -150,32 +150,32 @@ com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 3.31.8 + 3.31.9 com.google.cloud google-cloud-firestore - 3.31.8 + 3.31.9 com.google.cloud proto-google-cloud-firestore-bundle-v1 - 3.31.8 + 3.31.9 com.google.api.grpc proto-google-cloud-firestore-v1 - 3.31.8 + 3.31.9 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 3.31.8 + 3.31.9 com.google.api.grpc grpc-google-cloud-firestore-v1 - 3.31.8 + 3.31.9 diff --git a/proto-google-cloud-firestore-admin-v1/pom.xml b/proto-google-cloud-firestore-admin-v1/pom.xml index e18fd4fca1..09e3b24519 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 - 3.31.8 + 3.31.9 proto-google-cloud-firestore-admin-v1 PROTO library for proto-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 3.31.8 + 3.31.9 diff --git a/proto-google-cloud-firestore-bundle-v1/pom.xml b/proto-google-cloud-firestore-bundle-v1/pom.xml index 61b07b1f9b..d51249f92a 100644 --- a/proto-google-cloud-firestore-bundle-v1/pom.xml +++ b/proto-google-cloud-firestore-bundle-v1/pom.xml @@ -5,14 +5,14 @@ 4.0.0 proto-google-cloud-firestore-bundle-v1 - 3.31.8 + 3.31.9 proto-google-cloud-firestore-bundle-v1 PROTO library for proto-google-cloud-firestore-bundle-v1 com.google.cloud google-cloud-firestore-parent - 3.31.8 + 3.31.9 diff --git a/proto-google-cloud-firestore-v1/pom.xml b/proto-google-cloud-firestore-v1/pom.xml index 3d5c4ab84b..2c0616b644 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 - 3.31.8 + 3.31.9 proto-google-cloud-firestore-v1 PROTO library for proto-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 3.31.8 + 3.31.9 diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 60762e92d6..a090324fd0 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -30,7 +30,7 @@ com.google.cloud google-cloud-firestore - 3.31.7 + 3.31.8 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 44e384cc31..6eb5321bb0 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-firestore - 3.31.7 + 3.31.8 diff --git a/versions.txt b/versions.txt index 592c3e2110..7004033c29 100644 --- a/versions.txt +++ b/versions.txt @@ -1,11 +1,11 @@ # Format: # module:released-version:current-version -google-cloud-firestore:3.31.8:3.31.8 -google-cloud-firestore-admin:3.31.8:3.31.8 -google-cloud-firestore-bom:3.31.8:3.31.8 -grpc-google-cloud-firestore-admin-v1:3.31.8:3.31.8 -grpc-google-cloud-firestore-v1:3.31.8:3.31.8 -proto-google-cloud-firestore-admin-v1:3.31.8:3.31.8 -proto-google-cloud-firestore-v1:3.31.8:3.31.8 -proto-google-cloud-firestore-bundle-v1:3.31.8:3.31.8 +google-cloud-firestore:3.31.9:3.31.9 +google-cloud-firestore-admin:3.31.9:3.31.9 +google-cloud-firestore-bom:3.31.9:3.31.9 +grpc-google-cloud-firestore-admin-v1:3.31.9:3.31.9 +grpc-google-cloud-firestore-v1:3.31.9:3.31.9 +proto-google-cloud-firestore-admin-v1:3.31.9:3.31.9 +proto-google-cloud-firestore-v1:3.31.9:3.31.9 +proto-google-cloud-firestore-bundle-v1:3.31.9:3.31.9