diff --git a/Gemfile b/Gemfile index ecc2f33df..210afe2e5 100644 --- a/Gemfile +++ b/Gemfile @@ -11,4 +11,7 @@ group :development, :test do gem "rake-compiler-dock", "~> 1.0" gem "rdoc", "~> 6.4" gem "rspec", "~> 3.5" + # "bigdecimal" is a gem on ruby-3.4+ and it's optional for ruby-pg. + # Specs should succeed without it, but 4 examples are then excluded. + # gem "bigdecimal", "~> 3.0" end diff --git a/lib/pg/basic_type_map_for_queries.rb b/lib/pg/basic_type_map_for_queries.rb index 6776b48bb..d571387cc 100644 --- a/lib/pg/basic_type_map_for_queries.rb +++ b/lib/pg/basic_type_map_for_queries.rb @@ -166,6 +166,12 @@ def get_array_type(value) @textarray_encoder end + begin + require "bigdecimal" + has_bigdecimal = true + rescue LoadError + end + DEFAULT_TYPE_MAP = PG.make_shareable({ TrueClass => [1, 'bool', 'bool'], FalseClass => [1, 'bool', 'bool'], @@ -173,7 +179,6 @@ def get_array_type(value) # to unnecessary type conversions on server side. Integer => [0, 'int8'], Float => [0, 'float8'], - BigDecimal => [0, 'numeric'], Time => [0, 'timestamptz'], # We use text format and no type OID for IPAddr, because setting the OID can lead # to unnecessary inet/cidr conversions on the server side. @@ -181,7 +186,7 @@ def get_array_type(value) Hash => [0, 'json'], Array => :get_array_type, BinaryData => [1, 'bytea'], - }) + }.merge(has_bigdecimal ? {BigDecimal => [0, 'numeric']} : {})) private_constant :DEFAULT_TYPE_MAP DEFAULT_ARRAY_TYPE_MAP = PG.make_shareable({ @@ -190,9 +195,8 @@ def get_array_type(value) Integer => [0, '_int8'], String => [0, '_text'], Float => [0, '_float8'], - BigDecimal => [0, '_numeric'], Time => [0, '_timestamptz'], IPAddr => [0, '_inet'], - }) + }.merge(has_bigdecimal ? {BigDecimal => [0, '_numeric']} : {})) private_constant :DEFAULT_ARRAY_TYPE_MAP end diff --git a/lib/pg/basic_type_registry.rb b/lib/pg/basic_type_registry.rb index c84b6403a..e2a43f19f 100644 --- a/lib/pg/basic_type_registry.rb +++ b/lib/pg/basic_type_registry.rb @@ -225,7 +225,11 @@ def register_default_types alias_type 0, 'int8', 'int2' alias_type 0, 'oid', 'int2' - register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric + begin + require "bigdecimal" + register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric + rescue LoadError + end register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String alias_type 0, 'varchar', 'text' alias_type 0, 'char', 'text' diff --git a/spec/helpers.rb b/spec/helpers.rb index 017585fbe..8f313ecea 100644 --- a/spec/helpers.rb +++ b/spec/helpers.rb @@ -705,6 +705,11 @@ def with_env_vars(**kwargs) config.filter_run_excluding( :scheduler_address_resolve ) if RUBY_VERSION < "3.1" config.filter_run_excluding( :ipv6 ) if Addrinfo.getaddrinfo("localhost", nil, nil, :STREAM).size < 2 config.filter_run_excluding( :ractor ) unless defined?(Ractor) + begin + require "bigdecimal" + rescue LoadError + config.filter_run_excluding( :bigdecimal ) + end ### Automatically set up and tear down the database config.before(:suite) do |*args| diff --git a/spec/pg/basic_type_map_for_queries_spec.rb b/spec/pg/basic_type_map_for_queries_spec.rb index 5a8441b4c..660202086 100644 --- a/spec/pg/basic_type_map_for_queries_spec.rb +++ b/spec/pg/basic_type_map_for_queries_spec.rb @@ -54,7 +54,7 @@ args = [] pr = proc { |*a| args << a } PG::BasicTypeMapForQueries.new(@conn, registry: regi, if_undefined: pr) - expect( args.last ).to eq( ['bytea', 1] ) + expect( args.first ).to eq( ["bool", 1] ) end it "raises UndefinedEncoder for undefined types" do @@ -113,12 +113,11 @@ it "should do default array-as-array param encoding" do expect( basic_type_mapping.encode_array_as).to eq(:array) - res = @conn.exec_params( "SELECT $1,$2,$3,$4,$5,$6", [ + res = @conn.exec_params( "SELECT $1,$2,$3,$4,$5", [ [1, 2, 3], # Integer -> bigint[] [[1, 2], [3, nil]], # Integer two dimensions -> bigint[] [1.11, 2.21], # Float -> double precision[] ['/,"'.gsub("/", "\\"), nil, 'abcäöü'], # String -> text[] - [BigDecimal("123.45")], # BigDecimal -> numeric[] [IPAddr.new('1234::5678')], # IPAddr -> inet[] ], nil, basic_type_mapping ) @@ -127,11 +126,23 @@ '{{1,2},{3,NULL}}', '{1.11,2.21}', '{"//,/"",NULL,abcäöü}'.gsub("/", "\\"), - '{123.45}', '{1234::5678}', ]] ) - expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]', 'numeric[]', 'inet[]'] ) + expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]', 'inet[]'] ) + end + + it "should do bigdecimal array-as-array param encoding", :bigdecimal do + expect( basic_type_mapping.encode_array_as).to eq(:array) + res = @conn.exec_params( "SELECT $1", [ + [BigDecimal("123.45")], # BigDecimal -> numeric[] + ], nil, basic_type_mapping ) + + expect( res.values ).to eq( [[ + '{123.45}', + ]] ) + + expect( result_typenames(res) ).to eq( ['numeric[]'] ) end it "should do default array-as-array param encoding with Time objects" do @@ -205,7 +216,7 @@ end end - it "should do bigdecimal param encoding" do + it "should do bigdecimal param encoding", :bigdecimal do large = ('123456790'*10) << '.' << ('012345679') res = @conn.exec_params( "SELECT $1::numeric,$2::numeric", [BigDecimal('1'), BigDecimal(large)], nil, basic_type_mapping ) diff --git a/spec/pg/basic_type_map_for_results_spec.rb b/spec/pg/basic_type_map_for_results_spec.rb index 53b34cd14..fc74ab03f 100644 --- a/spec/pg/basic_type_map_for_results_spec.rb +++ b/spec/pg/basic_type_map_for_results_spec.rb @@ -253,7 +253,7 @@ end end - it "should do numeric type conversions" do + it "should do numeric type conversions", :bigdecimal do [0].each do |format| small = '123456790123.12' large = ('123456790'*10) << '.' << ('012345679') diff --git a/spec/pg/type_spec.rb b/spec/pg/type_spec.rb index f75a841d9..5af7f6c62 100644 --- a/spec/pg/type_spec.rb +++ b/spec/pg/type_spec.rb @@ -12,7 +12,12 @@ let!(:textdec_boolean) { PG::TextDecoder::Boolean.new } let!(:textenc_float) { PG::TextEncoder::Float.new } let!(:textdec_float) { PG::TextDecoder::Float.new } - let!(:textenc_numeric) { PG::TextEncoder::Numeric.new } + let!(:textenc_numeric) do + begin + PG::TextEncoder::Numeric.new + rescue LoadError + end + end let!(:textenc_string) { PG::TextEncoder::String.new } let!(:textdec_string) { PG::TextDecoder::String.new } let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new } @@ -366,7 +371,7 @@ def textdec_timestamptz_decode_should_fail(str) expect( textenc_float.encode(-Float::NAN) ).to eq( Float::NAN.to_s ) end - it "should encode various inputs to numeric format" do + it "should encode various inputs to numeric format", :bigdecimal do expect( textenc_numeric.encode(0) ).to eq( "0" ) expect( textenc_numeric.encode(1) ).to eq( "1" ) expect( textenc_numeric.encode(-12345678901234567890123) ).to eq( "-12345678901234567890123" )