Skip to content

Commit a6f4093

Browse files
jsimonweblesv
authored andcommitted
Add Spanner commit timestamp sample. (GoogleCloudPlatform#1072)
* Add Spanner commit timestamp sample. * Update pom file to use latest version.
1 parent 6fc6f51 commit a6f4093

File tree

3 files changed

+238
-2
lines changed

3 files changed

+238
-2
lines changed

spanner/cloud-client/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ limitations under the License.
128128
<dependency>
129129
<groupId>com.google.cloud</groupId>
130130
<artifactId>google-cloud-bom</artifactId>
131-
<version>0.37.0-alpha</version>
131+
<version>0.42.0-alpha</version>
132132
<type>pom</type>
133133
<scope>import</scope>
134134
</dependency>

spanner/cloud-client/src/main/java/com/example/spanner/SpannerSample.java

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import com.google.cloud.spanner.Struct;
3535
import com.google.cloud.spanner.TimestampBound;
3636
import com.google.cloud.spanner.TransactionContext;
37+
import com.google.cloud.spanner.Value;
3738
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
39+
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
3840
import java.util.ArrayList;
3941
import java.util.Arrays;
4042
import java.util.List;
@@ -48,6 +50,7 @@
4850
* <li> Writing, reading and executing SQL queries.
4951
* <li> Writing data using a read-write transaction.
5052
* <li> Using an index to read and execute SQL queries over data.
53+
* <li> Using commit timestamp for tracking when a record was last updated.
5154
* </ul>
5255
*/
5356
public class SpannerSample {
@@ -84,6 +87,24 @@ static class Album {
8487
}
8588
}
8689

90+
/**
91+
* Class to contain performance sample data.
92+
*/
93+
static class Performance {
94+
95+
final long singerId;
96+
final long venueId;
97+
final String eventDate;
98+
final long revenue;
99+
100+
Performance(long singerId, long venueId, String eventDate, long revenue) {
101+
this.singerId = singerId;
102+
this.venueId = venueId;
103+
this.eventDate = eventDate;
104+
this.revenue = revenue;
105+
}
106+
}
107+
87108
// [START spanner_insert_data]
88109
static final List<Singer> SINGERS =
89110
Arrays.asList(
@@ -102,6 +123,14 @@ static class Album {
102123
new Album(2, 3, "Terrified"));
103124
// [END spanner_insert_data]
104125

126+
// [START spanner_insert_data_with_timestamp_column]
127+
static final List<Performance> PERFORMANCES =
128+
Arrays.asList(
129+
new Performance(1, 4, "2017-10-05", 11000),
130+
new Performance(1, 19, "2017-11-02", 15000),
131+
new Performance(2, 42, "2017-12-23", 7000));
132+
// [END spanner_insert_data_with_timestamp_column]
133+
105134
// [START spanner_create_database]
106135
static void createDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) {
107136
Operation<Database, CreateDatabaseMetadata> op = dbAdminClient
@@ -126,6 +155,48 @@ static void createDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) {
126155
}
127156
// [END spanner_create_database]
128157

158+
// [START spanner_create_table_with_timestamp_column]
159+
static void createTableWithTimestamp(DatabaseAdminClient dbAdminClient, DatabaseId id) {
160+
Operation<Void, UpdateDatabaseDdlMetadata> op = dbAdminClient
161+
.updateDatabaseDdl(
162+
id.getInstanceId().getInstance(),
163+
id.getDatabase(),
164+
Arrays.asList(
165+
"CREATE TABLE Performances (\n"
166+
+ " SingerId INT64 NOT NULL,\n"
167+
+ " VenueId INT64 NOT NULL,\n"
168+
+ " EventDate Date,\n"
169+
+ " Revenue INT64, \n"
170+
+ " LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)\n"
171+
+ ") PRIMARY KEY (SingerId, VenueId, EventDate),\n"
172+
+ " INTERLEAVE IN PARENT Singers ON DELETE CASCADE"), null);
173+
op.waitFor().getResult();
174+
System.out.println("Created Performances table in database: [" + id + "]");
175+
}
176+
// [END spanner_create_table_with_timestamp_column]
177+
178+
// [START spanner_insert_data_with_timestamp_column]
179+
static void writeExampleDataWithTimestamp(DatabaseClient dbClient) {
180+
List<Mutation> mutations = new ArrayList<>();
181+
for (Performance performance : PERFORMANCES) {
182+
mutations.add(
183+
Mutation.newInsertBuilder("Performances")
184+
.set("SingerId")
185+
.to(performance.singerId)
186+
.set("VenueId")
187+
.to(performance.venueId)
188+
.set("EventDate")
189+
.to(performance.eventDate)
190+
.set("Revenue")
191+
.to(performance.revenue)
192+
.set("LastUpdateTime")
193+
.to(Value.COMMIT_TIMESTAMP)
194+
.build());
195+
}
196+
dbClient.write(mutations);
197+
}
198+
// [END spanner_insert_data_with_timestamp_column]
199+
129200
// [START spanner_insert_data]
130201
static void writeExampleData(DatabaseClient dbClient) {
131202
List<Mutation> mutations = new ArrayList<>();
@@ -422,6 +493,98 @@ static void readStaleData(DatabaseClient dbClient) {
422493
}
423494
// [END spanner_read_stale_data]
424495

496+
// [START spanner_add_timestamp_column]
497+
static void addCommitTimestamp(DatabaseAdminClient adminClient, DatabaseId dbId) {
498+
adminClient.updateDatabaseDdl(dbId.getInstanceId().getInstance(),
499+
dbId.getDatabase(),
500+
Arrays.asList(
501+
"ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP "
502+
+ "OPTIONS (allow_commit_timestamp=true)"),
503+
null).waitFor();
504+
System.out.println("Added LastUpdateTime as a commit timestamp column in Albums table.");
505+
}
506+
// [END spanner_add_timestamp_column]
507+
508+
// Before executing this method, a new column MarketingBudget has to be added to the Albums
509+
// table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64".
510+
// In addition this update expects the LastUpdateTime column added by applying the DDL statement
511+
// "ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true)"
512+
// [START spanner_update_data_with_timestamp_column]
513+
static void updateWithTimestamp(DatabaseClient dbClient) {
514+
// Mutation can be used to update/insert/delete a single row in a table. Here we use
515+
// newUpdateBuilder to create update mutations.
516+
List<Mutation> mutations =
517+
Arrays.asList(
518+
Mutation.newUpdateBuilder("Albums")
519+
.set("SingerId")
520+
.to(1)
521+
.set("AlbumId")
522+
.to(1)
523+
.set("MarketingBudget")
524+
.to(1000000)
525+
.set("LastUpdateTime")
526+
.to(Value.COMMIT_TIMESTAMP)
527+
.build(),
528+
Mutation.newUpdateBuilder("Albums")
529+
.set("SingerId")
530+
.to(2)
531+
.set("AlbumId")
532+
.to(2)
533+
.set("MarketingBudget")
534+
.to(750000)
535+
.set("LastUpdateTime")
536+
.to(Value.COMMIT_TIMESTAMP)
537+
.build());
538+
// This writes all the mutations to Cloud Spanner atomically.
539+
dbClient.write(mutations);
540+
}
541+
// [END spanner_update_data_with_timestamp_column]
542+
543+
// [START spanner_query_data_with_timestamp_column]
544+
static void queryMarketingBudgetWithTimestamp(DatabaseClient dbClient) {
545+
// Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
546+
// null.
547+
ResultSet resultSet =
548+
dbClient
549+
.singleUse()
550+
.executeQuery(Statement.of(
551+
"SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime FROM Albums"
552+
+ " ORDER BY LastUpdateTime DESC"));
553+
while (resultSet.next()) {
554+
System.out.printf(
555+
"%d %d %s %s\n",
556+
resultSet.getLong("SingerId"),
557+
resultSet.getLong("AlbumId"),
558+
// We check that the value is non null. ResultSet getters can only be used to retrieve
559+
// non null values.
560+
resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"),
561+
resultSet.isNull("LastUpdateTime") ? "NULL" : resultSet.getTimestamp("LastUpdateTime"));
562+
}
563+
}
564+
// [END spanner_query_data_with_timestamp_column]
565+
566+
static void queryPerformancesTable(DatabaseClient dbClient) {
567+
// Rows without an explicit value for Revenue will have a Revenue equal to
568+
// null.
569+
ResultSet resultSet =
570+
dbClient
571+
.singleUse()
572+
.executeQuery(Statement.of(
573+
"SELECT SingerId, VenueId, EventDate, Revenue, LastUpdateTime FROM Performances"
574+
+ " ORDER BY LastUpdateTime DESC"));
575+
while (resultSet.next()) {
576+
System.out.printf(
577+
"%d %d %s %s %s\n",
578+
resultSet.getLong("SingerId"),
579+
resultSet.getLong("VenueId"),
580+
resultSet.getDate("EventDate"),
581+
// We check that the value is non null. ResultSet getters can only be used to retrieve
582+
// non null values.
583+
resultSet.isNull("Revenue") ? "NULL" : resultSet.getLong("Revenue"),
584+
resultSet.getTimestamp("LastUpdateTime"));
585+
}
586+
}
587+
425588
static void run(DatabaseClient dbClient, DatabaseAdminClient dbAdminClient, String command,
426589
DatabaseId database) {
427590
switch (command) {
@@ -470,6 +633,24 @@ static void run(DatabaseClient dbClient, DatabaseAdminClient dbAdminClient, Stri
470633
case "readstaledata":
471634
readStaleData(dbClient);
472635
break;
636+
case "addcommittimestamp":
637+
addCommitTimestamp(dbAdminClient, database);
638+
break;
639+
case "updatewithtimestamp":
640+
updateWithTimestamp(dbClient);
641+
break;
642+
case "querywithtimestamp":
643+
queryMarketingBudgetWithTimestamp(dbClient);
644+
break;
645+
case "createtablewithtimestamp":
646+
createTableWithTimestamp(dbAdminClient, database);
647+
break;
648+
case "writewithtimestamp":
649+
writeExampleDataWithTimestamp(dbClient);
650+
break;
651+
case "queryperformancestable":
652+
queryPerformancesTable(dbClient);
653+
break;
473654
default:
474655
printUsageAndExit();
475656
}
@@ -484,6 +665,44 @@ static void printUsageAndExit() {
484665
" SpannerExample createdatabase my-instance example-db");
485666
System.err.println(
486667
" SpannerExample write my-instance example-db");
668+
System.err.println(
669+
" SpannerExample query my-instance example-db");
670+
System.err.println(
671+
" SpannerExample read my-instance example-db");
672+
System.err.println(
673+
" SpannerExample addmarketingbudget my-instance example-db");
674+
System.err.println(
675+
" SpannerExample update my-instance example-db");
676+
System.err.println(
677+
" SpannerExample writetransaction my-instance example-db");
678+
System.err.println(
679+
" SpannerExample querymarketingbudget my-instance example-db");
680+
System.err.println(
681+
" SpannerExample addindex my-instance example-db");
682+
System.err.println(
683+
" SpannerExample readindex my-instance example-db");
684+
System.err.println(
685+
" SpannerExample queryindex my-instance example-db");
686+
System.err.println(
687+
" SpannerExample addstoringindex my-instance example-db");
688+
System.err.println(
689+
" SpannerExample readstoringindex my-instance example-db");
690+
System.err.println(
691+
" SpannerExample readonlytransaction my-instance example-db");
692+
System.err.println(
693+
" SpannerExample readstaledata my-instance example-db");
694+
System.err.println(
695+
" SpannerExample addcommittimestamp my-instance example-db");
696+
System.err.println(
697+
" SpannerExample updatewithtimestamp my-instance example-db");
698+
System.err.println(
699+
" SpannerExample querywithtimestamp my-instance example-db");
700+
System.err.println(
701+
" SpannerExample createtablewithtimestamp my-instance example-db");
702+
System.err.println(
703+
" SpannerExample writewithtimestamp my-instance example-db");
704+
System.err.println(
705+
" SpannerExample queryperformancestable my-instance example-db");
487706
System.exit(1);
488707
}
489708

spanner/cloud-client/src/test/java/com/example/spanner/SpannerSampleIT.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void testSample() throws Exception {
8585
out = runSample("query");
8686
assertThat(out).contains("1 1 Total Junk");
8787
runSample("addmarketingbudget");
88-
88+
8989
// wait for 15 seconds to elapse and then run an update, and query for stale data
9090
lastUpdateDataTimeInMillis = System.currentTimeMillis();
9191
while (System.currentTimeMillis() < lastUpdateDataTimeInMillis + 16000) {
@@ -116,6 +116,23 @@ public void testSample() throws Exception {
116116

117117
out = runSample("readonlytransaction");
118118
assertThat(out.replaceAll("[\r\n]+", " ")).containsMatch("(Total Junk.*){2}");
119+
120+
out = runSample("addcommittimestamp");
121+
assertThat(out).contains("Added LastUpdateTime as a commit timestamp column");
122+
123+
runSample("updatewithtimestamp");
124+
out = runSample("querywithtimestamp");
125+
assertThat(out).contains("1 1 1000000");
126+
assertThat(out).contains("2 2 750000");
127+
128+
out = runSample("createtablewithtimestamp");
129+
assertThat(out).contains("Created Performances table in database");
130+
131+
runSample("writewithtimestamp");
132+
out = runSample("queryperformancestable");
133+
assertThat(out).contains("1 4 2017-10-05 11000");
134+
assertThat(out).contains("1 19 2017-11-02 15000");
135+
assertThat(out).contains("2 42 2017-12-23 7000");
119136
}
120137

121138
private String formatForTest(String name) {

0 commit comments

Comments
 (0)