Skip to content

Commit

Permalink
Make bigdecimal an optional dependency
Browse files Browse the repository at this point in the history
It's a gem in ruby-3.4+, so that users shouldn't be forced to use it.
  • Loading branch information
larskanis committed Feb 29, 2024
1 parent daec80f commit 5804665
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 14 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 8 additions & 4 deletions lib/pg/basic_type_map_for_queries.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,22 +166,27 @@ 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'],
# We use text format and no type OID for numbers, because setting the OID can lead
# 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.
IPAddr => [0, 'inet'],
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({
Expand All @@ -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
6 changes: 5 additions & 1 deletion lib/pg/basic_type_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 5 additions & 0 deletions spec/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
23 changes: 17 additions & 6 deletions spec/pg/basic_type_map_for_queries_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 )

Expand All @@ -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
Expand Down Expand Up @@ -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 )
Expand Down
2 changes: 1 addition & 1 deletion spec/pg/basic_type_map_for_results_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
9 changes: 7 additions & 2 deletions spec/pg/type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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" )
Expand Down

0 comments on commit 5804665

Please sign in to comment.