pg 1.1.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +0 -6595
  5. data/History.rdoc +63 -0
  6. data/Manifest.txt +3 -2
  7. data/README-Windows.rdoc +4 -4
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +43 -8
  10. data/Rakefile +3 -3
  11. data/Rakefile.cross +6 -3
  12. data/ext/errorcodes.def +64 -0
  13. data/ext/errorcodes.txt +18 -2
  14. data/ext/extconf.rb +6 -6
  15. data/ext/pg.c +132 -95
  16. data/ext/pg.h +20 -18
  17. data/ext/pg_binary_decoder.c +9 -9
  18. data/ext/pg_binary_encoder.c +13 -12
  19. data/ext/pg_coder.c +5 -5
  20. data/ext/pg_connection.c +388 -298
  21. data/ext/pg_copy_coder.c +5 -3
  22. data/ext/pg_record_coder.c +490 -0
  23. data/ext/pg_result.c +269 -123
  24. data/ext/pg_text_decoder.c +14 -8
  25. data/ext/pg_text_encoder.c +180 -48
  26. data/ext/pg_tuple.c +14 -6
  27. data/ext/pg_type_map.c +1 -1
  28. data/ext/pg_type_map_all_strings.c +4 -4
  29. data/ext/pg_type_map_by_class.c +4 -3
  30. data/ext/pg_type_map_by_column.c +7 -6
  31. data/ext/pg_type_map_by_mri_type.c +1 -1
  32. data/ext/pg_type_map_by_oid.c +3 -2
  33. data/ext/pg_type_map_in_ruby.c +1 -1
  34. data/ext/{util.c → pg_util.c} +5 -5
  35. data/ext/{util.h → pg_util.h} +0 -0
  36. data/lib/pg.rb +2 -3
  37. data/lib/pg/basic_type_mapping.rb +79 -16
  38. data/lib/pg/binary_decoder.rb +1 -0
  39. data/lib/pg/coder.rb +22 -1
  40. data/lib/pg/connection.rb +2 -2
  41. data/lib/pg/constants.rb +1 -0
  42. data/lib/pg/exceptions.rb +1 -0
  43. data/lib/pg/result.rb +13 -1
  44. data/lib/pg/text_decoder.rb +2 -3
  45. data/lib/pg/text_encoder.rb +8 -18
  46. data/lib/pg/type_map_by_column.rb +2 -1
  47. data/spec/helpers.rb +10 -8
  48. data/spec/pg/basic_type_mapping_spec.rb +150 -13
  49. data/spec/pg/connection_spec.rb +89 -50
  50. data/spec/pg/result_spec.rb +193 -3
  51. data/spec/pg/tuple_spec.rb +55 -2
  52. data/spec/pg/type_map_by_column_spec.rb +5 -1
  53. data/spec/pg/type_spec.rb +180 -6
  54. metadata +17 -15
  55. metadata.gz.sig +0 -0
@@ -1,4 +1,5 @@
1
1
  # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
@@ -1,4 +1,5 @@
1
1
  # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
@@ -1,4 +1,5 @@
1
1
  # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
@@ -9,12 +10,23 @@ class PG::Result
9
10
  #
10
11
  # +type_map+: a PG::TypeMap instance.
11
12
  #
12
- # See PG::BasicTypeMapForResults
13
+ # This method is equal to #type_map= , but returns self, so that calls can be chained.
14
+ #
15
+ # See also PG::BasicTypeMapForResults
13
16
  def map_types!(type_map)
14
17
  self.type_map = type_map
15
18
  return self
16
19
  end
17
20
 
21
+ # Set the data type for all field name returning methods.
22
+ #
23
+ # +type+: a Symbol defining the field name type.
24
+ #
25
+ # This method is equal to #field_name_type= , but returns self, so that calls can be chained.
26
+ def field_names_as(type)
27
+ self.field_name_type = type
28
+ return self
29
+ end
18
30
 
19
31
  ### Return a String representation of the object suitable for debugging.
20
32
  def inspect
@@ -1,4 +1,5 @@
1
1
  # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'date'
4
5
  require 'json'
@@ -6,10 +7,8 @@ require 'json'
6
7
  module PG
7
8
  module TextDecoder
8
9
  class Date < SimpleDecoder
9
- ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
10
-
11
10
  def decode(string, tuple=nil, field=nil)
12
- if string =~ ISO_DATE
11
+ if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
13
12
  ::Date.new $1.to_i, $2.to_i, $3.to_i
14
13
  else
15
14
  string
@@ -1,4 +1,5 @@
1
1
  # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'json'
4
5
  require 'ipaddr'
@@ -6,36 +7,26 @@ require 'ipaddr'
6
7
  module PG
7
8
  module TextEncoder
8
9
  class Date < SimpleEncoder
9
- STRFTIME_ISO_DATE = "%Y-%m-%d".freeze
10
10
  def encode(value)
11
- value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATE) : value
11
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : value
12
12
  end
13
13
  end
14
14
 
15
15
  class TimestampWithoutTimeZone < SimpleEncoder
16
- STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N".freeze
17
16
  def encode(value)
18
- value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE) : value
17
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N") : value
19
18
  end
20
19
  end
21
20
 
22
21
  class TimestampUtc < SimpleEncoder
23
- STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC = "%Y-%m-%d %H:%M:%S.%N".freeze
24
22
  def encode(value)
25
- value.respond_to?(:utc) ? value.utc.strftime(STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC) : value
23
+ value.respond_to?(:utc) ? value.utc.strftime("%Y-%m-%d %H:%M:%S.%N") : value
26
24
  end
27
25
  end
28
26
 
29
27
  class TimestampWithTimeZone < SimpleEncoder
30
- STRFTIME_ISO_DATETIME_WITH_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N %:z".freeze
31
28
  def encode(value)
32
- value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATETIME_WITH_TIMEZONE) : value
33
- end
34
- end
35
-
36
- class Numeric < SimpleEncoder
37
- def encode(value)
38
- value.is_a?(BigDecimal) ? value.to_s('F') : value
29
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N %:z") : value
39
30
  end
40
31
  end
41
32
 
@@ -51,12 +42,12 @@ module PG
51
42
  when IPAddr
52
43
  default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
53
44
  s = value.to_s
54
- if value.respond_to?(:prefix)
45
+ if value.respond_to?(:prefix)
55
46
  prefix = value.prefix
56
- else
47
+ else
57
48
  range = value.to_range
58
49
  prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
59
- end
50
+ end
60
51
  s << "/" << prefix.to_s if prefix != default_prefix
61
52
  s
62
53
  else
@@ -66,4 +57,3 @@ module PG
66
57
  end
67
58
  end
68
59
  end # module PG
69
-
@@ -1,4 +1,5 @@
1
1
  # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
@@ -9,7 +10,7 @@ class PG::TypeMapByColumn
9
10
  end
10
11
 
11
12
  def inspect
12
- type_strings = coders.map{|c| c ? "#{c.name}:#{c.format}" : 'nil' }
13
+ type_strings = coders.map{|c| c ? c.inspect_short : 'nil' }
13
14
  "#<#{self.class} #{type_strings.join(' ')}>"
14
15
  end
15
16
  end
@@ -172,18 +172,18 @@ module PG::TestingHelpers
172
172
  datadir = testdir + 'data'
173
173
  pidfile = datadir + 'postmaster.pid'
174
174
  if pidfile.exist? && pid = pidfile.read.chomp.to_i
175
- $stderr.puts "pidfile (%p) exists: %d" % [ pidfile, pid ]
175
+ trace "pidfile (%p) exists: %d" % [ pidfile, pid ]
176
176
  begin
177
177
  Process.kill( 0, pid )
178
178
  rescue Errno::ESRCH
179
- $stderr.puts "No postmaster running for %s" % [ datadir ]
179
+ trace "No postmaster running for %s" % [ datadir ]
180
180
  # Process isn't alive, so don't try to stop it
181
181
  else
182
- $stderr.puts "Stopping lingering database at PID %d" % [ pid ]
182
+ trace "Stopping lingering database at PID %d" % [ pid ]
183
183
  run 'pg_ctl', '-D', datadir.to_s, '-m', 'fast', 'stop'
184
184
  end
185
185
  else
186
- $stderr.puts "No pidfile (%p)" % [ pidfile ]
186
+ trace "No pidfile (%p)" % [ pidfile ]
187
187
  end
188
188
  end
189
189
  end
@@ -194,7 +194,7 @@ module PG::TestingHelpers
194
194
  require 'pg'
195
195
  stop_existing_postmasters()
196
196
 
197
- puts "Setting up test database for #{description}"
197
+ trace "Setting up test database for #{description}"
198
198
  @test_pgdata = TEST_DIRECTORY + 'data'
199
199
  @test_pgdata.mkpath
200
200
 
@@ -209,7 +209,7 @@ module PG::TestingHelpers
209
209
  begin
210
210
  unless (@test_pgdata+"postgresql.conf").exist?
211
211
  FileUtils.rm_rf( @test_pgdata, :verbose => $DEBUG )
212
- $stderr.puts "Running initdb"
212
+ trace "Running initdb"
213
213
  log_and_run @logfile, 'initdb', '-E', 'UTF8', '--no-locale', '-D', @test_pgdata.to_s
214
214
  end
215
215
 
@@ -218,7 +218,7 @@ module PG::TestingHelpers
218
218
  '-D', @test_pgdata.to_s, 'start'
219
219
  sleep 2
220
220
 
221
- $stderr.puts "Creating the test DB"
221
+ trace "Creating the test DB"
222
222
  log_and_run @logfile, 'psql', '-e', '-c', 'DROP DATABASE IF EXISTS test', 'postgres'
223
223
  log_and_run @logfile, 'createdb', '-e', 'test'
224
224
 
@@ -239,7 +239,7 @@ module PG::TestingHelpers
239
239
 
240
240
 
241
241
  def teardown_testing_db( conn )
242
- puts "Tearing down test database"
242
+ trace "Tearing down test database"
243
243
 
244
244
  if conn
245
245
  check_for_lingering_connections( conn )
@@ -376,5 +376,7 @@ RSpec.configure do |config|
376
376
  config.filter_run_excluding( :postgresql_93 ) if PG.library_version < 90300
377
377
  config.filter_run_excluding( :postgresql_94 ) if PG.library_version < 90400
378
378
  config.filter_run_excluding( :postgresql_95 ) if PG.library_version < 90500
379
+ config.filter_run_excluding( :postgresql_96 ) if PG.library_version < 90600
379
380
  config.filter_run_excluding( :postgresql_10 ) if PG.library_version < 100000
381
+ config.filter_run_excluding( :postgresql_12 ) if PG.library_version < 120000
380
382
  end
@@ -21,6 +21,14 @@ ensure
21
21
  end
22
22
  end
23
23
 
24
+ def expect_to_typecase_result_value_warning
25
+ warning = 'Warning: no type cast defined for type "name" with oid 19. '\
26
+ "Please cast this type explicitly to TEXT to be safe for future changes.\n"\
27
+ 'Warning: no type cast defined for type "regproc" with oid 24. '\
28
+ "Please cast this type explicitly to TEXT to be safe for future changes.\n"
29
+ expect { yield }.to output(warning).to_stderr
30
+ end
31
+
24
32
  describe 'Basic type mapping' do
25
33
 
26
34
  describe PG::BasicTypeMapForQueries do
@@ -32,8 +40,8 @@ describe 'Basic type mapping' do
32
40
  # Encoding Examples
33
41
  #
34
42
 
35
- it "should do basic param encoding", :ruby_19 do
36
- res = @conn.exec_params( "SELECT $1::int8,$2::float,$3,$4::TEXT",
43
+ it "should do basic param encoding" do
44
+ res = @conn.exec_params( "SELECT $1::int8, $2::float, $3, $4::TEXT",
37
45
  [1, 2.1, true, "b"], nil, basic_type_mapping )
38
46
 
39
47
  expect( res.values ).to eq( [
@@ -43,20 +51,126 @@ describe 'Basic type mapping' do
43
51
  expect( result_typenames(res) ).to eq( ['bigint', 'double precision', 'boolean', 'text'] )
44
52
  end
45
53
 
46
- it "should do array param encoding" do
47
- res = @conn.exec_params( "SELECT $1,$2,$3,$4", [
48
- [1, 2, 3], [[1, 2], [3, nil]],
49
- [1.11, 2.21],
50
- ['/,"'.gsub("/", "\\"), nil, 'abcäöü'],
54
+ it "should do basic Time encoding" do
55
+ res = @conn.exec_params( "SELECT $1 AT TIME ZONE '-02'",
56
+ [Time.new(2019, 12, 8, 20, 38, 12.123, "-01:00")], nil, basic_type_mapping )
57
+
58
+ expect( res.values ).to eq( [[ "2019-12-08 23:38:12.123" ]] )
59
+ end
60
+
61
+ it "should do basic param encoding of various float values" do
62
+ res = @conn.exec_params( "SELECT $1::float, $2::float, $3::float, $4::float, $5::float, $6::float, $7::float, $8::float, $9::float, $10::float, $11::float, $12::float",
63
+ [0, 7, 9, 0.1, 0.9, -0.11, 10.11,
64
+ 9876543210987654321e-400,
65
+ 9876543210987654321e400,
66
+ -1.234567890123456789e-280,
67
+ -1.234567890123456789e280,
68
+ 9876543210987654321e280
69
+ ], nil, basic_type_mapping )
70
+
71
+ expect( res.values[0][0, 9] ).to eq(
72
+ [ "0", "7", "9", "0.1", "0.9", "-0.11", "10.11", "0", "Infinity" ]
73
+ )
74
+
75
+ expect( res.values[0][9] ).to match( /^-1\.2345678901234\d*e\-280$/ )
76
+ expect( res.values[0][10] ).to match( /^-1\.2345678901234\d*e\+280$/ )
77
+ expect( res.values[0][11] ).to match( /^9\.8765432109876\d*e\+298$/ )
78
+
79
+ expect( result_typenames(res) ).to eq( ['double precision'] * 12 )
80
+ end
81
+
82
+ it "should do default array-as-array param encoding" do
83
+ expect( basic_type_mapping.encode_array_as).to eq(:array)
84
+ res = @conn.exec_params( "SELECT $1,$2,$3,$4,$5,$6", [
85
+ [1, 2, 3], # Integer -> bigint[]
86
+ [[1, 2], [3, nil]], # Integer two dimensions -> bigint[]
87
+ [1.11, 2.21], # Float -> double precision[]
88
+ ['/,"'.gsub("/", "\\"), nil, 'abcäöü'], # String -> text[]
89
+ [BigDecimal("123.45")], # BigDecimal -> numeric[]
90
+ [IPAddr.new('1234::5678')], # IPAddr -> inet[]
51
91
  ], nil, basic_type_mapping )
52
92
 
53
93
  expect( res.values ).to eq( [[
54
- '{1,2,3}', '{{1,2},{3,NULL}}',
94
+ '{1,2,3}',
95
+ '{{1,2},{3,NULL}}',
55
96
  '{1.11,2.21}',
56
97
  '{"//,/"",NULL,abcäöü}'.gsub("/", "\\"),
98
+ '{123.45}',
99
+ '{1234::5678}',
57
100
  ]] )
58
101
 
59
- expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]'] )
102
+ expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]', 'numeric[]', 'inet[]'] )
103
+ end
104
+
105
+ it "should do default array-as-array param encoding with Time objects" do
106
+ res = @conn.exec_params( "SELECT $1", [
107
+ [Time.new(2019, 12, 8, 20, 38, 12.123, "-01:00")], # Time -> timestamptz[]
108
+ ], nil, basic_type_mapping )
109
+
110
+ expect( res.values[0][0] ).to match( /\{\"2019-12-08 \d\d:38:12.123[+-]\d\d\"\}/ )
111
+ expect( result_typenames(res) ).to eq( ['timestamp with time zone[]'] )
112
+ end
113
+
114
+ it "should do array-as-json encoding" do
115
+ basic_type_mapping.encode_array_as = :json
116
+ expect( basic_type_mapping.encode_array_as).to eq(:json)
117
+
118
+ res = @conn.exec_params( "SELECT $1::JSON, $2::JSON", [
119
+ [1, {a: 5}, true, ["a", 2], [3.4, nil]],
120
+ ['/,"'.gsub("/", "\\"), nil, 'abcäöü'],
121
+ ], nil, basic_type_mapping )
122
+
123
+ expect( res.values ).to eq( [[
124
+ '[1,{"a":5},true,["a",2],[3.4,null]]',
125
+ '["//,/"",null,"abcäöü"]'.gsub("/", "\\"),
126
+ ]] )
127
+
128
+ expect( result_typenames(res) ).to eq( ['json', 'json'] )
129
+ end
130
+
131
+ it "should do hash-as-json encoding" do
132
+ res = @conn.exec_params( "SELECT $1::JSON, $2::JSON", [
133
+ {a: 5, b: ["a", 2], c: nil},
134
+ {qu: '/,"'.gsub("/", "\\"), ni: nil, uml: 'abcäöü'},
135
+ ], nil, basic_type_mapping )
136
+
137
+ expect( res.values ).to eq( [[
138
+ '{"a":5,"b":["a",2],"c":null}',
139
+ '{"qu":"//,/"","ni":null,"uml":"abcäöü"}'.gsub("/", "\\"),
140
+ ]] )
141
+
142
+ expect( result_typenames(res) ).to eq( ['json', 'json'] )
143
+ end
144
+
145
+ describe "Record encoding" do
146
+ before :all do
147
+ @conn.exec("CREATE TYPE test_record1 AS (i int, d float, t text)")
148
+ @conn.exec("CREATE TYPE test_record2 AS (i int, r test_record1)")
149
+ end
150
+
151
+ after :all do
152
+ @conn.exec("DROP TYPE IF EXISTS test_record2 CASCADE")
153
+ @conn.exec("DROP TYPE IF EXISTS test_record1 CASCADE")
154
+ end
155
+
156
+ it "should do array-as-record encoding" do
157
+ basic_type_mapping.encode_array_as = :record
158
+ expect( basic_type_mapping.encode_array_as).to eq(:record)
159
+
160
+ res = @conn.exec_params( "SELECT $1::test_record1, $2::test_record2, $3::text", [
161
+ [5, 3.4, "txt"],
162
+ [1, [2, 4.5, "bcd"]],
163
+ [4, 5, 6],
164
+ ], nil, basic_type_mapping )
165
+
166
+ expect( res.values ).to eq( [[
167
+ '(5,3.4,txt)',
168
+ '(1,"(2,4.5,bcd)")',
169
+ '("4","5","6")',
170
+ ]] )
171
+
172
+ expect( result_typenames(res) ).to eq( ['test_record1', 'test_record2', 'text'] )
173
+ end
60
174
  end
61
175
 
62
176
  it "should do bigdecimal param encoding" do
@@ -82,6 +196,23 @@ describe 'Basic type mapping' do
82
196
  expect( result_typenames(res) ).to eq( ['inet', 'inet', 'cidr', 'cidr'] )
83
197
  end
84
198
 
199
+ it "should do array of string encoding on unknown classes" do
200
+ iv = Class.new do
201
+ def to_s
202
+ "abc"
203
+ end
204
+ end.new
205
+ res = @conn.exec_params( "SELECT $1", [
206
+ [iv, iv], # Unknown -> text[]
207
+ ], nil, basic_type_mapping )
208
+
209
+ expect( res.values ).to eq( [[
210
+ '{abc,abc}',
211
+ ]] )
212
+
213
+ expect( result_typenames(res) ).to eq( ['text[]'] )
214
+ end
215
+
85
216
  end
86
217
 
87
218
 
@@ -95,7 +226,7 @@ describe 'Basic type mapping' do
95
226
  # Decoding Examples
96
227
  #
97
228
 
98
- it "should do OID based type conversions", :ruby_19 do
229
+ it "should do OID based type conversions" do
99
230
  res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, TRUE, '2013-06-30'::DATE, generate_series(4,5)" )
100
231
  expect( res.map_types!(basic_type_mapping).values ).to eq( [
101
232
  [ 1, 'a', 2.0, true, Date.new(2013,6,30), 4 ],
@@ -187,7 +318,9 @@ describe 'Basic type mapping' do
187
318
  it "should convert format #{format} timestamps per TimestampUtc" do
188
319
  restore_type("timestamp") do
189
320
  PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampUtc
190
- @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
321
+ expect_to_typecase_result_value_warning do
322
+ @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
323
+ end
191
324
  res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
192
325
  CAST('1913-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
193
326
  CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
@@ -209,7 +342,9 @@ describe 'Basic type mapping' do
209
342
  restore_type("timestamp") do
210
343
  PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampUtcToLocal
211
344
  PG::BasicTypeRegistry.register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampUtcToLocal
212
- @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
345
+ expect_to_typecase_result_value_warning do
346
+ @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
347
+ end
213
348
  res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
214
349
  CAST('1913-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
215
350
  CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
@@ -231,7 +366,9 @@ describe 'Basic type mapping' do
231
366
  restore_type("timestamp") do
232
367
  PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampLocal
233
368
  PG::BasicTypeRegistry.register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampLocal
234
- @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
369
+ expect_to_typecase_result_value_warning do
370
+ @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
371
+ end
235
372
  res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59' AS TIMESTAMP WITHOUT TIME ZONE),
236
373
  CAST('1913-12-31 23:58:59.1231' AS TIMESTAMP WITHOUT TIME ZONE),
237
374
  CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
@@ -288,7 +288,20 @@ describe PG::Connection do
288
288
  expect( @conn.host ).to eq( "localhost" )
289
289
  end
290
290
 
291
- EXPECTED_TRACE_OUTPUT = %{
291
+ it "can set error verbosity" do
292
+ old = @conn.set_error_verbosity( PG::PQERRORS_TERSE )
293
+ new = @conn.set_error_verbosity( old )
294
+ expect( new ).to eq( PG::PQERRORS_TERSE )
295
+ end
296
+
297
+ it "can set error context visibility", :postgresql_96 do
298
+ old = @conn.set_error_context_visibility( PG::PQSHOW_CONTEXT_NEVER )
299
+ new = @conn.set_error_context_visibility( old )
300
+ expect( new ).to eq( PG::PQSHOW_CONTEXT_NEVER )
301
+ end
302
+
303
+ let(:expected_trace_output) do
304
+ %{
292
305
  To backend> Msg Q
293
306
  To backend> "SELECT 1 AS one"
294
307
  To backend> Msg complete, length 21
@@ -316,6 +329,7 @@ describe PG::Connection do
316
329
  From backend (#4)> 5
317
330
  From backend> T
318
331
  }.gsub( /^\t{2}/, '' ).lstrip
332
+ end
319
333
 
320
334
  it "trace and untrace client-server communication", :unix do
321
335
  # be careful to explicitly close files so that the
@@ -341,7 +355,7 @@ describe PG::Connection do
341
355
  # From backend> T
342
356
  trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
343
357
 
344
- expect( trace_data ).to eq( EXPECTED_TRACE_OUTPUT )
358
+ expect( trace_data ).to eq( expected_trace_output )
345
359
  end
346
360
 
347
361
  it "allows a query to be cancelled" do
@@ -356,8 +370,6 @@ describe PG::Connection do
356
370
  end
357
371
 
358
372
  it "can stop a thread that runs a blocking query with async_exec" do
359
- pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
360
-
361
373
  start = Time.now
362
374
  t = Thread.new do
363
375
  @conn.async_exec( 'select pg_sleep(10)' )
@@ -371,24 +383,16 @@ describe PG::Connection do
371
383
 
372
384
  it "should work together with signal handlers", :unix do
373
385
  signal_received = false
374
- trap 'USR1' do
386
+ trap 'USR2' do
375
387
  signal_received = true
376
388
  end
377
389
 
378
390
  Thread.new do
379
391
  sleep 0.1
380
- Process.kill("USR1", Process.pid)
392
+ Process.kill("USR2", Process.pid)
381
393
  end
382
394
  @conn.exec("select pg_sleep(0.3)")
383
395
  expect( signal_received ).to be_truthy
384
-
385
- signal_received = false
386
- Thread.new do
387
- sleep 0.1
388
- Process.kill("USR1", Process.pid)
389
- end
390
- @conn.async_exec("select pg_sleep(0.3)")
391
- expect( signal_received ).to be_truthy
392
396
  end
393
397
 
394
398
 
@@ -571,7 +575,7 @@ describe PG::Connection do
571
575
  expect( @conn.wait_for_notify( 1 ) ).to be_nil
572
576
  expect( notices.first ).to_not be_nil
573
577
  et = Time.now
574
- expect( (et - notices.first[1]) ).to be >= 0.4
578
+ expect( (et - notices.first[1]) ).to be >= 0.3
575
579
  expect( (et - st) ).to be >= 0.9
576
580
  expect( (et - st) ).to be < 1.4
577
581
  end
@@ -675,7 +679,7 @@ describe PG::Connection do
675
679
  @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
676
680
  @conn.put_copy_data "xyz\n"
677
681
  end
678
- }.to raise_error(PG::Error, /invalid input syntax for integer/)
682
+ }.to raise_error(PG::Error, /invalid input syntax for .*integer/)
679
683
  end
680
684
  expect( @conn ).to still_be_usable
681
685
  end
@@ -1233,53 +1237,41 @@ describe PG::Connection do
1233
1237
 
1234
1238
  end
1235
1239
 
1236
- context "multinationalization support", :ruby_19 do
1240
+ context "multinationalization support" do
1237
1241
 
1238
1242
  describe "rubyforge #22925: m17n support" do
1239
1243
  it "should return results in the same encoding as the client (iso-8859-1)" do
1240
- out_string = nil
1241
- @conn.transaction do |conn|
1242
- conn.internal_encoding = 'iso8859-1'
1243
- res = conn.exec_params("VALUES ('fantasia')", [], 0)
1244
- out_string = res[0]['column1']
1245
- end
1244
+ @conn.internal_encoding = 'iso8859-1'
1245
+ res = @conn.exec_params("VALUES ('fantasia')", [], 0)
1246
+ out_string = res[0]['column1']
1246
1247
  expect( out_string ).to eq( 'fantasia' )
1247
1248
  expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
1248
1249
  end
1249
1250
 
1250
1251
  it "should return results in the same encoding as the client (utf-8)" do
1251
- out_string = nil
1252
- @conn.transaction do |conn|
1253
- conn.internal_encoding = 'utf-8'
1254
- res = conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1255
- out_string = res[0]['column1']
1256
- end
1252
+ @conn.internal_encoding = 'utf-8'
1253
+ res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1254
+ out_string = res[0]['column1']
1257
1255
  expect( out_string ).to eq( '世界線航跡蔵' )
1258
1256
  expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1259
1257
  end
1260
1258
 
1261
1259
  it "should return results in the same encoding as the client (EUC-JP)" do
1262
- out_string = nil
1263
- @conn.transaction do |conn|
1264
- conn.internal_encoding = 'EUC-JP'
1265
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1266
- res = conn.exec_params(stmt, [], 0)
1267
- out_string = res[0]['column1']
1268
- end
1260
+ @conn.internal_encoding = 'EUC-JP'
1261
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1262
+ res = @conn.exec_params(stmt, [], 0)
1263
+ out_string = res[0]['column1']
1269
1264
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1270
1265
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1271
1266
  end
1272
1267
 
1273
1268
  it "returns the results in the correct encoding even if the client_encoding has " +
1274
1269
  "changed since the results were fetched" do
1275
- out_string = nil
1276
- @conn.transaction do |conn|
1277
- conn.internal_encoding = 'EUC-JP'
1278
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1279
- res = conn.exec_params(stmt, [], 0)
1280
- conn.internal_encoding = 'utf-8'
1281
- out_string = res[0]['column1']
1282
- end
1270
+ @conn.internal_encoding = 'EUC-JP'
1271
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1272
+ res = @conn.exec_params(stmt, [], 0)
1273
+ @conn.internal_encoding = 'utf-8'
1274
+ out_string = res[0]['column1']
1283
1275
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1284
1276
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1285
1277
  end
@@ -1358,6 +1350,21 @@ describe PG::Connection do
1358
1350
  expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
1359
1351
  expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
1360
1352
  end
1353
+
1354
+ it "can use an encoding with high index for client encoding" do
1355
+ # Allocate a lot of encoding indices, so that MRI's ENCODING_INLINE_MAX is exceeded
1356
+ unless Encoding.name_list.include?("pgtest-0")
1357
+ 256.times do |eidx|
1358
+ Encoding::UTF_8.replicate("pgtest-#{eidx}")
1359
+ end
1360
+ end
1361
+
1362
+ # Now allocate the JOHAB encoding with an unusual high index
1363
+ @conn.set_client_encoding "JOHAB"
1364
+ val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
1365
+ expect( val.encoding.name ).to eq( "JOHAB" )
1366
+ end
1367
+
1361
1368
  end
1362
1369
 
1363
1370
  describe "respect and convert character encoding of input strings" do
@@ -1501,7 +1508,7 @@ describe PG::Connection do
1501
1508
 
1502
1509
  describe "Ruby 1.9.x default_internal encoding" do
1503
1510
 
1504
- it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
1511
+ it "honors the Encoding.default_internal if it's set and the synchronous interface is used", :without_transaction do
1505
1512
  @conn.transaction do |txn_conn|
1506
1513
  txn_conn.internal_encoding = Encoding::ISO8859_1
1507
1514
  txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
@@ -1603,9 +1610,8 @@ describe PG::Connection do
1603
1610
  end
1604
1611
  end
1605
1612
 
1606
- it "receives properly encoded text from wait_for_notify" do
1613
+ it "receives properly encoded text from wait_for_notify", :without_transaction do
1607
1614
  @conn.internal_encoding = 'utf-8'
1608
- @conn.exec( 'ROLLBACK' )
1609
1615
  @conn.exec( 'LISTEN "Möhre"' )
1610
1616
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1611
1617
  event, pid, msg = nil
@@ -1620,9 +1626,8 @@ describe PG::Connection do
1620
1626
  expect( msg.encoding ).to eq( Encoding::UTF_8 )
1621
1627
  end
1622
1628
 
1623
- it "returns properly encoded text from notifies" do
1629
+ it "returns properly encoded text from notifies", :without_transaction do
1624
1630
  @conn.internal_encoding = 'utf-8'
1625
- @conn.exec( 'ROLLBACK' )
1626
1631
  @conn.exec( 'LISTEN "Möhre"' )
1627
1632
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1628
1633
  @conn.exec( 'UNLISTEN "Möhre"' )
@@ -1636,7 +1641,7 @@ describe PG::Connection do
1636
1641
  end
1637
1642
  end
1638
1643
 
1639
- context "OS thread support", :ruby_19 do
1644
+ context "OS thread support" do
1640
1645
  it "Connection#exec shouldn't block a second thread" do
1641
1646
  t = Thread.new do
1642
1647
  @conn.exec( "select pg_sleep(1)" )
@@ -1831,6 +1836,40 @@ describe PG::Connection do
1831
1836
  end
1832
1837
  end
1833
1838
 
1839
+ describe :field_name_type do
1840
+ before :each do
1841
+ @conn2 = PG.connect(@conninfo)
1842
+ end
1843
+ after :each do
1844
+ @conn2.close
1845
+ end
1846
+
1847
+ it "uses string field names per default" do
1848
+ expect(@conn2.field_name_type).to eq(:string)
1849
+ end
1850
+
1851
+ it "can set string field names" do
1852
+ @conn2.field_name_type = :string
1853
+ expect(@conn2.field_name_type).to eq(:string)
1854
+ res = @conn2.exec("SELECT 1 as az")
1855
+ expect(res.field_name_type).to eq(:string)
1856
+ expect(res.fields).to eq(["az"])
1857
+ end
1858
+
1859
+ it "can set symbol field names" do
1860
+ @conn2.field_name_type = :symbol
1861
+ expect(@conn2.field_name_type).to eq(:symbol)
1862
+ res = @conn2.exec("SELECT 1 as az")
1863
+ expect(res.field_name_type).to eq(:symbol)
1864
+ expect(res.fields).to eq([:az])
1865
+ end
1866
+
1867
+ it "can't set invalid values" do
1868
+ expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
1869
+ expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
1870
+ end
1871
+ end
1872
+
1834
1873
  describe "deprecated forms of methods" do
1835
1874
  it "should forward exec to exec_params" do
1836
1875
  res = @conn.exec("VALUES($1::INT)", [7]).values