Skip to content

Commit 735bca5

Browse files
authored
feat: support Read RPC OrderBy (#3180)
1 parent 1fb54b9 commit 735bca5

File tree

5 files changed

+134
-3
lines changed

5 files changed

+134
-3
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java

+3
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,9 @@ ResultSet readInternalWithOptions(
902902
if (readOptions.hasDataBoostEnabled()) {
903903
builder.setDataBoostEnabled(readOptions.dataBoostEnabled());
904904
}
905+
if (readOptions.hasOrderBy()) {
906+
builder.setOrderBy(readOptions.orderBy());
907+
}
905908
if (readOptions.hasDirectedReadOptions()) {
906909
builder.setDirectedReadOptions(readOptions.directedReadOptions());
907910
} else if (defaultDirectedReadOptions != null) {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java

+59-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.common.base.Preconditions;
2020
import com.google.spanner.v1.DirectedReadOptions;
21+
import com.google.spanner.v1.ReadRequest.OrderBy;
2122
import com.google.spanner.v1.RequestOptions.Priority;
2223
import java.io.Serializable;
2324
import java.time.Duration;
@@ -51,6 +52,29 @@ public static RpcPriority fromProto(Priority proto) {
5152
}
5253
}
5354

55+
/**
56+
* OrderBy for an RPC invocation. The default orderby is {@link #PRIMARY_KEY}. This enum can be
57+
* used to control the order in which rows are returned from a read.
58+
*/
59+
public enum RpcOrderBy {
60+
PRIMARY_KEY(OrderBy.ORDER_BY_PRIMARY_KEY),
61+
NO_ORDER(OrderBy.ORDER_BY_NO_ORDER),
62+
UNSPECIFIED(OrderBy.ORDER_BY_UNSPECIFIED);
63+
64+
private final OrderBy proto;
65+
66+
RpcOrderBy(OrderBy proto) {
67+
this.proto = Preconditions.checkNotNull(proto);
68+
}
69+
70+
public static RpcOrderBy fromProto(OrderBy proto) {
71+
for (RpcOrderBy e : RpcOrderBy.values()) {
72+
if (e.proto.equals(proto)) return e;
73+
}
74+
return RpcOrderBy.UNSPECIFIED;
75+
}
76+
}
77+
5478
/** Marker interface to mark options applicable to both Read and Query operations */
5579
public interface ReadAndQueryOption extends ReadOption, QueryOption {}
5680

@@ -131,6 +155,11 @@ public static ReadOption limit(long limit) {
131155
return new LimitOption(limit);
132156
}
133157

158+
/** Specifies the order_by to use for the RPC. */
159+
public static ReadOption orderBy(RpcOrderBy orderBy) {
160+
return new OrderByOption(orderBy);
161+
}
162+
134163
/**
135164
* Specifying this will allow the client to prefetch up to {@code prefetchChunks} {@code
136165
* PartialResultSet} chunks for read and query. The data size of each chunk depends on the server
@@ -439,6 +468,7 @@ void appendToOptions(Options options) {
439468
private Boolean dataBoostEnabled;
440469
private DirectedReadOptions directedReadOptions;
441470
private DecodeMode decodeMode;
471+
private RpcOrderBy orderBy;
442472

443473
// Construction is via factory methods below.
444474
private Options() {}
@@ -567,6 +597,14 @@ DecodeMode decodeMode() {
567597
return decodeMode;
568598
}
569599

600+
boolean hasOrderBy() {
601+
return orderBy != null;
602+
}
603+
604+
OrderBy orderBy() {
605+
return orderBy == null ? null : orderBy.proto;
606+
}
607+
570608
@Override
571609
public String toString() {
572610
StringBuilder b = new StringBuilder();
@@ -620,6 +658,9 @@ public String toString() {
620658
if (decodeMode != null) {
621659
b.append("decodeMode: ").append(decodeMode).append(' ');
622660
}
661+
if (orderBy != null) {
662+
b.append("orderBy: ").append(orderBy).append(' ');
663+
}
623664
return b.toString();
624665
}
625666

@@ -658,7 +699,8 @@ public boolean equals(Object o) {
658699
&& Objects.equals(withOptimisticLock(), that.withOptimisticLock())
659700
&& Objects.equals(withExcludeTxnFromChangeStreams(), that.withExcludeTxnFromChangeStreams())
660701
&& Objects.equals(dataBoostEnabled(), that.dataBoostEnabled())
661-
&& Objects.equals(directedReadOptions(), that.directedReadOptions());
702+
&& Objects.equals(directedReadOptions(), that.directedReadOptions())
703+
&& Objects.equals(orderBy(), that.orderBy());
662704
}
663705

664706
@Override
@@ -715,6 +757,9 @@ public int hashCode() {
715757
if (decodeMode != null) {
716758
result = 31 * result + decodeMode.hashCode();
717759
}
760+
if (orderBy != null) {
761+
result = 31 * result + orderBy.hashCode();
762+
}
718763
return result;
719764
}
720765

@@ -795,6 +840,19 @@ void appendToOptions(Options options) {
795840
}
796841
}
797842

843+
static class OrderByOption extends InternalOption implements ReadOption {
844+
private final RpcOrderBy orderBy;
845+
846+
OrderByOption(RpcOrderBy orderBy) {
847+
this.orderBy = orderBy;
848+
}
849+
850+
@Override
851+
void appendToOptions(Options options) {
852+
options.orderBy = orderBy;
853+
}
854+
}
855+
798856
static final class DataBoostQueryOption extends InternalOption implements ReadAndQueryOption {
799857

800858
private final Boolean dataBoostEnabled;

google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractReadContextTest.java

+18
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@
3333
import com.google.spanner.v1.ExecuteSqlRequest;
3434
import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
3535
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
36+
import com.google.spanner.v1.ReadRequest;
37+
import com.google.spanner.v1.ReadRequest.OrderBy;
3638
import com.google.spanner.v1.RequestOptions;
3739
import com.google.spanner.v1.RequestOptions.Priority;
40+
import com.google.spanner.v1.SessionName;
3841
import com.google.spanner.v1.TransactionSelector;
3942
import java.util.ArrayList;
4043
import java.util.Collection;
@@ -223,6 +226,21 @@ public void testGetExecuteSqlRequestBuilderWithDataBoost() {
223226
assertTrue(request.getDataBoostEnabled());
224227
}
225228

229+
@Test
230+
public void testGetReadRequestBuilderWithOrderBy() {
231+
ReadRequest request =
232+
ReadRequest.newBuilder()
233+
.setSession(
234+
SessionName.of("[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]").toString())
235+
.setTransaction(TransactionSelector.newBuilder().build())
236+
.setTable("table110115790")
237+
.setIndex("index100346066")
238+
.addAllColumns(new ArrayList<String>())
239+
.setOrderByValue(2)
240+
.build();
241+
assertEquals(OrderBy.ORDER_BY_NO_ORDER, request.getOrderBy());
242+
}
243+
226244
@Test
227245
public void testGetExecuteBatchDmlRequestBuilderWithPriority() {
228246
ExecuteBatchDmlRequest.Builder request =

google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java

+23
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import com.google.cloud.spanner.AsyncTransactionManager.TransactionContextFuture;
5353
import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime;
5454
import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
55+
import com.google.cloud.spanner.Options.RpcOrderBy;
5556
import com.google.cloud.spanner.Options.RpcPriority;
5657
import com.google.cloud.spanner.Options.TransactionOption;
5758
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
@@ -89,6 +90,7 @@
8990
import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
9091
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
9192
import com.google.spanner.v1.ReadRequest;
93+
import com.google.spanner.v1.ReadRequest.OrderBy;
9294
import com.google.spanner.v1.RequestOptions.Priority;
9395
import com.google.spanner.v1.ResultSetMetadata;
9496
import com.google.spanner.v1.ResultSetStats;
@@ -1722,6 +1724,27 @@ public void testExecuteReadWithTag() {
17221724
assertThat(request.getRequestOptions().getTransactionTag()).isEmpty();
17231725
}
17241726

1727+
@Test
1728+
public void testExecuteReadWithOrderByOption() {
1729+
DatabaseClient client =
1730+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
1731+
try (ResultSet resultSet =
1732+
client
1733+
.singleUse()
1734+
.read(
1735+
READ_TABLE_NAME,
1736+
KeySet.singleKey(Key.of(1L)),
1737+
READ_COLUMN_NAMES,
1738+
Options.orderBy(RpcOrderBy.NO_ORDER))) {
1739+
consumeResults(resultSet);
1740+
}
1741+
1742+
List<ReadRequest> requests = mockSpanner.getRequestsOfType(ReadRequest.class);
1743+
assertThat(requests).hasSize(1);
1744+
ReadRequest request = requests.get(0);
1745+
assertEquals(OrderBy.ORDER_BY_NO_ORDER, request.getOrderBy());
1746+
}
1747+
17251748
@Test
17261749
public void testExecuteReadWithDirectedReadOptions() {
17271750
DatabaseClient client =

google-cloud-spanner/src/test/java/com/google/cloud/spanner/OptionsTest.java

+31-2
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@
1818

1919
import static com.google.common.truth.Truth.assertThat;
2020
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertFalse;
2122
import static org.junit.Assert.assertNotEquals;
2223
import static org.junit.Assert.assertNotNull;
2324
import static org.junit.Assert.assertNull;
2425
import static org.junit.Assert.assertThrows;
2526
import static org.junit.Assert.assertTrue;
2627

28+
import com.google.cloud.spanner.Options.RpcOrderBy;
2729
import com.google.cloud.spanner.Options.RpcPriority;
2830
import com.google.spanner.v1.DirectedReadOptions;
2931
import com.google.spanner.v1.DirectedReadOptions.IncludeReplicas;
3032
import com.google.spanner.v1.DirectedReadOptions.ReplicaSelection;
33+
import com.google.spanner.v1.ReadRequest.OrderBy;
3134
import com.google.spanner.v1.RequestOptions.Priority;
3235
import org.junit.Test;
3336
import org.junit.runner.RunWith;
@@ -79,14 +82,16 @@ public void allOptionsPresent() {
7982
Options.limit(10),
8083
Options.prefetchChunks(1),
8184
Options.dataBoostEnabled(true),
82-
Options.directedRead(DIRECTED_READ_OPTIONS));
85+
Options.directedRead(DIRECTED_READ_OPTIONS),
86+
Options.orderBy(RpcOrderBy.NO_ORDER));
8387
assertThat(options.hasLimit()).isTrue();
8488
assertThat(options.limit()).isEqualTo(10);
8589
assertThat(options.hasPrefetchChunks()).isTrue();
8690
assertThat(options.prefetchChunks()).isEqualTo(1);
8791
assertThat(options.hasDataBoostEnabled()).isTrue();
8892
assertTrue(options.dataBoostEnabled());
8993
assertTrue(options.hasDirectedReadOptions());
94+
assertTrue(options.hasOrderBy());
9095
assertEquals(DIRECTED_READ_OPTIONS, options.directedReadOptions());
9196
}
9297

@@ -101,6 +106,7 @@ public void allOptionsAbsent() {
101106
assertThat(options.hasTag()).isFalse();
102107
assertThat(options.hasDataBoostEnabled()).isFalse();
103108
assertThat(options.hasDirectedReadOptions()).isFalse();
109+
assertThat(options.hasOrderBy()).isFalse();
104110
assertNull(options.withExcludeTxnFromChangeStreams());
105111
assertThat(options.toString()).isEqualTo("");
106112
assertThat(options.equals(options)).isTrue();
@@ -182,7 +188,8 @@ public void readOptionsTest() {
182188
Options.limit(limit),
183189
Options.tag(tag),
184190
Options.dataBoostEnabled(true),
185-
Options.directedRead(DIRECTED_READ_OPTIONS));
191+
Options.directedRead(DIRECTED_READ_OPTIONS),
192+
Options.orderBy(RpcOrderBy.NO_ORDER));
186193

187194
assertThat(options.toString())
188195
.isEqualTo(
@@ -197,10 +204,14 @@ public void readOptionsTest() {
197204
+ " "
198205
+ "directedReadOptions: "
199206
+ DIRECTED_READ_OPTIONS
207+
+ " "
208+
+ "orderBy: "
209+
+ RpcOrderBy.NO_ORDER
200210
+ " ");
201211
assertThat(options.tag()).isEqualTo(tag);
202212
assertEquals(dataBoost, options.dataBoostEnabled());
203213
assertEquals(DIRECTED_READ_OPTIONS, options.directedReadOptions());
214+
assertEquals(OrderBy.ORDER_BY_NO_ORDER, options.orderBy());
204215
}
205216

206217
@Test
@@ -354,6 +365,24 @@ public void testTransactionOptionsPriority() {
354365
assertEquals("priority: " + priority + " ", options.toString());
355366
}
356367

368+
@Test
369+
public void testReadOptionsOrderBy() {
370+
RpcOrderBy orderBy = RpcOrderBy.NO_ORDER;
371+
Options options = Options.fromReadOptions(Options.orderBy(orderBy));
372+
assertTrue(options.hasOrderBy());
373+
assertEquals("orderBy: " + orderBy + " ", options.toString());
374+
}
375+
376+
@Test
377+
public void testReadOptionsWithOrderByEquality() {
378+
Options optionsWithNoOrderBy1 = Options.fromReadOptions(Options.orderBy(RpcOrderBy.NO_ORDER));
379+
Options optionsWithNoOrderBy2 = Options.fromReadOptions(Options.orderBy(RpcOrderBy.NO_ORDER));
380+
assertTrue(optionsWithNoOrderBy1.equals(optionsWithNoOrderBy2));
381+
382+
Options optionsWithPkOrder = Options.fromReadOptions(Options.orderBy(RpcOrderBy.PRIMARY_KEY));
383+
assertFalse(optionsWithNoOrderBy1.equals(optionsWithPkOrder));
384+
}
385+
357386
@Test
358387
public void testQueryOptionsPriority() {
359388
RpcPriority priority = RpcPriority.MEDIUM;

0 commit comments

Comments
 (0)