diff --git a/pom.xml b/pom.xml index ef8d9412ee3..d5401ec24b2 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,10 @@ 1.8 1.8 + + default-instance + mysample + quickstart-db @@ -112,6 +116,7 @@ monitoring/v2 monitoring/v3 pubsub/cloud-client + spanner/cloud-client speech/grpc storage/cloud-client storage/json-api diff --git a/spanner/cloud-client/README.md b/spanner/cloud-client/README.md new file mode 100644 index 00000000000..b028fff17cb --- /dev/null +++ b/spanner/cloud-client/README.md @@ -0,0 +1,35 @@ +# Getting Started with Cloud Spanner and the Google Cloud Client libraries + +[Cloud Spanner][Spanner] is a horizontally-scalable database-as-a-service +with transactions and SQL support. +These sample Java applications demonstrate how to access the Spanner API using +the [Google Cloud Client Library for Java][google-cloud-java]. + +[Spanner]: https://cloud.google.com/spanner/ +[google-cloud-java]: https://github.com/GoogleCloudPlatform/google-cloud-java + +## Quickstart + +Install [Maven](http://maven.apache.org/). + +Build your project with: + + mvn clean package -DskipTests + +You can then run a given `ClassName` via: + + mvn exec:java -Dexec.mainClass=com.example.spanner.ClassName \ + -DpropertyName=propertyValue \ + -Dexec.args="any arguments to the app" + +### Running a simple query (using the quickstart sample) + + mvn exec:java -Dexec.mainClass=com.example.spanner.QuickstartSample -Dexec.args="my-instance my-database" + +## Tutorial + +### Running the tutorial + mvn exec:java -Dexec.mainClass=com.example.spanner.SpannerSample -Dexec.args=" my-instance my-database" + +## Test + mvn verify -Dspanner.test.instance= -Dspanner.sample.database= -Dspanner.quickstart.database= diff --git a/spanner/cloud-client/pom.xml b/spanner/cloud-client/pom.xml new file mode 100644 index 00000000000..990954738e9 --- /dev/null +++ b/spanner/cloud-client/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + come.example.spanner + spanner-google-cloud-samples + jar + + + + doc-samples + com.google.cloud + 1.0.0 + ../.. + + + + 1.8 + 1.8 + UTF-8 + + + + + com.google.cloud + google-cloud-spanner + 0.9.2-beta + + + com.google.guava + guava-jdk5 + + + + + io.netty + netty-tcnative-boringssl-static + 1.1.33.Fork23 + runtime + + + com.google.guava + guava + 20.0 + + + + junit + junit + 4.12 + test + + + com.google.truth + truth + 0.31 + test + + + + + + maven-assembly-plugin + 2.5.5 + + + jar-with-dependencies + + + + com.example.spanner.SpannerSample + + + + + + make-assembly + package + + single + + + + + + + + diff --git a/spanner/cloud-client/src/main/java/com/example/spanner/QuickstartSample.java b/spanner/cloud-client/src/main/java/com/example/spanner/QuickstartSample.java new file mode 100644 index 00000000000..eeed92ff811 --- /dev/null +++ b/spanner/cloud-client/src/main/java/com/example/spanner/QuickstartSample.java @@ -0,0 +1,64 @@ +/* + Copyright 2017, 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_quickstart] +// Imports the Google Cloud client library +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; + +/** + * A quick start code for Cloud Spanner. It demonstrates how to setup the Cloud Spanner client and + * execute a simple query using it against an existing database. + */ +public class QuickstartSample { + public static void main(String... args) throws Exception { + + if (args.length != 2) { + System.err.println("Usage: QuickStartSample "); + return; + } + // Instantiates a client + SpannerOptions options = SpannerOptions.newBuilder().build(); + Spanner spanner = options.getService(); + + // Name of your database. Eg: projects/my-project/instances/instanceId/databases/databaseId + String instanceId = args[0]; + String databaseId = args[1]; + try { + // Creates a database client + DatabaseClient dbClient = spanner.getDatabaseClient(DatabaseId.of( + options.getProjectId(), instanceId, databaseId)); + // Queries the database + ResultSet resultSet = dbClient.singleUse().executeQuery(Statement.of("SELECT 1")); + + System.out.println("\n\nResults:"); + // Prints the results + while (resultSet.next()) { + System.out.printf("%d\n\n", resultSet.getLong(0)); + } + } finally { + // Closes the client which will free up the resources used + spanner.closeAsync().get(); + } + } +} +// [END spanner_quickstart] diff --git a/spanner/cloud-client/src/main/java/com/example/spanner/SpannerSample.java b/spanner/cloud-client/src/main/java/com/example/spanner/SpannerSample.java new file mode 100644 index 00000000000..3d9d9f736d0 --- /dev/null +++ b/spanner/cloud-client/src/main/java/com/example/spanner/SpannerSample.java @@ -0,0 +1,507 @@ +/* + * Copyright 2017 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 transaction_import] +import static com.google.cloud.spanner.TransactionRunner.TransactionCallable; +// [END transaction_import] + +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +// [START transaction_import] +import com.google.cloud.spanner.Key; +// [END transaction_import] +// [START read_import] +import com.google.cloud.spanner.KeySet; +// [END read_import] +// [START write_import] +import com.google.cloud.spanner.Mutation; +// [END write_import] +import com.google.cloud.spanner.Operation; +// [START read_only_transaction_import] +import com.google.cloud.spanner.ReadOnlyTransaction; +// [END read_only_transaction_import] +// [START query_import] +import com.google.cloud.spanner.ResultSet; +// [END query_import] +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +// [START query_import] +import com.google.cloud.spanner.Statement; +// [END query_import] +// [START transaction_import] +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.TransactionContext; +// [END transaction_import] +import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; + +// [START write_import] + +import java.util.ArrayList; +// [END write_import] +import java.util.Arrays; +import java.util.List; + +/** + * Example code for using the Cloud Spanner API. This example demonstrates all the common + * operations that can be done on Cloud Spanner. These are:

+ *

    + *
  • Creating a Cloud Spanner database. + *
  • Writing, reading and executing SQL queries. + *
  • Writing data using a read-write transaction. + *
  • Using an index to read and execute SQL queries over data. + *
+ * + */ +public class SpannerSample { + /** Class to contain singer sample data. */ + static class Singer { + final long singerId; + final String firstName; + final String lastName; + + Singer(long singerId, String firstName, String lastName) { + this.singerId = singerId; + this.firstName = firstName; + this.lastName = lastName; + } + } + + /** Class to contain album sample data. */ + static class Album { + final long singerId; + final long albumId; + final String albumTitle; + + Album(long singerId, long albumId, String albumTitle) { + this.singerId = singerId; + this.albumId = albumId; + this.albumTitle = albumTitle; + } + } + + // [START write] + static final List SINGERS = + Arrays.asList( + new Singer(1, "Marc", "Richards"), + new Singer(2, "Catalina", "Smith"), + new Singer(3, "Alice", "Trentor"), + new Singer(4, "Lea", "Martin"), + new Singer(5, "David", "Lomond")); + + static final List ALBUMS = + Arrays.asList( + new Album(1, 1, "Total Junk"), + new Album(1, 2, "Go, Go, Go"), + new Album(2, 1, "Green"), + new Album(2, 2, "Forever Hold Your Peace"), + new Album(2, 3, "Terrified")); + // [END write] + + static void createDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) { + Operation op = dbAdminClient + .createDatabase( + id.getInstanceId().getInstance(), + id.getDatabase(), + Arrays.asList( + "CREATE TABLE Singers (\n" + + " SingerId INT64 NOT NULL,\n" + + " FirstName STRING(1024),\n" + + " LastName STRING(1024),\n" + + " SingerInfo BYTES(MAX)\n" + + ") PRIMARY KEY (SingerId)", + "CREATE TABLE Albums (\n" + + " SingerId INT64 NOT NULL,\n" + + " AlbumId INT64 NOT NULL,\n" + + " AlbumTitle STRING(MAX)\n" + + ") PRIMARY KEY (SingerId, AlbumId),\n" + + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE")); + Database db = op.waitFor().getResult(); + System.out.println("Created database [" + db.getId() + "]"); + } + + // [START write] + static void writeExampleData(DatabaseClient dbClient) { + List mutations = new ArrayList<>(); + for (Singer singer : SINGERS) { + mutations.add( + Mutation.newInsertBuilder("Singers") + .set("SingerId") + .to(singer.singerId) + .set("FirstName") + .to(singer.firstName) + .set("LastName") + .to(singer.lastName) + .build()); + } + for (Album album : ALBUMS) { + mutations.add( + Mutation.newInsertBuilder("Albums") + .set("SingerId") + .to(album.singerId) + .set("AlbumId") + .to(album.albumId) + .set("AlbumTitle") + .to(album.albumTitle) + .build()); + } + dbClient.write(mutations); + } + // [END write] + + // [START query] + static void query(DatabaseClient dbClient) { + // singleUse() can be used to execute a single read or query against Cloud Spanner. + ResultSet resultSet = + dbClient + .singleUse() + .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums")); + while (resultSet.next()) { + System.out.printf( + "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2)); + } + } + // [END query] + + // [START read] + static void read(DatabaseClient dbClient) { + ResultSet resultSet = + dbClient + .singleUse() + .read("Albums", + // KeySet.all() can be used to read all rows in a table. KeySet exposes other + // methods to read only a subset of the table. + KeySet.all(), + Arrays.asList("SingerId", "AlbumId", "AlbumTitle")); + while (resultSet.next()) { + System.out.printf( + "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2)); + } + } + // [END read] + + // [START add_marketing_budget] + static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseId dbId) { + adminClient.updateDatabaseDdl(dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget INT64"), + null).waitFor(); + System.out.println("Added MarketingBudget column"); + } + // [END add_marketing_budget] + + // Before executing this method, a new column MarketingBudget has to be added to the Albums + // table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64". + // [START update] + static void update(DatabaseClient dbClient) { + // Mutation can be used to update/insert/delete a single row in a table. Here we use + // newUpdateBuilder to create update mutations. + List mutations = + Arrays.asList( + Mutation.newUpdateBuilder("Albums") + .set("SingerId") + .to(1) + .set("AlbumId") + .to(1) + .set("MarketingBudget") + .to(100000) + .build(), + Mutation.newUpdateBuilder("Albums") + .set("SingerId") + .to(2) + .set("AlbumId") + .to(2) + .set("MarketingBudget") + .to(500000) + .build()); + // This writes all the mutations to Cloud Spanner atomically. + dbClient.write(mutations); + } + // [END update] + + // [START transaction] + static void writeWithTransaction(DatabaseClient dbClient) { + dbClient + .readWriteTransaction() + .run( + new TransactionCallable() { + @Override + public Void run(TransactionContext transaction) throws Exception { + // Transfer marketing budget from one album to another. We do it in a transaction to + // ensure that the transfer is atomic. + Struct row = + transaction.readRow("Albums", Key.of(2, 2), Arrays.asList("MarketingBudget")); + long album2Budget = row.getLong(0); + // Transaction will only be committed if this condition still holds at the time of + // commit. Otherwise it will be aborted and the callable will be rerun by the + // client library. + if (album2Budget >= 300000) { + long album1Budget = + transaction + .readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget")) + .getLong(0); + long transfer = 200000; + album1Budget += transfer; + album2Budget -= transfer; + transaction.buffer( + Mutation.newUpdateBuilder("Albums") + .set("SingerId") + .to(1) + .set("AlbumId") + .to(1) + .set("MarketingBudget") + .to(album1Budget) + .build()); + transaction.buffer( + Mutation.newUpdateBuilder("Albums") + .set("SingerId") + .to(2) + .set("AlbumId") + .to(2) + .set("MarketingBudget") + .to(album2Budget) + .build()); + } + return null; + } + }); + } + // [END transaction] + + // [START query_new_column] + static void queryMarketingBudget(DatabaseClient dbClient) { + // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to + // null. + ResultSet resultSet = + dbClient + .singleUse() + .executeQuery(Statement.of("SELECT SingerId, AlbumId, MarketingBudget FROM Albums")); + while (resultSet.next()) { + System.out.printf( + "%d %d %s\n", + resultSet.getLong("SingerId"), + resultSet.getLong("AlbumId"), + // We check that the value is non null. ResultSet getters can only be used to retrieve + // non null values. + resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget")); + } + } + // [END query_new_column] + + // [START add_index] + static void addIndex(DatabaseAdminClient adminClient, DatabaseId dbId) { + adminClient.updateDatabaseDdl(dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"), + null).waitFor(); + System.out.println("Added AlbumsByAlbumTitle index"); + } + // [END add_index] + + // Before running this example, add the index AlbumsByAlbumTitle by applying the DDL statement + // "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)". + // [START query_index] + static void queryUsingIndex(DatabaseClient dbClient) { + ResultSet resultSet = + dbClient + .singleUse() + .executeQuery( + // We use FORCE_INDEX hint to specify which index to use. For more details see + // https://cloud.google.com/spanner/docs/query-syntax#from-clause + Statement.of( + "SELECT AlbumId, AlbumTitle, MarketingBudget\n" + + "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}\n" + + "WHERE AlbumTitle >= 'Aardvark' AND AlbumTitle < 'Goo'")); + while (resultSet.next()) { + System.out.printf( + "%d %s %s\n", + resultSet.getLong("AlbumId"), + resultSet.getString("AlbumTitle"), + resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget")); + } + } + // [END query_index] + + // [START read_index] + static void readUsingIndex(DatabaseClient dbClient) { + ResultSet resultSet = + dbClient + .singleUse() + .readUsingIndex( + "Albums", + "AlbumsByAlbumTitle", + KeySet.all(), + Arrays.asList("AlbumId", "AlbumTitle")); + while (resultSet.next()) { + System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1)); + } + } + // [END read_index] + + // [START add_storing_index] + static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseId dbId) { + adminClient.updateDatabaseDdl(dbId.getInstanceId().getInstance(), + dbId.getDatabase(), + Arrays.asList( + "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)"), + null).waitFor(); + System.out.println("Added AlbumsByAlbumTitle2 index"); + } + // [END add_storing_index] + + // Before running this example, create a storing index AlbumsByAlbumTitle2 by applying the DDL + // statement "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)". + // [START read_storing_index] + static void readStoringIndex(DatabaseClient dbClient) { + // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget. + ResultSet resultSet = + dbClient + .singleUse() + .readUsingIndex( + "Albums", + "AlbumsByAlbumTitle2", + KeySet.all(), + Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget")); + while (resultSet.next()) { + System.out.printf( + "%d %s %s\n", + resultSet.getLong(0), + resultSet.getString(1), + resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget")); + } + } + // [END read_storing_index] + + // [START read_only_transaction] + static void readOnlyTransaction(DatabaseClient dbClient) { + // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it. + // We use a try-with-resource block to automatically do so. + try (ReadOnlyTransaction transaction = dbClient.readOnlyTransaction()) { + ResultSet queryResultSet = + transaction.executeQuery( + Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums")); + while (queryResultSet.next()) { + System.out.printf( + "%d %d %s\n", + queryResultSet.getLong(0), queryResultSet.getLong(1), queryResultSet.getString(2)); + } + ResultSet readResultSet = + transaction.read( + "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle")); + while (readResultSet.next()) { + System.out.printf( + "%d %d %s\n", + readResultSet.getLong(0), readResultSet.getLong(1), readResultSet.getString(2)); + } + } + } + // [END read_only_transaction] + + static void run(DatabaseClient dbClient, DatabaseAdminClient dbAdminClient, String command, + DatabaseId database) { + switch (command) { + case "createdatabase": + createDatabase(dbAdminClient, database); + break; + case "write": + writeExampleData(dbClient); + break; + case "query": + query(dbClient); + break; + case "read": + read(dbClient); + break; + case "addmarketingbudget": + addMarketingBudget(dbAdminClient, database); + break; + case "update": + update(dbClient); + break; + case "writetransaction": + writeWithTransaction(dbClient); + break; + case "querymarketingbudget": + queryMarketingBudget(dbClient); + break; + case "addindex": + addIndex(dbAdminClient, database); + break; + case "readindex": + readUsingIndex(dbClient); + break; + case "queryindex": + queryUsingIndex(dbClient); + break; + case "addstoringindex": + addStoringIndex(dbAdminClient, database); + break; + case "readstoringindex": + readStoringIndex(dbClient); + break; + case "readonlytransaction": + readOnlyTransaction(dbClient); + break; + default: + printUsageAndExit(); + } + } + + static void printUsageAndExit() { + System.err.println("Usage:"); + System.err.println(" SpannerExample "); + System.err.println(""); + System.err.println("Examples:"); + System.err.println( + " SpannerExample createdatabase my-instance example-db"); + System.err.println( + " SpannerExample write my-instance example-db"); + System.exit(1); + } + + public static void main(String[] args) throws Exception { + if (args.length != 3) { + printUsageAndExit(); + } + // [START init_client] + SpannerOptions options = SpannerOptions.newBuilder().build(); + Spanner spanner = options.getService(); + try { + String command = args[0]; + DatabaseId db = DatabaseId.of(options.getProjectId(), args[1], args[2]); + // [END init_client] + // This will return the default project id based on the environment. + String clientProject = spanner.getOptions().getProjectId(); + if (!db.getInstanceId().getProject().equals(clientProject)) { + System.err.println("Invalid project specified. Project in the database id should match" + + "the project name set in the environment variable GCLOUD_PROJECT. Expected: " + + clientProject); + printUsageAndExit(); + } + // [START init_client] + DatabaseClient dbClient = spanner.getDatabaseClient(db); + DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + // [END init_client] + run(dbClient, dbAdminClient, command, db); + } finally { + spanner.closeAsync().get(); + } + System.out.println("Closed client"); + } +} diff --git a/spanner/cloud-client/src/test/java/com/example/spanner/QuickstartSampleIT.java b/spanner/cloud-client/src/test/java/com/example/spanner/QuickstartSampleIT.java new file mode 100644 index 00000000000..007fa464f1b --- /dev/null +++ b/spanner/cloud-client/src/test/java/com/example/spanner/QuickstartSampleIT.java @@ -0,0 +1,62 @@ +/* + Copyright 2017, 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; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +/** + * Tests for quickstart sample. + */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class QuickstartSampleIT { + private String instanceId = System.getProperty("spanner.test.instance"); + // This database needs to exist for test to pass. + private String dbId = System.getProperty("spanner.quickstart.database"); + private ByteArrayOutputStream bout; + private PrintStream out; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testQuickstart() throws Exception { + assertThat(instanceId).isNotNull(); + assertThat(dbId).isNotNull(); + QuickstartSample.main(instanceId, dbId); + String got = bout.toString(); + assertThat(got).contains("1"); + } +} diff --git a/spanner/cloud-client/src/test/java/com/example/spanner/SpannerSampleIT.java b/spanner/cloud-client/src/test/java/com/example/spanner/SpannerSampleIT.java new file mode 100644 index 00000000000..64eb8208071 --- /dev/null +++ b/spanner/cloud-client/src/test/java/com/example/spanner/SpannerSampleIT.java @@ -0,0 +1,112 @@ +/* + * Copyright 2017 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; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +/** + * Unit tests for {@code SpannerSample} + */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SpannerSampleIT { + // The instance needs to exist for tests to pass. + String instanceId = System.getProperty("spanner.test.instance"); + String databaseId = System.getProperty("spanner.sample.database"); + DatabaseId dbId; + DatabaseAdminClient dbClient; + + private String runSample(String command) throws Exception { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + System.setOut(out); + SpannerSample.main(new String[]{command, instanceId, databaseId}); + return bout.toString(); + } + + @Before + public void setUp() throws Exception { + SpannerOptions options = SpannerOptions.newBuilder().build(); + Spanner spanner = options.getService(); + dbClient = spanner.getDatabaseAdminClient(); + dbId = DatabaseId.of(options.getProjectId(), instanceId, databaseId); + dbClient.dropDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase()); + } + + @After + public void tearDown() throws Exception { + dbClient.dropDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase()); + } + + @Test + public void testSample() throws Exception { + assertThat(instanceId).isNotNull(); + assertThat(databaseId).isNotNull(); + String out = runSample("createdatabase"); + assertThat(out).contains("Created database"); + assertThat(out).contains(dbId.getName()); + + runSample("write"); + + out = runSample("read"); + assertThat(out).contains("1 1 Total Junk"); + + out = runSample("query"); + assertThat(out).contains("1 1 Total Junk"); + + runSample("addmarketingbudget"); + runSample("update"); + + runSample("writetransaction"); + + out = runSample("querymarketingbudget"); + assertThat(out).contains("1 1 300000"); + assertThat(out).contains("2 2 300000"); + + runSample("addindex"); + out = runSample("queryindex"); + assertThat(out).contains("Go, Go, Go"); + assertThat(out).contains("Forever Hold Your Peace"); + assertThat(out).doesNotContain("Green"); + + out = runSample("readindex"); + assertThat(out).contains("Go, Go, Go"); + assertThat(out).contains("Forever Hold Your Peace"); + assertThat(out).contains("Green"); + + runSample("addstoringindex"); + out = runSample("readstoringindex"); + assertThat(out).contains("300000"); + + out = runSample("readonlytransaction"); + assertThat(out.replaceAll("[\r\n]+", " ")).containsMatch("(Total Junk.*){2}"); + } +}