22
22
import com .google .cloud .ByteArray ;
23
23
import com .google .cloud .Date ;
24
24
import com .google .cloud .Timestamp ;
25
+ import com .google .cloud .spanner .Type .Code ;
26
+ import com .google .cloud .spanner .Type .StructField ;
25
27
import com .google .common .collect .ImmutableList ;
26
28
import com .google .common .primitives .Booleans ;
27
29
import com .google .common .primitives .Doubles ;
28
30
import com .google .common .primitives .Longs ;
29
-
30
31
import java .io .Serializable ;
31
32
import java .util .ArrayList ;
32
33
import java .util .List ;
33
34
import java .util .Objects ;
34
- import javax .annotation .Nullable ;
35
35
import javax .annotation .concurrent .Immutable ;
36
36
37
37
/**
38
- * Represents a value of {@link Type.Code#STRUCT}. Such values are a tuple of named and typed
39
- * columns, where individual columns may be null. Individual rows from a read or query operation can
40
- * be considered as structs; {@link ResultSet#getCurrentRowAsStruct()} allows an immutable struct to
41
- * be created from the row that the result set is currently positioned over.
38
+ * Represents a non-{@code NULL} value of {@link Type.Code#STRUCT}. Such values are a tuple of named
39
+ * and typed columns, where individual columns may be null. Individual rows from a read or query
40
+ * operation can be considered as structs; {@link ResultSet#getCurrentRowAsStruct()} allows an
41
+ * immutable struct to be created from the row that the result set is currently positioned over.
42
42
*
43
43
* <p>{@code Struct} instances are immutable.
44
+ *
45
+ * <p>This class does not support representing typed {@code NULL} {@code Struct} values.
46
+ *
47
+ * <p>However, struct values <em>inside</em> SQL queries are always typed and can be externally
48
+ * supplied to a query only in the form of struct/array-of-struct query parameter values for which
49
+ * typed {@code NULL} struct values can be specified in the following ways:
50
+ *
51
+ * <p>1. As a standalone {@code NULL} struct value or as a nested struct field value, constructed
52
+ * using {@link ValueBinder#to(Type, Struct)} or {@link Value#struct(Type, Struct)}.
53
+ *
54
+ * <p>2. As as a null {@code Struct} reference representing a {@code NULL} struct typed element
55
+ * value inside an array/list of '{@code Struct}' references, that is used to construct an
56
+ * array-of-struct value using {@link Value#structArray(Type, Iterable)} or {@link
57
+ * ValueBinder#toStructArray(Type, Iterable)}. In this case, the type of the {@code NULL} struct
58
+ * value is assumed to be the same as the explicitly specified struct element type of the
59
+ * array/list.
44
60
*/
45
61
@ Immutable
46
62
public abstract class Struct extends AbstractStructReader implements Serializable {
47
63
// Only implementations within the package are allowed.
48
64
Struct () {}
49
65
50
- /**
51
- * Returns a builder for creating a {@code Struct} instance. This is intended mainly for test
52
- * purposes: the library implementation is typically responsible for creating {@code Struct}
53
- * instances.
54
- */
66
+ /** Returns a builder for creating a non-{@code NULL} {@code Struct} instance. */
55
67
public static Builder newBuilder () {
56
68
return new Builder ();
57
69
}
58
70
59
- /** Builder for {@code Struct} instances. */
71
+ /** Builder for constructing non-{@code NULL} {@code Struct} instances. */
60
72
public static final class Builder {
61
73
private final List <Type .StructField > types = new ArrayList <>();
62
74
private final List <Value > values = new ArrayList <>();
@@ -76,32 +88,25 @@ Builder handle(Value value) {
76
88
};
77
89
}
78
90
79
- /** Returns a binder to set the value of a new field in the struct named {@code fieldName}. */
91
+ /**
92
+ * Returns a binder to set the value of a new field in the struct named {@code fieldName}.
93
+ *
94
+ * @param fieldName name of the field to set. Can be empty or the same as an existing field name
95
+ * in the {@code STRUCT}
96
+ */
80
97
public ValueBinder <Builder > set (String fieldName ) {
81
98
checkBindingInProgress (false );
82
99
currentField = checkNotNull (fieldName );
83
100
return binder ;
84
101
}
85
102
86
- /** Adds a new field named {@code fieldName} with the given value. */
87
- public Builder add (String fieldName , Value value ) {
103
+ /** Adds a new unnamed field {@code fieldName} with the given value. */
104
+ public Builder add (Value value ) {
88
105
checkBindingInProgress (false );
89
- addInternal (fieldName , value );
106
+ addInternal ("" , value );
90
107
return this ;
91
108
}
92
109
93
- /**
94
- * Adds a new field of type {@code ARRAY<STRUCT<fieldTypes>>} named {@code fieldName} with the
95
- * given element values. {@code elements} may be null, as may any member of {@code elements}.
96
- * All non-null members of {@code elements} must be of type {@code STRUCT<fieldTypes>}
97
- */
98
- public Builder add (
99
- String fieldName ,
100
- Iterable <Type .StructField > fieldTypes ,
101
- @ Nullable Iterable <Struct > elements ) {
102
- return add (fieldName , Value .structArray (fieldTypes , elements ));
103
- }
104
-
105
110
public Struct build () {
106
111
checkBindingInProgress (false );
107
112
return new ValueListStruct (types , values );
@@ -121,12 +126,42 @@ private void checkBindingInProgress(boolean expectInProgress) {
121
126
}
122
127
}
123
128
124
- /** Default implementation for test value structs produced by {@link Builder}. */
129
+ /**
130
+ * TODO(user) : Consider moving these methods to the StructReader interface once STRUCT-typed
131
+ * columns are supported in {@link ResultSet}.
132
+ */
133
+
134
+ /* Public methods for accessing struct-typed fields */
135
+ public Struct getStruct (int columnIndex ) {
136
+ checkNonNullStruct (columnIndex , columnIndex );
137
+ return getStructInternal (columnIndex );
138
+ }
139
+
140
+ public Struct getStruct (String columnName ) {
141
+ int columnIndex = getColumnIndex (columnName );
142
+ checkNonNullStruct (columnIndex , columnName );
143
+ return getStructInternal (columnIndex );
144
+ }
145
+
146
+ /* Sub-classes must implement this method */
147
+ protected abstract Struct getStructInternal (int columnIndex );
148
+
149
+ private void checkNonNullStruct (int columnIndex , Object columnNameForError ) {
150
+ Type actualType = getColumnType (columnIndex );
151
+ checkState (
152
+ actualType .getCode () == Code .STRUCT ,
153
+ "Column %s is not of correct type: expected STRUCT<...> but was %s" ,
154
+ columnNameForError ,
155
+ actualType );
156
+ checkNonNull (columnIndex , columnNameForError );
157
+ }
158
+
159
+ /** Default implementation for value structs produced by {@link Builder}. */
125
160
private static class ValueListStruct extends Struct {
126
161
private final Type type ;
127
162
private final List <Value > values ;
128
163
129
- private ValueListStruct (List < Type . StructField > types , List <Value > values ) {
164
+ private ValueListStruct (Iterable < StructField > types , Iterable <Value > values ) {
130
165
this .type = Type .struct (types );
131
166
this .values = ImmutableList .copyOf (values );
132
167
}
@@ -166,6 +201,11 @@ protected Date getDateInternal(int columnIndex) {
166
201
return values .get (columnIndex ).getDate ();
167
202
}
168
203
204
+ @ Override
205
+ protected Struct getStructInternal (int columnIndex ) {
206
+ return values .get (columnIndex ).getStruct ();
207
+ }
208
+
169
209
@ Override
170
210
protected boolean [] getBooleanArrayInternal (int columnIndex ) {
171
211
return Booleans .toArray (getBooleanListInternal (columnIndex ));
@@ -289,6 +329,8 @@ private Object getAsObject(int columnIndex) {
289
329
return getTimestampInternal (columnIndex );
290
330
case DATE :
291
331
return getDateInternal (columnIndex );
332
+ case STRUCT :
333
+ return getStructInternal (columnIndex );
292
334
case ARRAY :
293
335
switch (type .getArrayElementType ().getCode ()) {
294
336
case BOOL :
0 commit comments