Skip to content

Commit d5248cc

Browse files
committed
fix: handle empty partitioned queries correctly
1 parent 0b5269e commit d5248cc

File tree

2 files changed

+82
-15
lines changed

2 files changed

+82
-15
lines changed

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

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.cloud.spanner.SpannerException;
2424
import com.google.cloud.spanner.SpannerExceptionFactory;
2525
import com.google.cloud.spanner.Struct;
26+
import com.google.cloud.spanner.Type;
2627
import com.google.common.base.Preconditions;
2728
import com.google.common.base.Supplier;
2829
import com.google.spanner.v1.ResultSetMetadata;
@@ -61,18 +62,26 @@ public void run() {
6162
try (ResultSet resultSet = connection.runPartition(partitionId)) {
6263
boolean first = true;
6364
while (resultSet.next()) {
65+
Struct row = resultSet.getCurrentRowAsStruct();
6466
if (first) {
6567
queue.put(
6668
PartitionExecutorResult.dataAndMetadata(
67-
resultSet.getCurrentRowAsStruct(), resultSet.getMetadata()));
69+
row, resultSet.getType(), resultSet.getMetadata()));
6870
first = false;
6971
} else {
70-
queue.put(PartitionExecutorResult.data(resultSet.getCurrentRowAsStruct()));
72+
queue.put(PartitionExecutorResult.data(row));
7173
}
7274
if (shouldStop.get()) {
7375
break;
7476
}
7577
}
78+
if (first) {
79+
// Special case: The result set did not return any rows. Push the metadata to the merged
80+
// result set.
81+
queue.put(
82+
PartitionExecutorResult.typeAndMetadata(
83+
resultSet.getType(), resultSet.getMetadata()));
84+
}
7685
} catch (Throwable exception) {
7786
putWithoutInterruptPropagation(PartitionExecutorResult.exception(exception));
7887
} finally {
@@ -96,32 +105,47 @@ private void putWithoutInterruptPropagation(PartitionExecutorResult result) {
96105
static class PartitionExecutorResult {
97106
private final Struct data;
98107
private final Throwable exception;
108+
private final Type type;
99109
private final ResultSetMetadata metadata;
100110

101111
static PartitionExecutorResult data(Struct data) {
102-
return new PartitionExecutorResult(data, null, null);
112+
return new PartitionExecutorResult(data, null, null, null);
113+
}
114+
115+
static PartitionExecutorResult typeAndMetadata(Type type, ResultSetMetadata metadata) {
116+
return new PartitionExecutorResult(null, type, metadata, null);
103117
}
104118

105-
static PartitionExecutorResult dataAndMetadata(Struct data, ResultSetMetadata metadata) {
106-
return new PartitionExecutorResult(data, metadata, null);
119+
static PartitionExecutorResult dataAndMetadata(
120+
Struct data, Type type, ResultSetMetadata metadata) {
121+
return new PartitionExecutorResult(data, type, metadata, null);
107122
}
108123

109124
static PartitionExecutorResult exception(Throwable exception) {
110-
return new PartitionExecutorResult(null, null, exception);
125+
return new PartitionExecutorResult(null, null, null, exception);
111126
}
112127

113128
static PartitionExecutorResult finished() {
114-
return new PartitionExecutorResult(null, null, null);
129+
return new PartitionExecutorResult(null, null, null, null);
115130
}
116131

117-
private PartitionExecutorResult(Struct data, ResultSetMetadata metadata, Throwable exception) {
132+
private PartitionExecutorResult(
133+
Struct data, Type type, ResultSetMetadata metadata, Throwable exception) {
118134
this.data = data;
135+
this.type = type;
119136
this.metadata = metadata;
120137
this.exception = exception;
121138
}
122139

140+
boolean hasData() {
141+
return this.data != null;
142+
}
143+
123144
boolean isFinished() {
124-
return this.data == null && this.metadata == null && this.exception == null;
145+
return this.data == null
146+
&& this.type == null
147+
&& this.metadata == null
148+
&& this.exception == null;
125149
}
126150
}
127151

@@ -135,6 +159,7 @@ static class RowProducer implements Supplier<Struct> {
135159
private final AtomicInteger finishedCounter;
136160
private final LinkedBlockingDeque<PartitionExecutorResult> queue;
137161
private ResultSetMetadata metadata;
162+
private Type type;
138163
private Struct currentRow;
139164
private Throwable exception;
140165

@@ -185,8 +210,7 @@ boolean nextRow() throws Throwable {
185210
PartitionExecutorResult next;
186211
if ((next = queue.peek()) != null && !next.isFinished()) {
187212
// There's a valid result available. Return this quickly.
188-
setNextRow(queue.remove());
189-
return true;
213+
return setNextRow(queue.remove());
190214
}
191215
// Block until the next row is available.
192216
next = queue.take();
@@ -196,13 +220,12 @@ boolean nextRow() throws Throwable {
196220
return false;
197221
}
198222
} else {
199-
setNextRow(next);
200-
return true;
223+
return setNextRow(next);
201224
}
202225
}
203226
}
204227

205-
void setNextRow(PartitionExecutorResult next) throws Throwable {
228+
boolean setNextRow(PartitionExecutorResult next) throws Throwable {
206229
if (next.exception != null) {
207230
this.exception = next.exception;
208231
throw next.exception;
@@ -211,6 +234,10 @@ void setNextRow(PartitionExecutorResult next) throws Throwable {
211234
if (this.metadata == null && next.metadata != null) {
212235
this.metadata = next.metadata;
213236
}
237+
if (this.type == null && next.type != null) {
238+
this.type = next.type;
239+
}
240+
return next.hasData();
214241
}
215242

216243
@Override
@@ -223,6 +250,11 @@ public ResultSetMetadata getMetadata() {
223250
checkState(metadata != null, "next() call required");
224251
return metadata;
225252
}
253+
254+
public Type getType() {
255+
checkState(type != null, "next() call required");
256+
return type;
257+
}
226258
}
227259

228260
private final RowProducer rowProducer;
@@ -279,6 +311,12 @@ public ResultSetMetadata getMetadata() {
279311
return rowProducer.getMetadata();
280312
}
281313

314+
@Override
315+
public Type getType() {
316+
checkValidState();
317+
return rowProducer.getType();
318+
}
319+
282320
@Override
283321
public int getNumPartitions() {
284322
return rowProducer.partitionExecutors.size();

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ public void testRunPartitionedQuery() {
312312
int maxPartitions = 5;
313313
try (Connection connection = createConnection()) {
314314
connection.setAutocommit(true);
315-
try (ResultSet resultSet =
315+
try (PartitionedQueryResultSet resultSet =
316316
connection.runPartitionedQuery(
317317
statement, PartitionOptions.newBuilder().setMaxPartitions(maxPartitions).build())) {
318318
int rowCount = 0;
@@ -330,6 +330,35 @@ public void testRunPartitionedQuery() {
330330
assertEquals(1, mockSpanner.countRequestsOfType(PartitionQueryRequest.class));
331331
}
332332

333+
@Test
334+
public void testRunEmptyPartitionedQuery() {
335+
int generatedRowCount = 0;
336+
RandomResultSetGenerator generator = new RandomResultSetGenerator(generatedRowCount);
337+
Statement statement =
338+
Statement.newBuilder("select * from random_table where active=@active")
339+
.bind("active")
340+
.to(true)
341+
.build();
342+
mockSpanner.putStatementResult(StatementResult.query(statement, generator.generate()));
343+
344+
int maxPartitions = 5;
345+
try (Connection connection = createConnection()) {
346+
connection.setAutocommit(true);
347+
try (PartitionedQueryResultSet resultSet =
348+
connection.runPartitionedQuery(
349+
statement, PartitionOptions.newBuilder().setMaxPartitions(maxPartitions).build())) {
350+
assertFalse(resultSet.next());
351+
assertNotNull(resultSet.getMetadata());
352+
assertEquals(18, resultSet.getMetadata().getRowType().getFieldsCount());
353+
assertNotNull(resultSet.getType());
354+
assertEquals(18, resultSet.getType().getStructFields().size());
355+
}
356+
}
357+
assertEquals(1, mockSpanner.countRequestsOfType(CreateSessionRequest.class));
358+
assertEquals(1, mockSpanner.countRequestsOfType(BeginTransactionRequest.class));
359+
assertEquals(1, mockSpanner.countRequestsOfType(PartitionQueryRequest.class));
360+
}
361+
333362
@Test
334363
public void testRunPartitionedQueryUsingSql() {
335364
int generatedRowCount = 20;

0 commit comments

Comments
 (0)