diff --git a/README.md b/README.md index e78b8adb75..b8ee5aff86 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ If you are using Maven without BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.1.3') +implementation platform('com.google.cloud:libraries-bom:26.1.4') implementation 'com.google.cloud:google-cloud-spanner' ``` @@ -244,6 +244,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | Instance Operations | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/native-image/src/main/java/com/example/spanner/InstanceOperations.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/native-image/src/main/java/com/example/spanner/InstanceOperations.java) | | Native Image Spanner Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/native-image/src/main/java/com/example/spanner/NativeImageSpannerSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/native-image/src/main/java/com/example/spanner/NativeImageSpannerSample.java) | | Add Json Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/AddJsonColumnSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/AddJsonColumnSample.java) | +| Add Jsonb Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java) | | Add Numeric Column Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/AddNumericColumnSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/AddNumericColumnSample.java) | | Async Dml Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/AsyncDmlExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/AsyncDmlExample.java) | | Async Query Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/AsyncQueryExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/AsyncQueryExample.java) | @@ -282,6 +283,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | Pg Spanner Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java) | | Query Information Schema Database Options Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/QueryInformationSchemaDatabaseOptionsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/QueryInformationSchemaDatabaseOptionsSample.java) | | Query With Json Parameter Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/QueryWithJsonParameterSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/QueryWithJsonParameterSample.java) | +| Query With Jsonb Parameter Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/QueryWithJsonbParameterSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/QueryWithJsonbParameterSample.java) | | Query With Numeric Parameter Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/QueryWithNumericParameterSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/QueryWithNumericParameterSample.java) | | Quickstart Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/QuickstartSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/QuickstartSample.java) | | Restore Backup With Encryption Key | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/RestoreBackupWithEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/RestoreBackupWithEncryptionKey.java) | @@ -292,6 +294,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | Update Database With Default Leader Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java) | | Update Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java) | | Update Json Data Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateJsonDataSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateJsonDataSample.java) | +| Update Jsonb Data Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateJsonbDataSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateJsonbDataSample.java) | | Update Numeric Data Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateNumericDataSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateNumericDataSample.java) | diff --git a/samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java b/samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java new file mode 100644 index 0000000000..e8152c4fa0 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/AddJsonbColumnSample.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 Google Inc. + * + * 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.example.spanner; + +// [START spanner_postgresql_jsonb_add_column] +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import java.util.concurrent.ExecutionException; + +class AddJsonbColumnSample { + + static void addJsonbColumn() throws InterruptedException, ExecutionException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + String databaseId = "my-database"; + + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient(); + addJsonbColumn(adminClient, instanceId, databaseId); + } + } + + static void addJsonbColumn(DatabaseAdminClient adminClient, String instanceId, String databaseId) + throws InterruptedException, ExecutionException { + OperationFuture operation = + adminClient.updateDatabaseDdl( + instanceId, + databaseId, + ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSONB"), + null); + // Wait for the operation to finish. + // This will throw an ExecutionException if the operation fails. + operation.get(); + System.out.printf("Successfully added column `VenueDetails`%n"); + } +} +// [END spanner_postgresql_jsonb_add_column] diff --git a/samples/snippets/src/main/java/com/example/spanner/QueryWithJsonbParameterSample.java b/samples/snippets/src/main/java/com/example/spanner/QueryWithJsonbParameterSample.java new file mode 100644 index 0000000000..a0af5be1a9 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/QueryWithJsonbParameterSample.java @@ -0,0 +1,64 @@ +/* + * Copyright 2022 Google Inc. + * + * 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.example.spanner; + +// [START spanner_postgresql_jsonb_query_parameter] +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.Value; + +class QueryWithJsonbParameterSample { + + static void queryWithJsonbParameter() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + String databaseId = "my-database"; + + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); + queryWithJsonbParameter(client); + } + } + + static void queryWithJsonbParameter(DatabaseClient client) { + int rating = 2; + Statement statement = + Statement.newBuilder( + "SELECT VenueId, VenueDetails\n" + + "FROM Venues\n" + + "WHERE CAST(venuedetails ->> 'rating' " + + "AS INTEGER) > $1") + .bind("p1") + .to(Value.int64(rating)) + .build(); + try (ResultSet resultSet = client.singleUse().executeQuery(statement)) { + while (resultSet.next()) { + System.out.printf( + "VenueId: %s, VenueDetails: %s%n", + resultSet.getLong("venueid"), resultSet.getPgJsonb("venuedetails")); + } + } + } +} +// [END spanner_postgresql_jsonb_query_parameter] diff --git a/samples/snippets/src/main/java/com/example/spanner/UpdateJsonbDataSample.java b/samples/snippets/src/main/java/com/example/spanner/UpdateJsonbDataSample.java new file mode 100644 index 0000000000..48733474c6 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/UpdateJsonbDataSample.java @@ -0,0 +1,78 @@ +/* + * Copyright 2022 Google Inc. + * + * 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.example.spanner; + +// [START spanner_postgresql_jsonb_update_data] +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.Value; +import com.google.common.collect.ImmutableList; + +class UpdateJsonbDataSample { + + static void updateJsonbData() { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + String databaseId = "my-database"; + + try (Spanner spanner = + SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); + updateJsonbData(client); + } + } + + static void updateJsonbData(DatabaseClient client) { + // PG JSONB takes the last value in the case of duplicate keys. + // PG JSONB sorts first by key length and then lexicographically with + // equivalent key length. + client.write( + ImmutableList.of( + Mutation.newInsertOrUpdateBuilder("Venues") + .set("VenueId") + .to(4L) + .set("VenueDetails") + .to( + Value.pgJsonb( + "[{\"name\":\"room 1\",\"open\":true,\"name\":\"room 3\"}," + + "{\"name\":\"room 2\",\"open\":false}]")) + .build(), + Mutation.newInsertOrUpdateBuilder("Venues") + .set("VenueId") + .to(19L) + .set("VenueDetails") + .to(Value.pgJsonb("{\"rating\":9,\"open\":true}")) + .build(), + Mutation.newInsertOrUpdateBuilder("Venues") + .set("VenueId") + .to(42L) + .set("VenueDetails") + .to( + Value.pgJsonb( + "{\"name\":null," + + "\"open\":{\"Monday\":true,\"Tuesday\":false}," + + "\"tags\":[\"large\",\"airy\"]}")) + .build())); + System.out.println("Venues successfully updated"); + } +} +// [END spanner_postgresql_jsonb_update_data] diff --git a/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java b/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java index 035b7cdc36..d8e5b0c9c5 100644 --- a/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java +++ b/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java @@ -28,6 +28,7 @@ import com.google.cloud.spanner.Mutation; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.Value; import com.google.common.collect.ImmutableList; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.io.ByteArrayOutputStream; @@ -99,6 +100,7 @@ public static void createTestDatabase() throws Exception { "CREATE TABLE Venues (" + "VenueId bigint NOT NULL," + "Revenue NUMERIC," + + "VenueDetails JSONB," + "PRIMARY KEY (VenueId))"), null).get(); } @@ -206,4 +208,79 @@ public void queryWithNumericParameter_shouldReturnResults() { runExample(() -> PgQueryWithNumericParameterSample.queryWithNumericParameter(client)); assertThat(out).contains("4 35000"); } + + @Test + public void addJsonbColumn_shouldSuccessfullyAddColumn() + throws InterruptedException, ExecutionException { + OperationFuture operation = + spanner + .getDatabaseAdminClient() + .updateDatabaseDdl( + instanceId, + databaseId, + ImmutableList.of("ALTER TABLE Venues DROP COLUMN VenueDetails"), + null); + operation.get(); + String out = + runExample( + () -> { + try { + AddJsonbColumnSample.addJsonbColumn( + spanner.getDatabaseAdminClient(), instanceId, databaseId); + } catch (ExecutionException e) { + System.out.printf( + "Adding column `VenueDetails` failed: %s%n", e.getCause().getMessage()); + } catch (InterruptedException e) { + System.out.printf("Adding column `VenueDetails` was interrupted%n"); + } + }); + assertThat(out).contains("Successfully added column `VenueDetails`"); + } + + @Test + public void updateJsonbData_shouldWriteData() { + String projectId = spanner.getOptions().getProjectId(); + String out = + runExample( + () -> + UpdateJsonbDataSample.updateJsonbData( + spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)))); + assertThat(out).contains("Venues successfully updated"); + } + + @Test + public void queryWithJsonbParameter_shouldReturnResults() { + String projectId = spanner.getOptions().getProjectId(); + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); + client.write( + ImmutableList.of( + Mutation.newInsertOrUpdateBuilder("Venues") + .set("VenueId") + .to(4L) + .set("VenueDetails") + .to( + Value.pgJsonb( + "[{\"name\":\"room 1\",\"open\":true}," + + "{\"name\":\"room 2\",\"open\":false}]")) + .build(), + Mutation.newInsertOrUpdateBuilder("Venues") + .set("VenueId") + .to(19L) + .set("VenueDetails") + .to(Value.pgJsonb("{\"rating\":9,\"open\":true}")) + .build(), + Mutation.newInsertOrUpdateBuilder("Venues") + .set("VenueId") + .to(42L) + .set("VenueDetails") + .to( + Value.pgJsonb( + "{\"name\":null," + + "\"open\":{\"Monday\":true,\"Tuesday\":false}," + + "\"tags\":[\"large\",\"airy\"]}")) + .build())); + String out = runExample(() -> QueryWithJsonbParameterSample.queryWithJsonbParameter(client)); + assertThat(out).contains("VenueId: 19, VenueDetails: {\"open\": true, \"rating\": 9}"); + } }