Skip to content

Commit 5a4fc74

Browse files
agasheeshskuruppu
authored andcommitted
test: [WIP] add Commit Timestamp java integration tests for cloud spanner (#37)
* test: add Commit Timestamp java integration tests for cloud spanner * Use org.threeten.bp library instead of java.time for Java 7 compatibility * test: Fix java comment documentation style to use @test annotation right before function name * Fix formatting error in imports
1 parent e477af0 commit 5a4fc74

File tree

1 file changed

+319
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)