From f206247c8fc82946d0ed63b76486104e774aa297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 9 Feb 2026 14:01:17 +0100 Subject: [PATCH] feat: add support for the UUID data type Adds support for the new Spanner data type `UUID`. --- acceptance/cases/tasks/database_tasks_test.rb | 8 ++ acceptance/cases/type/all_types_test.rb | 19 ++- acceptance/schema/schema.rb | 2 + .../spanner/type_mapping.rb | 3 +- .../connection_adapters/spanner_adapter.rb | 3 + .../spanner_active_record_converter.rb | 1 + lib/active_record/type/spanner/uuid.rb | 19 +++ .../model_helper.rb | 40 +++++-- ...ner_active_record_with_mock_server_test.rb | 108 ++++++++++++------ .../03_create_all_native_migration_types.rb | 2 + ...10_create_parent_and_child_with_uuid_pk.rb | 5 +- .../migrations_with_mock_server_test.rb | 8 +- 12 files changed, 166 insertions(+), 52 deletions(-) create mode 100644 lib/active_record/type/spanner/uuid.rb diff --git a/acceptance/cases/tasks/database_tasks_test.rb b/acceptance/cases/tasks/database_tasks_test.rb index 87cbf60b..f7bb7c90 100644 --- a/acceptance/cases/tasks/database_tasks_test.rb +++ b/acceptance/cases/tasks/database_tasks_test.rb @@ -129,6 +129,7 @@ def expected_schema_sql_on_emulator col_date DATE, col_timestamp TIMESTAMP, col_json JSON, + col_uuid UUID, col_array_string ARRAY, col_array_int64 ARRAY, col_array_float64 ARRAY, @@ -138,6 +139,7 @@ def expected_schema_sql_on_emulator col_array_date ARRAY, col_array_timestamp ARRAY, col_array_json ARRAY, + col_array_uuid ARRAY, ) PRIMARY KEY(id); CREATE TABLE firms ( id INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE), @@ -287,6 +289,7 @@ def expected_schema_sql_on_emulator_7_1 col_date DATE, col_timestamp TIMESTAMP, col_json JSON, + col_uuid UUID, col_array_string ARRAY, col_array_int64 ARRAY, col_array_float64 ARRAY, @@ -296,6 +299,7 @@ def expected_schema_sql_on_emulator_7_1 col_array_date ARRAY, col_array_timestamp ARRAY, col_array_json ARRAY, + col_array_uuid ARRAY, ) PRIMARY KEY(id); CREATE TABLE firms ( id INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE), @@ -463,6 +467,7 @@ def expected_schema_sql_on_production col_date DATE, col_timestamp TIMESTAMP, col_json JSON, + col_uuid UUID, col_array_string ARRAY, col_array_int64 ARRAY, col_array_float64 ARRAY, @@ -472,6 +477,7 @@ def expected_schema_sql_on_production col_array_date ARRAY, col_array_timestamp ARRAY, col_array_json ARRAY, + col_array_uuid ARRAY, ) PRIMARY KEY(id); CREATE TABLE ar_internal_metadata ( key STRING(MAX) NOT NULL, @@ -624,6 +630,7 @@ def expected_schema_sql_on_production_7_1 col_date DATE, col_timestamp TIMESTAMP, col_json JSON, + col_uuid UUID, col_array_string ARRAY, col_array_int64 ARRAY, col_array_float64 ARRAY, @@ -633,6 +640,7 @@ def expected_schema_sql_on_production_7_1 col_array_date ARRAY, col_array_timestamp ARRAY, col_array_json ARRAY, + col_array_uuid ARRAY, ) PRIMARY KEY(id); CREATE TABLE ar_internal_metadata ( key STRING(MAX) NOT NULL, diff --git a/acceptance/cases/type/all_types_test.rb b/acceptance/cases/type/all_types_test.rb index fc0d3942..bd25b049 100644 --- a/acceptance/cases/type/all_types_test.rb +++ b/acceptance/cases/type/all_types_test.rb @@ -30,7 +30,8 @@ def create_test_record AllTypes.create col_string: "string", col_int64: 100, col_float64: 3.14, col_numeric: 6.626, col_bool: true, col_bytes: StringIO.new("bytes"), col_date: ::Date.new(2021, 6, 23), col_timestamp: ::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"), - col_json: { kind: "user_renamed", change: %w[jack john]}, + col_json: { kind: "user_renamed", change: %w[jack john] }, + col_uuid: "3840ba25-55df-4cb7-a210-1fe37278954f", col_array_string: ["string1", nil, "string2"], col_array_int64: [100, nil, 200, "300"], col_array_float64: [3.14, nil, 2.0/3.0, "3.14"], @@ -42,7 +43,8 @@ def create_test_record ::Time.new(2021, 6, 24, 17, 8, 21, "+02:00"), "2021-06-25 17:08:21 +02:00"], col_array_json: [{ kind: "user_renamed", change: %w[jack john]}, nil, \ { kind: "user_renamed", change: %w[alice meredith]}, - "{\"kind\":\"user_renamed\",\"change\":[\"bob\",\"carol\"]}"] + "{\"kind\":\"user_renamed\",\"change\":[\"bob\",\"carol\"]}"], + col_array_uuid: ["e986c19c-9bf3-44f8-851d-710ad3d0067b", nil, "a02f50b6-2860-48dc-acd6-49b42abc3094"] end def test_create_record @@ -67,6 +69,7 @@ def test_create_record assert_equal ::Time.new(2021, 6, 23, 17, 8, 21, "+02:00").utc, record.col_timestamp.utc assert_equal ({"kind" => "user_renamed", "change" => %w[jack john]}), record.col_json + assert_equal "3840ba25-55df-4cb7-a210-1fe37278954f", record.col_uuid assert_equal ["string1", nil, "string2"], record.col_array_string assert_equal [100, nil, 200, 300], record.col_array_int64 @@ -85,6 +88,7 @@ def test_create_record {"kind" => "user_renamed", "change" => %w[alice meredith]}, \ "{\"kind\":\"user_renamed\",\"change\":[\"bob\",\"carol\"]}"], record.col_array_json + assert_equal ["e986c19c-9bf3-44f8-851d-710ad3d0067b", nil, "a02f50b6-2860-48dc-acd6-49b42abc3094"], record.col_array_uuid end end @@ -100,6 +104,7 @@ def test_update_record col_date: ::Date.new(2021, 6, 28), col_timestamp: ::Time.new(2021, 6, 28, 11, 22, 21, "+02:00"), col_json: { kind: "user_created", change: %w[jack alice]}, + col_uuid: "b493579f-f4f6-41f6-a520-8373ecf1cde4", col_array_string: ["new string 1", "new string 2"], col_array_int64: [300, 200, 100], col_array_float64: [1.1, 2.2, 3.3], @@ -108,7 +113,8 @@ def test_update_record col_array_bytes: [StringIO.new("new bytes 1"), StringIO.new("new bytes 2")], col_array_date: [::Date.new(2021, 6, 28)], col_array_timestamp: [::Time.utc(2020, 12, 31, 0, 0, 0)], - col_array_json: [{ kind: "user_created", change: %w[jack alice]}] + col_array_json: [{ kind: "user_created", change: %w[jack alice]}], + col_array_uuid: ["a664b1eb-4dc7-4795-9ed8-28fab6644cea", nil, "1c794d81-bc6d-4771-9c92-a920d28a7aaa"] end # Verify that the record was updated. @@ -123,6 +129,7 @@ def test_update_record assert_equal ::Time.new(2021, 6, 28, 11, 22, 21, "+02:00").utc, record.col_timestamp.utc assert_equal ({"kind" => "user_created", "change" => %w[jack alice]}), record.col_json + assert_equal "b493579f-f4f6-41f6-a520-8373ecf1cde4", record.col_uuid assert_equal ["new string 1", "new string 2"], record.col_array_string assert_equal [300, 200, 100], record.col_array_int64 @@ -135,6 +142,8 @@ def test_update_record assert_equal [::Time.utc(2020, 12, 31, 0, 0, 0)], record.col_array_timestamp.map(&:utc) assert_equal [{"kind" => "user_created", "change" => %w[jack alice]}], record.col_array_json + assert_equal ["a664b1eb-4dc7-4795-9ed8-28fab6644cea", nil, "1c794d81-bc6d-4771-9c92-a920d28a7aaa"], + record.col_array_uuid end end @@ -151,7 +160,8 @@ def test_create_empty_arrays col_array_bytes: [], col_array_date: [], col_array_timestamp: [], - col_array_json: [] + col_array_json: [], + col_array_uuid: [] end record = AllTypes.find record.id @@ -164,6 +174,7 @@ def test_create_empty_arrays assert_equal [], record.col_array_date assert_equal [], record.col_array_timestamp assert_equal [], record.col_array_json + assert_equal [], record.col_array_uuid end end end diff --git a/acceptance/schema/schema.rb b/acceptance/schema/schema.rb index ec20a8f6..01e9f868 100644 --- a/acceptance/schema/schema.rb +++ b/acceptance/schema/schema.rb @@ -24,6 +24,7 @@ def create_tables_in_test_schema t.column :col_date, :date t.column :col_timestamp, :datetime t.column :col_json, :json + t.column :col_uuid, :uuid t.column :col_array_string, :string, array: true t.column :col_array_int64, :bigint, array: true @@ -34,6 +35,7 @@ def create_tables_in_test_schema t.column :col_array_date, :date, array: true t.column :col_array_timestamp, :datetime, array: true t.column :col_array_json, :json, array: true + t.column :col_array_uuid, :uuid, array: true end create_table :firms do |t| diff --git a/lib/active_record/connection_adapters/spanner/type_mapping.rb b/lib/active_record/connection_adapters/spanner/type_mapping.rb index e10f5035..f861ad4a 100644 --- a/lib/active_record/connection_adapters/spanner/type_mapping.rb +++ b/lib/active_record/connection_adapters/spanner/type_mapping.rb @@ -21,5 +21,6 @@ date: { name: "DATE" }, binary: { name: "BYTES", limit: "MAX" }, boolean: { name: "BOOL" }, - json: { name: "JSON" } + json: { name: "JSON" }, + uuid: { name: "UUID" } }.freeze diff --git a/lib/active_record/connection_adapters/spanner_adapter.rb b/lib/active_record/connection_adapters/spanner_adapter.rb index 2d91c5b5..a03f30ad 100644 --- a/lib/active_record/connection_adapters/spanner_adapter.rb +++ b/lib/active_record/connection_adapters/spanner_adapter.rb @@ -20,6 +20,7 @@ require "active_record/type/spanner/bytes" require "active_record/type/spanner/spanner_active_record_converter" require "active_record/type/spanner/time" +require "active_record/type/spanner/uuid" require "arel/visitors/spanner" require "activerecord_spanner_adapter/base" require "activerecord_spanner_adapter/connection" @@ -250,6 +251,7 @@ def initialize_type_map m = type_map register_class_with_limit m, %r{^STRING}i, Type::String m.register_type "TIMESTAMP", ActiveRecord::Type::Spanner::Time.new m.register_type "JSON", ActiveRecord::Type::Json.new + m.register_type "UUID", ActiveRecord::Type::Spanner::Uuid.new register_array_types m end @@ -265,6 +267,7 @@ def register_array_types m m.register_type %r{^ARRAY}i, Type::Spanner::Array.new(Type::String.new) m.register_type %r{^ARRAY}i, Type::Spanner::Array.new(ActiveRecord::Type::Spanner::Time.new) m.register_type %r{^ARRAY}i, Type::Spanner::Array.new(ActiveRecord::Type::Json.new) + m.register_type %r{^ARRAY}i, Type::Spanner::Array.new(ActiveRecord::Type::Spanner::Uuid.new) end def extract_limit sql_type diff --git a/lib/active_record/type/spanner/spanner_active_record_converter.rb b/lib/active_record/type/spanner/spanner_active_record_converter.rb index c4d98fd4..f23f8ac4 100644 --- a/lib/active_record/type/spanner/spanner_active_record_converter.rb +++ b/lib/active_record/type/spanner/spanner_active_record_converter.rb @@ -37,6 +37,7 @@ def self.convert_active_model_type_to_spanner type # rubocop:disable Metrics/Cyc when ActiveModel::Type::DateTime, ActiveModel::Type::Time, ActiveRecord::Type::Spanner::Time then :TIMESTAMP when ActiveModel::Type::Date then :DATE when ActiveRecord::Type::Json then :JSON + when ActiveRecord::Type::Spanner::Uuid then :UUID when ActiveRecord::Type::Spanner::Array then [convert_active_model_type_to_spanner(type.element_type)] end end diff --git a/lib/active_record/type/spanner/uuid.rb b/lib/active_record/type/spanner/uuid.rb new file mode 100644 index 00000000..1509c3cb --- /dev/null +++ b/lib/active_record/type/spanner/uuid.rb @@ -0,0 +1,19 @@ +# Copyright 2026 Google LLC +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. + +# frozen_string_literal: true + +module ActiveRecord + module Type + module Spanner + class Uuid < ActiveModel::Type::Value + def type + :uuid + end + end + end + end +end diff --git a/test/activerecord_spanner_mock_server/model_helper.rb b/test/activerecord_spanner_mock_server/model_helper.rb index 9953fea6..4dd88256 100644 --- a/test/activerecord_spanner_mock_server/model_helper.rb +++ b/test/activerecord_spanner_mock_server/model_helper.rb @@ -537,6 +537,17 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "NO"), ) result_set.rows.push row + row = ListValue.new + row.values.push( + Value.new(string_value: "col_uuid"), + Value.new(string_value: "UUID"), + Value.new(string_value: "YES"), + Value.new(null_value: "NULL_VALUE"), + Value.new(null_value: "NULL_VALUE"), + Value.new(string_value: "11"), + Value.new(string_value: "NO"), + ) + result_set.rows.push row row = ListValue.new row.values.push( @@ -545,7 +556,7 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "11"), + Value.new(string_value: "12"), Value.new(string_value: "NO"), ) result_set.rows.push row @@ -556,7 +567,7 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "12"), + Value.new(string_value: "13"), Value.new(string_value: "NO"), ) result_set.rows.push row @@ -567,7 +578,7 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "13"), + Value.new(string_value: "14"), Value.new(string_value: "NO"), ) result_set.rows.push row @@ -578,7 +589,7 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "14"), + Value.new(string_value: "15"), Value.new(string_value: "NO"), ) result_set.rows.push row @@ -589,7 +600,7 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "15"), + Value.new(string_value: "16"), Value.new(string_value: "NO"), ) result_set.rows.push row @@ -600,7 +611,7 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "16"), + Value.new(string_value: "17"), Value.new(string_value: "NO"), ) result_set.rows.push row @@ -611,7 +622,7 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "17"), + Value.new(string_value: "18"), Value.new(string_value: "NO"), ) result_set.rows.push row @@ -622,7 +633,7 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "18"), + Value.new(string_value: "19"), Value.new(string_value: "NO"), ) result_set.rows.push row @@ -633,10 +644,21 @@ def self.register_all_types_columns_result spanner_mock_server Value.new(string_value: "YES"), Value.new(null_value: "NULL_VALUE"), Value.new(null_value: "NULL_VALUE"), - Value.new(string_value: "19"), + Value.new(string_value: "20"), Value.new(string_value: "NO"), ) result_set.rows.push row + row = ListValue.new + row.values.push( + Value.new(string_value: "col_array_uuid"), + Value.new(string_value: "ARRAY"), + Value.new(string_value: "YES"), + Value.new(null_value: "NULL_VALUE"), + Value.new(null_value: "NULL_VALUE"), + Value.new(string_value: "21"), + Value.new(string_value: "NO"), + ) + result_set.rows.push row spanner_mock_server.put_statement_result sql, StatementResult.new(result_set) end diff --git a/test/activerecord_spanner_mock_server/spanner_active_record_with_mock_server_test.rb b/test/activerecord_spanner_mock_server/spanner_active_record_with_mock_server_test.rb index 8ef962cd..897315eb 100644 --- a/test/activerecord_spanner_mock_server/spanner_active_record_with_mock_server_test.rb +++ b/test/activerecord_spanner_mock_server/spanner_active_record_with_mock_server_test.rb @@ -6,6 +6,7 @@ # # frozen_string_literal: true +require "securerandom" require "sqlite3" require_relative "./base_spanner_mock_server_test" require_relative "models/other_adapter" @@ -960,10 +961,13 @@ def test_create_record_with_commit_timestamp_using_mutation end def test_create_all_types_using_mutation + uuid_value = SecureRandom.uuid + uuid_array = [SecureRandom.uuid, nil, SecureRandom.uuid] AllTypes.create col_string: "string", col_int64: 100, col_float64: 3.14, col_numeric: 6.626, col_bool: true, col_bytes: StringIO.new("bytes"), col_date: ::Date.new(2021, 6, 23), col_timestamp: ::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"), col_json: { kind: "user_renamed", change: %w[jack john]}, + col_uuid: uuid_value, col_array_string: ["string1", nil, "string2"], col_array_int64: [100, nil, 200], col_array_float64: [3.14, nil, 2.0/3.0], @@ -974,7 +978,8 @@ def test_create_all_types_using_mutation col_array_timestamp: [::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"), nil, \ ::Time.new(2021, 6, 24, 17, 8, 21, "+02:00")], col_array_json: [{ kind: "user_renamed", change: %w[jack john]}, nil, \ - { kind: "user_renamed", change: %w[alice meredith]}] + { kind: "user_renamed", change: %w[alice meredith]}], + col_array_uuid: uuid_array commit_requests = @mock.requests.select { |req| req.is_a?(Google::Cloud::Spanner::V1::CommitRequest) } assert_equal 1, commit_requests.length @@ -994,6 +999,7 @@ def test_create_all_types_using_mutation assert_equal "col_date", mutation.insert.columns[col_index += 1] assert_equal "col_timestamp", mutation.insert.columns[col_index += 1] assert_equal "col_json", mutation.insert.columns[col_index += 1] + assert_equal "col_uuid", mutation.insert.columns[col_index += 1] assert_equal "col_array_string", mutation.insert.columns[col_index += 1] assert_equal "col_array_int64", mutation.insert.columns[col_index += 1] @@ -1004,6 +1010,7 @@ def test_create_all_types_using_mutation assert_equal "col_array_date", mutation.insert.columns[col_index += 1] assert_equal "col_array_timestamp", mutation.insert.columns[col_index += 1] assert_equal "col_array_json", mutation.insert.columns[col_index += 1] + assert_equal "col_array_uuid", mutation.insert.columns[col_index += 1] value_index = -1 assert_equal 1, mutation.insert.values.length @@ -1016,6 +1023,7 @@ def test_create_all_types_using_mutation assert_equal "2021-06-23", mutation.insert.values[0][value_index += 1] assert_equal "2021-06-23T15:08:21.000000000Z", mutation.insert.values[0][value_index += 1] assert_equal "{\"kind\":\"user_renamed\",\"change\":[\"jack\",\"john\"]}", mutation.insert.values[0][value_index += 1] + assert_equal uuid_value, mutation.insert.values[0][value_index += 1] assert_equal create_list_value(["string1", nil, "string2"]), mutation.insert.values[0][value_index += 1] assert_equal create_list_value(["100", nil, "200"]), mutation.insert.values[0][value_index += 1] @@ -1035,34 +1043,33 @@ def test_create_all_types_using_mutation nil, "2021-06-24T15:08:21.000000000Z" ]), mutation.insert.values[0][value_index += 1] - json_list = create_list_value([ - "{\"kind\":\"user_renamed\",\"change\":[\"jack\",\"john\"]}", - nil, - "{\"kind\":\"user_renamed\",\"change\":[\"alice\",\"meredith\"]}" - ]) assert_equal create_list_value([ "{\"kind\":\"user_renamed\",\"change\":[\"jack\",\"john\"]}", nil, "{\"kind\":\"user_renamed\",\"change\":[\"alice\",\"meredith\"]}" ]), mutation.insert.values[0][value_index += 1] + assert_equal create_list_value(uuid_array), mutation.insert.values[0][value_index += 1] end def test_create_all_types_using_dml sql = "INSERT INTO `all_types` (`col_string`, `col_int64`, `col_float64`, `col_numeric`, `col_bool`, " \ - "`col_bytes`, `col_date`, `col_timestamp`, `col_json`, `col_array_string`, `col_array_int64`, " \ + "`col_bytes`, `col_date`, `col_timestamp`, `col_json`, `col_uuid`, `col_array_string`, `col_array_int64`, " \ "`col_array_float64`, `col_array_numeric`, `col_array_bool`, `col_array_bytes`, `col_array_date`, "\ - "`col_array_timestamp`, `col_array_json`, `id`) "\ + "`col_array_timestamp`, `col_array_json`, `col_array_uuid`, `id`) "\ "VALUES (@p1, @p2, @p3, @p4, @p5, @p6, " \ "@p7, @p8, @p9, @p10, @p11, " \ "@p12, @p13, @p14, @p15, " \ - "@p16, @p17, @p18, @p19)" + "@p16, @p17, @p18, @p19, @p20, @p21)" @mock.put_statement_result sql, StatementResult.new(1) + uuid_value = SecureRandom.uuid + uuid_array = [SecureRandom.uuid, nil, SecureRandom.uuid] AllTypes.transaction do AllTypes.create col_string: "string", col_int64: 100, col_float64: 3.14, col_numeric: 6.626, col_bool: true, col_bytes: StringIO.new("bytes"), col_date: ::Date.new(2021, 6, 23), col_timestamp: ::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"), col_json: { kind: "user_renamed", change: %w[jack john]}, + col_uuid: uuid_value, col_array_string: ["string1", nil, "string2"], col_array_int64: [100, nil, 200], col_array_float64: [3.14, nil, 2.0/3.0], @@ -1073,7 +1080,8 @@ def test_create_all_types_using_dml col_array_timestamp: [::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"), nil, \ ::Time.new(2021, 6, 24, 17, 8, 21, "+02:00")], col_array_json: [{ kind: "user_renamed", change: %w[jack john]}, nil, \ - { kind: "user_renamed", change: %w[alice meredith]}] + { kind: "user_renamed", change: %w[alice meredith]}], + col_array_uuid: uuid_array end commit_requests = @mock.requests.select { |req| req.is_a?(Google::Cloud::Spanner::V1::CommitRequest) } @@ -1101,40 +1109,43 @@ def test_create_all_types_using_dml assert_equal :TIMESTAMP, request.param_types["p8"].code assert_equal "{\"kind\":\"user_renamed\",\"change\":[\"jack\",\"john\"]}", request.params["p9"] assert_equal :JSON, request.param_types["p9"].code + assert_equal uuid_value, request.params["p10"] + assert_equal :UUID, request.param_types["p10"].code - assert_equal create_list_value(["string1", nil, "string2"]), request.params["p10"] - assert_equal :ARRAY, request.param_types["p10"].code - assert_equal :STRING, request.param_types["p10"].array_element_type.code - assert_equal create_list_value(["100", nil, "200"]), request.params["p11"] + assert_equal create_list_value(["string1", nil, "string2"]), request.params["p11"] assert_equal :ARRAY, request.param_types["p11"].code - assert_equal :INT64, request.param_types["p11"].array_element_type.code - assert_equal create_list_value([3.14, nil, 2.0/3.0]), request.params["p12"] + assert_equal :STRING, request.param_types["p11"].array_element_type.code + assert_equal create_list_value(["100", nil, "200"]), request.params["p12"] assert_equal :ARRAY, request.param_types["p12"].code - assert_equal :FLOAT64, request.param_types["p12"].array_element_type.code - assert_equal create_list_value(["6.626", nil, "3.2"]), request.params["p13"] + assert_equal :INT64, request.param_types["p12"].array_element_type.code + assert_equal create_list_value([3.14, nil, 2.0/3.0]), request.params["p13"] assert_equal :ARRAY, request.param_types["p13"].code - assert_equal :NUMERIC, request.param_types["p13"].array_element_type.code - assert_equal create_list_value([true, nil, false]), request.params["p14"] + assert_equal :FLOAT64, request.param_types["p13"].array_element_type.code + assert_equal create_list_value(["6.626", nil, "3.2"]), request.params["p14"] assert_equal :ARRAY, request.param_types["p14"].code - assert_equal :BOOL, request.param_types["p14"].array_element_type.code - assert_equal create_list_value([Base64.urlsafe_encode64("bytes1"), nil, Base64.urlsafe_encode64("bytes2")]), - request.params["p15"] + assert_equal :NUMERIC, request.param_types["p14"].array_element_type.code + assert_equal create_list_value([true, nil, false]), request.params["p15"] assert_equal :ARRAY, request.param_types["p15"].code - assert_equal :BYTES, request.param_types["p15"].array_element_type.code - assert_equal create_list_value(["2021-06-23", nil, "2021-06-24"]), request.params["p16"] + assert_equal :BOOL, request.param_types["p15"].array_element_type.code + assert_equal create_list_value([Base64.urlsafe_encode64("bytes1"), nil, Base64.urlsafe_encode64("bytes2")]), + request.params["p16"] assert_equal :ARRAY, request.param_types["p16"].code - assert_equal :DATE, request.param_types["p16"].array_element_type.code - assert_equal create_list_value(["2021-06-23T15:08:21.000000000Z", nil, "2021-06-24T15:08:21.000000000Z"]), - request.params["p17"] + assert_equal :BYTES, request.param_types["p16"].array_element_type.code + assert_equal create_list_value(["2021-06-23", nil, "2021-06-24"]), request.params["p17"] assert_equal :ARRAY, request.param_types["p17"].code - assert_equal :TIMESTAMP, request.param_types["p17"].array_element_type.code + assert_equal :DATE, request.param_types["p17"].array_element_type.code assert_equal create_list_value(["2021-06-23T15:08:21.000000000Z", nil, "2021-06-24T15:08:21.000000000Z"]), - request.params["p17"] + request.params["p18"] assert_equal :ARRAY, request.param_types["p18"].code - assert_equal :JSON, request.param_types["p18"].array_element_type.code + assert_equal :TIMESTAMP, request.param_types["p18"].array_element_type.code assert_equal create_list_value(["{\"kind\":\"user_renamed\",\"change\":[\"jack\",\"john\"]}", nil, \ "{\"kind\":\"user_renamed\",\"change\":[\"alice\",\"meredith\"]}"]), - request.params["p18"] + request.params["p19"] + assert_equal :ARRAY, request.param_types["p19"].code + assert_equal :JSON, request.param_types["p19"].array_element_type.code + assert_equal create_list_value(uuid_array), request.params["p20"] + assert_equal :ARRAY, request.param_types["p20"].code + assert_equal :UUID, request.param_types["p20"].array_element_type.code end def test_get_json @@ -1168,6 +1179,39 @@ def test_get_json assert_equal [{"key1"=>"value1"}, nil, {"key2"=>"value2"}], row.col_array_json end + def test_get_uuid + uuid_value = SecureRandom.uuid + uuid_array_value = [SecureRandom.uuid, nil, SecureRandom.uuid] + sql = "SELECT `all_types`.* FROM `all_types` WHERE `all_types`.`col_int64` = @p1 LIMIT @p2" + col_id = Google::Cloud::Spanner::V1::StructType::Field.new name: "col_int64", type: Google::Cloud::Spanner::V1::Type.new(code: Google::Cloud::Spanner::V1::TypeCode::INT64) + col_uuid = Google::Cloud::Spanner::V1::StructType::Field.new name: "col_uuid", type: Google::Cloud::Spanner::V1::Type.new(code: Google::Cloud::Spanner::V1::TypeCode::UUID) + col_uuid_array = Google::Cloud::Spanner::V1::StructType::Field.new name: "col_array_uuid", type: Google::Cloud::Spanner::V1::Type.new( + code: Google::Cloud::Spanner::V1::TypeCode::ARRAY, + array_element_type: Google::Cloud::Spanner::V1::Type.new(code: Google::Cloud::Spanner::V1::TypeCode::UUID) + ) + metadata = Google::Cloud::Spanner::V1::ResultSetMetadata.new row_type: Google::Cloud::Spanner::V1::StructType.new + metadata.row_type.fields.push(col_id, col_uuid, col_uuid_array) + result_set = Google::Cloud::Spanner::V1::ResultSet.new metadata: metadata + row = Google::Protobuf::ListValue.new + uuid_array = Google::Protobuf::ListValue.new + uuid_array.values.push( + Google::Protobuf::Value.new(string_value: uuid_array_value[0]), + Google::Protobuf::Value.new(null_value: "NULL_VALUE"), + Google::Protobuf::Value.new(string_value: uuid_array_value[2]) + ) + row.values.push( + Google::Protobuf::Value.new(string_value: "1"), + Google::Protobuf::Value.new(string_value: uuid_value), + Google::Protobuf::Value.new(list_value: uuid_array) + ) + result_set.rows.push row + @mock.put_statement_result sql, StatementResult.new(result_set) + + row = AllTypes.find_by col_int64: 1 + assert_equal uuid_value, row.col_uuid + assert_equal uuid_array_value, row.col_array_uuid + end + def test_delete_associated_records sql = "SELECT `singers`.* FROM `singers` WHERE `singers`.`id` = @p1 LIMIT @p2" @mock.put_statement_result sql, MockServerTests::create_random_singers_result(1) diff --git a/test/migrations_with_mock_server/db/migrate/03_create_all_native_migration_types.rb b/test/migrations_with_mock_server/db/migrate/03_create_all_native_migration_types.rb index df658955..8f7fcf7e 100644 --- a/test/migrations_with_mock_server/db/migrate/03_create_all_native_migration_types.rb +++ b/test/migrations_with_mock_server/db/migrate/03_create_all_native_migration_types.rb @@ -22,6 +22,7 @@ def change t.column :col_binary, :binary t.column :col_boolean, :boolean t.column :col_json, :json + t.column :col_uuid, :uuid t.column :col_array_string, :string, array: true t.column :col_array_text, :text, array: true @@ -36,6 +37,7 @@ def change t.column :col_array_binary, :binary, array: true t.column :col_array_boolean, :boolean, array: true t.column :col_array_json, :json, array: true + t.column :col_array_uuid, :uuid, array: true end end end diff --git a/test/migrations_with_mock_server/db/migrate/10_create_parent_and_child_with_uuid_pk.rb b/test/migrations_with_mock_server/db/migrate/10_create_parent_and_child_with_uuid_pk.rb index 8393f235..cd644956 100644 --- a/test/migrations_with_mock_server/db/migrate/10_create_parent_and_child_with_uuid_pk.rb +++ b/test/migrations_with_mock_server/db/migrate/10_create_parent_and_child_with_uuid_pk.rb @@ -11,18 +11,17 @@ def change # Execute the entire migration as one DDL batch. connection.ddl_batch do create_table :parent_with_uuid_pk, id: false do |t| - t.primary_key :parentid, :string, limit: 36 + t.primary_key :parentid, :uuid t.string :first_name t.string :last_name end create_table :child_with_uuid_pk, id: false do |t| t.interleave_in :parent_with_uuid_pk - t.parent_key :parentid, type: 'STRING(36)' + t.parent_key :parentid, type: "UUID" t.primary_key :childid t.string :title end end end end - diff --git a/test/migrations_with_mock_server/migrations_with_mock_server_test.rb b/test/migrations_with_mock_server/migrations_with_mock_server_test.rb index 923a1472..297dcc9b 100644 --- a/test/migrations_with_mock_server/migrations_with_mock_server_test.rb +++ b/test/migrations_with_mock_server/migrations_with_mock_server_test.rb @@ -190,6 +190,7 @@ def test_create_all_native_migration_types expectedDdl << "`col_binary` BYTES(MAX), " expectedDdl << "`col_boolean` BOOL, " expectedDdl << "`col_json` JSON, " + expectedDdl << "`col_uuid` UUID, " expectedDdl << "`col_array_string` ARRAY, " expectedDdl << "`col_array_text` ARRAY, " expectedDdl << "`col_array_integer` ARRAY, " @@ -202,7 +203,8 @@ def test_create_all_native_migration_types expectedDdl << "`col_array_date` ARRAY, " expectedDdl << "`col_array_binary` ARRAY, " expectedDdl << "`col_array_boolean` ARRAY, " - expectedDdl << "`col_array_json` ARRAY) " + expectedDdl << "`col_array_json` ARRAY, " + expectedDdl << "`col_array_uuid` ARRAY) " expectedDdl << "PRIMARY KEY (`id`)" assert_equal expectedDdl, ddl_requests[2].statements[0] @@ -288,12 +290,12 @@ def test_interleaved_table_with_uuid_pk assert_equal 2, ddl_requests[2].statements.length expectedDdl = "CREATE TABLE `parent_with_uuid_pk` " - expectedDdl << "(`parentid` STRING(36) NOT NULL, `first_name` STRING(MAX), `last_name` STRING(MAX)) " + expectedDdl << "(`parentid` UUID NOT NULL, `first_name` STRING(MAX), `last_name` STRING(MAX)) " expectedDdl << "PRIMARY KEY (`parentid`)" assert_equal expectedDdl, ddl_requests[2].statements[0] expectedDdl = "CREATE TABLE `child_with_uuid_pk` " - expectedDdl << "(`parentid` STRING(36) NOT NULL, `childid` INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE), `title` STRING(MAX)) " + expectedDdl << "(`parentid` UUID NOT NULL, `childid` INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE), `title` STRING(MAX)) " expectedDdl << "PRIMARY KEY (`parentid`, `childid`), INTERLEAVE IN PARENT `parent_with_uuid_pk`" assert_equal expectedDdl, ddl_requests[2].statements[1] end