Skip to content

Commit 41be1b6

Browse files
committed
Fix edge cases with UTF-8 strings in ChecksumResultSet
1 parent c6532ac commit 41be1b6

2 files changed

Lines changed: 73 additions & 3 deletions

File tree

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ChecksumResultSet.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ private void putString(String stringValue) {
329329
if (buffer == null || (buffer.capacity() < MAX_BUFFER_SIZE && buffer.capacity() < length)) {
330330
// Create a ByteBuffer with a maximum buffer size.
331331
// This buffer is re-used for all string values in the result set.
332-
buffer = ByteBuffer.allocate(Math.min(MAX_BUFFER_SIZE, length));
332+
buffer = ByteBuffer.allocate(Math.max(4, Math.min(MAX_BUFFER_SIZE, length)));
333333
} else {
334334
buffer.clear();
335335
}
@@ -349,8 +349,8 @@ private void putString(String stringValue) {
349349
buffer.flip();
350350
// Put the bytes from the buffer into the digest.
351351
digest.update(buffer);
352-
// Flip the buffer again, so we can repeat and write to the start of the buffer again.
353-
buffer.flip();
352+
// Clear the buffer, so we can repeat and write to the start of the buffer again.
353+
buffer.clear();
354354
}
355355
}
356356
}

java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,4 +448,74 @@ public void testRetry() {
448448
() -> resultSet.retry(abortedException));
449449
}
450450
}
451+
452+
@Test
453+
public void testEmptyString() {
454+
Type type = Type.struct(StructField.of("stringVal", Type.string()));
455+
Struct row = Struct.newBuilder().set("stringVal").to("").build();
456+
457+
ParsedStatement parsedStatement = mock(ParsedStatement.class);
458+
Statement statement = Statement.of("select * from foo");
459+
when(parsedStatement.getStatement()).thenReturn(statement);
460+
ReadWriteTransaction transaction = mock(ReadWriteTransaction.class);
461+
when(transaction.runWithRetry(any(Callable.class)))
462+
.thenAnswer(invocationOnMock -> ((Callable<?>) invocationOnMock.getArgument(0)).call());
463+
when(transaction.getStatementExecutor()).thenReturn(mock(StatementExecutor.class));
464+
465+
ResultSet queryResult = ResultSets.forRows(type, ImmutableList.of(row));
466+
ChecksumResultSet resultSet =
467+
new ChecksumResultSet(
468+
transaction,
469+
DirectExecuteResultSet.ofResultSet(queryResult),
470+
parsedStatement,
471+
AnalyzeMode.NONE);
472+
assertTrue(resultSet.next());
473+
}
474+
475+
@Test
476+
public void testSingleCharMultiByteString() {
477+
Type type = Type.struct(StructField.of("stringVal", Type.string()));
478+
// "ä" is 1 char, but 2 bytes in UTF-8.
479+
Struct row = Struct.newBuilder().set("stringVal").to("ä").build();
480+
481+
ParsedStatement parsedStatement = mock(ParsedStatement.class);
482+
Statement statement = Statement.of("select * from foo");
483+
when(parsedStatement.getStatement()).thenReturn(statement);
484+
ReadWriteTransaction transaction = mock(ReadWriteTransaction.class);
485+
when(transaction.runWithRetry(any(Callable.class)))
486+
.thenAnswer(invocationOnMock -> ((Callable<?>) invocationOnMock.getArgument(0)).call());
487+
when(transaction.getStatementExecutor()).thenReturn(mock(StatementExecutor.class));
488+
489+
ResultSet queryResult = ResultSets.forRows(type, ImmutableList.of(row));
490+
ChecksumResultSet resultSet =
491+
new ChecksumResultSet(
492+
transaction,
493+
DirectExecuteResultSet.ofResultSet(queryResult),
494+
parsedStatement,
495+
AnalyzeMode.NONE);
496+
assertTrue(resultSet.next());
497+
}
498+
499+
@Test
500+
public void testLongMixedUtf8String() {
501+
Type type = Type.struct(StructField.of("stringVal", Type.string()));
502+
Struct row = Struct.newBuilder().set("stringVal").to("aaa\uD841\uDF0E").build();
503+
504+
ParsedStatement parsedStatement = mock(ParsedStatement.class);
505+
Statement statement = Statement.of("select * from foo");
506+
when(parsedStatement.getStatement()).thenReturn(statement);
507+
ReadWriteTransaction transaction = mock(ReadWriteTransaction.class);
508+
when(transaction.runWithRetry(any(Callable.class)))
509+
.thenAnswer(invocationOnMock -> ((Callable<?>) invocationOnMock.getArgument(0)).call());
510+
when(transaction.getStatementExecutor()).thenReturn(mock(StatementExecutor.class));
511+
512+
ResultSet queryResult = ResultSets.forRows(type, ImmutableList.of(row));
513+
ChecksumResultSet resultSet =
514+
new ChecksumResultSet(
515+
transaction,
516+
DirectExecuteResultSet.ofResultSet(queryResult),
517+
parsedStatement,
518+
AnalyzeMode.NONE);
519+
assertTrue(resultSet.next());
520+
}
451521
}

0 commit comments

Comments
 (0)