/
Key.java
309 lines (284 loc) · 10.4 KB
/
Key.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.spanner;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.cloud.ByteArray;
import com.google.cloud.Date;
import com.google.cloud.Timestamp;
import com.google.common.base.Joiner;
import com.google.protobuf.ListValue;
import com.google.protobuf.NullValue;
import com.google.protobuf.Value;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
/**
* Represents a row key in a Cloud Spanner table or index. A key is a tuple of values constrained to
* the scalar Cloud Spanner types: currently these are {@code BOOLEAN}, {@code INT64}, {@code
* FLOAT64}, {@code STRING}, {@code BYTES} and {@code TIMESTAMP}. Values may be null where the table
* definition permits it.
*
* <p>{@code Key} is used to define the row, or endpoints of a range of rows, to retrieve in read
* operations or to delete in a mutation.
*
* <p>{@code Key} instances are immutable.
*/
public final class Key implements Serializable {
private static final Joiner joiner = Joiner.on(',').useForNull("<null>");
private static final com.google.protobuf.Value NULL_PROTO =
Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
private static final long serialVersionUID = 4433485671785063530L;
private final List<Object> parts;
private Key(List<Object> parts) {
this.parts = Collections.unmodifiableList(parts);
}
/**
* Construct a key with parts specified by {@code values}. Each object in {@code values} must be
* either {@code null} or one of the following supported types:
*
* <ul>
* <li>{@code Boolean} for the {@code BOOL} Cloud Spanner type
* <li>{@code Integer}, {@code Long} for the {@code INT64} Cloud Spanner type
* <li>{@code Float}, {@code Double} for the {@code FLOAT64} Cloud Spanner type
* <li>{@code BigDecimal} for the {@code NUMERIC} Cloud Spanner type
* <li>{@code String} for the {@code STRING} Cloud Spanner type
* <li>{@code String} for the {@code JSON} Cloud Spanner type
* <li>{@link ByteArray} for the {@code BYTES} Cloud Spanner type
* <li>{@link Timestamp} for the {@code TIMESTAMP} Cloud Spanner type
* <li>{@link Date} for the {@code DATE} Cloud Spanner type
* </ul>
*
* @throws IllegalArgumentException if any member of {@code values} is not a supported type
*/
public static Key of(Object... values) {
// A literal Key.of(null) results in a null array being passed. Provide a clearer error.
checkNotNull(
values,
"'values' cannot be null. For a literal key containing a single null value, "
+ "call Key.of((Object) null).");
Builder b = new Builder(false /* builder never leaves this scope */);
for (Object value : values) {
b.appendObject(value);
}
return b.build();
}
/** Returns a new builder for constructing a key. */
public static Builder newBuilder() {
return new Builder(true /* escaped */);
}
/** Builder for {@link Key} instances. */
public static class Builder {
/**
* Indicates whether this builder can escape the scope of this class. If so, we must assume that
* the builder can be modified after {@code build()} is called and so we perform a defensive
* copy.
*/
private final boolean canEscape;
private final ArrayList<Object> buffer = new ArrayList<>();
private Builder(boolean canEscape) {
this.canEscape = canEscape;
}
private Builder(Key key) {
canEscape = true;
buffer.addAll(key.parts);
}
/** Appends a {@code BOOL} value to the key. */
public Builder append(@Nullable Boolean value) {
buffer.add(value);
return this;
}
/** Appends an {@code INT64} value to the key. */
public Builder append(long value) {
buffer.add(value);
return this;
}
/** Appends an {@code INT64} value to the key. */
public Builder append(@Nullable Long value) {
buffer.add(value);
return this;
}
/** Appends a {@code FLOAT64} value to the key. */
public Builder append(double value) {
buffer.add(value);
return this;
}
/** Appends a {@code FLOAT64} value to the key. */
public Builder append(@Nullable Double value) {
buffer.add(value);
return this;
}
/** Appends a {@code NUMERIC} value to the key. */
public Builder append(@Nullable BigDecimal value) {
buffer.add(value);
return this;
}
/** Appends a {@code STRING} value to the key. */
public Builder append(@Nullable String value) {
buffer.add(value);
return this;
}
/** Appends a {@code BYTES} value to the key. */
public Builder append(@Nullable ByteArray value) {
buffer.add(value);
return this;
}
/** Appends a {@code TIMESTAMP} value to the key */
public Builder append(@Nullable Timestamp value) {
buffer.add(value);
return this;
}
/** Appends a {@code DATE} value to the key */
public Builder append(@Nullable Date value) {
buffer.add(value);
return this;
}
/**
* Appends an object following the same conversion rules as {@link Key#of(Object...)}. When
* using the {@code Builder}, most code should prefer using the strongly typed {@code
* append(...)} methods, for both performance and the benefit of compile-time checking.
*/
public Builder appendObject(@Nullable Object value) {
if (value == null) {
append((Boolean) null);
} else if (value instanceof Boolean) {
append((Boolean) value);
} else if (value instanceof Integer) {
append((Integer) value);
} else if (value instanceof Long) {
append((Long) value);
} else if (value instanceof Float) {
append((Float) value);
} else if (value instanceof Double) {
append((Double) value);
} else if (value instanceof BigDecimal) {
append((BigDecimal) value);
} else if (value instanceof String) {
append((String) value);
} else if (value instanceof ByteArray) {
append((ByteArray) value);
} else if (value instanceof Timestamp) {
append((Timestamp) value);
} else if (value instanceof Date) {
append((Date) value);
} else {
throw new IllegalArgumentException(
"Unsupported type ["
+ value.getClass().getCanonicalName()
+ "] for argument: "
+ value);
}
return this;
}
public Key build() {
if (canEscape) {
// Copy buffer to preserve immutability contract.
return new Key(new ArrayList<>(buffer));
} else {
// Internal use of builder that does not escape; no need for defensive copy.
return new Key(buffer);
}
}
}
/** Returns the number of parts in this key, including {@code null} values. */
public int size() {
return parts.size();
}
/**
* Returns the parts in this key. Each part is represented by the corresponding Cloud Spanner
* type's canonical Java type, as listed below. Note that other types supported by {@link
* #of(Object...)} are converted to one of the canonical types.
*
* <ul>
* <li>{@code BOOL} is represented by {@code Boolean}
* <li>{@code INT64} is represented by {@code Long}
* <li>{@code FLOAT64} is represented by {@code Double}
* <li>{@code NUMERIC} is represented by {@code BigDecimal}
* <li>{@code STRING} is represented by {@code String}
* <li>{@code JSON} is represented by {@code String}
* <li>{@code BYTES} is represented by {@link ByteArray}
* <li>{@code TIMESTAMP} is represented by {@link Timestamp}
* <li>{@code DATE} is represented by {@link Date}
* </ul>
*
* @return an unmodifiable list containing the key parts
*/
public Iterable<Object> getParts() {
return parts;
}
/** Returns a builder initialized with the value of this key. */
public Builder toBuilder() {
return new Builder(this);
}
void toString(StringBuilder b) {
// TODO(user): Consider limiting the length of string output.
// Note: the format produced should match that used for keys in error messages yielded by the
// backend.
b.append('[');
joiner.appendTo(b, parts);
b.append(']');
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
toString(b);
return b.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Key that = (Key) o;
return parts.equals(that.parts);
}
@Override
public int hashCode() {
return parts.hashCode();
}
ListValue toProto() {
ListValue.Builder builder = ListValue.newBuilder();
for (Object part : parts) {
if (part == null) {
builder.addValues(NULL_PROTO);
} else if (part instanceof Boolean) {
builder.addValuesBuilder().setBoolValue((Boolean) part);
} else if (part instanceof Long) {
builder.addValuesBuilder().setStringValue(part.toString());
} else if (part instanceof Double) {
builder.addValuesBuilder().setNumberValue((Double) part);
} else if (part instanceof BigDecimal) {
builder.addValuesBuilder().setStringValue(part.toString());
} else if (part instanceof String) {
builder.addValuesBuilder().setStringValue((String) part);
} else if (part instanceof ByteArray) {
builder.addValuesBuilder().setStringValue(((ByteArray) part).toBase64());
} else if (part instanceof Timestamp) {
builder.addValuesBuilder().setStringValue(part.toString());
} else if (part instanceof Date) {
builder.addValuesBuilder().setStringValue(part.toString());
} else {
throw new AssertionError("Illegal key part: " + part.getClass());
}
}
return builder.build();
}
}