Skip to content

Commit 6c97b99

Browse files
test: Proto Column Integration tests (#2212)
* test: Adding Integration tests for Proto Messages & Enums * test: Adding additional test for Parameterized Queries, Primary Keys & Invalid Wire type errors. * style: Formatting * style: Formatting * test: Updating instance and db name * test: Adding inter compatability check while writing data
1 parent 1fb6e94 commit 6c97b99

File tree

1 file changed

+355
-0
lines changed

1 file changed

+355
-0
lines changed
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
/*
2+
* Copyright 2022 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 org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertThrows;
21+
import static org.junit.Assume.assumeFalse;
22+
23+
import com.google.cloud.ByteArray;
24+
import com.google.cloud.spanner.DatabaseClient;
25+
import com.google.cloud.spanner.DatabaseId;
26+
import com.google.cloud.spanner.Key;
27+
import com.google.cloud.spanner.KeySet;
28+
import com.google.cloud.spanner.Mutation;
29+
import com.google.cloud.spanner.ParallelIntegrationTest;
30+
import com.google.cloud.spanner.ResultSet;
31+
import com.google.cloud.spanner.SingerProto.Genre;
32+
import com.google.cloud.spanner.SingerProto.SingerInfo;
33+
import com.google.cloud.spanner.Spanner;
34+
import com.google.cloud.spanner.SpannerException;
35+
import com.google.cloud.spanner.SpannerOptions;
36+
import com.google.cloud.spanner.Statement;
37+
import com.google.cloud.spanner.testing.EmulatorSpannerHelper;
38+
import com.google.common.collect.ImmutableList;
39+
import com.google.protobuf.AbstractMessage;
40+
import com.google.protobuf.InvalidProtocolBufferException.InvalidWireTypeException;
41+
import com.google.protobuf.ProtocolMessageEnum;
42+
import com.google.spanner.admin.database.v1.Backup;
43+
import java.util.Arrays;
44+
import java.util.Collections;
45+
import java.util.List;
46+
import org.junit.After;
47+
import org.junit.BeforeClass;
48+
import org.junit.Test;
49+
import org.junit.experimental.categories.Category;
50+
import org.junit.runner.RunWith;
51+
import org.junit.runners.JUnit4;
52+
53+
/** Integrations Tests to test Proto Columns & Enums */
54+
@Category(ParallelIntegrationTest.class)
55+
@RunWith(JUnit4.class)
56+
public class ITProtoColumnTest {
57+
private static String projectId;
58+
private static String instanceId;
59+
private static String databaseId;
60+
private static DatabaseClient databaseClient;
61+
62+
@BeforeClass
63+
public static void beforeClass() throws Exception {
64+
assumeFalse(
65+
"Proto Column is not supported in the emulator", EmulatorSpannerHelper.isUsingEmulator());
66+
// ToDo: Update project, instance and database parameter before GA
67+
projectId = "span-cloud-testing";
68+
databaseId = "int_test_proto_column_db";
69+
instanceId = "integration-test-proto-column";
70+
71+
Spanner spanner =
72+
SpannerOptions.newBuilder()
73+
.setHost("https://staging-wrenchworks.sandbox.googleapis.com")
74+
.setProjectId(projectId)
75+
.build()
76+
.getService();
77+
78+
databaseClient = spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
79+
}
80+
81+
@After
82+
public void after() throws Exception {
83+
databaseClient.write(ImmutableList.of(Mutation.delete("Types", KeySet.all())));
84+
databaseClient.write(ImmutableList.of(Mutation.delete("Singers", KeySet.all())));
85+
}
86+
87+
/**
88+
* Test to check updates and read queries on Proto column and Enums and their arrays. Test also
89+
* checks for compatability between following types: 1. Proto Messages & Bytes 2. Proto Enums &
90+
* Int64
91+
*
92+
* <p>Table `Types` was created through gcloud using following DDL:
93+
* ************************************** CREATE TABLE Types ( RowID INT64 NOT NULL, Int64a INT64,
94+
* Bytes BYTES(MAX), Int64Array ARRAY<INT64>, BytesArray ARRAY<BYTES(MAX)>, ProtoMessage
95+
* spanner.examples.music.SingerInfo, ProtoEnum spanner.examples.music.Genre, ProtoMessageArray
96+
* ARRAY<spanner.examples.music.SingerInfo>, ProtoEnumArray ARRAY<spanner.examples.music.Genre>, )
97+
* PRIMARY KEY (RowID); **************************************
98+
*/
99+
@Test
100+
public void testProtoUpdateAndRead() {
101+
assumeFalse(
102+
"Proto Column is not supported in the emulator", EmulatorSpannerHelper.isUsingEmulator());
103+
SingerInfo singerInfo =
104+
SingerInfo.newBuilder().setSingerId(11).setNationality("Country1").build();
105+
ByteArray singerInfoBytes = ByteArray.copyFrom(singerInfo.toByteArray());
106+
107+
Genre genre = Genre.JAZZ;
108+
long genreConst = genre.getNumber();
109+
110+
List<AbstractMessage> singerInfoList =
111+
Arrays.asList(singerInfo, null, SingerInfo.getDefaultInstance());
112+
List<ByteArray> singerInfoBytesList =
113+
Arrays.asList(
114+
singerInfoBytes,
115+
null,
116+
ByteArray.copyFrom(SingerInfo.getDefaultInstance().toByteArray()));
117+
118+
List<ProtocolMessageEnum> enumList = Arrays.asList(Genre.FOLK, null, Genre.ROCK);
119+
List<Long> enumConstList =
120+
Arrays.asList((long) Genre.FOLK_VALUE, null, (long) Genre.ROCK_VALUE);
121+
122+
// Inserting two rows with same data except rowID as it's used as PK.
123+
databaseClient.write(
124+
ImmutableList.of(
125+
Mutation.newInsertOrUpdateBuilder("Types")
126+
.set("RowID")
127+
.to(11)
128+
.set("Int64a")
129+
.to(genreConst)
130+
.set("Bytes")
131+
.to(singerInfoBytes)
132+
.set("Int64Array")
133+
.toInt64Array(enumConstList)
134+
.set("BytesArray")
135+
.toBytesArray(singerInfoBytesList)
136+
.set("ProtoMessage")
137+
.to(singerInfo)
138+
.set("ProtoEnum")
139+
.to(genre)
140+
.set("ProtoMessageArray")
141+
.toProtoMessageArray(singerInfoList, SingerInfo.getDescriptor())
142+
.set("ProtoEnumArray")
143+
.toProtoEnumArray(enumList, Genre.getDescriptor())
144+
.build(),
145+
//Inter Compatability check between ProtoMessages/Bytes and Int64/Enum.
146+
Mutation.newInsertOrUpdateBuilder("Types")
147+
.set("RowID")
148+
.to(12)
149+
.set("Int64a")
150+
.to(genre)
151+
.set("Bytes")
152+
.to(singerInfo)
153+
.set("Int64Array")
154+
.toProtoEnumArray(enumList, Genre.getDescriptor())
155+
.set("BytesArray")
156+
.toProtoMessageArray(singerInfoList, SingerInfo.getDescriptor())
157+
.set("ProtoMessage")
158+
.to(singerInfoBytes)
159+
.set("ProtoEnum")
160+
.to(genreConst)
161+
.set("ProtoMessageArray")
162+
.toBytesArray(singerInfoBytesList)
163+
.set("ProtoEnumArray")
164+
.toInt64Array(enumConstList)
165+
.build()));
166+
167+
try (ResultSet resultSet =
168+
databaseClient.singleUse().executeQuery(Statement.of("SELECT * FROM " + "Types"))) {
169+
170+
for(int i=0;i<2;i++) {
171+
resultSet.next();
172+
assertEquals(11 + i, resultSet.getLong("RowID"));
173+
assertEquals(genreConst, resultSet.getLong("Int64a"));
174+
assertEquals(singerInfoBytes, resultSet.getBytes("Bytes"));
175+
assertEquals(enumConstList, resultSet.getLongList("Int64Array"));
176+
assertEquals(singerInfoBytesList, resultSet.getBytesList("BytesArray"));
177+
assertEquals(
178+
singerInfo, resultSet.getProtoMessage("ProtoMessage", SingerInfo.getDefaultInstance()));
179+
assertEquals(genre, resultSet.getProtoEnum("ProtoEnum", Genre::forNumber));
180+
assertEquals(
181+
singerInfoList,
182+
resultSet.getProtoMessageList("ProtoMessageArray", SingerInfo.getDefaultInstance()));
183+
assertEquals(enumList, resultSet.getProtoEnumList("ProtoEnumArray", Genre::forNumber));
184+
185+
// Check compatability between Proto Messages & Bytes
186+
assertEquals(singerInfoBytes, resultSet.getBytes("ProtoMessage"));
187+
assertEquals(singerInfo, resultSet.getProtoMessage("Bytes", SingerInfo.getDefaultInstance()));
188+
189+
assertEquals(singerInfoBytesList, resultSet.getBytesList("ProtoMessageArray"));
190+
assertEquals(
191+
singerInfoList,
192+
resultSet.getProtoMessageList("BytesArray", SingerInfo.getDefaultInstance()));
193+
194+
// Check compatability between Proto Enum & Int64
195+
assertEquals(genreConst, resultSet.getLong("ProtoEnum"));
196+
assertEquals(genre, resultSet.getProtoEnum("Int64a", Genre::forNumber));
197+
198+
assertEquals(enumConstList, resultSet.getLongList("ProtoEnumArray"));
199+
assertEquals(enumList, resultSet.getProtoEnumList("Int64Array", Genre::forNumber));
200+
}
201+
}
202+
}
203+
204+
/**
205+
* Test to check Parameterized Queries, Primary Keys and Indexes.
206+
*
207+
* <p>Table `Singers` and Index `SingerByNationalityAndGenre` for Proto column integration tests
208+
* is created through gcloud using following DDL:
209+
*
210+
* <p>************************************** CREATE TABLE Singers ( SingerId INT64 NOT NULL,
211+
* FirstName STRING(1024), LastName STRING(1024), SingerInfo spanner.examples.music.SingerInfo,
212+
* SingerGenre spanner.examples.music.Genre, SingerNationality STRING(1024) AS
213+
* (SingerInfo.nationality) STORED, ) PRIMARY KEY (SingerNationality, SingerGenre);
214+
*
215+
* <p>CREATE INDEX SingerByNationalityAndGenre ON Singers(SingerNationality, SingerGenre) STORING
216+
* (SingerId, FirstName, LastName); **************************************
217+
*/
218+
@Test
219+
public void testProtoColumnsDMLParameterizedQueriesAndPKIndexes() {
220+
assumeFalse(
221+
"Proto Column is not supported in the emulator", EmulatorSpannerHelper.isUsingEmulator());
222+
223+
SingerInfo singerInfo1 =
224+
SingerInfo.newBuilder().setSingerId(11).setNationality("Country1").build();
225+
Genre genre1 = Genre.FOLK;
226+
227+
SingerInfo singerInfo2 =
228+
SingerInfo.newBuilder().setSingerId(11).setNationality("Country2").build();
229+
Genre genre2 = Genre.JAZZ;
230+
231+
databaseClient
232+
.readWriteTransaction()
233+
.run(
234+
transaction -> {
235+
Statement statement1 =
236+
Statement.newBuilder(
237+
"INSERT INTO Singers (SingerId, FirstName, LastName, SingerInfo, SingerGenre) VALUES (11, \"FirstName1\", \"LastName1\", @singerInfo, @singerGenre)")
238+
.bind("singerInfo")
239+
.to(singerInfo1)
240+
.bind("singerGenre")
241+
.to(genre1)
242+
.build();
243+
244+
Statement statement2 =
245+
Statement.newBuilder(
246+
"INSERT INTO Singers (SingerId, FirstName, LastName, SingerInfo, SingerGenre) VALUES (22, \"FirstName2\", \"LastName2\", @singerInfo, @singerGenre)")
247+
.bind("singerInfo")
248+
.to(singerInfo2)
249+
.bind("singerGenre")
250+
.to(genre2)
251+
.build();
252+
253+
transaction.batchUpdate(Arrays.asList(statement1, statement2));
254+
return null;
255+
});
256+
257+
// Read all rows based on Proto Message field and Proto Enum Primary key column values
258+
ResultSet resultSet1 =
259+
databaseClient
260+
.singleUse()
261+
.read(
262+
"Singers",
263+
KeySet.newBuilder()
264+
.addKey(Key.of("Country1", Genre.FOLK))
265+
.addKey(Key.of("Country2", Genre.JAZZ))
266+
.build(),
267+
Arrays.asList("SingerId", "FirstName", "LastName", "SingerInfo", "SingerGenre"));
268+
269+
resultSet1.next();
270+
assertEquals(11, resultSet1.getLong("SingerId"));
271+
assertEquals("FirstName1", resultSet1.getString("FirstName"));
272+
assertEquals("LastName1", resultSet1.getString("LastName"));
273+
assertEquals(
274+
singerInfo1, resultSet1.getProtoMessage("SingerInfo", SingerInfo.getDefaultInstance()));
275+
assertEquals(genre1, resultSet1.getProtoEnum("SingerGenre", Genre::forNumber));
276+
277+
resultSet1.next();
278+
assertEquals(22, resultSet1.getLong("SingerId"));
279+
assertEquals("FirstName2", resultSet1.getString("FirstName"));
280+
assertEquals("LastName2", resultSet1.getString("LastName"));
281+
assertEquals(
282+
singerInfo2, resultSet1.getProtoMessage("SingerInfo", SingerInfo.getDefaultInstance()));
283+
assertEquals(genre2, resultSet1.getProtoEnum("SingerGenre", Genre::forNumber));
284+
285+
// Read rows using Index on Proto Message field and Proto Enum column
286+
ResultSet resultSet2 =
287+
databaseClient
288+
.singleUse()
289+
.readUsingIndex(
290+
"Singers",
291+
"SingerByNationalityAndGenre",
292+
KeySet.singleKey(Key.of("Country2", Genre.JAZZ)),
293+
Arrays.asList("SingerId", "FirstName", "LastName"));
294+
resultSet2.next();
295+
assertEquals(22, resultSet2.getLong("SingerId"));
296+
assertEquals("FirstName2", resultSet2.getString("FirstName"));
297+
assertEquals("LastName2", resultSet2.getString("LastName"));
298+
299+
// Filter using Parameterized DQL
300+
ResultSet resultSet3 =
301+
databaseClient
302+
.singleUse()
303+
.executeQuery(
304+
Statement.newBuilder(
305+
"SELECT SingerId, SingerInfo, SingerGenre FROM "
306+
+ "Singers WHERE SingerInfo.Nationality=@country AND SingerGenre=@genre")
307+
.bind("country")
308+
.to("Country2")
309+
.bind("genre")
310+
.to(Genre.JAZZ)
311+
.build());
312+
313+
resultSet3.next();
314+
assertEquals(22, resultSet1.getLong("SingerId"));
315+
assertEquals(
316+
singerInfo2, resultSet1.getProtoMessage("SingerInfo", SingerInfo.getDefaultInstance()));
317+
assertEquals(genre2, resultSet1.getProtoEnum("SingerGenre", Genre::forNumber));
318+
}
319+
320+
/**
321+
* Test the exception in case Invalid protocol message object is provided while deserializing the
322+
* data.
323+
*/
324+
@Test
325+
public void testProtoMessageDeserializationError() {
326+
assumeFalse(
327+
"Proto Column is not supported in the emulator", EmulatorSpannerHelper.isUsingEmulator());
328+
329+
SingerInfo singerInfo =
330+
SingerInfo.newBuilder().setSingerId(11).setNationality("Country1").build();
331+
332+
databaseClient.write(
333+
ImmutableList.of(
334+
Mutation.newInsertOrUpdateBuilder("Types")
335+
.set("RowID")
336+
.to(11)
337+
.set("ProtoMessage")
338+
.to(singerInfo)
339+
.build()));
340+
341+
ResultSet resultSet =
342+
databaseClient
343+
.singleUse()
344+
.read("Types", KeySet.all(), Collections.singletonList("ProtoMessage"));
345+
resultSet.next();
346+
347+
SpannerException e =
348+
assertThrows(
349+
SpannerException.class,
350+
() -> resultSet.getProtoMessage("ProtoMessage", Backup.getDefaultInstance()));
351+
352+
// Underlying cause is InvalidWireTypeException
353+
assertEquals(InvalidWireTypeException.class, e.getCause().getClass());
354+
}
355+
}

0 commit comments

Comments
 (0)