pg 0.17.1 → 1.2.3

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 (86) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/BSDL +2 -2
  4. data/ChangeLog +0 -3506
  5. data/History.rdoc +308 -0
  6. data/Manifest.txt +35 -19
  7. data/README-Windows.rdoc +17 -28
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +113 -14
  10. data/Rakefile +67 -30
  11. data/Rakefile.cross +109 -83
  12. data/ext/errorcodes.def +101 -0
  13. data/ext/errorcodes.rb +1 -1
  14. data/ext/errorcodes.txt +33 -2
  15. data/ext/extconf.rb +55 -58
  16. data/ext/gvl_wrappers.c +4 -0
  17. data/ext/gvl_wrappers.h +27 -39
  18. data/ext/pg.c +262 -130
  19. data/ext/pg.h +266 -54
  20. data/ext/pg_binary_decoder.c +229 -0
  21. data/ext/pg_binary_encoder.c +163 -0
  22. data/ext/pg_coder.c +561 -0
  23. data/ext/pg_connection.c +1689 -990
  24. data/ext/pg_copy_coder.c +599 -0
  25. data/ext/pg_errors.c +6 -0
  26. data/ext/pg_record_coder.c +491 -0
  27. data/ext/pg_result.c +897 -164
  28. data/ext/pg_text_decoder.c +987 -0
  29. data/ext/pg_text_encoder.c +814 -0
  30. data/ext/pg_tuple.c +549 -0
  31. data/ext/pg_type_map.c +166 -0
  32. data/ext/pg_type_map_all_strings.c +116 -0
  33. data/ext/pg_type_map_by_class.c +244 -0
  34. data/ext/pg_type_map_by_column.c +313 -0
  35. data/ext/pg_type_map_by_mri_type.c +284 -0
  36. data/ext/pg_type_map_by_oid.c +356 -0
  37. data/ext/pg_type_map_in_ruby.c +299 -0
  38. data/ext/pg_util.c +149 -0
  39. data/ext/pg_util.h +65 -0
  40. data/lib/pg/basic_type_mapping.rb +522 -0
  41. data/lib/pg/binary_decoder.rb +23 -0
  42. data/lib/pg/coder.rb +104 -0
  43. data/lib/pg/connection.rb +153 -41
  44. data/lib/pg/constants.rb +2 -1
  45. data/lib/pg/exceptions.rb +2 -1
  46. data/lib/pg/result.rb +33 -6
  47. data/lib/pg/text_decoder.rb +46 -0
  48. data/lib/pg/text_encoder.rb +59 -0
  49. data/lib/pg/tuple.rb +30 -0
  50. data/lib/pg/type_map_by_column.rb +16 -0
  51. data/lib/pg.rb +29 -9
  52. data/spec/{lib/helpers.rb → helpers.rb} +151 -64
  53. data/spec/pg/basic_type_mapping_spec.rb +630 -0
  54. data/spec/pg/connection_spec.rb +1180 -477
  55. data/spec/pg/connection_sync_spec.rb +41 -0
  56. data/spec/pg/result_spec.rb +456 -120
  57. data/spec/pg/tuple_spec.rb +333 -0
  58. data/spec/pg/type_map_by_class_spec.rb +138 -0
  59. data/spec/pg/type_map_by_column_spec.rb +226 -0
  60. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  61. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  62. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  63. data/spec/pg/type_map_spec.rb +22 -0
  64. data/spec/pg/type_spec.rb +1123 -0
  65. data/spec/pg_spec.rb +26 -20
  66. data.tar.gz.sig +0 -0
  67. metadata +148 -91
  68. metadata.gz.sig +0 -0
  69. data/sample/array_insert.rb +0 -20
  70. data/sample/async_api.rb +0 -106
  71. data/sample/async_copyto.rb +0 -39
  72. data/sample/async_mixed.rb +0 -56
  73. data/sample/check_conn.rb +0 -21
  74. data/sample/copyfrom.rb +0 -81
  75. data/sample/copyto.rb +0 -19
  76. data/sample/cursor.rb +0 -21
  77. data/sample/disk_usage_report.rb +0 -186
  78. data/sample/issue-119.rb +0 -94
  79. data/sample/losample.rb +0 -69
  80. data/sample/minimal-testcase.rb +0 -17
  81. data/sample/notify_wait.rb +0 -72
  82. data/sample/pg_statistics.rb +0 -294
  83. data/sample/replication_monitor.rb +0 -231
  84. data/sample/test_binary_values.rb +0 -33
  85. data/sample/wal_shipper.rb +0 -434
  86. data/sample/warehouse_partitions.rb +0 -320
@@ -1,49 +1,14 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  #encoding: utf-8
3
3
 
4
- BEGIN {
5
- require 'pathname'
4
+ require_relative '../helpers'
6
5
 
7
- basedir = Pathname( __FILE__ ).dirname.parent.parent
8
- libdir = basedir + 'lib'
9
-
10
- $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
11
- $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
12
- }
13
-
14
- require 'rspec'
15
- require 'spec/lib/helpers'
16
6
  require 'timeout'
17
7
  require 'socket'
18
8
  require 'pg'
19
9
 
20
10
  describe PG::Connection do
21
11
 
22
- before( :all ) do
23
- @conn = setup_testing_db( described_class.name )
24
- end
25
-
26
- before( :each ) do
27
- @conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
28
- if PG.respond_to?( :library_version )
29
- @conn.exec_params %Q{SET application_name TO '%s'} %
30
- [@conn.escape_string(example.description[0,60])]
31
- end
32
- end
33
-
34
- after( :each ) do
35
- @conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
36
- end
37
-
38
- after( :all ) do
39
- teardown_testing_db( @conn )
40
- end
41
-
42
-
43
- #
44
- # Examples
45
- #
46
-
47
12
  it "can create a connection option string from a Hash of options" do
48
13
  optstring = described_class.parse_connect_args(
49
14
  :host => 'pgsql.example.com',
@@ -51,54 +16,130 @@ describe PG::Connection do
51
16
  'sslmode' => 'require'
52
17
  )
53
18
 
54
- optstring.should be_a( String )
55
- optstring.should =~ /(^|\s)host='pgsql.example.com'/
56
- optstring.should =~ /(^|\s)dbname='db01'/
57
- optstring.should =~ /(^|\s)sslmode='require'/
19
+ expect( optstring ).to be_a( String )
20
+ expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
21
+ expect( optstring ).to match( /(^|\s)dbname='db01'/ )
22
+ expect( optstring ).to match( /(^|\s)sslmode='require'/ )
58
23
  end
59
24
 
60
25
  it "can create a connection option string from positional parameters" do
61
26
  optstring = described_class.parse_connect_args( 'pgsql.example.com', nil, '-c geqo=off', nil,
62
27
  'sales' )
63
28
 
64
- optstring.should be_a( String )
65
- optstring.should =~ /(^|\s)host='pgsql.example.com'/
66
- optstring.should =~ /(^|\s)dbname='sales'/
67
- optstring.should =~ /(^|\s)options='-c geqo=off'/
29
+ expect( optstring ).to be_a( String )
30
+ expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
31
+ expect( optstring ).to match( /(^|\s)dbname='sales'/ )
32
+ expect( optstring ).to match( /(^|\s)options='-c geqo=off'/ )
68
33
 
69
- optstring.should_not =~ /port=/
70
- optstring.should_not =~ /tty=/
34
+ expect( optstring ).to_not match( /port=/ )
35
+ expect( optstring ).to_not match( /tty=/ )
71
36
  end
72
37
 
73
38
  it "can create a connection option string from a mix of positional and hash parameters" do
74
39
  optstring = described_class.parse_connect_args( 'pgsql.example.com',
75
40
  :dbname => 'licensing', :user => 'jrandom' )
76
41
 
77
- optstring.should be_a( String )
78
- optstring.should =~ /(^|\s)host='pgsql.example.com'/
79
- optstring.should =~ /(^|\s)dbname='licensing'/
80
- optstring.should =~ /(^|\s)user='jrandom'/
42
+ expect( optstring ).to be_a( String )
43
+ expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
44
+ expect( optstring ).to match( /(^|\s)dbname='licensing'/ )
45
+ expect( optstring ).to match( /(^|\s)user='jrandom'/ )
46
+ end
47
+
48
+ it "can create a connection option string from an option string and a hash" do
49
+ optstring = described_class.parse_connect_args( 'dbname=original', :user => 'jrandom' )
50
+
51
+ expect( optstring ).to be_a( String )
52
+ expect( optstring ).to match( /(^|\s)dbname=original/ )
53
+ expect( optstring ).to match( /(^|\s)user='jrandom'/ )
81
54
  end
82
55
 
83
56
  it "escapes single quotes and backslashes in connection parameters" do
84
- described_class.parse_connect_args( "DB 'browser' \\" ).
85
- should =~ /host='DB \\'browser\\' \\\\'/
57
+ expect(
58
+ described_class.parse_connect_args( "DB 'browser' \\" )
59
+ ).to match( /host='DB \\'browser\\' \\\\'/ )
60
+
61
+ end
62
+
63
+ let(:uri) { 'postgresql://user:pass@pgsql.example.com:222/db01?sslmode=require' }
64
+
65
+ it "can connect using a URI" do
66
+ string = described_class.parse_connect_args( uri )
67
+
68
+ expect( string ).to be_a( String )
69
+ expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
70
+ expect( string ).to match( %r{\?.*sslmode=require} )
71
+
72
+ string = described_class.parse_connect_args( URI.parse(uri) )
73
+
74
+ expect( string ).to be_a( String )
75
+ expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
76
+ expect( string ).to match( %r{\?.*sslmode=require} )
77
+ end
78
+
79
+ it "can create a connection URI from a URI and a hash" do
80
+ string = described_class.parse_connect_args( uri, :connect_timeout => 2 )
81
+
82
+ expect( string ).to be_a( String )
83
+ expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
84
+ expect( string ).to match( %r{\?.*sslmode=require} )
85
+ expect( string ).to match( %r{\?.*connect_timeout=2} )
86
86
 
87
+ string = described_class.parse_connect_args( uri,
88
+ :user => 'a',
89
+ :password => 'b',
90
+ :host => 'localhost',
91
+ :port => 555,
92
+ :dbname => 'x' )
93
+
94
+ expect( string ).to be_a( String )
95
+ expect( string ).to match( %r{^postgresql://\?} )
96
+ expect( string ).to match( %r{\?.*user=a} )
97
+ expect( string ).to match( %r{\?.*password=b} )
98
+ expect( string ).to match( %r{\?.*host=localhost} )
99
+ expect( string ).to match( %r{\?.*port=555} )
100
+ expect( string ).to match( %r{\?.*dbname=x} )
101
+ end
102
+
103
+ it "can create a connection URI with a non-standard domain socket directory" do
104
+ string = described_class.parse_connect_args( 'postgresql://%2Fvar%2Flib%2Fpostgresql/dbname' )
105
+
106
+ expect( string ).to be_a( String )
107
+ expect( string ).to match( %r{^postgresql://%2Fvar%2Flib%2Fpostgresql/dbname} )
108
+
109
+ string = described_class.
110
+ parse_connect_args( 'postgresql:///dbname', :host => '/var/lib/postgresql' )
111
+
112
+ expect( string ).to be_a( String )
113
+ expect( string ).to match( %r{^postgresql:///dbname\?} )
114
+ expect( string ).to match( %r{\?.*host=%2Fvar%2Flib%2Fpostgresql} )
87
115
  end
88
116
 
89
117
  it "connects with defaults if no connection parameters are given" do
90
- described_class.parse_connect_args.should == ''
118
+ expect( described_class.parse_connect_args ).to eq( '' )
91
119
  end
92
120
 
93
121
  it "connects successfully with connection string" do
94
- tmpconn = described_class.connect(@conninfo)
95
- tmpconn.status.should == PG::CONNECTION_OK
122
+ conninfo_with_colon_in_password = "host=localhost user=a port=555 dbname=test password=a:a"
123
+
124
+ string = described_class.parse_connect_args( conninfo_with_colon_in_password )
125
+
126
+ expect( string ).to be_a( String )
127
+ expect( string ).to match( %r{(^|\s)user=a} )
128
+ expect( string ).to match( %r{(^|\s)password=a:a} )
129
+ expect( string ).to match( %r{(^|\s)host=localhost} )
130
+ expect( string ).to match( %r{(^|\s)port=555} )
131
+ expect( string ).to match( %r{(^|\s)dbname=test} )
132
+ end
133
+
134
+ it "connects successfully with connection string" do
135
+ tmpconn = described_class.connect( @conninfo )
136
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
96
137
  tmpconn.finish
97
138
  end
98
139
 
99
140
  it "connects using 7 arguments converted to strings" do
100
- tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
101
- tmpconn.status.should == PG::CONNECTION_OK
141
+ tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
142
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
102
143
  tmpconn.finish
103
144
  end
104
145
 
@@ -107,74 +148,117 @@ describe PG::Connection do
107
148
  :host => 'localhost',
108
149
  :port => @port,
109
150
  :dbname => :test)
110
- tmpconn.status.should == PG::CONNECTION_OK
151
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
111
152
  tmpconn.finish
112
153
  end
113
154
 
114
- it "connects using a hash of optional connection parameters", :postgresql_90 do
155
+ it "connects using a hash of optional connection parameters" do
115
156
  tmpconn = described_class.connect(
116
157
  :host => 'localhost',
117
158
  :port => @port,
118
159
  :dbname => :test,
119
160
  :keepalives => 1)
120
- tmpconn.status.should == PG::CONNECTION_OK
161
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
121
162
  tmpconn.finish
122
163
  end
123
164
 
124
165
  it "raises an exception when connecting with an invalid number of arguments" do
125
166
  expect {
126
- described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
127
- }.to raise_error( ArgumentError, /extra positional parameter/i )
167
+ described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'the-extra-arg' )
168
+ }.to raise_error do |error|
169
+ expect( error ).to be_an( ArgumentError )
170
+ expect( error.message ).to match( /extra positional parameter/i )
171
+ expect( error.message ).to match( /8/ )
172
+ expect( error.message ).to match( /the-extra-arg/ )
173
+ end
128
174
  end
129
175
 
130
- it "can connect asynchronously", :socket_io do
176
+ it "can connect asynchronously" do
131
177
  tmpconn = described_class.connect_start( @conninfo )
132
- tmpconn.should be_a( described_class )
133
- socket = tmpconn.socket_io
134
- status = tmpconn.connect_poll
135
-
136
- while status != PG::PGRES_POLLING_OK
137
- if status == PG::PGRES_POLLING_READING
138
- select( [socket], [], [], 5.0 ) or
139
- raise "Asynchronous connection timed out!"
178
+ expect( tmpconn ).to be_a( described_class )
140
179
 
141
- elsif status == PG::PGRES_POLLING_WRITING
142
- select( [], [socket], [], 5.0 ) or
143
- raise "Asynchronous connection timed out!"
144
- end
145
- status = tmpconn.connect_poll
146
- end
147
-
148
- tmpconn.status.should == PG::CONNECTION_OK
180
+ wait_for_polling_ok(tmpconn)
181
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
149
182
  tmpconn.finish
150
183
  end
151
184
 
152
- it "can connect asynchronously for the duration of a block", :socket_io do
185
+ it "can connect asynchronously for the duration of a block" do
153
186
  conn = nil
154
187
 
155
188
  described_class.connect_start(@conninfo) do |tmpconn|
156
- tmpconn.should be_a( described_class )
189
+ expect( tmpconn ).to be_a( described_class )
157
190
  conn = tmpconn
158
- socket = tmpconn.socket_io
159
- status = tmpconn.connect_poll
160
191
 
161
- while status != PG::PGRES_POLLING_OK
162
- if status == PG::PGRES_POLLING_READING
163
- if(not select([socket],[],[],5.0))
164
- raise "Asynchronous connection timed out!"
165
- end
166
- elsif(status == PG::PGRES_POLLING_WRITING)
167
- if(not select([],[socket],[],5.0))
168
- raise "Asynchronous connection timed out!"
169
- end
170
- end
171
- status = tmpconn.connect_poll
172
- end
192
+ wait_for_polling_ok(tmpconn)
193
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
194
+ end
195
+
196
+ expect( conn ).to be_finished()
197
+ end
198
+
199
+ context "with async established connection" do
200
+ before :each do
201
+ @conn2 = described_class.connect_start( @conninfo )
202
+ wait_for_polling_ok(@conn2)
203
+ expect( @conn2 ).to still_be_usable
204
+ end
205
+
206
+ after :each do
207
+ expect( @conn2 ).to still_be_usable
208
+ @conn2.close
209
+ end
210
+
211
+ it "conn.send_query and IO.select work" do
212
+ @conn2.send_query("SELECT 1")
213
+ res = wait_for_query_result(@conn2)
214
+ expect( res.values ).to eq([["1"]])
215
+ end
173
216
 
174
- tmpconn.status.should == PG::CONNECTION_OK
217
+ it "conn.send_query and conn.block work" do
218
+ @conn2.send_query("SELECT 2")
219
+ @conn2.block
220
+ res = @conn2.get_last_result
221
+ expect( res.values ).to eq([["2"]])
175
222
  end
176
223
 
177
- conn.should be_finished()
224
+ it "conn.async_query works" do
225
+ res = @conn2.async_query("SELECT 3")
226
+ expect( res.values ).to eq([["3"]])
227
+ expect( @conn2 ).to still_be_usable
228
+
229
+ res = @conn2.query("SELECT 4")
230
+ end
231
+
232
+ it "can use conn.reset_start to restart the connection" do
233
+ ios = IO.pipe
234
+ conn = described_class.connect_start( @conninfo )
235
+ wait_for_polling_ok(conn)
236
+
237
+ # Close the two pipe file descriptors, so that the file descriptor of
238
+ # newly established connection is probably distinct from the previous one.
239
+ ios.each(&:close)
240
+ conn.reset_start
241
+ wait_for_polling_ok(conn, :reset_poll)
242
+
243
+ # The new connection should work even when the file descriptor has changed.
244
+ conn.send_query("SELECT 1")
245
+ res = wait_for_query_result(conn)
246
+ expect( res.values ).to eq([["1"]])
247
+
248
+ conn.close
249
+ end
250
+
251
+ it "should properly close a socket IO when GC'ed" do
252
+ # This results in
253
+ # Errno::ENOTSOCK: An operation was attempted on something that is not a socket.
254
+ # on Windows when rb_w32_unwrap_io_handle() isn't called in pgconn_gc_free().
255
+ 5.times do
256
+ conn = described_class.connect( @conninfo )
257
+ conn.socket_io.close
258
+ end
259
+ GC.start
260
+ IO.pipe.each(&:close)
261
+ end
178
262
  end
179
263
 
180
264
  it "raises proper error when sending fails" do
@@ -186,13 +270,38 @@ describe PG::Connection do
186
270
  described_class.connect(@conninfo).finish
187
271
  sleep 0.5
188
272
  res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
189
- WHERE usename IS NOT NULL])
273
+ WHERE usename IS NOT NULL AND application_name != ''])
190
274
  # there's still the global @conn, but should be no more
191
- res[0]['n'].should == '1'
275
+ expect( res[0]['n'] ).to eq( '1' )
276
+ end
277
+
278
+ it "can retrieve it's connection parameters for the established connection" do
279
+ expect( @conn.db ).to eq( "test" )
280
+ expect( @conn.user ).to be_a_kind_of( String )
281
+ expect( @conn.pass ).to eq( "" )
282
+ expect( @conn.port ).to eq( @port )
283
+ expect( @conn.tty ).to eq( "" )
284
+ expect( @conn.options ).to eq( "" )
285
+ end
286
+ it "can retrieve it's connection parameters for the established connection",
287
+ skip: RUBY_PLATFORM=~/x64-mingw/ ? "host segfaults on Windows-x64" : false do
288
+ expect( @conn.host ).to eq( "localhost" )
192
289
  end
193
290
 
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
194
302
 
195
- EXPECTED_TRACE_OUTPUT = %{
303
+ let(:expected_trace_output) do
304
+ %{
196
305
  To backend> Msg Q
197
306
  To backend> "SELECT 1 AS one"
198
307
  To backend> Msg complete, length 21
@@ -220,6 +329,7 @@ describe PG::Connection do
220
329
  From backend (#4)> 5
221
330
  From backend> T
222
331
  }.gsub( /^\t{2}/, '' ).lstrip
332
+ end
223
333
 
224
334
  it "trace and untrace client-server communication", :unix do
225
335
  # be careful to explicitly close files so that the
@@ -230,25 +340,22 @@ describe PG::Connection do
230
340
  @conn.trace( trace_io )
231
341
  trace_io.close
232
342
 
233
- res = @conn.exec("SELECT 1 AS one")
343
+ @conn.exec("SELECT 1 AS one")
234
344
  @conn.untrace
235
345
 
236
- res = @conn.exec("SELECT 2 AS two")
346
+ @conn.exec("SELECT 2 AS two")
237
347
 
238
348
  trace_data = trace_file.read
239
349
 
240
- expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
241
- # For PostgreSQL < 9.0, the output will be different:
242
- # -From backend (#4)> 13
243
- # -From backend> "SELECT 1"
244
- # +From backend (#4)> 11
245
- # +From backend> "SELECT"
246
- if @conn.server_version < 90000
247
- expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
248
- expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
249
- end
350
+ # For async_exec the output will be different:
351
+ # From backend> Z
352
+ # From backend (#4)> 5
353
+ # +From backend> Z
354
+ # +From backend (#4)> 5
355
+ # From backend> T
356
+ trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
250
357
 
251
- trace_data.should == expected_trace_output
358
+ expect( trace_data ).to eq( expected_trace_output )
252
359
  end
253
360
 
254
361
  it "allows a query to be cancelled" do
@@ -259,7 +366,7 @@ describe PG::Connection do
259
366
  if(tmpres.result_status != PG::PGRES_TUPLES_OK)
260
367
  error = true
261
368
  end
262
- error.should == true
369
+ expect( error ).to eq( true )
263
370
  end
264
371
 
265
372
  it "can stop a thread that runs a blocking query with async_exec" do
@@ -271,29 +378,21 @@ describe PG::Connection do
271
378
 
272
379
  t.kill
273
380
  t.join
274
- (Time.now - start).should < 10
381
+ expect( (Time.now - start) ).to be < 10
275
382
  end
276
383
 
277
- it "should work together with signal handlers" do
384
+ it "should work together with signal handlers", :unix do
278
385
  signal_received = false
279
- trap 'USR1' do
386
+ trap 'USR2' do
280
387
  signal_received = true
281
388
  end
282
389
 
283
390
  Thread.new do
284
391
  sleep 0.1
285
- Process.kill("USR1", Process.pid)
392
+ Process.kill("USR2", Process.pid)
286
393
  end
287
394
  @conn.exec("select pg_sleep(0.3)")
288
- signal_received.should be_true
289
-
290
- signal_received = false
291
- Thread.new do
292
- sleep 0.1
293
- Process.kill("USR1", Process.pid)
294
- end
295
- @conn.async_exec("select pg_sleep(0.3)")
296
- signal_received.should be_true
395
+ expect( signal_received ).to be_truthy
297
396
  end
298
397
 
299
398
 
@@ -305,15 +404,19 @@ describe PG::Connection do
305
404
  res = nil
306
405
  @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
307
406
 
308
- expect {
309
- res = @conn.transaction do
310
- @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
311
- raise "Oh noes! All pie is gone!"
312
- end
313
- }.to raise_exception( RuntimeError, /all pie is gone/i )
407
+ begin
408
+ expect {
409
+ res = @conn.transaction do
410
+ @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
411
+ raise "Oh noes! All pie is gone!"
412
+ end
413
+ }.to raise_exception( RuntimeError, /all pie is gone/i )
314
414
 
315
- res = @conn.exec( "SELECT * FROM pie" )
316
- res.ntuples.should == 0
415
+ res = @conn.exec( "SELECT * FROM pie" )
416
+ expect( res.ntuples ).to eq( 0 )
417
+ ensure
418
+ @conn.exec( "DROP TABLE pie" )
419
+ end
317
420
  end
318
421
 
319
422
  it "returns the block result from Connection#transaction" do
@@ -323,7 +426,7 @@ describe PG::Connection do
323
426
  res = @conn.transaction do
324
427
  "transaction result"
325
428
  end
326
- res.should == "transaction result"
429
+ expect( res ).to eq( "transaction result" )
327
430
  end
328
431
 
329
432
  it "not read past the end of a large object" do
@@ -331,33 +434,45 @@ describe PG::Connection do
331
434
  oid = @conn.lo_create( 0 )
332
435
  fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
333
436
  @conn.lo_write( fd, "foobar" )
334
- @conn.lo_read( fd, 10 ).should be_nil()
437
+ expect( @conn.lo_read( fd, 10 ) ).to be_nil()
335
438
  @conn.lo_lseek( fd, 0, PG::SEEK_SET )
336
- @conn.lo_read( fd, 10 ).should == 'foobar'
439
+ expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
337
440
  end
338
441
  end
339
442
 
340
-
341
- it "supports parameters passed to #exec (backward compatibility)" do
342
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
343
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
344
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
345
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
346
-
347
- res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
348
- res.values.should == [ ['Wally'], ['Sally'] ]
349
- end
350
-
351
443
  it "supports explicitly calling #exec_params" do
352
444
  @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
353
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
354
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
355
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
445
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
446
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
447
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
356
448
 
357
449
  res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
358
- res.values.should == [ ['Wally'], ['Sally'] ]
450
+ expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
359
451
  end
360
452
 
453
+ it "supports hash form parameters for #exec_params" do
454
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
455
+ hash_param_nil = { value: nil, type: 17, format: 1 }
456
+ res = @conn.exec_params( "SELECT $1, $2",
457
+ [ hash_param_bin, hash_param_nil ] )
458
+ expect( res.values ).to eq( [["\\x00ff", nil]] )
459
+ expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
460
+ end
461
+
462
+ it "should work with arbitrary number of params" do
463
+ begin
464
+ 3.step( 12, 0.2 ) do |exp|
465
+ num_params = (2 ** exp).to_i
466
+ sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
467
+ params = num_params.times.to_a
468
+ res = @conn.exec_params( "SELECT #{sql}", params )
469
+ expect( res.nfields ).to eq( num_params )
470
+ expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
471
+ end
472
+ rescue PG::ProgramLimitExceeded
473
+ # Stop silently if the server complains about too many params
474
+ end
475
+ end
361
476
 
362
477
  it "can wait for NOTIFY events" do
363
478
  @conn.exec( 'ROLLBACK' )
@@ -373,7 +488,7 @@ describe PG::Connection do
373
488
  end
374
489
  end
375
490
 
376
- @conn.wait_for_notify( 10 ).should == 'woo'
491
+ expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
377
492
  @conn.exec( 'UNLISTEN woo' )
378
493
 
379
494
  t.join
@@ -395,8 +510,8 @@ describe PG::Connection do
395
510
 
396
511
  eventpid = event = nil
397
512
  @conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
398
- event.should == 'woo'
399
- eventpid.should be_an( Integer )
513
+ expect( event ).to eq( 'woo' )
514
+ expect( eventpid ).to be_an( Integer )
400
515
 
401
516
  @conn.exec( 'UNLISTEN woo' )
402
517
 
@@ -423,8 +538,8 @@ describe PG::Connection do
423
538
  channels << @conn.wait_for_notify( 2 )
424
539
  end
425
540
 
426
- channels.should have( 3 ).members
427
- channels.should include( 'woo', 'war', 'woz' )
541
+ expect( channels.size ).to eq( 3 )
542
+ expect( channels ).to include( 'woo', 'war', 'woz' )
428
543
 
429
544
  @conn.exec( 'UNLISTEN woz' )
430
545
  @conn.exec( 'UNLISTEN war' )
@@ -446,52 +561,52 @@ describe PG::Connection do
446
561
  # Cause the notification to buffer, but not be read yet
447
562
  @conn.exec( 'SELECT 1' )
448
563
 
449
- @conn.wait_for_notify( 10 ).should == 'woo'
564
+ expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
450
565
  @conn.exec( 'UNLISTEN woo' )
451
566
  end
452
567
 
453
- it "can receive notices while waiting for NOTIFY without exceeding the timeout", :postgresql_90 do
568
+ it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
454
569
  notices = []
455
570
  @conn.set_notice_processor do |msg|
456
571
  notices << [msg, Time.now]
457
572
  end
458
573
  st = Time.now
459
574
  @conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
460
- @conn.wait_for_notify( 1 ).should be_nil
461
- notices.first.should_not be_nil
575
+ expect( @conn.wait_for_notify( 1 ) ).to be_nil
576
+ expect( notices.first ).to_not be_nil
462
577
  et = Time.now
463
- (et - notices.first[1]).should >= 0.4
464
- (et - st).should >= 0.9
465
- (et - st).should < 1.4
578
+ expect( (et - notices.first[1]) ).to be >= 0.3
579
+ expect( (et - st) ).to be >= 0.9
580
+ expect( (et - st) ).to be < 1.4
466
581
  end
467
582
 
468
583
  it "yields the result if block is given to exec" do
469
584
  rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
470
585
  values = []
471
- result.should be_kind_of( PG::Result )
472
- result.ntuples.should == 2
586
+ expect( result ).to be_kind_of( PG::Result )
587
+ expect( result.ntuples ).to eq( 2 )
473
588
  result.each do |tuple|
474
589
  values << tuple['a']
475
590
  end
476
591
  values
477
592
  end
478
593
 
479
- rval.should have( 2 ).members
480
- rval.should include( '5678', '1234' )
594
+ expect( rval.size ).to eq( 2 )
595
+ expect( rval ).to include( '5678', '1234' )
481
596
  end
482
597
 
483
598
  it "can process #copy_data output queries" do
484
599
  rows = []
485
600
  res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
486
- res.result_status.should == PG::PGRES_COPY_OUT
487
- res.nfields.should == 1
601
+ expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
602
+ expect( res.nfields ).to eq( 1 )
488
603
  while row=@conn.get_copy_data
489
604
  rows << row
490
605
  end
491
606
  end
492
- rows.should == ["1\n", "2\n"]
493
- res2.result_status.should == PG::PGRES_COMMAND_OK
494
- verify_clean_exec_status
607
+ expect( rows ).to eq( ["1\n", "2\n"] )
608
+ expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
609
+ expect( @conn ).to still_be_usable
495
610
  end
496
611
 
497
612
  it "can handle incomplete #copy_data output queries" do
@@ -500,7 +615,7 @@ describe PG::Connection do
500
615
  @conn.get_copy_data
501
616
  end
502
617
  }.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
503
- verify_clean_exec_status
618
+ expect( @conn ).to still_be_usable
504
619
  end
505
620
 
506
621
  it "can handle client errors in #copy_data for output" do
@@ -509,7 +624,7 @@ describe PG::Connection do
509
624
  raise "boom"
510
625
  end
511
626
  }.to raise_error(RuntimeError, "boom")
512
- verify_clean_exec_status
627
+ expect( @conn ).to still_be_usable
513
628
  end
514
629
 
515
630
  it "can handle server errors in #copy_data for output" do
@@ -523,23 +638,23 @@ describe PG::Connection do
523
638
  end
524
639
  }.to raise_error(PG::Error, /test-error/)
525
640
  end
526
- verify_clean_exec_status
641
+ expect( @conn ).to still_be_usable
527
642
  end
528
643
 
529
644
  it "can process #copy_data input queries" do
530
645
  @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
531
646
  res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
532
- res.result_status.should == PG::PGRES_COPY_IN
533
- res.nfields.should == 1
647
+ expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
648
+ expect( res.nfields ).to eq( 1 )
534
649
  @conn.put_copy_data "1\n"
535
650
  @conn.put_copy_data "2\n"
536
651
  end
537
- res2.result_status.should == PG::PGRES_COMMAND_OK
652
+ expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
538
653
 
539
- verify_clean_exec_status
654
+ expect( @conn ).to still_be_usable
540
655
 
541
656
  res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
542
- res.values.should == [["1"], ["2"]]
657
+ expect( res.values ).to eq( [["1"], ["2"]] )
543
658
  end
544
659
 
545
660
  it "can handle client errors in #copy_data for input" do
@@ -552,7 +667,8 @@ describe PG::Connection do
552
667
  end
553
668
  }.to raise_error(RuntimeError, "boom")
554
669
  end
555
- verify_clean_exec_status
670
+
671
+ expect( @conn ).to still_be_usable
556
672
  end
557
673
 
558
674
  it "can handle server errors in #copy_data for input" do
@@ -563,9 +679,34 @@ describe PG::Connection do
563
679
  @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
564
680
  @conn.put_copy_data "xyz\n"
565
681
  end
566
- }.to raise_error(PG::Error, /invalid input syntax for integer/)
682
+ }.to raise_error(PG::Error, /invalid input syntax for .*integer/)
683
+ end
684
+ expect( @conn ).to still_be_usable
685
+ end
686
+
687
+ it "gracefully handle SQL statements while in #copy_data for input" do
688
+ @conn.exec "ROLLBACK"
689
+ @conn.transaction do
690
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
691
+ expect {
692
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
693
+ @conn.exec "SELECT 1"
694
+ end
695
+ }.to raise_error(PG::Error, /no COPY in progress/)
567
696
  end
568
- verify_clean_exec_status
697
+ expect( @conn ).to still_be_usable
698
+ end
699
+
700
+ it "gracefully handle SQL statements while in #copy_data for output" do
701
+ @conn.exec "ROLLBACK"
702
+ @conn.transaction do
703
+ expect {
704
+ @conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
705
+ @conn.exec "SELECT 3"
706
+ end
707
+ }.to raise_error(PG::Error, /no COPY in progress/)
708
+ end
709
+ expect( @conn ).to still_be_usable
569
710
  end
570
711
 
571
712
  it "should raise an error for non copy statements in #copy_data" do
@@ -573,7 +714,7 @@ describe PG::Connection do
573
714
  @conn.copy_data( "SELECT 1" ){}
574
715
  }.to raise_error(ArgumentError, /no COPY/)
575
716
 
576
- verify_clean_exec_status
717
+ expect( @conn ).to still_be_usable
577
718
  end
578
719
 
579
720
  it "correctly finishes COPY queries passed to #async_exec" do
@@ -589,8 +730,8 @@ describe PG::Connection do
589
730
  results << data if data
590
731
  end until data.nil?
591
732
 
592
- results.should have( 2 ).members
593
- results.should include( "1\n", "2\n" )
733
+ expect( results.size ).to eq( 2 )
734
+ expect( results ).to include( "1\n", "2\n" )
594
735
  end
595
736
 
596
737
 
@@ -602,54 +743,138 @@ describe PG::Connection do
602
743
  end
603
744
 
604
745
  sleep 0.5
605
- t.should be_alive()
746
+ expect( t ).to be_alive()
606
747
  @conn.cancel
607
748
  t.join
608
- (Time.now - start).should < 3
749
+ expect( (Time.now - start) ).to be < 3
609
750
  end
610
751
 
611
752
  it "described_class#block should allow a timeout" do
612
- @conn.send_query( "select pg_sleep(3)" )
753
+ @conn.send_query( "select pg_sleep(1)" )
613
754
 
614
755
  start = Time.now
615
- @conn.block( 0.1 )
756
+ @conn.block( 0.3 )
616
757
  finish = Time.now
617
758
 
618
- (finish - start).should be_within( 0.05 ).of( 0.1 )
759
+ expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
619
760
  end
620
761
 
762
+ it "can return the default connection options" do
763
+ expect( described_class.conndefaults ).to be_a( Array )
764
+ expect( described_class.conndefaults ).to all( be_a(Hash) )
765
+ expect( described_class.conndefaults[0] ).to include( :keyword, :label, :dispchar, :dispsize )
766
+ expect( @conn.conndefaults ).to eq( described_class.conndefaults )
767
+ end
621
768
 
622
- it "can encrypt a string given a password and username" do
623
- described_class.encrypt_password("postgres", "postgres").
624
- should =~ /\S+/
769
+ it "can return the default connection options as a Hash" do
770
+ expect( described_class.conndefaults_hash ).to be_a( Hash )
771
+ expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
772
+ expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
773
+ expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
625
774
  end
626
775
 
776
+ it "can return the connection's connection options", :postgresql_93 do
777
+ expect( @conn.conninfo ).to be_a( Array )
778
+ expect( @conn.conninfo ).to all( be_a(Hash) )
779
+ expect( @conn.conninfo[0] ).to include( :keyword, :label, :dispchar, :dispsize )
780
+ end
627
781
 
628
- it "raises an appropriate error if either of the required arguments for encrypt_password " +
629
- "is not valid" do
630
- expect {
631
- described_class.encrypt_password( nil, nil )
632
- }.to raise_error( TypeError )
633
- expect {
634
- described_class.encrypt_password( "postgres", nil )
635
- }.to raise_error( TypeError )
636
- expect {
637
- described_class.encrypt_password( nil, "postgres" )
638
- }.to raise_error( TypeError )
782
+
783
+ it "can return the connection's connection options as a Hash", :postgresql_93 do
784
+ expect( @conn.conninfo_hash ).to be_a( Hash )
785
+ expect( @conn.conninfo_hash ).to include( :user, :password, :connect_timeout, :dbname, :host )
786
+ expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
787
+ end
788
+
789
+ describe "connection information related to SSL" do
790
+
791
+ it "can retrieve connection's ssl state", :postgresql_95 do
792
+ expect( @conn.ssl_in_use? ).to be false
793
+ end
794
+
795
+ it "can retrieve connection's ssl attribute_names", :postgresql_95 do
796
+ expect( @conn.ssl_attribute_names ).to be_a(Array)
797
+ end
798
+
799
+ it "can retrieve a single ssl connection attribute", :postgresql_95 do
800
+ expect( @conn.ssl_attribute('dbname') ).to eq( nil )
801
+ end
802
+
803
+ it "can retrieve all connection's ssl attributes", :postgresql_95 do
804
+ expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
805
+ end
806
+ end
807
+
808
+
809
+ it "honors the connect_timeout connection parameter", :postgresql_93 do
810
+ conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
811
+ begin
812
+ expect( conn.conninfo_hash[:connect_timeout] ).to eq( "11" )
813
+ ensure
814
+ conn.finish
815
+ end
816
+ end
817
+
818
+ describe "deprecated password encryption method" do
819
+ it "can encrypt password for a given user" do
820
+ expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
821
+ end
822
+
823
+ it "raises an appropriate error if either of the required arguments is not valid" do
824
+ expect {
825
+ described_class.encrypt_password( nil, nil )
826
+ }.to raise_error( TypeError )
827
+ expect {
828
+ described_class.encrypt_password( "postgres", nil )
829
+ }.to raise_error( TypeError )
830
+ expect {
831
+ described_class.encrypt_password( nil, "postgres" )
832
+ }.to raise_error( TypeError )
833
+ end
834
+ end
835
+
836
+ describe "password encryption method", :postgresql_10 do
837
+ it "can encrypt without algorithm" do
838
+ expect( @conn.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
839
+ expect( @conn.encrypt_password("postgres", "postgres", nil) ).to match( /\S+/ )
840
+ end
841
+
842
+ it "can encrypt with algorithm" do
843
+ expect( @conn.encrypt_password("postgres", "postgres", "md5") ).to match( /md5\S+/i )
844
+ expect( @conn.encrypt_password("postgres", "postgres", "scram-sha-256") ).to match( /SCRAM-SHA-256\S+/i )
845
+ end
846
+
847
+ it "raises an appropriate error if either of the required arguments is not valid" do
848
+ expect {
849
+ @conn.encrypt_password( nil, nil )
850
+ }.to raise_error( TypeError )
851
+ expect {
852
+ @conn.encrypt_password( "postgres", nil )
853
+ }.to raise_error( TypeError )
854
+ expect {
855
+ @conn.encrypt_password( nil, "postgres" )
856
+ }.to raise_error( TypeError )
857
+ expect {
858
+ @conn.encrypt_password( "postgres", "postgres", :invalid )
859
+ }.to raise_error( TypeError )
860
+ expect {
861
+ @conn.encrypt_password( "postgres", "postgres", "invalid" )
862
+ }.to raise_error( PG::Error, /unrecognized/ )
863
+ end
639
864
  end
640
865
 
641
866
 
642
867
  it "allows fetching a column of values from a result by column number" do
643
868
  res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
644
- res.column_values( 0 ).should == %w[1 2 3]
645
- res.column_values( 1 ).should == %w[2 3 4]
869
+ expect( res.column_values( 0 ) ).to eq( %w[1 2 3] )
870
+ expect( res.column_values( 1 ) ).to eq( %w[2 3 4] )
646
871
  end
647
872
 
648
873
 
649
874
  it "allows fetching a column of values from a result by field name" do
650
875
  res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
651
- res.field_values( 'column1' ).should == %w[1 2 3]
652
- res.field_values( 'column2' ).should == %w[2 3 4]
876
+ expect( res.field_values( 'column1' ) ).to eq( %w[1 2 3] )
877
+ expect( res.field_values( 'column2' ) ).to eq( %w[2 3 4] )
653
878
  end
654
879
 
655
880
 
@@ -677,26 +902,44 @@ describe PG::Connection do
677
902
  end
678
903
 
679
904
 
680
- it "can connect asynchronously", :socket_io do
905
+ it "handles server close while asynchronous connect" do
681
906
  serv = TCPServer.new( '127.0.0.1', 54320 )
682
907
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
683
- [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK].should include conn.connect_poll
908
+ expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
684
909
  select( nil, [conn.socket_io], nil, 0.2 )
685
910
  serv.close
686
911
  if conn.connect_poll == PG::PGRES_POLLING_READING
687
912
  select( [conn.socket_io], nil, nil, 0.2 )
688
913
  end
689
- conn.connect_poll.should == PG::PGRES_POLLING_FAILED
914
+ expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
690
915
  end
691
916
 
692
- it "discards previous results (if any) before waiting on an #async_exec"
917
+ it "discards previous results at #discard_results" do
918
+ @conn.send_query( "select 1" )
919
+ @conn.discard_results
920
+ @conn.send_query( "select 41 as one" )
921
+ res = @conn.get_last_result
922
+ expect( res.to_a ).to eq( [{ 'one' => '41' }] )
923
+ end
924
+
925
+ it "discards previous results (if any) before waiting on #exec" do
926
+ @conn.send_query( "select 1" )
927
+ res = @conn.exec( "select 42 as one" )
928
+ expect( res.to_a ).to eq( [{ 'one' => '42' }] )
929
+ end
693
930
 
694
- it "calls the block if one is provided to #async_exec" do
931
+ it "discards previous errors before waiting on #exec", :without_transaction do
932
+ @conn.send_query( "ERROR" )
933
+ res = @conn.exec( "select 43 as one" )
934
+ expect( res.to_a ).to eq( [{ 'one' => '43' }] )
935
+ end
936
+
937
+ it "calls the block if one is provided to #exec" do
695
938
  result = nil
696
- @conn.async_exec( "select 47 as one" ) do |pg_res|
939
+ @conn.exec( "select 47 as one" ) do |pg_res|
697
940
  result = pg_res[0]
698
941
  end
699
- result.should == { 'one' => '47' }
942
+ expect( result ).to eq( { 'one' => '47' } )
700
943
  end
701
944
 
702
945
  it "raises a rescue-able error if #finish is called twice", :without_transaction do
@@ -706,20 +949,34 @@ describe PG::Connection do
706
949
  expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
707
950
  end
708
951
 
709
- it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
952
+ it "can use conn.reset to restart the connection" do
953
+ ios = IO.pipe
954
+ conn = PG.connect( @conninfo )
955
+
956
+ # Close the two pipe file descriptors, so that the file descriptor of
957
+ # newly established connection is probably distinct from the previous one.
958
+ ios.each(&:close)
959
+ conn.reset
960
+
961
+ # The new connection should work even when the file descriptor has changed.
962
+ expect( conn.exec("SELECT 1").values ).to eq([["1"]])
963
+ conn.close
964
+ end
965
+
966
+ it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction do
710
967
  conn = PG.connect( @conninfo )
711
968
  io = conn.socket_io
712
969
  conn.finish
713
- io.should be_closed()
970
+ expect( io ).to be_closed()
714
971
  expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
715
972
  end
716
973
 
717
- it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
974
+ it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction do
718
975
  conn = PG.connect( @conninfo )
719
976
  io = conn.socket_io
720
977
  conn.reset
721
- io.should be_closed()
722
- conn.socket_io.should_not equal( io )
978
+ expect( io ).to be_closed()
979
+ expect( conn.socket_io ).to_not equal( io )
723
980
  conn.finish
724
981
  end
725
982
 
@@ -734,157 +991,156 @@ describe PG::Connection do
734
991
  expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
735
992
  end
736
993
 
737
- context "under PostgreSQL 9", :postgresql_90 do
994
+ it "sets the fallback_application_name on new connections" do
995
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
738
996
 
739
- before( :each ) do
740
- pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
741
- end
997
+ conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
998
+ expect( conn_name ).to include( $0[0..10] )
999
+ expect( conn_name ).to include( $0[-10..-1] )
1000
+ expect( conn_name.length ).to be <= 64
1001
+ end
742
1002
 
743
- it "sets the fallback_application_name on new connections" do
1003
+ it "sets a shortened fallback_application_name on new connections" do
1004
+ old_0 = $0
1005
+ begin
1006
+ $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
744
1007
  conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
745
- connection_string_should_contain_application_name(conn_string, $0)
746
- end
747
-
748
- it "sets a shortened fallback_application_name on new connections" do
749
- old_0 = $0
750
- begin
751
- $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
752
- conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
753
- connection_string_should_contain_application_name(conn_string, $0)
754
- ensure
755
- $0 = old_0
756
- end
1008
+ conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
1009
+ expect( conn_name ).to include( $0[0..10] )
1010
+ expect( conn_name ).to include( $0[-10..-1] )
1011
+ expect( conn_name.length ).to be <= 64
1012
+ ensure
1013
+ $0 = old_0
757
1014
  end
1015
+ end
758
1016
 
759
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
760
- "any number of arguments" do
761
-
762
- @conn.exec( 'ROLLBACK' )
763
- @conn.exec( 'LISTEN knees' )
1017
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1018
+ "any number of arguments" do
764
1019
 
765
- conn = described_class.connect( @conninfo )
766
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
767
- conn.finish
1020
+ @conn.exec( 'ROLLBACK' )
1021
+ @conn.exec( 'LISTEN knees' )
768
1022
 
769
- event, pid, msg = nil
770
- @conn.wait_for_notify( 10 ) do |*args|
771
- event, pid, msg = *args
772
- end
773
- @conn.exec( 'UNLISTEN knees' )
1023
+ conn = described_class.connect( @conninfo )
1024
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1025
+ conn.finish
774
1026
 
775
- event.should == 'knees'
776
- pid.should be_a_kind_of( Integer )
777
- msg.should == 'skirt and boots'
1027
+ event, pid, msg = nil
1028
+ @conn.wait_for_notify( 10 ) do |*args|
1029
+ event, pid, msg = *args
778
1030
  end
1031
+ @conn.exec( 'UNLISTEN knees' )
779
1032
 
780
- it "accepts nil as the timeout in #wait_for_notify " do
781
- @conn.exec( 'ROLLBACK' )
782
- @conn.exec( 'LISTEN knees' )
1033
+ expect( event ).to eq( 'knees' )
1034
+ expect( pid ).to be_a_kind_of( Integer )
1035
+ expect( msg ).to eq( 'skirt and boots' )
1036
+ end
783
1037
 
784
- conn = described_class.connect( @conninfo )
785
- conn.exec( %Q{NOTIFY knees} )
786
- conn.finish
1038
+ it "accepts nil as the timeout in #wait_for_notify " do
1039
+ @conn.exec( 'ROLLBACK' )
1040
+ @conn.exec( 'LISTEN knees' )
787
1041
 
788
- event, pid = nil
789
- @conn.wait_for_notify( nil ) do |*args|
790
- event, pid = *args
791
- end
792
- @conn.exec( 'UNLISTEN knees' )
1042
+ conn = described_class.connect( @conninfo )
1043
+ conn.exec( %Q{NOTIFY knees} )
1044
+ conn.finish
793
1045
 
794
- event.should == 'knees'
795
- pid.should be_a_kind_of( Integer )
1046
+ event, pid = nil
1047
+ @conn.wait_for_notify( nil ) do |*args|
1048
+ event, pid = *args
796
1049
  end
1050
+ @conn.exec( 'UNLISTEN knees' )
797
1051
 
798
- it "sends nil as the payload if the notification wasn't given one" do
799
- @conn.exec( 'ROLLBACK' )
800
- @conn.exec( 'LISTEN knees' )
1052
+ expect( event ).to eq( 'knees' )
1053
+ expect( pid ).to be_a_kind_of( Integer )
1054
+ end
801
1055
 
802
- conn = described_class.connect( @conninfo )
803
- conn.exec( %Q{NOTIFY knees} )
804
- conn.finish
1056
+ it "sends nil as the payload if the notification wasn't given one" do
1057
+ @conn.exec( 'ROLLBACK' )
1058
+ @conn.exec( 'LISTEN knees' )
805
1059
 
806
- payload = :notnil
807
- @conn.wait_for_notify( nil ) do |*args|
808
- payload = args[ 2 ]
809
- end
810
- @conn.exec( 'UNLISTEN knees' )
1060
+ conn = described_class.connect( @conninfo )
1061
+ conn.exec( %Q{NOTIFY knees} )
1062
+ conn.finish
811
1063
 
812
- payload.should be_nil()
1064
+ payload = :notnil
1065
+ @conn.wait_for_notify( nil ) do |*args|
1066
+ payload = args[ 2 ]
813
1067
  end
1068
+ @conn.exec( 'UNLISTEN knees' )
814
1069
 
815
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
816
- "two arguments" do
1070
+ expect( payload ).to be_nil()
1071
+ end
817
1072
 
818
- @conn.exec( 'ROLLBACK' )
819
- @conn.exec( 'LISTEN knees' )
1073
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1074
+ "two arguments" do
820
1075
 
821
- conn = described_class.connect( @conninfo )
822
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
823
- conn.finish
1076
+ @conn.exec( 'ROLLBACK' )
1077
+ @conn.exec( 'LISTEN knees' )
824
1078
 
825
- event, pid, msg = nil
826
- @conn.wait_for_notify( 10 ) do |arg1, arg2|
827
- event, pid, msg = arg1, arg2
828
- end
829
- @conn.exec( 'UNLISTEN knees' )
1079
+ conn = described_class.connect( @conninfo )
1080
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1081
+ conn.finish
830
1082
 
831
- event.should == 'knees'
832
- pid.should be_a_kind_of( Integer )
833
- msg.should be_nil()
1083
+ event, pid, msg = nil
1084
+ @conn.wait_for_notify( 10 ) do |arg1, arg2|
1085
+ event, pid, msg = arg1, arg2
834
1086
  end
1087
+ @conn.exec( 'UNLISTEN knees' )
835
1088
 
836
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
837
- "doesn't accept arguments" do
1089
+ expect( event ).to eq( 'knees' )
1090
+ expect( pid ).to be_a_kind_of( Integer )
1091
+ expect( msg ).to be_nil()
1092
+ end
838
1093
 
839
- @conn.exec( 'ROLLBACK' )
840
- @conn.exec( 'LISTEN knees' )
1094
+ it "calls the block supplied to wait_for_notify with the notify payload if it " +
1095
+ "doesn't accept arguments" do
841
1096
 
842
- conn = described_class.connect( @conninfo )
843
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
844
- conn.finish
1097
+ @conn.exec( 'ROLLBACK' )
1098
+ @conn.exec( 'LISTEN knees' )
845
1099
 
846
- notification_received = false
847
- @conn.wait_for_notify( 10 ) do
848
- notification_received = true
849
- end
850
- @conn.exec( 'UNLISTEN knees' )
1100
+ conn = described_class.connect( @conninfo )
1101
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1102
+ conn.finish
851
1103
 
852
- notification_received.should be_true()
1104
+ notification_received = false
1105
+ @conn.wait_for_notify( 10 ) do
1106
+ notification_received = true
853
1107
  end
1108
+ @conn.exec( 'UNLISTEN knees' )
854
1109
 
855
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
856
- "three arguments" do
1110
+ expect( notification_received ).to be_truthy()
1111
+ end
857
1112
 
858
- @conn.exec( 'ROLLBACK' )
859
- @conn.exec( 'LISTEN knees' )
1113
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1114
+ "three arguments" do
860
1115
 
861
- conn = described_class.connect( @conninfo )
862
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
863
- conn.finish
1116
+ @conn.exec( 'ROLLBACK' )
1117
+ @conn.exec( 'LISTEN knees' )
864
1118
 
865
- event, pid, msg = nil
866
- @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
867
- event, pid, msg = arg1, arg2, arg3
868
- end
869
- @conn.exec( 'UNLISTEN knees' )
1119
+ conn = described_class.connect( @conninfo )
1120
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1121
+ conn.finish
870
1122
 
871
- event.should == 'knees'
872
- pid.should be_a_kind_of( Integer )
873
- msg.should == 'skirt and boots'
1123
+ event, pid, msg = nil
1124
+ @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
1125
+ event, pid, msg = arg1, arg2, arg3
874
1126
  end
1127
+ @conn.exec( 'UNLISTEN knees' )
875
1128
 
1129
+ expect( event ).to eq( 'knees' )
1130
+ expect( pid ).to be_a_kind_of( Integer )
1131
+ expect( msg ).to eq( 'skirt and boots' )
876
1132
  end
877
1133
 
878
- context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
1134
+ context "server ping", :without_transaction do
879
1135
 
880
1136
  it "pings successfully with connection string" do
881
1137
  ping = described_class.ping(@conninfo)
882
- ping.should == PG::PQPING_OK
1138
+ expect( ping ).to eq( PG::PQPING_OK )
883
1139
  end
884
1140
 
885
1141
  it "pings using 7 arguments converted to strings" do
886
1142
  ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
887
- ping.should == PG::PQPING_OK
1143
+ expect( ping ).to eq( PG::PQPING_OK )
888
1144
  end
889
1145
 
890
1146
  it "pings using a hash of connection parameters" do
@@ -892,7 +1148,7 @@ describe PG::Connection do
892
1148
  :host => 'localhost',
893
1149
  :port => @port,
894
1150
  :dbname => :test)
895
- ping.should == PG::PQPING_OK
1151
+ expect( ping ).to eq( PG::PQPING_OK )
896
1152
  end
897
1153
 
898
1154
  it "returns correct response when ping connection cannot be established" do
@@ -900,205 +1156,359 @@ describe PG::Connection do
900
1156
  :host => 'localhost',
901
1157
  :port => 9999,
902
1158
  :dbname => :test)
903
- ping.should == PG::PQPING_NO_RESPONSE
1159
+ expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
904
1160
  end
905
1161
 
906
- it "returns correct response when ping connection arguments are wrong" do
1162
+ it "returns error when ping connection arguments are wrong" do
907
1163
  ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
908
- ping.should == PG::PQPING_NO_ATTEMPT
1164
+ expect( ping ).to_not eq( PG::PQPING_OK )
909
1165
  end
910
1166
 
1167
+ it "returns correct response when ping connection arguments are wrong" do
1168
+ ping = described_class.ping(
1169
+ :host => 'localhost',
1170
+ :invalid_option => 9999,
1171
+ :dbname => :test)
1172
+ expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1173
+ end
911
1174
 
912
1175
  end
913
1176
 
914
- context "under PostgreSQL 9.2 client library", :postgresql_92 do
915
- describe "set_single_row_mode" do
1177
+ describe "set_single_row_mode" do
916
1178
 
917
- it "raises an error when called at the wrong time" do
918
- expect {
919
- @conn.set_single_row_mode
920
- }.to raise_error(PG::Error)
1179
+ it "raises an error when called at the wrong time" do
1180
+ expect {
1181
+ @conn.set_single_row_mode
1182
+ }.to raise_error(PG::Error)
1183
+ end
1184
+
1185
+ it "should work in single row mode" do
1186
+ @conn.send_query( "SELECT generate_series(1,10)" )
1187
+ @conn.set_single_row_mode
1188
+
1189
+ results = []
1190
+ loop do
1191
+ @conn.block
1192
+ res = @conn.get_result or break
1193
+ results << res
921
1194
  end
1195
+ expect( results.length ).to eq( 11 )
1196
+ results[0..-2].each do |res|
1197
+ expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1198
+ values = res.field_values('generate_series')
1199
+ expect( values.length ).to eq( 1 )
1200
+ expect( values.first.to_i ).to be > 0
1201
+ end
1202
+ expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
1203
+ expect( results.last.ntuples ).to eq( 0 )
1204
+ end
922
1205
 
923
- it "should work in single row mode" do
924
- @conn.send_query( "SELECT generate_series(1,10)" )
925
- @conn.set_single_row_mode
1206
+ it "should receive rows before entire query is finished" do
1207
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
1208
+ @conn.set_single_row_mode
926
1209
 
927
- results = []
928
- loop do
929
- @conn.block
930
- res = @conn.get_result or break
931
- results << res
932
- end
933
- results.length.should == 11
934
- results[0..-2].each do |res|
935
- res.result_status.should == PG::PGRES_SINGLE_TUPLE
936
- values = res.field_values('generate_series')
937
- values.length.should == 1
938
- values.first.to_i.should > 0
939
- end
940
- results.last.result_status.should == PG::PGRES_TUPLES_OK
941
- results.last.ntuples.should == 0
1210
+ start_time = Time.now
1211
+ first_row_time = nil
1212
+ loop do
1213
+ res = @conn.get_result or break
1214
+ res.check
1215
+ first_row_time = Time.now unless first_row_time
942
1216
  end
1217
+ expect( (Time.now - start_time) ).to be >= 0.9
1218
+ expect( (first_row_time - start_time) ).to be < 0.9
1219
+ end
943
1220
 
944
- it "should receive rows before entire query is finished" do
945
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
946
- @conn.set_single_row_mode
1221
+ it "should receive rows before entire query fails" do
1222
+ @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
1223
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
1224
+ @conn.set_single_row_mode
947
1225
 
948
- start_time = Time.now
949
- first_row_time = nil
1226
+ first_result = nil
1227
+ expect do
950
1228
  loop do
951
1229
  res = @conn.get_result or break
952
1230
  res.check
953
- first_row_time = Time.now unless first_row_time
1231
+ first_result ||= res
954
1232
  end
955
- (Time.now - start_time).should >= 1.0
956
- (first_row_time - start_time).should < 1.0
957
- end
958
-
959
- it "should receive rows before entire query fails" do
960
- @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
961
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
962
- @conn.set_single_row_mode
963
-
964
- first_result = nil
965
- expect do
966
- loop do
967
- res = @conn.get_result or break
968
- res.check
969
- first_result ||= res
970
- end
971
- end.to raise_error(PG::Error)
972
- first_result.kind_of?(PG::Result).should be_true
973
- first_result.result_status.should == PG::PGRES_SINGLE_TUPLE
974
- end
1233
+ end.to raise_error(PG::Error)
1234
+ expect( first_result.kind_of?(PG::Result) ).to be_truthy
1235
+ expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
975
1236
  end
1237
+
976
1238
  end
977
1239
 
978
- context "multinationalization support", :ruby_19 do
1240
+ context "multinationalization support" do
979
1241
 
980
1242
  describe "rubyforge #22925: m17n support" do
981
1243
  it "should return results in the same encoding as the client (iso-8859-1)" do
982
- out_string = nil
983
- @conn.transaction do |conn|
984
- conn.internal_encoding = 'iso8859-1'
985
- res = conn.exec("VALUES ('fantasia')", [], 0)
986
- out_string = res[0]['column1']
987
- end
988
- out_string.should == 'fantasia'
989
- out_string.encoding.should == Encoding::ISO8859_1
1244
+ @conn.internal_encoding = 'iso8859-1'
1245
+ res = @conn.exec_params("VALUES ('fantasia')", [], 0)
1246
+ out_string = res[0]['column1']
1247
+ expect( out_string ).to eq( 'fantasia' )
1248
+ expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
990
1249
  end
991
1250
 
992
1251
  it "should return results in the same encoding as the client (utf-8)" do
993
- out_string = nil
994
- @conn.transaction do |conn|
995
- conn.internal_encoding = 'utf-8'
996
- res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
997
- out_string = res[0]['column1']
998
- end
999
- out_string.should == '世界線航跡蔵'
1000
- out_string.encoding.should == Encoding::UTF_8
1252
+ @conn.internal_encoding = 'utf-8'
1253
+ res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1254
+ out_string = res[0]['column1']
1255
+ expect( out_string ).to eq( '世界線航跡蔵' )
1256
+ expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1001
1257
  end
1002
1258
 
1003
1259
  it "should return results in the same encoding as the client (EUC-JP)" do
1004
- out_string = nil
1005
- @conn.transaction do |conn|
1006
- conn.internal_encoding = 'EUC-JP'
1007
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1008
- res = conn.exec(stmt, [], 0)
1009
- out_string = res[0]['column1']
1010
- end
1011
- out_string.should == '世界線航跡蔵'.encode('EUC-JP')
1012
- out_string.encoding.should == Encoding::EUC_JP
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']
1264
+ expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1265
+ expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1013
1266
  end
1014
1267
 
1015
1268
  it "returns the results in the correct encoding even if the client_encoding has " +
1016
1269
  "changed since the results were fetched" do
1017
- out_string = nil
1018
- @conn.transaction do |conn|
1019
- conn.internal_encoding = 'EUC-JP'
1020
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1021
- res = conn.exec(stmt, [], 0)
1022
- conn.internal_encoding = 'utf-8'
1023
- out_string = res[0]['column1']
1024
- end
1025
- out_string.should == '世界線航跡蔵'.encode('EUC-JP')
1026
- out_string.encoding.should == Encoding::EUC_JP
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']
1275
+ expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1276
+ expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1027
1277
  end
1028
1278
 
1029
1279
  it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
1030
1280
  @conn.exec "SET client_encoding TO SQL_ASCII"
1031
- @conn.internal_encoding.should == Encoding::ASCII_8BIT
1281
+ expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1032
1282
  end
1033
1283
 
1034
- it "works around the unsupported JOHAB encoding by returning stuff in 'ASCII_8BIT'" do
1035
- pending "figuring out how to create a string in the JOHAB encoding" do
1036
- out_string = nil
1037
- @conn.transaction do |conn|
1038
- conn.exec( "set client_encoding = 'JOHAB';" )
1039
- stmt = "VALUES ('foo')".encode('JOHAB')
1040
- res = conn.exec( stmt, [], 0 )
1041
- out_string = res[0]['column1']
1042
- end
1043
- out_string.should == 'foo'.encode( Encoding::ASCII_8BIT )
1044
- out_string.encoding.should == Encoding::ASCII_8BIT
1045
- end
1284
+ it "the connection should use JOHAB dummy encoding when it's set to JOHAB" do
1285
+ @conn.set_client_encoding "JOHAB"
1286
+ val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
1287
+ expect( val.encoding.name ).to eq( "JOHAB" )
1288
+ expect( val.unpack("H*")[0] ).to eq( "dc65" )
1289
+ end
1290
+
1291
+ it "can retrieve server encoding as text" do
1292
+ enc = @conn.parameter_status "server_encoding"
1293
+ expect( enc ).to eq( "UTF8" )
1294
+ end
1295
+
1296
+ it "can retrieve server encoding as ruby encoding" do
1297
+ expect( @conn.external_encoding ).to eq( Encoding::UTF_8 )
1046
1298
  end
1047
1299
 
1048
1300
  it "uses the client encoding for escaped string" do
1049
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1301
+ original = "Möhre to 'scape".encode( "utf-16be" )
1050
1302
  @conn.set_client_encoding( "euc_jp" )
1051
1303
  escaped = @conn.escape( original )
1052
- escaped.encoding.should == Encoding::EUC_JP
1053
- escaped.should == "string to"
1304
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1305
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
1054
1306
  end
1055
1307
 
1056
- it "uses the client encoding for escaped literal", :postgresql_90 do
1057
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1308
+ it "uses the client encoding for escaped literal" do
1309
+ original = "Möhre to 'scape".encode( "utf-16be" )
1058
1310
  @conn.set_client_encoding( "euc_jp" )
1059
1311
  escaped = @conn.escape_literal( original )
1060
- escaped.encoding.should == Encoding::EUC_JP
1061
- escaped.should == "'string to'"
1312
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1313
+ expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
1062
1314
  end
1063
1315
 
1064
- it "uses the client encoding for escaped identifier", :postgresql_90 do
1065
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1316
+ it "uses the client encoding for escaped identifier" do
1317
+ original = "Möhre to 'scape".encode( "utf-16le" )
1066
1318
  @conn.set_client_encoding( "euc_jp" )
1067
1319
  escaped = @conn.escape_identifier( original )
1068
- escaped.encoding.should == Encoding::EUC_JP
1069
- escaped.should == "\"string to\""
1320
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1321
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1070
1322
  end
1071
1323
 
1072
1324
  it "uses the client encoding for quote_ident" do
1073
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1325
+ original = "Möhre to 'scape".encode( "utf-16le" )
1074
1326
  @conn.set_client_encoding( "euc_jp" )
1075
1327
  escaped = @conn.quote_ident( original )
1076
- escaped.encoding.should == Encoding::EUC_JP
1077
- escaped.should == "\"string to\""
1328
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1329
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1078
1330
  end
1079
1331
 
1080
1332
  it "uses the previous string encoding for escaped string" do
1081
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1333
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1082
1334
  @conn.set_client_encoding( "euc_jp" )
1083
1335
  escaped = described_class.escape( original )
1084
- escaped.encoding.should == Encoding::ISO8859_1
1085
- escaped.should == "string to"
1336
+ expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1337
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
1086
1338
  end
1087
1339
 
1088
1340
  it "uses the previous string encoding for quote_ident" do
1089
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1341
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1090
1342
  @conn.set_client_encoding( "euc_jp" )
1091
1343
  escaped = described_class.quote_ident( original )
1092
- escaped.encoding.should == Encoding::ISO8859_1
1093
- escaped.should == "\"string to\""
1344
+ expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1345
+ expect( escaped.encode ).to eq( "\"Möhre to 'scape\"".encode(Encoding::ISO8859_1) )
1346
+ end
1347
+
1348
+ it "raises appropriate error if set_client_encoding is called with invalid arguments" do
1349
+ expect { @conn.set_client_encoding( "invalid" ) }.to raise_error(PG::Error, /invalid value/)
1350
+ expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
1351
+ expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
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
+
1368
+ end
1369
+
1370
+ describe "respect and convert character encoding of input strings" do
1371
+ before :each do
1372
+ @conn.internal_encoding = __ENCODING__
1373
+ end
1374
+
1375
+ it "should convert query string and parameters to #exec_params" do
1376
+ r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
1377
+ ['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
1378
+ expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1379
+ end
1380
+
1381
+ it "should convert query string to #exec" do
1382
+ r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
1383
+ expect( r.values ).to eq( [['grün']] )
1384
+ end
1385
+
1386
+ it "should convert strings and parameters to #prepare and #exec_prepared" do
1387
+ @conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
1388
+ r = @conn.exec_prepared("weiß1".encode("utf-32le"),
1389
+ ['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
1390
+ expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1391
+ end
1392
+
1393
+ it "should convert strings to #describe_prepared" do
1394
+ @conn.prepare("weiß2", "VALUES(123)")
1395
+ r = @conn.describe_prepared("weiß2".encode("utf-16be"))
1396
+ expect( r.nfields ).to eq( 1 )
1397
+ end
1398
+
1399
+ it "should convert strings to #describe_portal" do
1400
+ @conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
1401
+ r = @conn.describe_portal("cörsör".encode("utf-16le"))
1402
+ expect( r.nfields ).to eq( 3 )
1403
+ end
1404
+
1405
+ it "should convert query string to #send_query" do
1406
+ @conn.send_query("VALUES('grün')".encode("utf-16be"))
1407
+ expect( @conn.get_last_result.values ).to eq( [['grün']] )
1408
+ end
1409
+
1410
+ it "should convert query string and parameters to #send_query_params" do
1411
+ @conn.send_query_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
1412
+ ['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
1413
+ expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1414
+ end
1415
+
1416
+ it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
1417
+ @conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
1418
+ @conn.get_last_result
1419
+ @conn.send_query_prepared("weiß3".encode("utf-32le"),
1420
+ ['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
1421
+ expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1422
+ end
1423
+
1424
+ it "should convert strings to #send_describe_prepared" do
1425
+ @conn.prepare("weiß4", "VALUES(123)")
1426
+ @conn.send_describe_prepared("weiß4".encode("utf-16be"))
1427
+ expect( @conn.get_last_result.nfields ).to eq( 1 )
1094
1428
  end
1095
1429
 
1430
+ it "should convert strings to #send_describe_portal" do
1431
+ @conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
1432
+ @conn.send_describe_portal("cörsör".encode("utf-16le"))
1433
+ expect( @conn.get_last_result.nfields ).to eq( 3 )
1434
+ end
1435
+
1436
+ it "should convert error string to #put_copy_end" do
1437
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1438
+ @conn.exec( "COPY copytable FROM STDIN" )
1439
+ @conn.put_copy_end("grün".encode("utf-16be"))
1440
+ expect( @conn.get_result.error_message ).to match(/grün/)
1441
+ @conn.get_result
1442
+ end
1443
+ end
1444
+
1445
+ it "rejects command strings with zero bytes" do
1446
+ expect{ @conn.exec( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1447
+ expect{ @conn.exec_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1448
+ expect{ @conn.prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
1449
+ expect{ @conn.prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1450
+ expect{ @conn.exec_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1451
+ expect{ @conn.describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1452
+ expect{ @conn.describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1453
+ expect{ @conn.send_query( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1454
+ expect{ @conn.send_query_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1455
+ expect{ @conn.send_prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
1456
+ expect{ @conn.send_prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1457
+ expect{ @conn.send_query_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1458
+ expect{ @conn.send_describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1459
+ expect{ @conn.send_describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1460
+ end
1461
+
1462
+ it "rejects query params with zero bytes" do
1463
+ expect{ @conn.exec_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1464
+ expect{ @conn.exec_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1465
+ expect{ @conn.send_query_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1466
+ expect{ @conn.send_query_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1467
+ end
1468
+
1469
+ it "rejects string with zero bytes in escape" do
1470
+ expect{ @conn.escape( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1471
+ end
1472
+
1473
+ it "rejects string with zero bytes in escape_literal" do
1474
+ expect{ @conn.escape_literal( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1475
+ end
1476
+
1477
+ it "rejects string with zero bytes in escape_identifier" do
1478
+ expect{ @conn.escape_identifier( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1479
+ end
1480
+
1481
+ it "rejects string with zero bytes in quote_ident" do
1482
+ expect{ described_class.quote_ident( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1483
+ end
1484
+
1485
+ it "rejects Array with string with zero bytes" do
1486
+ original = ["xyz", "2\x00"]
1487
+ expect{ described_class.quote_ident( original ) }.to raise_error(ArgumentError, /null byte/)
1488
+ end
1489
+
1490
+ it "can quote bigger strings with quote_ident" do
1491
+ original = "'01234567\"" * 100
1492
+ escaped = described_class.quote_ident( original )
1493
+ expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
1096
1494
  end
1097
1495
 
1496
+ it "can quote Arrays with quote_ident" do
1497
+ original = "'01234567\""
1498
+ escaped = described_class.quote_ident( [original]*3 )
1499
+ expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
1500
+ expect( escaped ).to eq( expected.join(".") )
1501
+ end
1502
+
1503
+ it "will raise a TypeError for invalid arguments to quote_ident" do
1504
+ expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
1505
+ expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
1506
+ expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
1507
+ end
1098
1508
 
1099
1509
  describe "Ruby 1.9.x default_internal encoding" do
1100
1510
 
1101
- 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
1102
1512
  @conn.transaction do |txn_conn|
1103
1513
  txn_conn.internal_encoding = Encoding::ISO8859_1
1104
1514
  txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
@@ -1107,13 +1517,14 @@ describe PG::Connection do
1107
1517
 
1108
1518
  begin
1109
1519
  prev_encoding = Encoding.default_internal
1110
- Encoding.default_internal = Encoding::UTF_8
1520
+ Encoding.default_internal = Encoding::ISO8859_2
1111
1521
 
1112
1522
  conn = PG.connect( @conninfo )
1113
- conn.internal_encoding.should == Encoding::UTF_8
1523
+ expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
1114
1524
  res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1115
- res[0]['foo'].encoding.should == Encoding::UTF_8
1525
+ expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
1116
1526
  ensure
1527
+ conn.exec( "DROP TABLE defaultinternaltest" )
1117
1528
  conn.finish if conn
1118
1529
  Encoding.default_internal = prev_encoding
1119
1530
  end
@@ -1126,7 +1537,7 @@ describe PG::Connection do
1126
1537
 
1127
1538
  @conn.set_default_encoding
1128
1539
 
1129
- @conn.internal_encoding.should == Encoding::KOI8_R
1540
+ expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
1130
1541
  ensure
1131
1542
  Encoding.default_internal = prev_encoding
1132
1543
  end
@@ -1147,7 +1558,7 @@ describe PG::Connection do
1147
1558
  query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
1148
1559
  conn.exec( query )
1149
1560
  rescue => err
1150
- err.message.encoding.should == Encoding::ISO8859_15
1561
+ expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
1151
1562
  else
1152
1563
  fail "No exception raised?!"
1153
1564
  end
@@ -1156,7 +1567,22 @@ describe PG::Connection do
1156
1567
  conn.finish if conn
1157
1568
  end
1158
1569
 
1159
- it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1570
+ it "handles clearing result in or after set_notice_receiver" do
1571
+ r = nil
1572
+ @conn.set_notice_receiver do |result|
1573
+ r = result
1574
+ expect( r.cleared? ).to eq(false)
1575
+ end
1576
+ @conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
1577
+ sleep 0.2
1578
+ expect( r ).to be_a( PG::Result )
1579
+ expect( r.cleared? ).to eq(true)
1580
+ expect( r.autoclear? ).to eq(true)
1581
+ r.clear
1582
+ @conn.set_notice_receiver
1583
+ end
1584
+
1585
+ it "receives properly encoded messages in the notice callbacks" do
1160
1586
  [:receiver, :processor].each do |kind|
1161
1587
  notices = []
1162
1588
  @conn.internal_encoding = 'utf-8'
@@ -1174,19 +1600,18 @@ describe PG::Connection do
1174
1600
  @conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
1175
1601
  end
1176
1602
 
1177
- notices.length.should == 3
1603
+ expect( notices.length ).to eq( 3 )
1178
1604
  notices.each do |notice|
1179
- notice.should =~ /^NOTICE:.*世界線航跡蔵/
1180
- notice.encoding.should == Encoding::UTF_8
1605
+ expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
1606
+ expect( notice.encoding ).to eq( Encoding::UTF_8 )
1181
1607
  end
1182
1608
  @conn.set_notice_receiver
1183
1609
  @conn.set_notice_processor
1184
1610
  end
1185
1611
  end
1186
1612
 
1187
- it "receives properly encoded text from wait_for_notify", :postgresql_90 do
1613
+ it "receives properly encoded text from wait_for_notify", :without_transaction do
1188
1614
  @conn.internal_encoding = 'utf-8'
1189
- @conn.exec( 'ROLLBACK' )
1190
1615
  @conn.exec( 'LISTEN "Möhre"' )
1191
1616
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1192
1617
  event, pid, msg = nil
@@ -1195,36 +1620,36 @@ describe PG::Connection do
1195
1620
  end
1196
1621
  @conn.exec( 'UNLISTEN "Möhre"' )
1197
1622
 
1198
- event.should == "Möhre"
1199
- event.encoding.should == Encoding::UTF_8
1200
- msg.should == '世界線航跡蔵'
1201
- msg.encoding.should == Encoding::UTF_8
1623
+ expect( event ).to eq( "Möhre" )
1624
+ expect( event.encoding ).to eq( Encoding::UTF_8 )
1625
+ expect( pid ).to be_a_kind_of(Integer)
1626
+ expect( msg ).to eq( '世界線航跡蔵' )
1627
+ expect( msg.encoding ).to eq( Encoding::UTF_8 )
1202
1628
  end
1203
1629
 
1204
- it "returns properly encoded text from notifies", :postgresql_90 do
1630
+ it "returns properly encoded text from notifies", :without_transaction do
1205
1631
  @conn.internal_encoding = 'utf-8'
1206
- @conn.exec( 'ROLLBACK' )
1207
1632
  @conn.exec( 'LISTEN "Möhre"' )
1208
1633
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1209
1634
  @conn.exec( 'UNLISTEN "Möhre"' )
1210
1635
 
1211
1636
  notification = @conn.notifies
1212
- notification[:relname].should == "Möhre"
1213
- notification[:relname].encoding.should == Encoding::UTF_8
1214
- notification[:extra].should == '世界線航跡蔵'
1215
- notification[:extra].encoding.should == Encoding::UTF_8
1216
- notification[:be_pid].should > 0
1637
+ expect( notification[:relname] ).to eq( "Möhre" )
1638
+ expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
1639
+ expect( notification[:extra] ).to eq( '世界線航跡蔵' )
1640
+ expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
1641
+ expect( notification[:be_pid] ).to be > 0
1217
1642
  end
1218
1643
  end
1219
1644
 
1220
- context "OS thread support", :ruby_19 do
1645
+ context "OS thread support" do
1221
1646
  it "Connection#exec shouldn't block a second thread" do
1222
1647
  t = Thread.new do
1223
1648
  @conn.exec( "select pg_sleep(1)" )
1224
1649
  end
1225
1650
 
1226
1651
  sleep 0.5
1227
- t.should be_alive()
1652
+ expect( t ).to be_alive()
1228
1653
  t.join
1229
1654
  end
1230
1655
 
@@ -1238,9 +1663,287 @@ describe PG::Connection do
1238
1663
  end
1239
1664
 
1240
1665
  sleep 0.5
1241
- t.should be_alive()
1666
+ expect( t ).to be_alive()
1242
1667
  serv.close
1243
1668
  t.join
1244
1669
  end
1245
1670
  end
1671
+
1672
+ describe "type casting" do
1673
+ it "should raise an error on invalid param mapping" do
1674
+ expect{
1675
+ @conn.exec_params( "SELECT 1", [], nil, :invalid )
1676
+ }.to raise_error(TypeError)
1677
+ end
1678
+
1679
+ it "should return nil if no type mapping is set" do
1680
+ expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
1681
+ expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
1682
+ end
1683
+
1684
+ it "shouldn't type map params unless requested" do
1685
+ if @conn.server_version < 100000
1686
+ expect{
1687
+ @conn.exec_params( "SELECT $1", [5] )
1688
+ }.to raise_error(PG::IndeterminateDatatype)
1689
+ else
1690
+ # PostgreSQL-10 maps to TEXT type (OID 25)
1691
+ expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
1692
+ end
1693
+ end
1694
+
1695
+ it "should raise an error on invalid encoder to put_copy_data" do
1696
+ expect{
1697
+ @conn.put_copy_data [1], :invalid
1698
+ }.to raise_error(TypeError)
1699
+ end
1700
+
1701
+ it "can type cast parameters to put_copy_data with explicit encoder" do
1702
+ tm = PG::TypeMapByColumn.new [nil]
1703
+ row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1704
+
1705
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1706
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1707
+ @conn.put_copy_data [1], row_encoder
1708
+ @conn.put_copy_data ["2"], row_encoder
1709
+ end
1710
+
1711
+ @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1712
+ @conn.put_copy_data [3]
1713
+ @conn.put_copy_data ["4"]
1714
+ end
1715
+
1716
+ res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
1717
+ expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
1718
+ end
1719
+
1720
+ context "with default query type map" do
1721
+ before :each do
1722
+ @conn2 = described_class.new(@conninfo)
1723
+ tm = PG::TypeMapByClass.new
1724
+ tm[Integer] = PG::TextEncoder::Integer.new oid: 20
1725
+ @conn2.type_map_for_queries = tm
1726
+
1727
+ row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1728
+ @conn2.encoder_for_put_copy_data = row_encoder
1729
+ end
1730
+ after :each do
1731
+ @conn2.close
1732
+ end
1733
+
1734
+ it "should respect a type mapping for params and it's OID and format code" do
1735
+ res = @conn2.exec_params( "SELECT $1", [5] )
1736
+ expect( res.values ).to eq( [["5"]] )
1737
+ expect( res.ftype(0) ).to eq( 20 )
1738
+ end
1739
+
1740
+ it "should return the current type mapping" do
1741
+ expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
1742
+ end
1743
+
1744
+ it "should work with arbitrary number of params in conjunction with type casting" do
1745
+ begin
1746
+ 3.step( 12, 0.2 ) do |exp|
1747
+ num_params = (2 ** exp).to_i
1748
+ sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
1749
+ params = num_params.times.to_a
1750
+ res = @conn2.exec_params( "SELECT #{sql}", params )
1751
+ expect( res.nfields ).to eq( num_params )
1752
+ expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
1753
+ end
1754
+ rescue PG::ProgramLimitExceeded
1755
+ # Stop silently as soon the server complains about too many params
1756
+ end
1757
+ end
1758
+
1759
+ it "can process #copy_data input queries with row encoder and respects character encoding" do
1760
+ @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1761
+ @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1762
+ @conn2.put_copy_data [1]
1763
+ @conn2.put_copy_data ["Möhre".encode("utf-16le")]
1764
+ end
1765
+
1766
+ res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1767
+ expect( res.values ).to eq( [["1"], ["Möhre"]] )
1768
+ end
1769
+ end
1770
+
1771
+ context "with default result type map" do
1772
+ before :each do
1773
+ @conn2 = described_class.new(@conninfo)
1774
+ tm = PG::TypeMapByOid.new
1775
+ tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
1776
+ @conn2.type_map_for_results = tm
1777
+
1778
+ row_decoder = PG::TextDecoder::CopyRow.new
1779
+ @conn2.decoder_for_get_copy_data = row_decoder
1780
+ end
1781
+ after :each do
1782
+ @conn2.close
1783
+ end
1784
+
1785
+ it "should respect a type mapping for result" do
1786
+ res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
1787
+ expect( res.values ).to eq( [[5]] )
1788
+ end
1789
+
1790
+ it "should return the current type mapping" do
1791
+ expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
1792
+ end
1793
+
1794
+ it "should work with arbitrary number of params in conjunction with type casting" do
1795
+ begin
1796
+ 3.step( 12, 0.2 ) do |exp|
1797
+ num_params = (2 ** exp).to_i
1798
+ sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
1799
+ params = num_params.times.to_a
1800
+ res = @conn2.exec_params( "SELECT #{sql}", params )
1801
+ expect( res.nfields ).to eq( num_params )
1802
+ expect( res.values ).to eq( [num_params.times.to_a] )
1803
+ end
1804
+ rescue PG::ProgramLimitExceeded
1805
+ # Stop silently as soon the server complains about too many params
1806
+ end
1807
+ end
1808
+
1809
+ it "can process #copy_data output with row decoder and respects character encoding" do
1810
+ @conn2.internal_encoding = Encoding::ISO8859_1
1811
+ rows = []
1812
+ @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
1813
+ while row=@conn2.get_copy_data
1814
+ rows << row
1815
+ end
1816
+ end
1817
+ expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
1818
+ expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
1819
+ end
1820
+
1821
+ it "can type cast #copy_data output with explicit decoder" do
1822
+ tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
1823
+ row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
1824
+ rows = []
1825
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
1826
+ while row=@conn.get_copy_data
1827
+ rows << row
1828
+ end
1829
+ end
1830
+ @conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
1831
+ while row=@conn.get_copy_data( false, row_decoder )
1832
+ rows << row
1833
+ end
1834
+ end
1835
+ expect( rows ).to eq( [[1], [2], [3], [4]] )
1836
+ end
1837
+ end
1838
+ end
1839
+
1840
+ describe :field_name_type do
1841
+ before :each do
1842
+ @conn2 = PG.connect(@conninfo)
1843
+ end
1844
+ after :each do
1845
+ @conn2.close
1846
+ end
1847
+
1848
+ it "uses string field names per default" do
1849
+ expect(@conn2.field_name_type).to eq(:string)
1850
+ end
1851
+
1852
+ it "can set string field names" do
1853
+ @conn2.field_name_type = :string
1854
+ expect(@conn2.field_name_type).to eq(:string)
1855
+ res = @conn2.exec("SELECT 1 as az")
1856
+ expect(res.field_name_type).to eq(:string)
1857
+ expect(res.fields).to eq(["az"])
1858
+ end
1859
+
1860
+ it "can set symbol field names" do
1861
+ @conn2.field_name_type = :symbol
1862
+ expect(@conn2.field_name_type).to eq(:symbol)
1863
+ res = @conn2.exec("SELECT 1 as az")
1864
+ expect(res.field_name_type).to eq(:symbol)
1865
+ expect(res.fields).to eq([:az])
1866
+ end
1867
+
1868
+ it "can't set invalid values" do
1869
+ expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
1870
+ expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
1871
+ end
1872
+ end
1873
+
1874
+ describe "deprecated forms of methods" do
1875
+ if PG::VERSION < "2"
1876
+ it "should forward exec to exec_params" do
1877
+ res = @conn.exec("VALUES($1::INT)", [7]).values
1878
+ expect(res).to eq( [["7"]] )
1879
+ res = @conn.exec("VALUES($1::INT)", [7], 1).values
1880
+ expect(res).to eq( [[[7].pack("N")]] )
1881
+ res = @conn.exec("VALUES(8)", [], 1).values
1882
+ expect(res).to eq( [[[8].pack("N")]] )
1883
+ end
1884
+
1885
+ it "should forward exec_params to exec" do
1886
+ res = @conn.exec_params("VALUES(3); VALUES(4)").values
1887
+ expect(res).to eq( [["4"]] )
1888
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil).values
1889
+ expect(res).to eq( [["4"]] )
1890
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil).values
1891
+ expect(res).to eq( [["4"]] )
1892
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, 1).values
1893
+ expect(res).to eq( [["4"]] )
1894
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil).values
1895
+ expect(res).to eq( [["4"]] )
1896
+ expect{
1897
+ @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil, nil).values
1898
+ }.to raise_error(ArgumentError)
1899
+ end
1900
+
1901
+ it "should forward send_query to send_query_params" do
1902
+ @conn.send_query("VALUES($1)", [5])
1903
+ expect(@conn.get_last_result.values).to eq( [["5"]] )
1904
+ end
1905
+
1906
+ it "should respond_to socket", :unix do
1907
+ expect( @conn.socket ).to eq( @conn.socket_io.fileno )
1908
+ end
1909
+ else
1910
+ # Method forwarding removed by PG::VERSION >= "2"
1911
+ it "shouldn't forward exec to exec_params" do
1912
+ expect do
1913
+ @conn.exec("VALUES($1::INT)", [7])
1914
+ end.to raise_error(ArgumentError)
1915
+ end
1916
+
1917
+ it "shouldn't forward exec_params to exec" do
1918
+ expect do
1919
+ @conn.exec_params("VALUES(3); VALUES(4)")
1920
+ end.to raise_error(ArgumentError)
1921
+ end
1922
+
1923
+ it "shouldn't forward send_query to send_query_params" do
1924
+ expect do
1925
+ @conn.send_query("VALUES($1)", [5])
1926
+ end.to raise_error(ArgumentError)
1927
+ end
1928
+
1929
+ it "shouldn't forward async_exec_params to async_exec" do
1930
+ expect do
1931
+ @conn.async_exec_params("VALUES(1)")
1932
+ end.to raise_error(ArgumentError)
1933
+ end
1934
+
1935
+ it "shouldn't respond_to socket" do
1936
+ expect do
1937
+ @conn.socket
1938
+ end.to raise_error(ArgumentError)
1939
+ end
1940
+ end
1941
+
1942
+ it "shouldn't forward send_query_params to send_query" do
1943
+ expect{ @conn.send_query_params("VALUES(4)").values }
1944
+ .to raise_error(ArgumentError)
1945
+ expect{ @conn.send_query_params("VALUES(4)", nil).values }
1946
+ .to raise_error(TypeError)
1947
+ end
1948
+ end
1246
1949
  end