Skip to content

Commit 908adbc

Browse files
authored
feat: Support :UUID data type (#139)
1 parent d8bd5f1 commit 908adbc

13 files changed

Lines changed: 307 additions & 1 deletion

File tree

acceptance/data/fixtures.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
require "securerandom"
1415

1516
module Acceptance
1617
module Fixtures
@@ -49,6 +50,7 @@ def stuffs_ddl_statement
4950
byte BYTES(MAX),
5051
date DATE,
5152
timestamp TIMESTAMP OPTIONS (allow_commit_timestamp=true),
53+
uuid UUID,
5254
numeric NUMERIC,
5355
json JSON,
5456
ints ARRAY<INT64>,
@@ -59,6 +61,7 @@ def stuffs_ddl_statement
5961
bytes ARRAY<BYTES(MAX)>,
6062
dates ARRAY<DATE>,
6163
timestamps ARRAY<TIMESTAMP>,
64+
uuids ARRAY<UUID>,
6265
numerics ARRAY<NUMERIC>,
6366
json_array ARRAY<JSON>
6467
) PRIMARY KEY (id)
@@ -236,6 +239,7 @@ def stuffs_table_types
236239
byte: :BYTES,
237240
date: :DATE,
238241
timestamp: :TIMESTAMP,
242+
uuid: :UUID,
239243
json: :JSON,
240244
ints: [:INT64],
241245
floats: [:FLOAT64],
@@ -258,6 +262,7 @@ def stuffs_random_row id = SecureRandom.int64
258262
byte: File.open("acceptance/data/face.jpg", "rb"),
259263
date: Date.today + rand(-100..100),
260264
timestamp: Time.now + rand((-60 * 60 * 24.0)..(60 * 60 * 24.0)),
265+
uuid: SecureRandom.uuid,
261266
json: { venue: "Yellow Lake", rating: 10 },
262267
ints: rand(2..10).times.map { rand(0..1000) },
263268
floats: rand(2..10).times.map { rand(0.0..100.0) },
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
require "spanner_helper"
16+
17+
describe "Spanner Client", :params, :uuid, :spanner do
18+
let(:db) { spanner_client }
19+
20+
it "queries and returns an :UUID parameter" do
21+
skip if emulator_enabled?
22+
23+
uuid = SecureRandom.uuid
24+
results = db.execute_query "SELECT @value AS value", params: { value: uuid }, types: { value: :UUID }
25+
26+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
27+
_(results.fields[:value]).must_equal :UUID
28+
_(results.rows.first[:value]).must_equal uuid
29+
end
30+
31+
it "queries and returns a NULL :UUID parameter" do
32+
skip if emulator_enabled?
33+
34+
results = db.execute_query "SELECT @value AS value", params: { value: nil }, types: { value: :UUID }
35+
36+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
37+
_(results.fields[:value]).must_equal :UUID
38+
_(results.rows.first[:value]).must_be :nil?
39+
end
40+
41+
it "queries and returns an array of :UUID parameters" do
42+
skip if emulator_enabled?
43+
44+
uuids = [SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid]
45+
results = db.execute_query "SELECT @value AS value", params: { value: uuids }, types: { value: [:UUID] }
46+
47+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
48+
_(results.fields[:value]).must_equal [:UUID]
49+
_(results.rows.first[:value]).must_equal uuids
50+
end
51+
52+
it "queries and returns an array of :UUID parameters with a nil value" do
53+
skip if emulator_enabled?
54+
55+
uuids = [nil, SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid]
56+
results = db.execute_query "SELECT @value AS value", params: { value: uuids }, types: { value: [:UUID] }
57+
58+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
59+
_(results.fields[:value]).must_equal [:UUID]
60+
_(results.rows.first[:value]).must_equal uuids
61+
end
62+
63+
it "queries and returns an empty array of :UUID parameters" do
64+
skip if emulator_enabled?
65+
66+
results = db.execute_query "SELECT @value AS value", params: { value: [] }, types: { value: [:UUID] }
67+
68+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
69+
_(results.fields[:value]).must_equal [:UUID]
70+
_(results.rows.first[:value]).must_equal []
71+
end
72+
73+
it "queries and returns a NULL array of :UUID parameters" do
74+
skip if emulator_enabled?
75+
76+
results = db.execute_query "SELECT @value AS value", params: { value: nil }, types: { value: [:UUID] }
77+
78+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
79+
_(results.fields[:value]).must_equal [:UUID]
80+
_(results.rows.first[:value]).must_be :nil?
81+
end
82+
end
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
require "spanner_helper"
16+
17+
describe "Spanner Client", :types, :uuid, :spanner do
18+
let(:db) { spanner_client }
19+
let(:table_name) { "stuffs" }
20+
21+
it "writes and reads :UUID" do
22+
skip if emulator_enabled?
23+
24+
id = SecureRandom.int64
25+
uuid = SecureRandom.uuid
26+
db.upsert table_name, { id: id, uuid: uuid }
27+
results = db.read table_name, [:id, :uuid], keys: id
28+
29+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
30+
_(results.fields.to_h).must_equal({ id: :INT64, uuid: :UUID })
31+
_(results.rows.first.to_h).must_equal({ id: id, uuid: uuid })
32+
end
33+
34+
it "writes and queries :UUID" do
35+
skip if emulator_enabled?
36+
37+
id = SecureRandom.int64
38+
uuid = SecureRandom.uuid
39+
db.upsert table_name, { id: id, uuid: uuid }
40+
results = db.execute_query "SELECT id, uuid FROM #{table_name} WHERE id = @id", params: { id: id }
41+
42+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
43+
_(results.fields.to_h).must_equal({ id: :INT64, uuid: :UUID })
44+
_(results.rows.first.to_h).must_equal({ id: id, uuid: uuid })
45+
end
46+
47+
it "writes and reads NULL :UUID" do
48+
skip if emulator_enabled?
49+
50+
id = SecureRandom.int64
51+
db.upsert table_name, { id: id, uuid: nil }
52+
results = db.read table_name, [:id, :uuid], keys: id
53+
54+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
55+
_(results.fields.to_h).must_equal({ id: :INT64, uuid: :UUID })
56+
_(results.rows.first.to_h).must_equal({ id: id, uuid: nil })
57+
end
58+
59+
it "writes and queries NULL :UUID" do
60+
skip if emulator_enabled?
61+
62+
id = SecureRandom.int64
63+
db.upsert table_name, { id: id, uuid: nil }
64+
results = db.execute_query "SELECT id, uuid FROM #{table_name} WHERE id = @id", params: { id: id }
65+
66+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
67+
_(results.fields.to_h).must_equal({ id: :INT64, uuid: :UUID })
68+
_(results.rows.first.to_h).must_equal({ id: id, uuid: nil })
69+
end
70+
71+
it "writes and reads array of :UUID" do
72+
skip if emulator_enabled?
73+
74+
id = SecureRandom.int64
75+
uuids = [SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid]
76+
db.upsert table_name, { id: id, uuids: uuids }
77+
results = db.read table_name, [:id, :uuids], keys: id
78+
79+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
80+
_(results.fields.to_h).must_equal({ id: :INT64, uuids: [:UUID] })
81+
_(results.rows.first.to_h).must_equal({ id: id, uuids: [uuids[0], uuids[1], uuids[2]] })
82+
end
83+
84+
it "writes and queries array of :UUID" do
85+
skip if emulator_enabled?
86+
87+
id = SecureRandom.int64
88+
uuids = [nil, SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid]
89+
db.upsert table_name, { id: id, uuids: uuids }
90+
results = db.execute_query "SELECT id, uuids FROM #{table_name} WHERE id = @id", params: { id: id }
91+
92+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
93+
_(results.fields.to_h).must_equal({ id: :INT64, uuids: [:UUID] })
94+
_(results.rows.first.to_h).must_equal({ id: id, uuids: [uuids[0], uuids[1], uuids[2], uuids[3]] })
95+
end
96+
97+
it "writes and reads array of :UUID with NULL" do
98+
skip if emulator_enabled?
99+
100+
id = SecureRandom.int64
101+
uuids = [nil, SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid]
102+
db.upsert table_name, { id: id, uuids: uuids }
103+
results = db.read table_name, [:id, :uuids], keys: id
104+
105+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
106+
_(results.fields.to_h).must_equal({ id: :INT64, uuids: [:UUID] })
107+
_(results.rows.first.to_h).must_equal({ id: id, uuids: [uuids[0], uuids[1], uuids[2], uuids[3]] })
108+
end
109+
110+
it "writes and queries array of :UUID with NULL" do
111+
skip if emulator_enabled?
112+
113+
id = SecureRandom.int64
114+
uuids = [nil, SecureRandom.uuid, SecureRandom.uuid, SecureRandom.uuid]
115+
db.upsert table_name, { id: id, uuids: uuids }
116+
results = db.execute_query "SELECT id, uuids FROM #{table_name} WHERE id = @id", params: { id: id }
117+
118+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
119+
_(results.fields.to_h).must_equal({ id: :INT64, uuids: [:UUID] })
120+
_(results.rows.first.to_h).must_equal({ id: id, uuids: [uuids[0], uuids[1], uuids[2], uuids[3]] })
121+
end
122+
123+
it "writes and reads empty array of :UUID" do
124+
skip if emulator_enabled?
125+
126+
id = SecureRandom.int64
127+
db.upsert table_name, { id: id, uuids: [] }
128+
results = db.read table_name, [:id, :uuids], keys: id
129+
130+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
131+
_(results.fields.to_h).must_equal({ id: :INT64, uuids: [:UUID] })
132+
_(results.rows.first.to_h).must_equal({ id: id, uuids: [] })
133+
end
134+
135+
it "writes and queries empty array of :UUID" do
136+
skip if emulator_enabled?
137+
138+
id = SecureRandom.int64
139+
db.upsert table_name, { id: id, uuids: [] }
140+
results = db.execute_query "SELECT id, uuids FROM #{table_name} WHERE id = @id", params: { id: id }
141+
142+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
143+
_(results.fields.to_h).must_equal({ id: :INT64, uuids: [:UUID] })
144+
_(results.rows.first.to_h).must_equal({ id: id, uuids: [] })
145+
end
146+
147+
it "writes and reads NULL array of :UUID" do
148+
skip if emulator_enabled?
149+
150+
id = SecureRandom.int64
151+
db.upsert table_name, { id: id, uuids: nil }
152+
results = db.read table_name, [:id, :uuids], keys: id
153+
154+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
155+
_(results.fields.to_h).must_equal({ id: :INT64, uuids: [:UUID] })
156+
_(results.rows.first.to_h).must_equal({ id: id, uuids: nil })
157+
end
158+
159+
it "writes and queries NULL array of :UUID" do
160+
skip if emulator_enabled?
161+
162+
id = SecureRandom.int64
163+
db.upsert table_name, { id: id, uuids: nil }
164+
results = db.execute_query "SELECT id, uuids FROM #{table_name} WHERE id = @id", params: { id: id }
165+
166+
_(results).must_be_kind_of Google::Cloud::Spanner::Results
167+
_(results.fields.to_h).must_equal({ id: :INT64, uuids: [:UUID] })
168+
_(results.rows.first.to_h).must_equal({ id: id, uuids: nil })
169+
end
170+
end

google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ def load_partition serialized_partition
334334
# * `:INT64`
335335
# * `:STRING`
336336
# * `:TIMESTAMP`
337+
# * `:UUID`
337338
# * `Array` - Lists are specified by providing the type code in an
338339
# array. For example, an array of integers are specified as
339340
# `[:INT64]`.

google-cloud-spanner/lib/google/cloud/spanner/batch_snapshot.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def timestamp
138138
# | `FLOAT64` | `Float` | |
139139
# | `FLOAT32` | `Float` | |
140140
# | `STRING` | `String` | |
141+
# | `UUID` | `String` | |
141142
# | `DATE` | `Date` | |
142143
# | `TIMESTAMP` | `Time`, `DateTime` | |
143144
# | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
@@ -166,6 +167,7 @@ def timestamp
166167
# * `:INT64`
167168
# * `:STRING`
168169
# * `:TIMESTAMP`
170+
# * `:UUID`
169171
# * `Array` - Lists are specified by providing the type code in an
170172
# array. For example, an array of integers are specified as
171173
# `[:INT64]`.
@@ -489,6 +491,7 @@ def close
489491
# | `FLOAT64` | `Float` | |
490492
# | `FLOAT32` | `Float` | |
491493
# | `STRING` | `String` | |
494+
# | `UUID` | `String` | |
492495
# | `DATE` | `Date` | |
493496
# | `TIMESTAMP` | `Time`, `DateTime` | |
494497
# | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
@@ -517,6 +520,7 @@ def close
517520
# * `:INT64`
518521
# * `:STRING`
519522
# * `:TIMESTAMP`
523+
# * `:UUID`
520524
# * `Array` - Lists are specified by providing the type code in an
521525
# array. For example, an array of integers are specified as
522526
# `[:INT64]`.

google-cloud-spanner/lib/google/cloud/spanner/client.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ def directed_read_options
180180
# | `FLOAT32` | `Float` | |
181181
# | `NUMERIC` | `BigDecimal` | |
182182
# | `STRING` | `String` | |
183+
# | `UUID` | `String` | |
183184
# | `DATE` | `Date` | |
184185
# | `TIMESTAMP` | `Time`, `DateTime` | |
185186
# | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
@@ -209,6 +210,7 @@ def directed_read_options
209210
# * `:INT64`
210211
# * `:STRING`
211212
# * `:TIMESTAMP`
213+
# * `:UUID`
212214
# * `Array` - Lists are specified by providing the type code in an
213215
# array. For example, an array of integers are specified as
214216
# `[:INT64]`.
@@ -618,6 +620,7 @@ def execute_query sql, params: nil, types: nil, single_use: nil,
618620
# | `FLOAT32` | `Float` | |
619621
# | `NUMERIC` | `BigDecimal` | |
620622
# | `STRING` | `String` | |
623+
# | `UUID` | `String` | |
621624
# | `DATE` | `Date` | |
622625
# | `TIMESTAMP` | `Time`, `DateTime` | |
623626
# | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
@@ -648,6 +651,7 @@ def execute_query sql, params: nil, types: nil, single_use: nil,
648651
# * `:INT64`
649652
# * `:STRING`
650653
# * `:TIMESTAMP`
654+
# * `:UUID`
651655
# * `Array` - Lists are specified by providing the type code in an
652656
# array. For example, an array of integers are specified as
653657
# `[:INT64]`.

google-cloud-spanner/lib/google/cloud/spanner/convert.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ def grpc_value_to_object value, type
261261
descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(type.proto_type_fqn).msgclass
262262
content = Base64.decode64 value.string_value
263263
descriptor.decode content
264+
when :UUID
265+
value.string_value
264266
end
265267
end
266268

google-cloud-spanner/lib/google/cloud/spanner/fields.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class Fields
6868
# * `:INT64`
6969
# * `:STRING`
7070
# * `:TIMESTAMP`
71+
# * `:UUID`
7172
# * `:PROTO`
7273
# * `Array` - Lists are specified by providing the type code in an
7374
# array. For example, an array of integers are specified as

0 commit comments

Comments
 (0)