pg 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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