From df2aab246b6d88b1c185a9bc1c9ff0b25d495308 Mon Sep 17 00:00:00 2001 From: Nivedha Date: Fri, 21 Oct 2022 14:33:48 +0530 Subject: [PATCH] feat(spanner): support pg jsonb (#19116) --- acceptance/data/fixtures.rb | 2 + .../spanner/client/params/pgjsonb_test.rb | 82 +++++++++ .../spanner/client/types/pgjsonb_test.rb | 160 ++++++++++++++++++ .../lib/google/cloud/spanner/convert.rb | 2 + .../convert/grpc_type_for_field_test.rb | 100 +++++++++++ 5 files changed, 346 insertions(+) create mode 100644 google-cloud-spanner/acceptance/spanner/client/params/pgjsonb_test.rb create mode 100644 google-cloud-spanner/acceptance/spanner/client/types/pgjsonb_test.rb create mode 100644 google-cloud-spanner/test/google/cloud/spanner/convert/grpc_type_for_field_test.rb diff --git a/acceptance/data/fixtures.rb b/acceptance/data/fixtures.rb index 4515cd744bd9..8839147f11cb 100644 --- a/acceptance/data/fixtures.rb +++ b/acceptance/data/fixtures.rb @@ -75,6 +75,8 @@ def stuff_pg_ddl_statement date DATE, numerics numeric[], dates DATE[], + json jsonb, + -- json_array jsonb[], PRIMARY KEY(id) ); STUFFS diff --git a/google-cloud-spanner/acceptance/spanner/client/params/pgjsonb_test.rb b/google-cloud-spanner/acceptance/spanner/client/params/pgjsonb_test.rb new file mode 100644 index 000000000000..da6ac340d8a5 --- /dev/null +++ b/google-cloud-spanner/acceptance/spanner/client/params/pgjsonb_test.rb @@ -0,0 +1,82 @@ +# Copyright 2022 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 +# +# https://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. + +require "spanner_helper" + +describe "Spanner Client", :params, :pgjsonb, :spanner do + let(:db) { spanner_pg_client } + let(:json_params) { { "venue" => "abc", "rating" => 10 } } + let :json_array_params do + 3.times.map do |i| + { "venue" => "abc-#{i}", "rating" => 10 + i } + end + end + + before do + skip if emulator_enabled? + end + + it "queries and returns a string parameter" do + results = db.execute_query "SELECT $1 AS value", params: { p1: json_params }, types: { p1: :PG_JSONB } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields[:value]).must_equal :JSON + _(results.rows.first[:value]).must_equal json_params + end + + it "queries and returns a NULL string parameter" do + results = db.execute_query "SELECT $1 AS value", params: { p1: nil }, types: { p1: :PG_JSONB } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields[:value]).must_equal :JSON + _(results.rows.first[:value]).must_be :nil? + end + + it "queries and returns an array of json parameters" do + skip "Arrays not supported yet" + results = db.execute_query "SELECT $1 AS value", params: { p1: json_array_params }, types: { p1: [:PG_JSONB] } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields[:value]).must_equal [:JSON] + _(results.rows.first[:value]).must_equal json_array_params + end + + it "queries and returns an array of json parameters with a nil value" do + skip "Arrays not supported yet" + params = [nil].concat json_array_params + results = db.execute_query "SELECT $1 AS value", params: { p1: params }, types: { p1: [:PG_JSONB] } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields[:value]).must_equal [:JSON] + _(results.rows.first[:value]).must_equal params + end + + it "queries and returns an empty array of json parameters" do + skip "Arrays not supported yet" + results = db.execute_query "SELECT $1 AS value", params: { p1: [] }, types: { p1: [:PG_JSONB] } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields[:value]).must_equal [:JSON] + _(results.rows.first[:value]).must_equal [] + end + + it "queries and returns a NULL array of json parameters" do + skip "Arrays not supported yet" + results = db.execute_query "SELECT $1 AS value", params: { p1: nil }, types: { p1: [:PG_JSONB] } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields[:value]).must_equal [:JSON] + _(results.rows.first[:value]).must_be :nil? + end +end diff --git a/google-cloud-spanner/acceptance/spanner/client/types/pgjsonb_test.rb b/google-cloud-spanner/acceptance/spanner/client/types/pgjsonb_test.rb new file mode 100644 index 000000000000..651ad7a3b385 --- /dev/null +++ b/google-cloud-spanner/acceptance/spanner/client/types/pgjsonb_test.rb @@ -0,0 +1,160 @@ +# Copyright 2021 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 +# +# https://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. + +require "spanner_helper" + +describe "Spanner Client", :types, :json, :spanner do + let(:db) { spanner_pg_client } + let(:table_name) { "stuffs" } + let(:json_params) { { "venue" => "abc", "rating" => 10 } } + let :json_array_params do + 3.times.map do |i| + { "venue" => "abc-#{i}", "rating" => 10 + i } + end + end + + before do + skip if emulator_enabled? + end + + it "writes and reads json" do + id = SecureRandom.int64 + db.upsert table_name, { id: id, json: json_params } + results = db.read table_name, [:id, :json], keys: id + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json: :JSON }) + _(results.rows.first.to_h).must_equal({ id: id, json: json_params }) + end + + it "writes and queries json" do + id = SecureRandom.int64 + db.upsert table_name, { id: id, json: json_params } + results = db.execute_query "SELECT id, json FROM #{table_name} WHERE id = $1", params: { p1: id } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json: :JSON }) + _(results.rows.first.to_h).must_equal({ id: id, json: json_params }) + end + + it "writes and reads NULL json" do + id = SecureRandom.int64 + db.upsert table_name, { id: id, json: nil } + results = db.read table_name, [:id, :json], keys: id + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json: :JSON }) + _(results.rows.first.to_h).must_equal({ id: id, json: nil }) + end + + it "writes and queries NULL json" do + id = SecureRandom.int64 + db.upsert table_name, { id: id, json: nil } + results = db.execute_query "SELECT id, json FROM #{table_name} WHERE id = $1", params: { p1: id } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json: :JSON }) + _(results.rows.first.to_h).must_equal({ id: id, json: nil }) + end + + it "writes and reads array of json" do + skip "Arrays not supported yet" + id = SecureRandom.int64 + db.upsert table_name, { id: id, json_array: json_array_params } + results = db.read table_name, [:id, :json_array], keys: id + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json_array: [:JSON] }) + _(results.rows.first.to_h).must_equal({ id: id, json_array: json_array_params }) + end + + it "writes and queries array of json" do + skip "Arrays not supported yet" + id = SecureRandom.int64 + db.upsert table_name, { id: id, json_array: json_array_params } + results = db.execute_query "SELECT id, json_array FROM #{table_name} WHERE id = $1", params: { p1: id } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json_array: [:JSON] }) + _(results.rows.first.to_h).must_equal({ id: id, json_array: json_array_params }) + end + + it "writes and reads array of json with NULL" do + skip "Arrays not supported yet" + id = SecureRandom.int64 + params = [nil].concat json_array_params + db.upsert table_name, { id: id, json_array: params } + results = db.read table_name, [:id, :json_array], keys: id + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json_array: [:JSON] }) + _(results.rows.first.to_h).must_equal({ id: id, json_array: params }) + end + + it "writes and queries array of json with NULL" do + skip "Arrays not supported yet" + id = SecureRandom.int64 + params = [nil].concat json_array_params + db.upsert table_name, { id: id, json_array: params } + results = db.execute_query "SELECT id, json_array FROM #{table_name} WHERE id = $1", params: { p1: id } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json_array: [:JSON] }) + _(results.rows.first.to_h).must_equal({ id: id, json_array: params }) + end + + it "writes and reads empty array of json" do + skip "Arrays not supported yet" + id = SecureRandom.int64 + db.upsert table_name, { id: id, json_array: [] } + results = db.read table_name, [:id, :json_array], keys: id + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json_array: [:JSON] }) + _(results.rows.first.to_h).must_equal({ id: id, json_array: [] }) + end + + it "writes and queries empty array of json array" do + skip "Arrays not supported yet" + id = SecureRandom.int64 + db.upsert table_name, { id: id, json_array: [] } + results = db.execute_query "SELECT id, json_array FROM #{table_name} WHERE id = $1", params: { p1: id } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json_array: [:JSON] }) + _(results.rows.first.to_h).must_equal({ id: id, json_array: [] }) + end + + it "writes and reads NULL array of json" do + skip "Arrays not supported yet" + id = SecureRandom.int64 + db.upsert table_name, { id: id, json_array: nil } + results = db.read table_name, [:id, :json_array], keys: id + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json_array: [:JSON] }) + _(results.rows.first.to_h).must_equal({ id: id, json_array: nil }) + end + + it "writes and queries NULL array of json" do + skip "Arrays not supported yet" + id = SecureRandom.int64 + db.upsert table_name, { id: id, json_array: nil } + results = db.execute_query "SELECT id, json_array FROM #{table_name} WHERE id = $1", params: { p1: id } + + _(results).must_be_kind_of Google::Cloud::Spanner::Results + _(results.fields.to_h).must_equal({ id: :INT64, json_array: [:JSON] }) + _(results.rows.first.to_h).must_equal({ id: id, json_array: nil }) + end +end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/convert.rb b/google-cloud-spanner/lib/google/cloud/spanner/convert.rb index 99bff0c322fb..2f86eb696765 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/convert.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/convert.rb @@ -182,6 +182,8 @@ def grpc_type_for_field field ) when :PG_NUMERIC V1::Type.new(code: :NUMERIC, type_annotation: :PG_NUMERIC) + when :PG_JSONB + V1::Type.new(code: :JSON, type_annotation: :PG_JSONB) else V1::Type.new(code: field) end diff --git a/google-cloud-spanner/test/google/cloud/spanner/convert/grpc_type_for_field_test.rb b/google-cloud-spanner/test/google/cloud/spanner/convert/grpc_type_for_field_test.rb new file mode 100644 index 000000000000..3971970c2777 --- /dev/null +++ b/google-cloud-spanner/test/google/cloud/spanner/convert/grpc_type_for_field_test.rb @@ -0,0 +1,100 @@ +# Copyright 2022 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 +# +# https://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. + +require "helper" +require "bigdecimal" + +describe Google::Cloud::Spanner::Convert, :grpc_type_for_field, :mock_spanner do + + it "converts a BOOL value" do + field = :BOOL + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :BOOL + end + + it "converts a INT64 value" do + field = :INT64 + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :INT64 + end + + it "converts a FLOAT64 value" do + field = :FLOAT64 + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :FLOAT64 + end + + it "converts a TIMESTAMP value" do + field = :TIMESTAMP + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :TIMESTAMP + end + + it "converts a DATE value" do + field = :DATE + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :DATE + end + + it "converts a STRING value" do + field = :STRING + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :STRING + end + + it "converts a BYTES value" do + field = :BYTES + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :BYTES + end + + it "converts an ARRAY of INT64 values" do + field = [:INT64] + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :ARRAY + assert_equal type.array_element_type.code, :INT64 + end + + it "converts a STRUCT value" do + field = :STRUCT + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :STRUCT + end + + it "converts a NUMERIC value" do + field = :NUMERIC + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :NUMERIC + end + + it "converts a JSON value" do + field = :JSON + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :JSON + end + + it "converts a PG_JSONB value" do + field = :PG_JSONB + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :JSON + assert_equal type.type_annotation, :PG_JSONB + end + + it "converts a PG_NUMERIC value" do + field = :PG_NUMERIC + type = Google::Cloud::Spanner::Convert.grpc_type_for_field field + assert_equal type.code, :NUMERIC + assert_equal type.type_annotation, :PG_NUMERIC + end +end