google-cloud-spanner 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/google/cloud/spanner.rb +33 -0
- data/lib/google/cloud/spanner/batch_client.rb +74 -0
- data/lib/google/cloud/spanner/batch_snapshot.rb +127 -42
- data/lib/google/cloud/spanner/client.rb +143 -33
- data/lib/google/cloud/spanner/commit.rb +5 -5
- data/lib/google/cloud/spanner/convert.rb +125 -110
- data/lib/google/cloud/spanner/data.rb +39 -6
- data/lib/google/cloud/spanner/fields.rb +252 -60
- data/lib/google/cloud/spanner/results.rb +4 -3
- data/lib/google/cloud/spanner/session.rb +87 -25
- data/lib/google/cloud/spanner/snapshot.rb +168 -25
- data/lib/google/cloud/spanner/transaction.rb +165 -25
- data/lib/google/cloud/spanner/v1/doc/overview.rb +2 -2
- data/lib/google/cloud/spanner/version.rb +1 -1
- metadata +3 -3
@@ -99,7 +99,7 @@ module Google
|
|
99
99
|
Google::Spanner::V1::Mutation.new(
|
100
100
|
insert_or_update: Google::Spanner::V1::Mutation::Write.new(
|
101
101
|
table: table, columns: row.keys.map(&:to_s),
|
102
|
-
values: [Convert.
|
102
|
+
values: [Convert.object_to_grpc_value(row.values).list_value]
|
103
103
|
)
|
104
104
|
)
|
105
105
|
end
|
@@ -157,7 +157,7 @@ module Google
|
|
157
157
|
Google::Spanner::V1::Mutation.new(
|
158
158
|
insert: Google::Spanner::V1::Mutation::Write.new(
|
159
159
|
table: table, columns: row.keys.map(&:to_s),
|
160
|
-
values: [Convert.
|
160
|
+
values: [Convert.object_to_grpc_value(row.values).list_value]
|
161
161
|
)
|
162
162
|
)
|
163
163
|
end
|
@@ -214,7 +214,7 @@ module Google
|
|
214
214
|
Google::Spanner::V1::Mutation.new(
|
215
215
|
update: Google::Spanner::V1::Mutation::Write.new(
|
216
216
|
table: table, columns: row.keys.map(&:to_s),
|
217
|
-
values: [Convert.
|
217
|
+
values: [Convert.object_to_grpc_value(row.values).list_value]
|
218
218
|
)
|
219
219
|
)
|
220
220
|
end
|
@@ -273,7 +273,7 @@ module Google
|
|
273
273
|
Google::Spanner::V1::Mutation.new(
|
274
274
|
replace: Google::Spanner::V1::Mutation::Write.new(
|
275
275
|
table: table, columns: row.keys.map(&:to_s),
|
276
|
-
values: [Convert.
|
276
|
+
values: [Convert.object_to_grpc_value(row.values).list_value]
|
277
277
|
)
|
278
278
|
)
|
279
279
|
end
|
@@ -334,7 +334,7 @@ module Google
|
|
334
334
|
end
|
335
335
|
key_list = keys.map do |key|
|
336
336
|
key = [key] unless key.is_a? Array
|
337
|
-
Convert.
|
337
|
+
Convert.object_to_grpc_value(key).list_value
|
338
338
|
end
|
339
339
|
Google::Spanner::V1::KeySet.new keys: key_list
|
340
340
|
end
|
@@ -31,106 +31,43 @@ module Google
|
|
31
31
|
def to_query_params params, types = nil
|
32
32
|
types ||= {}
|
33
33
|
formatted_params = params.map do |key, obj|
|
34
|
-
[String(key),
|
34
|
+
[String(key), object_to_grpc_value_and_type(obj, types[key])]
|
35
35
|
end
|
36
36
|
Hash[formatted_params]
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
39
|
+
def object_to_grpc_value_and_type obj, field = nil
|
40
40
|
obj = obj.to_column_value if obj.respond_to? :to_column_value
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
[Google::Protobuf::Value.new(null_value: :NULL_VALUE),
|
45
|
-
Google::Spanner::V1::Type.new(
|
46
|
-
code: :ARRAY, array_element_type:
|
47
|
-
Google::Spanner::V1::Type.new(code: type.first))]
|
48
|
-
# elsif type.is_a? Fields
|
49
|
-
else
|
50
|
-
[Google::Protobuf::Value.new(null_value: :NULL_VALUE),
|
51
|
-
Google::Spanner::V1::Type.new(code: type)]
|
52
|
-
end
|
53
|
-
else
|
54
|
-
raise ArgumentError, "Must provide type for nil values."
|
55
|
-
end
|
56
|
-
elsif String === obj
|
57
|
-
[raw_to_value(obj), Google::Spanner::V1::Type.new(code: :STRING)]
|
58
|
-
elsif Symbol === obj
|
59
|
-
[raw_to_value(obj.to_s),
|
60
|
-
Google::Spanner::V1::Type.new(code: :STRING)]
|
61
|
-
elsif TrueClass === obj
|
62
|
-
[raw_to_value(obj), Google::Spanner::V1::Type.new(code: :BOOL)]
|
63
|
-
elsif FalseClass === obj
|
64
|
-
[raw_to_value(obj), Google::Spanner::V1::Type.new(code: :BOOL)]
|
65
|
-
elsif Integer === obj
|
66
|
-
[raw_to_value(obj.to_s),
|
67
|
-
Google::Spanner::V1::Type.new(code: :INT64)]
|
68
|
-
elsif Numeric === obj # Any number not an integer gets to be a float
|
69
|
-
[raw_to_value(obj),
|
70
|
-
Google::Spanner::V1::Type.new(code: :FLOAT64)]
|
71
|
-
elsif Time === obj || DateTime === obj
|
72
|
-
[raw_to_value(obj),
|
73
|
-
Google::Spanner::V1::Type.new(code: :TIMESTAMP)]
|
74
|
-
elsif Date === obj
|
75
|
-
[raw_to_value(obj.to_s),
|
76
|
-
Google::Spanner::V1::Type.new(code: :DATE)]
|
77
|
-
elsif Array === obj
|
78
|
-
if type && !type.is_a?(Array)
|
79
|
-
raise ArgumentError, "Array values must have an Array type."
|
80
|
-
end
|
81
|
-
type ||= begin
|
82
|
-
# Find the param type for the first non-nil item the list
|
83
|
-
nested_param_value = obj.detect { |x| !x.nil? }
|
84
|
-
if nested_param_value.nil?
|
85
|
-
raise ArgumentError, "Array values must have an Array type."
|
86
|
-
end
|
87
|
-
[raw_to_value_and_type(nested_param_value).last.code]
|
88
|
-
end
|
89
|
-
[raw_to_value(obj),
|
90
|
-
Google::Spanner::V1::Type.new(
|
91
|
-
code: :ARRAY, array_element_type:
|
92
|
-
Google::Spanner::V1::Type.new(code: type.first))]
|
93
|
-
elsif Hash === obj
|
94
|
-
field_pairs = obj.map do |key, value|
|
95
|
-
[key, raw_to_value_and_type(value).last]
|
96
|
-
end
|
97
|
-
formatted_fields = field_pairs.map do |name, param_type|
|
98
|
-
Google::Spanner::V1::StructType::Field.new(
|
99
|
-
name: String(name), type: param_type
|
100
|
-
)
|
101
|
-
end
|
102
|
-
[raw_to_value(obj),
|
103
|
-
Google::Spanner::V1::Type.new(
|
104
|
-
code: :STRUCT,
|
105
|
-
struct_type: Google::Spanner::V1::StructType.new(
|
106
|
-
fields: formatted_fields
|
107
|
-
))]
|
108
|
-
elsif obj.respond_to?(:read) && obj.respond_to?(:rewind)
|
109
|
-
obj.rewind
|
110
|
-
[raw_to_value(obj),
|
111
|
-
Google::Spanner::V1::Type.new(code: :BYTES)]
|
112
|
-
else
|
113
|
-
raise ArgumentError,
|
114
|
-
"A parameter of type #{obj.class} is not supported."
|
41
|
+
|
42
|
+
if obj.respond_to? :to_grpc_value_and_type
|
43
|
+
return obj.to_grpc_value_and_type
|
115
44
|
end
|
45
|
+
|
46
|
+
field ||= field_for_object obj
|
47
|
+
[object_to_grpc_value(obj, field), grpc_type_for_field(field)]
|
116
48
|
end
|
117
49
|
|
118
|
-
def
|
50
|
+
def object_to_grpc_value obj, field = nil
|
119
51
|
obj = obj.to_column_value if obj.respond_to? :to_column_value
|
120
52
|
|
121
|
-
if
|
53
|
+
if obj.respond_to? :to_grpc_value_and_type
|
54
|
+
return obj.to_grpc_value_and_type.first
|
55
|
+
end
|
56
|
+
|
57
|
+
case obj
|
58
|
+
when NilClass
|
122
59
|
Google::Protobuf::Value.new null_value: :NULL_VALUE
|
123
|
-
|
124
|
-
Google::Protobuf::Value.new string_value: obj
|
125
|
-
elsif Symbol === obj
|
60
|
+
when String
|
126
61
|
Google::Protobuf::Value.new string_value: obj.to_s
|
127
|
-
|
62
|
+
when Symbol
|
63
|
+
Google::Protobuf::Value.new string_value: obj.to_s
|
64
|
+
when TrueClass
|
128
65
|
Google::Protobuf::Value.new bool_value: true
|
129
|
-
|
66
|
+
when FalseClass
|
130
67
|
Google::Protobuf::Value.new bool_value: false
|
131
|
-
|
68
|
+
when Integer
|
132
69
|
Google::Protobuf::Value.new string_value: obj.to_s
|
133
|
-
|
70
|
+
when Numeric
|
134
71
|
if obj == Float::INFINITY
|
135
72
|
Google::Protobuf::Value.new string_value: "Infinity"
|
136
73
|
elsif obj == -Float::INFINITY
|
@@ -140,42 +77,110 @@ module Google
|
|
140
77
|
else
|
141
78
|
Google::Protobuf::Value.new number_value: obj.to_f
|
142
79
|
end
|
143
|
-
|
80
|
+
when Time
|
81
|
+
Google::Protobuf::Value.new(string_value:
|
82
|
+
obj.to_time.utc.strftime("%FT%T.%NZ"))
|
83
|
+
when DateTime
|
144
84
|
Google::Protobuf::Value.new(string_value:
|
145
85
|
obj.to_time.utc.strftime("%FT%T.%NZ"))
|
146
|
-
|
86
|
+
when Date
|
147
87
|
Google::Protobuf::Value.new string_value: obj.to_s
|
148
|
-
|
88
|
+
when Array
|
89
|
+
arr_field = nil
|
90
|
+
arr_field = field.first if Array === field
|
149
91
|
Google::Protobuf::Value.new list_value:
|
150
92
|
Google::Protobuf::ListValue.new(values:
|
151
|
-
obj.map { |o|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
encoded_content = Base64.strict_encode64(content)
|
160
|
-
Google::Protobuf::Value.new(string_value: encoded_content)
|
93
|
+
obj.map { |o| object_to_grpc_value(o, arr_field) })
|
94
|
+
when Hash
|
95
|
+
if field.is_a? Fields
|
96
|
+
field.struct(obj).to_grpc_value
|
97
|
+
else
|
98
|
+
raise ArgumentError,
|
99
|
+
"A hash value cannot be set to type #{field}."
|
100
|
+
end
|
161
101
|
else
|
162
|
-
|
163
|
-
|
102
|
+
if obj.respond_to?(:read) && obj.respond_to?(:rewind)
|
103
|
+
obj.rewind
|
104
|
+
content = obj.read.force_encoding("ASCII-8BIT")
|
105
|
+
encoded_content = Base64.strict_encode64(content)
|
106
|
+
Google::Protobuf::Value.new(string_value: encoded_content)
|
107
|
+
else
|
108
|
+
raise ArgumentError,
|
109
|
+
"A value of type #{obj.class} is not supported."
|
110
|
+
end
|
164
111
|
end
|
165
112
|
end
|
166
113
|
|
167
|
-
def
|
168
|
-
|
169
|
-
|
114
|
+
def field_for_object obj
|
115
|
+
case obj
|
116
|
+
when NilClass
|
117
|
+
raise ArgumentError, "Cannot determine type for nil values."
|
118
|
+
when String
|
119
|
+
:STRING
|
120
|
+
when Symbol
|
121
|
+
:STRING
|
122
|
+
when TrueClass
|
123
|
+
:BOOL
|
124
|
+
when FalseClass
|
125
|
+
:BOOL
|
126
|
+
when Integer
|
127
|
+
:INT64
|
128
|
+
when Numeric
|
129
|
+
:FLOAT64
|
130
|
+
when Time
|
131
|
+
:TIMESTAMP
|
132
|
+
when DateTime
|
133
|
+
:TIMESTAMP
|
134
|
+
when Date
|
135
|
+
:DATE
|
136
|
+
when Array
|
137
|
+
if obj.empty?
|
138
|
+
raise ArgumentError,
|
139
|
+
"Cannot determine type for empty array values."
|
140
|
+
end
|
141
|
+
non_nil_fields = obj.compact.map { |e| field_for_object e }.compact
|
142
|
+
if non_nil_fields.empty?
|
143
|
+
raise ArgumentError,
|
144
|
+
"Cannot determine type for array of nil values."
|
145
|
+
end
|
146
|
+
if non_nil_fields.uniq.count > 1
|
147
|
+
raise ArgumentError,
|
148
|
+
"Cannot determine type for array of different values."
|
149
|
+
end
|
150
|
+
[non_nil_fields.first]
|
151
|
+
when Hash
|
152
|
+
raw_type_pairs = obj.map do |key, value|
|
153
|
+
[key, field_for_object(value)]
|
154
|
+
end
|
155
|
+
Fields.new Hash[raw_type_pairs]
|
156
|
+
when Data
|
157
|
+
obj.fields
|
158
|
+
else
|
159
|
+
if obj.respond_to?(:read) && obj.respond_to?(:rewind)
|
160
|
+
:BYTES
|
161
|
+
else
|
162
|
+
raise ArgumentError,
|
163
|
+
"Cannot determine type for #{obj.class} values."
|
164
|
+
end
|
170
165
|
end
|
171
166
|
end
|
172
167
|
|
173
|
-
def
|
174
|
-
|
168
|
+
def grpc_type_for_field field
|
169
|
+
return field.to_grpc_type if field.respond_to? :to_grpc_type
|
170
|
+
|
171
|
+
if Array === field
|
172
|
+
Google::Spanner::V1::Type.new(
|
173
|
+
code: :ARRAY,
|
174
|
+
array_element_type: grpc_type_for_field(field.first)
|
175
|
+
)
|
176
|
+
else
|
177
|
+
Google::Spanner::V1::Type.new(code: field)
|
178
|
+
end
|
175
179
|
end
|
176
180
|
|
177
|
-
def
|
181
|
+
def grpc_value_to_object value, type
|
178
182
|
return nil if value.kind == :null_value
|
183
|
+
|
179
184
|
case type.code
|
180
185
|
when :BOOL
|
181
186
|
value.bool_value
|
@@ -205,13 +210,23 @@ module Google
|
|
205
210
|
StringIO.new Base64.decode64 value.string_value
|
206
211
|
when :ARRAY
|
207
212
|
value.list_value.values.map do |v|
|
208
|
-
|
213
|
+
grpc_value_to_object v, type.array_element_type
|
209
214
|
end
|
210
215
|
when :STRUCT
|
211
216
|
Data.from_grpc value.list_value.values, type.struct_type.fields
|
212
217
|
end
|
213
218
|
end
|
214
219
|
|
220
|
+
def row_to_pairs row_types, row
|
221
|
+
row_types.zip(row).map do |field, value|
|
222
|
+
[field.name.to_sym, grpc_value_to_object(value, field.type)]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def row_to_object row_types, row
|
227
|
+
Hash[row_to_pairs(row_types, row)]
|
228
|
+
end
|
229
|
+
|
215
230
|
def number_to_duration number
|
216
231
|
return nil if number.nil?
|
217
232
|
|
@@ -247,8 +262,8 @@ module Google
|
|
247
262
|
|
248
263
|
def to_key_range range
|
249
264
|
range_opts = {
|
250
|
-
start_closed:
|
251
|
-
end_closed:
|
265
|
+
start_closed: object_to_grpc_value(Array(range.begin)).list_value,
|
266
|
+
end_closed: object_to_grpc_value(Array(range.end)).list_value }
|
252
267
|
|
253
268
|
if range.respond_to?(:exclude_begin?) && range.exclude_begin?
|
254
269
|
range_opts[:start_open] = range_opts[:start_closed]
|
@@ -274,7 +289,7 @@ module Google
|
|
274
289
|
|
275
290
|
key_list = keys.map do |key|
|
276
291
|
key = [key] unless key.is_a? Array
|
277
|
-
|
292
|
+
object_to_grpc_value(key).list_value
|
278
293
|
end
|
279
294
|
Google::Spanner::V1::KeySet.new keys: key_list
|
280
295
|
end
|
@@ -42,7 +42,8 @@ module Google
|
|
42
42
|
#
|
43
43
|
class Data
|
44
44
|
##
|
45
|
-
# Returns the
|
45
|
+
# Returns the configuration object ({Fields}) of the names and types of
|
46
|
+
# the data.
|
46
47
|
#
|
47
48
|
# @return [Array<Array>] An array containing name and value pairs.
|
48
49
|
#
|
@@ -109,14 +110,15 @@ module Google
|
|
109
110
|
#
|
110
111
|
def [] key
|
111
112
|
if key.is_a? Integer
|
112
|
-
return Convert.
|
113
|
-
|
113
|
+
return Convert.grpc_value_to_object(@grpc_values[key],
|
114
|
+
@grpc_fields[key].type)
|
114
115
|
end
|
115
116
|
name_count = @grpc_fields.find_all { |f| f.name == String(key) }.count
|
116
117
|
return nil if name_count.zero?
|
117
118
|
raise DuplicateNameError if name_count > 1
|
118
119
|
index = @grpc_fields.find_index { |f| f.name == String(key) }
|
119
|
-
Convert.
|
120
|
+
Convert.grpc_value_to_object(@grpc_values[index],
|
121
|
+
@grpc_fields[index].type)
|
120
122
|
end
|
121
123
|
|
122
124
|
##
|
@@ -184,13 +186,44 @@ module Google
|
|
184
186
|
"#<#{self.class.name} #{self}>"
|
185
187
|
end
|
186
188
|
|
189
|
+
##
|
190
|
+
# @private
|
191
|
+
def to_grpc_value
|
192
|
+
if @grpc_values.nil?
|
193
|
+
return Google::Protobuf::Value.new null_value: :NULL_VALUE
|
194
|
+
end
|
195
|
+
|
196
|
+
Google::Protobuf::Value.new(
|
197
|
+
list_value: Google::Protobuf::ListValue.new(
|
198
|
+
values: @grpc_values
|
199
|
+
)
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# @private
|
205
|
+
def to_grpc_type
|
206
|
+
Google::Spanner::V1::Type.new(
|
207
|
+
code: :STRUCT,
|
208
|
+
struct_type: Google::Spanner::V1::StructType.new(
|
209
|
+
fields: @grpc_fields
|
210
|
+
)
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# @private
|
216
|
+
def to_grpc_value_and_type
|
217
|
+
[to_grpc_value, to_grpc_type]
|
218
|
+
end
|
219
|
+
|
187
220
|
##
|
188
221
|
# @private Creates a new Data instance from
|
189
222
|
# Spanner values and fields.
|
190
223
|
def self.from_grpc grpc_values, grpc_fields
|
191
224
|
new.tap do |d|
|
192
|
-
d.instance_variable_set :@grpc_values, grpc_values
|
193
|
-
d.instance_variable_set :@grpc_fields, grpc_fields
|
225
|
+
d.instance_variable_set :@grpc_values, Array(grpc_values)
|
226
|
+
d.instance_variable_set :@grpc_fields, Array(grpc_fields)
|
194
227
|
end
|
195
228
|
end
|
196
229
|
end
|
@@ -36,25 +36,91 @@ module Google
|
|
36
36
|
# results = db.execute "SELECT * FROM users"
|
37
37
|
#
|
38
38
|
# results.fields.pairs.each do |name, type|
|
39
|
-
# puts "Column #{name} is type {type}"
|
39
|
+
# puts "Column #{name} is type #{type}"
|
40
40
|
# end
|
41
41
|
#
|
42
42
|
class Fields
|
43
43
|
##
|
44
|
-
#
|
44
|
+
# Creates {Fields} object from types. See {Client#fields}.
|
45
|
+
#
|
46
|
+
# This object can be used to create {Data} objects by providing values
|
47
|
+
# that match the field types. See {Fields#struct}.
|
48
|
+
#
|
49
|
+
# See [Data Types - Constructing a
|
50
|
+
# STRUCT](https://cloud.google.com/spanner/docs/data-types#constructing-a-struct).
|
51
|
+
#
|
52
|
+
# @param [Array, Hash] types Accepts an array or hash types.
|
53
|
+
#
|
54
|
+
# Arrays can contain just the type value, or a sub-array of the
|
55
|
+
# field's name and type value. Hash keys must contain the field name
|
56
|
+
# as a `Symbol` or `String`, or the field position as an `Integer`.
|
57
|
+
# Hash values must contain the type value. If a Hash is used the
|
58
|
+
# fields will be created using the same order as the Hash keys.
|
59
|
+
#
|
60
|
+
# Supported type values incude:
|
61
|
+
#
|
62
|
+
# * `:BOOL`
|
63
|
+
# * `:BYTES`
|
64
|
+
# * `:DATE`
|
65
|
+
# * `:FLOAT64`
|
66
|
+
# * `:INT64`
|
67
|
+
# * `:STRING`
|
68
|
+
# * `:TIMESTAMP`
|
69
|
+
# * `Array` - Lists are specified by providing the type code in an
|
70
|
+
# array. For example, an array of integers are specified as
|
71
|
+
# `[:INT64]`.
|
72
|
+
# * {Fields} - Nested Structs are specified by providing a Fields
|
73
|
+
# object.
|
74
|
+
#
|
75
|
+
# @return [Fields] The fields of the given types.
|
76
|
+
#
|
77
|
+
# @example Create a STRUCT value with named fields using Fields object:
|
78
|
+
# require "google/cloud/spanner"
|
79
|
+
#
|
80
|
+
# named_type = Google::Cloud::Spanner::Fields.new(
|
81
|
+
# { id: :INT64, name: :STRING, active: :BOOL }
|
82
|
+
# )
|
83
|
+
# named_data = named_type.struct(
|
84
|
+
# { id: 42, name: nil, active: false }
|
85
|
+
# )
|
86
|
+
#
|
87
|
+
# @example Create a STRUCT value with anonymous field names:
|
88
|
+
# require "google/cloud/spanner"
|
89
|
+
#
|
90
|
+
# anon_type = Google::Cloud::Spanner::Fields.new(
|
91
|
+
# [:INT64, :STRING, :BOOL]
|
92
|
+
# )
|
93
|
+
# anon_data = anon_type.struct [42, nil, false]
|
94
|
+
#
|
95
|
+
# @example Create a STRUCT value with duplicate field names:
|
96
|
+
# require "google/cloud/spanner"
|
97
|
+
#
|
98
|
+
# dup_type = Google::Cloud::Spanner::Fields.new(
|
99
|
+
# [[:x, :INT64], [:x, :STRING], [:x, :BOOL]]
|
100
|
+
# )
|
101
|
+
# dup_data = dup_type.struct [42, nil, false]
|
102
|
+
#
|
45
103
|
def initialize types
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@fields << field(type)
|
50
|
-
end
|
51
|
-
elsif types.is_a? Hash
|
52
|
-
types.each do |type|
|
53
|
-
@fields << field(type)
|
54
|
-
end
|
55
|
-
else
|
104
|
+
types = types.to_a if types.is_a? Hash
|
105
|
+
|
106
|
+
unless types.is_a? Array
|
56
107
|
raise ArgumentError, "can only accept Array or Hash"
|
57
108
|
end
|
109
|
+
|
110
|
+
sorted_types, unsorted_types = types.partition do |type|
|
111
|
+
type.is_a?(Array) && type.count == 2 && type.first.is_a?(Integer)
|
112
|
+
end
|
113
|
+
|
114
|
+
verify_sorted_types! sorted_types, types.count
|
115
|
+
|
116
|
+
@grpc_fields = Array.new(types.count) do |index|
|
117
|
+
sorted_type = sorted_types.assoc index
|
118
|
+
if sorted_type
|
119
|
+
to_grpc_field sorted_type.last
|
120
|
+
else
|
121
|
+
to_grpc_field unsorted_types.shift
|
122
|
+
end
|
123
|
+
end
|
58
124
|
end
|
59
125
|
|
60
126
|
##
|
@@ -66,7 +132,7 @@ module Google
|
|
66
132
|
# @return [Array<Symbol>] An array containing the types of the data.
|
67
133
|
#
|
68
134
|
def types
|
69
|
-
@
|
135
|
+
@grpc_fields.map(&:type).map do |type|
|
70
136
|
if type.code == :ARRAY
|
71
137
|
if type.array_element_type.code == :STRUCT
|
72
138
|
[Fields.from_grpc(type.array_element_type.struct_type.fields)]
|
@@ -90,7 +156,7 @@ module Google
|
|
90
156
|
# data.
|
91
157
|
#
|
92
158
|
def keys
|
93
|
-
@
|
159
|
+
@grpc_fields.map.with_index do |field, index|
|
94
160
|
if field.name.empty?
|
95
161
|
index
|
96
162
|
else
|
@@ -134,69 +200,157 @@ module Google
|
|
134
200
|
#
|
135
201
|
def [] key
|
136
202
|
return types[key] if key.is_a? Integer
|
137
|
-
name_count = @
|
203
|
+
name_count = @grpc_fields.find_all { |f| f.name == String(key) }.count
|
138
204
|
return nil if name_count.zero?
|
139
205
|
raise DuplicateNameError if name_count > 1
|
140
|
-
index = @
|
206
|
+
index = @grpc_fields.find_index { |f| f.name == String(key) }
|
141
207
|
types[index]
|
142
208
|
end
|
143
209
|
|
210
|
+
# rubocop:disable all
|
211
|
+
|
144
212
|
##
|
145
|
-
#
|
146
|
-
#
|
213
|
+
# Creates a new {Data} object given the data values matching the fields.
|
214
|
+
# Can be provided as either an Array of values, or a Hash where the hash
|
215
|
+
# keys match the field name or match the index position of the field.
|
147
216
|
#
|
148
|
-
#
|
149
|
-
#
|
217
|
+
# For more information, see [Data Types - Constructing a
|
218
|
+
# STRUCT](https://cloud.google.com/spanner/docs/data-types#constructing-a-struct).
|
150
219
|
#
|
151
|
-
# @
|
220
|
+
# @param [Array, Hash] data Accepts an array or hash data values.
|
152
221
|
#
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
222
|
+
# Arrays can contain just the data value, nested arrays will be
|
223
|
+
# treated as lists of values. Values must be provided in the same
|
224
|
+
# order as the fields, and there is no way to associate values to the
|
225
|
+
# field names.
|
226
|
+
#
|
227
|
+
# Hash keys must contain the field name as a `Symbol` or `String`, or
|
228
|
+
# the field position as an `Integer`. Hash values must contain the
|
229
|
+
# data value. Hash values will be matched to the fields, so they don't
|
230
|
+
# need to match the same order as the fields.
|
231
|
+
#
|
232
|
+
# @return [Data] A new Data object.
|
233
|
+
#
|
234
|
+
# @example Create a STRUCT value with named fields using Fields object:
|
235
|
+
# require "google/cloud/spanner"
|
236
|
+
#
|
237
|
+
# spanner = Google::Cloud::Spanner.new
|
238
|
+
#
|
239
|
+
# db = spanner.client "my-instance", "my-database"
|
240
|
+
#
|
241
|
+
# named_type = db.fields(
|
242
|
+
# { id: :INT64, name: :STRING, active: :BOOL }
|
243
|
+
# )
|
244
|
+
# named_data = named_type.struct(
|
245
|
+
# { id: 42, name: nil, active: false }
|
246
|
+
# )
|
247
|
+
#
|
248
|
+
# @example Create a STRUCT value with anonymous field names:
|
249
|
+
# require "google/cloud/spanner"
|
250
|
+
#
|
251
|
+
# spanner = Google::Cloud::Spanner.new
|
252
|
+
#
|
253
|
+
# db = spanner.client "my-instance", "my-database"
|
254
|
+
#
|
255
|
+
# anon_type = db.fields [:INT64, :STRING, :BOOL]
|
256
|
+
# anon_data = anon_type.struct [42, nil, false]
|
257
|
+
#
|
258
|
+
# @example Create a STRUCT value with duplicate field names:
|
259
|
+
# require "google/cloud/spanner"
|
260
|
+
#
|
261
|
+
# spanner = Google::Cloud::Spanner.new
|
262
|
+
#
|
263
|
+
# db = spanner.client "my-instance", "my-database"
|
264
|
+
#
|
265
|
+
# dup_type = db.fields [[:x, :INT64], [:x, :STRING], [:x, :BOOL]]
|
266
|
+
# dup_data = dup_type.struct [42, nil, false]
|
267
|
+
#
|
268
|
+
def struct data
|
269
|
+
# create local copy of types so they are parsed just once.
|
270
|
+
cached_types = types
|
271
|
+
if data.nil?
|
272
|
+
return Data.from_grpc nil, @grpc_fields
|
273
|
+
elsif data.is_a? Array
|
274
|
+
# Convert data in the order it was recieved
|
275
|
+
values = data.map.with_index do |datum, index|
|
276
|
+
Convert.object_to_grpc_value_and_type(datum, cached_types[index]).first
|
162
277
|
end
|
278
|
+
return Data.from_grpc values, @grpc_fields
|
279
|
+
elsif data.is_a? Hash
|
280
|
+
# Pull values from hash in order of the fields,
|
281
|
+
# we can't always trust the Hash to be in order.
|
282
|
+
values = @grpc_fields.map.with_index do |field, index|
|
283
|
+
if data.key? index
|
284
|
+
Convert.object_to_grpc_value_and_type(data[index],
|
285
|
+
cached_types[index]).first
|
286
|
+
elsif !field.name.to_s.empty?
|
287
|
+
if data.key? field.name.to_s
|
288
|
+
Convert.object_to_grpc_value_and_type(data[field.name.to_s],
|
289
|
+
cached_types[index]).first
|
290
|
+
elsif data.key? field.name.to_s.to_sym
|
291
|
+
Convert.object_to_grpc_value_and_type(data[field.name.to_s.to_sym],
|
292
|
+
cached_types[index]).first
|
293
|
+
else
|
294
|
+
raise "data value for field #{field.name} missing"
|
295
|
+
end
|
296
|
+
else
|
297
|
+
raise "data value for field #{index} missing"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
return Data.from_grpc values, @grpc_fields
|
163
301
|
end
|
302
|
+
raise ArgumentError, "can only accept Array or Hash"
|
303
|
+
end
|
304
|
+
alias data struct
|
305
|
+
alias new struct
|
306
|
+
|
307
|
+
# rubocop:enable all
|
308
|
+
|
309
|
+
##
|
310
|
+
# Returns the type codes as an array. Do not use this method if the data
|
311
|
+
# has more than one member with the same name. (See {#duplicate_names?})
|
312
|
+
#
|
313
|
+
# @return [Array<Symbol|Array<Symbol>|Fields|Array<Fields>>] An array
|
314
|
+
# containing the type codes.
|
315
|
+
#
|
316
|
+
def to_a
|
317
|
+
types
|
164
318
|
end
|
165
319
|
|
166
320
|
##
|
167
321
|
# Returns the names or indexes and corresponding type codes as a hash.
|
168
322
|
#
|
169
|
-
# @
|
170
|
-
#
|
323
|
+
# @raise [Google::Cloud::Spanner::DuplicateNameError] if the data
|
324
|
+
# contains duplicate names.
|
325
|
+
#
|
326
|
+
# @return [Hash<(Symbol|Integer) =>
|
327
|
+
# (Symbol|Array<Symbol>|Fields|Array<Fields>)] A hash containing the
|
328
|
+
# names or indexes and corresponding types.
|
171
329
|
#
|
172
330
|
def to_h
|
173
331
|
raise DuplicateNameError if duplicate_names?
|
174
|
-
|
175
|
-
if value.is_a? Fields
|
176
|
-
[key, value.to_h]
|
177
|
-
elsif value.is_a? Array
|
178
|
-
[key, value.map { |v| v.is_a?(Fields) ? v.to_h : v }]
|
179
|
-
else
|
180
|
-
[key, value]
|
181
|
-
end
|
182
|
-
end
|
183
|
-
Hash[hashified_pairs]
|
332
|
+
Hash[pairs]
|
184
333
|
end
|
185
334
|
|
186
335
|
# @private
|
187
|
-
def
|
188
|
-
|
189
|
-
data = data.values if data.is_a?(Hash)
|
190
|
-
values = data.map { |datum| Convert.raw_to_value datum }
|
191
|
-
Data.from_grpc values, @fields
|
336
|
+
def count
|
337
|
+
@grpc_fields.count
|
192
338
|
end
|
193
|
-
alias
|
339
|
+
alias size count
|
194
340
|
|
195
341
|
# @private
|
196
342
|
def == other
|
197
343
|
return false unless other.is_a? Fields
|
198
344
|
pairs == other.pairs
|
199
345
|
end
|
346
|
+
alias eql? ==
|
347
|
+
|
348
|
+
# @private
|
349
|
+
def hash
|
350
|
+
# The Protobuf object looks to maintain consistent hash values
|
351
|
+
# for objects with the same configuration.
|
352
|
+
to_grpc_type.hash
|
353
|
+
end
|
200
354
|
|
201
355
|
# @private
|
202
356
|
def to_s
|
@@ -215,40 +369,78 @@ module Google
|
|
215
369
|
"#<#{self.class.name} #{self}>"
|
216
370
|
end
|
217
371
|
|
372
|
+
##
|
373
|
+
# @private
|
374
|
+
def to_grpc_type
|
375
|
+
Google::Spanner::V1::Type.new(
|
376
|
+
code: :STRUCT,
|
377
|
+
struct_type: Google::Spanner::V1::StructType.new(
|
378
|
+
fields: @grpc_fields
|
379
|
+
)
|
380
|
+
)
|
381
|
+
end
|
382
|
+
|
218
383
|
##
|
219
384
|
# @private Creates a new Fields instance from a
|
220
385
|
# Google::Spanner::V1::Metadata::Row_type::Fields.
|
221
386
|
def self.from_grpc fields
|
222
387
|
new([]).tap do |f|
|
223
|
-
f.instance_variable_set :@
|
388
|
+
f.instance_variable_set :@grpc_fields, Array(fields)
|
224
389
|
end
|
225
390
|
end
|
226
391
|
|
227
392
|
protected
|
228
393
|
|
229
|
-
|
394
|
+
# rubocop:disable all
|
395
|
+
|
396
|
+
def verify_sorted_types! sorted_types, total_count
|
397
|
+
sorted_unique_positions = sorted_types.map(&:first).uniq.sort
|
398
|
+
return if sorted_unique_positions.empty?
|
399
|
+
|
400
|
+
if sorted_unique_positions.first < 0
|
401
|
+
raise ArgumentError, "cannot specify position less than 0"
|
402
|
+
end
|
403
|
+
if sorted_unique_positions.last >= total_count
|
404
|
+
raise ArgumentError, "cannot specify position more than field count"
|
405
|
+
end
|
406
|
+
if sorted_types.count != sorted_unique_positions.count
|
407
|
+
raise ArgumentError, "cannot specify position more than once"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def to_grpc_field pair
|
230
412
|
if pair.is_a?(Array)
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
413
|
+
if pair.count == 2
|
414
|
+
if pair.first.is_a?(Integer)
|
415
|
+
Google::Spanner::V1::StructType::Field.new(
|
416
|
+
type: Convert.grpc_type_for_field(pair.last)
|
417
|
+
)
|
418
|
+
else
|
419
|
+
Google::Spanner::V1::StructType::Field.new(
|
420
|
+
name: String(pair.first),
|
421
|
+
type: Convert.grpc_type_for_field(pair.last)
|
422
|
+
)
|
423
|
+
end
|
238
424
|
else
|
239
425
|
Google::Spanner::V1::StructType::Field.new(
|
240
|
-
|
241
|
-
|
426
|
+
type: Google::Spanner::V1::Type.new(
|
427
|
+
code: :ARRAY,
|
428
|
+
array_element_type: Convert.grpc_type_for_field(pair.last)
|
429
|
+
)
|
242
430
|
)
|
243
431
|
end
|
244
432
|
else
|
433
|
+
# TODO: Handle Fields object
|
434
|
+
# TODO: Handle Hash by creating Fields object
|
245
435
|
unless pair.is_a?(Symbol)
|
246
436
|
raise ArgumentError, "type must be a symbol"
|
247
437
|
end
|
248
438
|
Google::Spanner::V1::StructType::Field.new(
|
249
|
-
type:
|
439
|
+
type: Convert.grpc_type_for_field(pair)
|
250
440
|
)
|
251
441
|
end
|
442
|
+
|
443
|
+
# rubocop:enable all
|
252
444
|
end
|
253
445
|
end
|
254
446
|
end
|