Skip to content

Commit 1cdc696

Browse files
committed
Add Commit Timestamp java integration tests for cloud spanner
1 parent fa2b471 commit 1cdc696

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
package com.google.cloud.spanner.it;
2+
3+
import static com.google.cloud.spanner.SpannerMatchers.isSpannerException;
4+
import static com.google.common.truth.Truth.assertThat;
5+
6+
import com.google.cloud.Timestamp;
7+
import com.google.cloud.spanner.Database;
8+
import com.google.cloud.spanner.DatabaseAdminClient;
9+
import com.google.cloud.spanner.DatabaseClient;
10+
import com.google.cloud.spanner.ErrorCode;
11+
import com.google.cloud.spanner.IntegrationTest;
12+
import com.google.cloud.spanner.IntegrationTestEnv;
13+
import com.google.cloud.spanner.Key;
14+
import com.google.cloud.spanner.Mutation;
15+
import com.google.cloud.spanner.SpannerExceptionFactory;
16+
import com.google.cloud.spanner.Struct;
17+
import com.google.cloud.spanner.TimestampBound;
18+
import com.google.cloud.spanner.Value;
19+
import com.google.cloud.spanner.testing.RemoteSpannerHelper;
20+
import com.google.common.collect.ImmutableList;
21+
import java.time.Duration;
22+
import java.time.Instant;
23+
import java.util.Arrays;
24+
import java.util.concurrent.ExecutionException;
25+
import org.junit.Before;
26+
import org.junit.ClassRule;
27+
import org.junit.Rule;
28+
import org.junit.Test;
29+
import org.junit.experimental.categories.Category;
30+
import org.junit.rules.ExpectedException;
31+
import org.junit.runner.RunWith;
32+
import org.junit.runners.JUnit4;
33+
34+
/** Integration test for commit timestamp of Cloud Spanner. */
35+
@Category(IntegrationTest.class)
36+
@RunWith(JUnit4.class)
37+
public class ITCommitTimestampTest {
38+
@ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv();
39+
@Rule public final ExpectedException expectedException = ExpectedException.none();
40+
private Database db;
41+
private DatabaseClient client;
42+
private DatabaseAdminClient dbAdminClient;
43+
private RemoteSpannerHelper testHelper;
44+
private String instanceId;
45+
private String databaseId;
46+
47+
@Before
48+
public void setUp() throws Exception {
49+
testHelper = env.getTestHelper();
50+
db =
51+
testHelper.createTestDatabase(
52+
"CREATE TABLE T ("
53+
+ "K STRING(MAX) NOT NULL,"
54+
+ "T1 TIMESTAMP OPTIONS (allow_commit_timestamp = true),"
55+
+ "T2 TIMESTAMP OPTIONS (allow_commit_timestamp = true),"
56+
+ "T3 TIMESTAMP,"
57+
+ ") PRIMARY KEY (K)");
58+
client = testHelper.getDatabaseClient(db);
59+
dbAdminClient = testHelper.getClient().getDatabaseAdminClient();
60+
instanceId = testHelper.getInstanceId().getInstance();
61+
databaseId = db.getId().getDatabase();
62+
}
63+
64+
private Timestamp write(Mutation m) {
65+
return client.write(Arrays.asList(m));
66+
}
67+
68+
private Struct readRow(DatabaseClient client, String table, Key key, String... columns) {
69+
return client.singleUse(TimestampBound.strong()).readRow(table, key, Arrays.asList(columns));
70+
}
71+
72+
@Test
73+
public void writeCommitTimestamp() {
74+
// 1. timestamps auto populated and returned should be the same
75+
Timestamp commitTimestamp =
76+
write(
77+
Mutation.newInsertOrUpdateBuilder("T")
78+
.set("K")
79+
.to("a")
80+
.set("T1")
81+
.to(Value.COMMIT_TIMESTAMP)
82+
.set("T2")
83+
.to(Value.COMMIT_TIMESTAMP)
84+
.build());
85+
Struct row = readRow(client, "T", Key.of("a"), "T1", "T2");
86+
assertThat(row.getTimestamp(0)).isEqualTo(commitTimestamp);
87+
assertThat(row.getTimestamp(1)).isEqualTo(commitTimestamp);
88+
89+
// 2. attempt to write CommitTimestamp to not enabled column should fail
90+
// error_catalog error CommitTimestampOptionNotEnabled
91+
expectedException.expect(isSpannerException(ErrorCode.FAILED_PRECONDITION));
92+
expectedException.expectMessage("allow_commit_timestamp column option is not");
93+
write(
94+
Mutation.newInsertOrUpdateBuilder("T")
95+
.set("K")
96+
.to("a")
97+
.set("T3")
98+
.to(Value.COMMIT_TIMESTAMP)
99+
.build());
100+
}
101+
102+
@Test
103+
public void consistency() {
104+
// 1. timestamps populated are consistent in order
105+
write(
106+
Mutation.newInsertOrUpdateBuilder("T")
107+
.set("K")
108+
.to("a")
109+
.set("T1")
110+
.to(Value.COMMIT_TIMESTAMP)
111+
.build());
112+
write(
113+
Mutation.newInsertOrUpdateBuilder("T")
114+
.set("K")
115+
.to("b")
116+
.set("T1")
117+
.to(Value.COMMIT_TIMESTAMP)
118+
.build());
119+
Struct row1 = readRow(client, "T", Key.of("a"), "T1");
120+
Struct row2 = readRow(client, "T", Key.of("b"), "T1");
121+
assertThat(row2.getTimestamp(0)).isGreaterThan(row1.getTimestamp(0));
122+
}
123+
124+
@Test
125+
public void schemaChangeTimestampInFuture() throws Exception {
126+
write(
127+
Mutation.newInsertOrUpdateBuilder("T")
128+
.set("K")
129+
.to("a")
130+
.set("T3")
131+
.to(Timestamp.MAX_VALUE)
132+
.build());
133+
134+
// error_catalog error CommitTimestampNotInFuture
135+
expectedException.expectCause(isSpannerException(ErrorCode.FAILED_PRECONDITION));
136+
expectedException.expectMessage("has a timestamp in the future at key");
137+
String statement = "ALTER TABLE T ALTER COLUMN T3 SET OPTIONS (allow_commit_timestamp=true)";
138+
try {
139+
dbAdminClient
140+
.updateDatabaseDdl(instanceId, databaseId, ImmutableList.of(statement), null)
141+
.get();
142+
} catch (ExecutionException e) {
143+
throw SpannerExceptionFactory.newSpannerException(e.getCause());
144+
}
145+
}
146+
147+
@Test
148+
public void insertTimestampInFuture() {
149+
// error_catalog error TimestampInFuture
150+
expectedException.expect(isSpannerException(ErrorCode.FAILED_PRECONDITION));
151+
expectedException.expectMessage("in the future");
152+
write(
153+
Mutation.newInsertOrUpdateBuilder("T")
154+
.set("K")
155+
.to("a")
156+
.set("T1")
157+
.to(Timestamp.MAX_VALUE)
158+
.build());
159+
}
160+
161+
@Test
162+
public void invalidColumnOption() throws Exception {
163+
// error_catalog error DDLStatementWithError
164+
expectedException.expectCause(isSpannerException(ErrorCode.INVALID_ARGUMENT));
165+
expectedException.expectMessage("Option: bogus is unknown.");
166+
String statement = "ALTER TABLE T ALTER COLUMN T3 SET OPTIONS (bogus=null)";
167+
dbAdminClient
168+
.updateDatabaseDdl(instanceId, databaseId, ImmutableList.of(statement), null)
169+
.get();
170+
}
171+
172+
@Test
173+
public void invalidColumnOptionValue() throws Exception {
174+
// error_catalog error DDLStatementWithErrors
175+
expectedException.expectCause(isSpannerException(ErrorCode.INVALID_ARGUMENT));
176+
expectedException.expectMessage("Errors parsing Spanner DDL statement");
177+
String statement = "ALTER TABLE T ALTER COLUMN T3 SET OPTIONS (allow_commit_timestamp=bogus)";
178+
dbAdminClient
179+
.updateDatabaseDdl(instanceId, databaseId, ImmutableList.of(statement), null)
180+
.get();
181+
}
182+
183+
@Test
184+
public void invalidColumnType() throws Exception {
185+
// error_catalog error OptionErrorList
186+
expectedException.expect(isSpannerException(ErrorCode.FAILED_PRECONDITION));
187+
expectedException.expectMessage("Option only allowed on TIMESTAMP columns");
188+
String statement = "ALTER TABLE T ADD COLUMN T4 INT64 OPTIONS (allow_commit_timestamp=true)";
189+
try {
190+
dbAdminClient
191+
.updateDatabaseDdl(instanceId, databaseId, ImmutableList.of(statement), null)
192+
.get();
193+
} catch (ExecutionException e) {
194+
throw SpannerExceptionFactory.newSpannerException(e.getCause());
195+
}
196+
}
197+
198+
private void alterColumnOption(String databaseId, String table, String opt) throws Exception {
199+
String statement =
200+
"ALTER TABLE "
201+
+ table
202+
+ " ALTER COLUMN ts"
203+
+ " SET OPTIONS (allow_commit_timestamp="
204+
+ opt
205+
+ ")";
206+
dbAdminClient
207+
.updateDatabaseDdl(instanceId, databaseId, ImmutableList.of(statement), null)
208+
.get();
209+
}
210+
211+
private void writeAndVerify(DatabaseClient client, Timestamp ts) {
212+
Timestamp commitTimestamp =
213+
client.write(
214+
Arrays.asList(
215+
Mutation.newInsertOrUpdateBuilder("T1").set("ts").to(ts).build(),
216+
Mutation.newInsertOrUpdateBuilder("T2").set("ts").to(ts).build(),
217+
Mutation.newInsertOrUpdateBuilder("T3").set("ts").to(ts).build()));
218+
if (ts == Value.COMMIT_TIMESTAMP) {
219+
ts = commitTimestamp;
220+
}
221+
assertThat(readRow(client, "T1", Key.of(ts), "ts").getTimestamp(0)).isEqualTo(ts);
222+
assertThat(readRow(client, "T2", Key.of(ts), "ts").getTimestamp(0)).isEqualTo(ts);
223+
assertThat(readRow(client, "T3", Key.of(ts), "ts").getTimestamp(0)).isEqualTo(ts);
224+
}
225+
226+
@Test
227+
// 1) Write timestamps in the past
228+
// 2) Set all interleaved tables allow_commmit_timestamp=true
229+
// 3) Use commit timestamp in all tables
230+
// 4) Set all interleaved tables allow_commmit_timestamp=null
231+
// 5) Write timestamps in the future
232+
public void interleavedTable() throws Exception {
233+
Database db =
234+
testHelper.createTestDatabase(
235+
"CREATE TABLE T1 (ts TIMESTAMP) PRIMARY KEY (ts)",
236+
"CREATE TABLE T2 (ts TIMESTAMP) PRIMARY KEY (ts), INTERLEAVE IN PARENT T1",
237+
"CREATE TABLE T3 (ts TIMESTAMP) PRIMARY KEY (ts), INTERLEAVE IN PARENT T2");
238+
DatabaseClient client = testHelper.getDatabaseClient(db);
239+
String databaseId = db.getId().getDatabase();
240+
241+
Timestamp timeNow = Timestamp.ofTimeMicroseconds(Instant.now().toEpochMilli() * 1000);
242+
Timestamp timeFuture =
243+
Timestamp.ofTimeMicroseconds(
244+
Instant.now().plus(Duration.ofDays(300)).toEpochMilli() * 1000);
245+
246+
writeAndVerify(client, timeNow);
247+
248+
alterColumnOption(databaseId, "T1", "true");
249+
alterColumnOption(databaseId, "T2", "true");
250+
alterColumnOption(databaseId, "T3", "true");
251+
writeAndVerify(client, Value.COMMIT_TIMESTAMP);
252+
253+
alterColumnOption(databaseId, "T1", "null");
254+
alterColumnOption(databaseId, "T2", "null");
255+
alterColumnOption(databaseId, "T3", "null");
256+
writeAndVerify(client, timeFuture);
257+
}
258+
259+
@Test
260+
// In interleaved table, use of commit timestamp in child table is not allowed
261+
// if parent tables are not allow_commmit_timestamp=true
262+
public void interleavedTableHierarchy1() {
263+
Database db =
264+
testHelper.createTestDatabase(
265+
"CREATE TABLE T1 (ts TIMESTAMP) PRIMARY KEY (ts)",
266+
"CREATE TABLE T2 (ts TIMESTAMP) PRIMARY KEY (ts), INTERLEAVE IN PARENT T1",
267+
"CREATE TABLE T3 (ts TIMESTAMP OPTIONS (allow_commit_timestamp = true)) "
268+
+ "PRIMARY KEY (ts), INTERLEAVE IN PARENT T2");
269+
DatabaseClient client = testHelper.getDatabaseClient(db);
270+
db.getId().getDatabase();
271+
272+
// error_catalog error CommitTimestampOptionNotEnabled
273+
expectedException.expect(isSpannerException(ErrorCode.FAILED_PRECONDITION));
274+
expectedException.expectMessage(
275+
"corresponding shared key columns in this table's interleaved table hierarchy");
276+
client.write(
277+
Arrays.asList(
278+
Mutation.newInsertOrUpdateBuilder("T3").set("ts").to(Value.COMMIT_TIMESTAMP).build()));
279+
}
280+
281+
@Test
282+
// In interleaved table, use of commit timestamp in parent table is not
283+
// allowed if child tables are not allow_commmit_timestamp=true
284+
public void interleavedTableHierarchy2() {
285+
Database db =
286+
testHelper.createTestDatabase(
287+
"CREATE TABLE T1 (ts TIMESTAMP OPTIONS (allow_commit_timestamp = true)) "
288+
+ "PRIMARY KEY (ts)",
289+
"CREATE TABLE T2 (ts TIMESTAMP) PRIMARY KEY (ts), INTERLEAVE IN PARENT T1",
290+
"CREATE TABLE T3 (ts TIMESTAMP OPTIONS (allow_commit_timestamp = true)) "
291+
+ "PRIMARY KEY (ts), INTERLEAVE IN PARENT T2");
292+
DatabaseClient client = testHelper.getDatabaseClient(db);
293+
db.getId().getDatabase();
294+
295+
// error_catalog error CommitTimestampOptionNotEnabled
296+
expectedException.expect(isSpannerException(ErrorCode.FAILED_PRECONDITION));
297+
expectedException.expectMessage(
298+
"corresponding shared key columns in this table's interleaved table hierarchy");
299+
client.write(
300+
Arrays.asList(
301+
Mutation.newInsertOrUpdateBuilder("T1").set("ts").to(Value.COMMIT_TIMESTAMP).build()));
302+
}
303+
}

0 commit comments

Comments
 (0)