pg 0.15.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.tar.gz.sig +0 -0
  4. data/BSDL +2 -2
  5. data/ChangeLog +0 -3022
  6. data/History.rdoc +370 -4
  7. data/Manifest.txt +39 -19
  8. data/README-Windows.rdoc +17 -28
  9. data/README.ja.rdoc +1 -2
  10. data/README.rdoc +113 -14
  11. data/Rakefile +97 -36
  12. data/Rakefile.cross +109 -83
  13. data/ext/errorcodes.def +1032 -0
  14. data/ext/errorcodes.rb +45 -0
  15. data/ext/errorcodes.txt +494 -0
  16. data/ext/extconf.rb +55 -52
  17. data/ext/gvl_wrappers.c +4 -0
  18. data/ext/gvl_wrappers.h +94 -38
  19. data/ext/pg.c +273 -121
  20. data/ext/pg.h +292 -50
  21. data/ext/pg_binary_decoder.c +229 -0
  22. data/ext/pg_binary_encoder.c +163 -0
  23. data/ext/pg_coder.c +561 -0
  24. data/ext/pg_connection.c +1811 -1051
  25. data/ext/pg_copy_coder.c +599 -0
  26. data/ext/pg_errors.c +95 -0
  27. data/ext/pg_record_coder.c +491 -0
  28. data/ext/pg_result.c +917 -203
  29. data/ext/pg_text_decoder.c +987 -0
  30. data/ext/pg_text_encoder.c +814 -0
  31. data/ext/pg_tuple.c +549 -0
  32. data/ext/pg_type_map.c +166 -0
  33. data/ext/pg_type_map_all_strings.c +116 -0
  34. data/ext/pg_type_map_by_class.c +244 -0
  35. data/ext/pg_type_map_by_column.c +313 -0
  36. data/ext/pg_type_map_by_mri_type.c +284 -0
  37. data/ext/pg_type_map_by_oid.c +356 -0
  38. data/ext/pg_type_map_in_ruby.c +299 -0
  39. data/ext/pg_util.c +149 -0
  40. data/ext/pg_util.h +65 -0
  41. data/lib/pg.rb +31 -9
  42. data/lib/pg/basic_type_mapping.rb +522 -0
  43. data/lib/pg/binary_decoder.rb +23 -0
  44. data/lib/pg/coder.rb +104 -0
  45. data/lib/pg/connection.rb +235 -30
  46. data/lib/pg/constants.rb +2 -1
  47. data/lib/pg/exceptions.rb +2 -1
  48. data/lib/pg/result.rb +33 -6
  49. data/lib/pg/text_decoder.rb +46 -0
  50. data/lib/pg/text_encoder.rb +59 -0
  51. data/lib/pg/tuple.rb +30 -0
  52. data/lib/pg/type_map_by_column.rb +16 -0
  53. data/spec/{lib/helpers.rb → helpers.rb} +154 -52
  54. data/spec/pg/basic_type_mapping_spec.rb +630 -0
  55. data/spec/pg/connection_spec.rb +1352 -426
  56. data/spec/pg/connection_sync_spec.rb +41 -0
  57. data/spec/pg/result_spec.rb +508 -105
  58. data/spec/pg/tuple_spec.rb +333 -0
  59. data/spec/pg/type_map_by_class_spec.rb +138 -0
  60. data/spec/pg/type_map_by_column_spec.rb +226 -0
  61. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  62. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  63. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  64. data/spec/pg/type_map_spec.rb +22 -0
  65. data/spec/pg/type_spec.rb +1123 -0
  66. data/spec/pg_spec.rb +35 -16
  67. metadata +163 -84
  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\\' \\\\'/ )
86
60
 
87
61
  end
88
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
+
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} )
115
+ end
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( '' )
119
+ end
120
+
121
+ it "connects successfully with connection string" do
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} )
91
132
  end
92
133
 
93
134
  it "connects successfully with connection string" do
94
- tmpconn = described_class.connect(@conninfo)
95
- tmpconn.status.should == PG::CONNECTION_OK
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,87 +148,160 @@ 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
173
210
 
174
- tmpconn.status.should == PG::CONNECTION_OK
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"]])
175
215
  end
176
216
 
177
- conn.should be_finished()
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"]])
222
+ end
223
+
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
262
+ end
263
+
264
+ it "raises proper error when sending fails" do
265
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
266
+ expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
178
267
  end
179
268
 
180
269
  it "doesn't leave stale server connections after finish" do
181
270
  described_class.connect(@conninfo).finish
182
271
  sleep 0.5
183
272
  res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
184
- WHERE usename IS NOT NULL])
273
+ WHERE usename IS NOT NULL AND application_name != ''])
185
274
  # there's still the global @conn, but should be no more
186
- res[0]['n'].should == '1'
275
+ expect( res[0]['n'] ).to eq( '1' )
187
276
  end
188
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" )
289
+ end
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
189
296
 
190
- EXPECTED_TRACE_OUTPUT = %{
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
+ %{
191
305
  To backend> Msg Q
192
306
  To backend> "SELECT 1 AS one"
193
307
  To backend> Msg complete, length 21
@@ -215,6 +329,7 @@ describe PG::Connection do
215
329
  From backend (#4)> 5
216
330
  From backend> T
217
331
  }.gsub( /^\t{2}/, '' ).lstrip
332
+ end
218
333
 
219
334
  it "trace and untrace client-server communication", :unix do
220
335
  # be careful to explicitly close files so that the
@@ -225,25 +340,22 @@ describe PG::Connection do
225
340
  @conn.trace( trace_io )
226
341
  trace_io.close
227
342
 
228
- res = @conn.exec("SELECT 1 AS one")
343
+ @conn.exec("SELECT 1 AS one")
229
344
  @conn.untrace
230
345
 
231
- res = @conn.exec("SELECT 2 AS two")
346
+ @conn.exec("SELECT 2 AS two")
232
347
 
233
348
  trace_data = trace_file.read
234
349
 
235
- expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
236
- # For PostgreSQL < 9.0, the output will be different:
237
- # -From backend (#4)> 13
238
- # -From backend> "SELECT 1"
239
- # +From backend (#4)> 11
240
- # +From backend> "SELECT"
241
- if @conn.server_version < 90000
242
- expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
243
- expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
244
- 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' )
245
357
 
246
- trace_data.should == expected_trace_output
358
+ expect( trace_data ).to eq( expected_trace_output )
247
359
  end
248
360
 
249
361
  it "allows a query to be cancelled" do
@@ -254,9 +366,36 @@ describe PG::Connection do
254
366
  if(tmpres.result_status != PG::PGRES_TUPLES_OK)
255
367
  error = true
256
368
  end
257
- error.should == true
369
+ expect( error ).to eq( true )
370
+ end
371
+
372
+ it "can stop a thread that runs a blocking query with async_exec" do
373
+ start = Time.now
374
+ t = Thread.new do
375
+ @conn.async_exec( 'select pg_sleep(10)' )
376
+ end
377
+ sleep 0.1
378
+
379
+ t.kill
380
+ t.join
381
+ expect( (Time.now - start) ).to be < 10
258
382
  end
259
383
 
384
+ it "should work together with signal handlers", :unix do
385
+ signal_received = false
386
+ trap 'USR2' do
387
+ signal_received = true
388
+ end
389
+
390
+ Thread.new do
391
+ sleep 0.1
392
+ Process.kill("USR2", Process.pid)
393
+ end
394
+ @conn.exec("select pg_sleep(0.3)")
395
+ expect( signal_received ).to be_truthy
396
+ end
397
+
398
+
260
399
  it "automatically rolls back a transaction started with Connection#transaction if an exception " +
261
400
  "is raised" do
262
401
  # abort the per-example transaction so we can test our own
@@ -265,15 +404,29 @@ describe PG::Connection do
265
404
  res = nil
266
405
  @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
267
406
 
268
- expect {
269
- res = @conn.transaction do
270
- @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
271
- raise "Oh noes! All pie is gone!"
272
- end
273
- }.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 )
274
414
 
275
- res = @conn.exec( "SELECT * FROM pie" )
276
- 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
420
+ end
421
+
422
+ it "returns the block result from Connection#transaction" do
423
+ # abort the per-example transaction so we can test our own
424
+ @conn.exec( 'ROLLBACK' )
425
+
426
+ res = @conn.transaction do
427
+ "transaction result"
428
+ end
429
+ expect( res ).to eq( "transaction result" )
277
430
  end
278
431
 
279
432
  it "not read past the end of a large object" do
@@ -281,33 +434,45 @@ describe PG::Connection do
281
434
  oid = @conn.lo_create( 0 )
282
435
  fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
283
436
  @conn.lo_write( fd, "foobar" )
284
- @conn.lo_read( fd, 10 ).should be_nil()
437
+ expect( @conn.lo_read( fd, 10 ) ).to be_nil()
285
438
  @conn.lo_lseek( fd, 0, PG::SEEK_SET )
286
- @conn.lo_read( fd, 10 ).should == 'foobar'
439
+ expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
287
440
  end
288
441
  end
289
442
 
290
-
291
- it "supports parameters passed to #exec (backward compatibility)" do
292
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
293
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
294
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
295
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
296
-
297
- res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
298
- res.values.should == [ ['Wally'], ['Sally'] ]
299
- end
300
-
301
443
  it "supports explicitly calling #exec_params" do
302
444
  @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
303
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
304
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
305
- @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] )
306
448
 
307
449
  res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
308
- res.values.should == [ ['Wally'], ['Sally'] ]
450
+ expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
451
+ end
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'] )
309
460
  end
310
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
311
476
 
312
477
  it "can wait for NOTIFY events" do
313
478
  @conn.exec( 'ROLLBACK' )
@@ -323,7 +488,7 @@ describe PG::Connection do
323
488
  end
324
489
  end
325
490
 
326
- @conn.wait_for_notify( 10 ).should == 'woo'
491
+ expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
327
492
  @conn.exec( 'UNLISTEN woo' )
328
493
 
329
494
  t.join
@@ -345,8 +510,8 @@ describe PG::Connection do
345
510
 
346
511
  eventpid = event = nil
347
512
  @conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
348
- event.should == 'woo'
349
- eventpid.should be_an( Integer )
513
+ expect( event ).to eq( 'woo' )
514
+ expect( eventpid ).to be_an( Integer )
350
515
 
351
516
  @conn.exec( 'UNLISTEN woo' )
352
517
 
@@ -373,8 +538,8 @@ describe PG::Connection do
373
538
  channels << @conn.wait_for_notify( 2 )
374
539
  end
375
540
 
376
- channels.should have( 3 ).members
377
- channels.should include( 'woo', 'war', 'woz' )
541
+ expect( channels.size ).to eq( 3 )
542
+ expect( channels ).to include( 'woo', 'war', 'woz' )
378
543
 
379
544
  @conn.exec( 'UNLISTEN woz' )
380
545
  @conn.exec( 'UNLISTEN war' )
@@ -396,25 +561,161 @@ describe PG::Connection do
396
561
  # Cause the notification to buffer, but not be read yet
397
562
  @conn.exec( 'SELECT 1' )
398
563
 
399
- @conn.wait_for_notify( 10 ).should == 'woo'
564
+ expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
400
565
  @conn.exec( 'UNLISTEN woo' )
401
566
  end
402
567
 
568
+ it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
569
+ notices = []
570
+ @conn.set_notice_processor do |msg|
571
+ notices << [msg, Time.now]
572
+ end
573
+ st = Time.now
574
+ @conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
575
+ expect( @conn.wait_for_notify( 1 ) ).to be_nil
576
+ expect( notices.first ).to_not be_nil
577
+ et = Time.now
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
581
+ end
582
+
403
583
  it "yields the result if block is given to exec" do
404
584
  rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
405
585
  values = []
406
- result.should be_kind_of( PG::Result )
407
- result.ntuples.should == 2
586
+ expect( result ).to be_kind_of( PG::Result )
587
+ expect( result.ntuples ).to eq( 2 )
408
588
  result.each do |tuple|
409
589
  values << tuple['a']
410
590
  end
411
591
  values
412
592
  end
413
593
 
414
- rval.should have( 2 ).members
415
- rval.should include( '5678', '1234' )
594
+ expect( rval.size ).to eq( 2 )
595
+ expect( rval ).to include( '5678', '1234' )
416
596
  end
417
597
 
598
+ it "can process #copy_data output queries" do
599
+ rows = []
600
+ res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
601
+ expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
602
+ expect( res.nfields ).to eq( 1 )
603
+ while row=@conn.get_copy_data
604
+ rows << row
605
+ end
606
+ end
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
610
+ end
611
+
612
+ it "can handle incomplete #copy_data output queries" do
613
+ expect {
614
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
615
+ @conn.get_copy_data
616
+ end
617
+ }.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
618
+ expect( @conn ).to still_be_usable
619
+ end
620
+
621
+ it "can handle client errors in #copy_data for output" do
622
+ expect {
623
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do
624
+ raise "boom"
625
+ end
626
+ }.to raise_error(RuntimeError, "boom")
627
+ expect( @conn ).to still_be_usable
628
+ end
629
+
630
+ it "can handle server errors in #copy_data for output" do
631
+ @conn.exec "ROLLBACK"
632
+ @conn.transaction do
633
+ @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
634
+ expect {
635
+ @conn.copy_data( "COPY (SELECT errfunc()) TO STDOUT" ) do |res|
636
+ while @conn.get_copy_data
637
+ end
638
+ end
639
+ }.to raise_error(PG::Error, /test-error/)
640
+ end
641
+ expect( @conn ).to still_be_usable
642
+ end
643
+
644
+ it "can process #copy_data input queries" do
645
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
646
+ res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
647
+ expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
648
+ expect( res.nfields ).to eq( 1 )
649
+ @conn.put_copy_data "1\n"
650
+ @conn.put_copy_data "2\n"
651
+ end
652
+ expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
653
+
654
+ expect( @conn ).to still_be_usable
655
+
656
+ res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
657
+ expect( res.values ).to eq( [["1"], ["2"]] )
658
+ end
659
+
660
+ it "can handle client errors in #copy_data for input" do
661
+ @conn.exec "ROLLBACK"
662
+ @conn.transaction do
663
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
664
+ expect {
665
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
666
+ raise "boom"
667
+ end
668
+ }.to raise_error(RuntimeError, "boom")
669
+ end
670
+
671
+ expect( @conn ).to still_be_usable
672
+ end
673
+
674
+ it "can handle server errors in #copy_data for input" do
675
+ @conn.exec "ROLLBACK"
676
+ @conn.transaction do
677
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
678
+ expect {
679
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
680
+ @conn.put_copy_data "xyz\n"
681
+ end
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/)
696
+ end
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
710
+ end
711
+
712
+ it "should raise an error for non copy statements in #copy_data" do
713
+ expect {
714
+ @conn.copy_data( "SELECT 1" ){}
715
+ }.to raise_error(ArgumentError, /no COPY/)
716
+
717
+ expect( @conn ).to still_be_usable
718
+ end
418
719
 
419
720
  it "correctly finishes COPY queries passed to #async_exec" do
420
721
  @conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
@@ -429,8 +730,8 @@ describe PG::Connection do
429
730
  results << data if data
430
731
  end until data.nil?
431
732
 
432
- results.should have( 2 ).members
433
- results.should include( "1\n", "2\n" )
733
+ expect( results.size ).to eq( 2 )
734
+ expect( results ).to include( "1\n", "2\n" )
434
735
  end
435
736
 
436
737
 
@@ -442,54 +743,138 @@ describe PG::Connection do
442
743
  end
443
744
 
444
745
  sleep 0.5
445
- t.should be_alive()
746
+ expect( t ).to be_alive()
446
747
  @conn.cancel
447
748
  t.join
448
- (Time.now - start).should < 3
749
+ expect( (Time.now - start) ).to be < 3
449
750
  end
450
751
 
451
752
  it "described_class#block should allow a timeout" do
452
- @conn.send_query( "select pg_sleep(3)" )
753
+ @conn.send_query( "select pg_sleep(1)" )
453
754
 
454
755
  start = Time.now
455
- @conn.block( 0.1 )
756
+ @conn.block( 0.3 )
456
757
  finish = Time.now
457
758
 
458
- (finish - start).should be_within( 0.05 ).of( 0.1 )
759
+ expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
459
760
  end
460
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
461
768
 
462
- it "can encrypt a string given a password and username" do
463
- described_class.encrypt_password("postgres", "postgres").
464
- 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 )
465
774
  end
466
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
467
781
 
468
- it "raises an appropriate error if either of the required arguments for encrypt_password " +
469
- "is not valid" do
470
- expect {
471
- described_class.encrypt_password( nil, nil )
472
- }.to raise_error( TypeError )
473
- expect {
474
- described_class.encrypt_password( "postgres", nil )
475
- }.to raise_error( TypeError )
476
- expect {
477
- described_class.encrypt_password( nil, "postgres" )
478
- }.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
479
864
  end
480
865
 
481
866
 
482
867
  it "allows fetching a column of values from a result by column number" do
483
868
  res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
484
- res.column_values( 0 ).should == %w[1 2 3]
485
- 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] )
486
871
  end
487
872
 
488
873
 
489
874
  it "allows fetching a column of values from a result by field name" do
490
875
  res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
491
- res.field_values( 'column1' ).should == %w[1 2 3]
492
- 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] )
493
878
  end
494
879
 
495
880
 
@@ -517,202 +902,245 @@ describe PG::Connection do
517
902
  end
518
903
 
519
904
 
520
- it "can connect asynchronously", :socket_io do
905
+ it "handles server close while asynchronous connect" do
521
906
  serv = TCPServer.new( '127.0.0.1', 54320 )
522
907
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
523
- [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
524
909
  select( nil, [conn.socket_io], nil, 0.2 )
525
910
  serv.close
526
911
  if conn.connect_poll == PG::PGRES_POLLING_READING
527
912
  select( [conn.socket_io], nil, nil, 0.2 )
528
913
  end
529
- conn.connect_poll.should == PG::PGRES_POLLING_FAILED
914
+ expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
915
+ end
916
+
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' }] )
530
923
  end
531
924
 
532
- it "discards previous results (if any) before waiting on an #async_exec"
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
533
930
 
534
- 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
535
938
  result = nil
536
- @conn.async_exec( "select 47 as one" ) do |pg_res|
939
+ @conn.exec( "select 47 as one" ) do |pg_res|
537
940
  result = pg_res[0]
538
941
  end
539
- result.should == { 'one' => '47' }
942
+ expect( result ).to eq( { 'one' => '47' } )
540
943
  end
541
944
 
542
945
  it "raises a rescue-able error if #finish is called twice", :without_transaction do
543
946
  conn = PG.connect( @conninfo )
544
947
 
545
948
  conn.finish
546
- expect { conn.finish }.to raise_error( PG::Error, /connection is closed/i )
949
+ expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
950
+ end
951
+
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
547
964
  end
548
965
 
549
- it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
966
+ it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction do
550
967
  conn = PG.connect( @conninfo )
551
968
  io = conn.socket_io
552
969
  conn.finish
553
- io.should be_closed()
554
- expect { conn.socket_io }.to raise_error( PG::Error, /connection is closed/i )
970
+ expect( io ).to be_closed()
971
+ expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
555
972
  end
556
973
 
557
- 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
558
975
  conn = PG.connect( @conninfo )
559
976
  io = conn.socket_io
560
977
  conn.reset
561
- io.should be_closed()
562
- conn.socket_io.should_not equal( io )
978
+ expect( io ).to be_closed()
979
+ expect( conn.socket_io ).to_not equal( io )
563
980
  conn.finish
564
981
  end
565
982
 
566
-
567
- context "under PostgreSQL 9", :postgresql_90 do
568
-
569
- before( :each ) do
570
- pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
983
+ it "block should raise ConnectionBad for a closed connection" do
984
+ serv = TCPServer.new( '127.0.0.1', 54320 )
985
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
986
+ while [PG::CONNECTION_STARTED, PG::CONNECTION_MADE].include?(conn.connect_poll)
987
+ sleep 0.1
571
988
  end
989
+ serv.close
990
+ expect{ conn.block }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
991
+ expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
992
+ end
572
993
 
573
- it "sets the fallback_application_name on new connections" do
574
- PG::Connection.parse_connect_args( 'dbname=test' ).should include( $0 )
575
- end
994
+ it "sets the fallback_application_name on new connections" do
995
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
576
996
 
577
- it "sets a shortened fallback_application_name on new connections" do
578
- old_0 = $0
579
- begin
580
- $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
581
- PG::Connection.parse_connect_args( 'dbname=test' ).should match(/\/this\/is\/a.*\.\.\..*\/beloved\/ruby/)
582
- ensure
583
- $0 = old_0
584
- end
585
- 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
586
1002
 
587
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
588
- "any number of arguments" 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"
1007
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
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
1014
+ end
1015
+ end
589
1016
 
590
- @conn.exec( 'ROLLBACK' )
591
- @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
592
1019
 
593
- conn = described_class.connect( @conninfo )
594
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
595
- conn.finish
1020
+ @conn.exec( 'ROLLBACK' )
1021
+ @conn.exec( 'LISTEN knees' )
596
1022
 
597
- event, pid, msg = nil
598
- @conn.wait_for_notify( 10 ) do |*args|
599
- event, pid, msg = *args
600
- end
601
- @conn.exec( 'UNLISTEN knees' )
1023
+ conn = described_class.connect( @conninfo )
1024
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1025
+ conn.finish
602
1026
 
603
- event.should == 'knees'
604
- pid.should be_a_kind_of( Integer )
605
- msg.should == 'skirt and boots'
1027
+ event, pid, msg = nil
1028
+ @conn.wait_for_notify( 10 ) do |*args|
1029
+ event, pid, msg = *args
606
1030
  end
1031
+ @conn.exec( 'UNLISTEN knees' )
607
1032
 
608
- it "accepts nil as the timeout in #wait_for_notify " do
609
- @conn.exec( 'ROLLBACK' )
610
- @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
611
1037
 
612
- conn = described_class.connect( @conninfo )
613
- conn.exec( %Q{NOTIFY knees} )
614
- conn.finish
1038
+ it "accepts nil as the timeout in #wait_for_notify " do
1039
+ @conn.exec( 'ROLLBACK' )
1040
+ @conn.exec( 'LISTEN knees' )
615
1041
 
616
- event, pid = nil
617
- @conn.wait_for_notify( nil ) do |*args|
618
- event, pid = *args
619
- end
620
- @conn.exec( 'UNLISTEN knees' )
1042
+ conn = described_class.connect( @conninfo )
1043
+ conn.exec( %Q{NOTIFY knees} )
1044
+ conn.finish
621
1045
 
622
- event.should == 'knees'
623
- pid.should be_a_kind_of( Integer )
1046
+ event, pid = nil
1047
+ @conn.wait_for_notify( nil ) do |*args|
1048
+ event, pid = *args
624
1049
  end
1050
+ @conn.exec( 'UNLISTEN knees' )
625
1051
 
626
- it "sends nil as the payload if the notification wasn't given one" do
627
- @conn.exec( 'ROLLBACK' )
628
- @conn.exec( 'LISTEN knees' )
1052
+ expect( event ).to eq( 'knees' )
1053
+ expect( pid ).to be_a_kind_of( Integer )
1054
+ end
629
1055
 
630
- conn = described_class.connect( @conninfo )
631
- conn.exec( %Q{NOTIFY knees} )
632
- 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' )
633
1059
 
634
- payload = :notnil
635
- @conn.wait_for_notify( nil ) do |*args|
636
- payload = args[ 2 ]
637
- end
638
- @conn.exec( 'UNLISTEN knees' )
1060
+ conn = described_class.connect( @conninfo )
1061
+ conn.exec( %Q{NOTIFY knees} )
1062
+ conn.finish
639
1063
 
640
- payload.should be_nil()
1064
+ payload = :notnil
1065
+ @conn.wait_for_notify( nil ) do |*args|
1066
+ payload = args[ 2 ]
641
1067
  end
1068
+ @conn.exec( 'UNLISTEN knees' )
642
1069
 
643
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
644
- "two arguments" do
1070
+ expect( payload ).to be_nil()
1071
+ end
645
1072
 
646
- @conn.exec( 'ROLLBACK' )
647
- @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
648
1075
 
649
- conn = described_class.connect( @conninfo )
650
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
651
- conn.finish
1076
+ @conn.exec( 'ROLLBACK' )
1077
+ @conn.exec( 'LISTEN knees' )
652
1078
 
653
- event, pid, msg = nil
654
- @conn.wait_for_notify( 10 ) do |arg1, arg2|
655
- event, pid, msg = arg1, arg2
656
- end
657
- @conn.exec( 'UNLISTEN knees' )
1079
+ conn = described_class.connect( @conninfo )
1080
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1081
+ conn.finish
658
1082
 
659
- event.should == 'knees'
660
- pid.should be_a_kind_of( Integer )
661
- msg.should be_nil()
1083
+ event, pid, msg = nil
1084
+ @conn.wait_for_notify( 10 ) do |arg1, arg2|
1085
+ event, pid, msg = arg1, arg2
662
1086
  end
1087
+ @conn.exec( 'UNLISTEN knees' )
663
1088
 
664
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
665
- "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
666
1093
 
667
- @conn.exec( 'ROLLBACK' )
668
- @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
669
1096
 
670
- conn = described_class.connect( @conninfo )
671
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
672
- conn.finish
1097
+ @conn.exec( 'ROLLBACK' )
1098
+ @conn.exec( 'LISTEN knees' )
673
1099
 
674
- notification_received = false
675
- @conn.wait_for_notify( 10 ) do
676
- notification_received = true
677
- end
678
- @conn.exec( 'UNLISTEN knees' )
1100
+ conn = described_class.connect( @conninfo )
1101
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1102
+ conn.finish
679
1103
 
680
- notification_received.should be_true()
1104
+ notification_received = false
1105
+ @conn.wait_for_notify( 10 ) do
1106
+ notification_received = true
681
1107
  end
1108
+ @conn.exec( 'UNLISTEN knees' )
682
1109
 
683
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
684
- "three arguments" do
1110
+ expect( notification_received ).to be_truthy()
1111
+ end
685
1112
 
686
- @conn.exec( 'ROLLBACK' )
687
- @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
688
1115
 
689
- conn = described_class.connect( @conninfo )
690
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
691
- conn.finish
1116
+ @conn.exec( 'ROLLBACK' )
1117
+ @conn.exec( 'LISTEN knees' )
692
1118
 
693
- event, pid, msg = nil
694
- @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
695
- event, pid, msg = arg1, arg2, arg3
696
- end
697
- @conn.exec( 'UNLISTEN knees' )
1119
+ conn = described_class.connect( @conninfo )
1120
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1121
+ conn.finish
698
1122
 
699
- event.should == 'knees'
700
- pid.should be_a_kind_of( Integer )
701
- 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
702
1126
  end
1127
+ @conn.exec( 'UNLISTEN knees' )
703
1128
 
1129
+ expect( event ).to eq( 'knees' )
1130
+ expect( pid ).to be_a_kind_of( Integer )
1131
+ expect( msg ).to eq( 'skirt and boots' )
704
1132
  end
705
1133
 
706
- context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
1134
+ context "server ping", :without_transaction do
707
1135
 
708
1136
  it "pings successfully with connection string" do
709
1137
  ping = described_class.ping(@conninfo)
710
- ping.should == PG::PQPING_OK
1138
+ expect( ping ).to eq( PG::PQPING_OK )
711
1139
  end
712
1140
 
713
1141
  it "pings using 7 arguments converted to strings" do
714
1142
  ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
715
- ping.should == PG::PQPING_OK
1143
+ expect( ping ).to eq( PG::PQPING_OK )
716
1144
  end
717
1145
 
718
1146
  it "pings using a hash of connection parameters" do
@@ -720,7 +1148,7 @@ describe PG::Connection do
720
1148
  :host => 'localhost',
721
1149
  :port => @port,
722
1150
  :dbname => :test)
723
- ping.should == PG::PQPING_OK
1151
+ expect( ping ).to eq( PG::PQPING_OK )
724
1152
  end
725
1153
 
726
1154
  it "returns correct response when ping connection cannot be established" do
@@ -728,169 +1156,359 @@ describe PG::Connection do
728
1156
  :host => 'localhost',
729
1157
  :port => 9999,
730
1158
  :dbname => :test)
731
- ping.should == PG::PQPING_NO_RESPONSE
1159
+ expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
732
1160
  end
733
1161
 
734
- it "returns correct response when ping connection arguments are wrong" do
1162
+ it "returns error when ping connection arguments are wrong" do
735
1163
  ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
736
- ping.should == PG::PQPING_NO_ATTEMPT
1164
+ expect( ping ).to_not eq( PG::PQPING_OK )
737
1165
  end
738
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
739
1174
 
740
1175
  end
741
1176
 
742
- context "under PostgreSQL 9.2 client library", :postgresql_92 do
743
- describe "set_single_row_mode" do
1177
+ describe "set_single_row_mode" do
744
1178
 
745
- it "raises an error when called at the wrong time" do
746
- expect {
747
- @conn.set_single_row_mode
748
- }.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
749
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
750
1205
 
751
- it "should work in single row mode" do
752
- @conn.send_query( "SELECT generate_series(1,10)" )
753
- @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
754
1209
 
755
- results = []
756
- loop do
757
- @conn.block
758
- res = @conn.get_result or break
759
- results << res
760
- end
761
- results.length.should == 11
762
- results[0..-2].each do |res|
763
- res.result_status.should == PG::PGRES_SINGLE_TUPLE
764
- values = res.field_values('generate_series')
765
- values.length.should == 1
766
- values.first.to_i.should > 0
767
- end
768
- results.last.result_status.should == PG::PGRES_TUPLES_OK
769
- 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
770
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
771
1220
 
772
- it "should receive rows before entire query is finished" do
773
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
774
- @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
775
1225
 
776
- start_time = Time.now
777
- first_row_time = nil
1226
+ first_result = nil
1227
+ expect do
778
1228
  loop do
779
1229
  res = @conn.get_result or break
780
1230
  res.check
781
- first_row_time = Time.now unless first_row_time
1231
+ first_result ||= res
782
1232
  end
783
- (Time.now - start_time).should >= 1.0
784
- (first_row_time - start_time).should < 1.0
785
- end
786
-
787
- it "should receive rows before entire query fails" do
788
- @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
789
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
790
- @conn.set_single_row_mode
791
-
792
- first_result = nil
793
- expect do
794
- loop do
795
- res = @conn.get_result or break
796
- res.check
797
- first_result ||= res
798
- end
799
- end.to raise_error(PG::Error)
800
- first_result.kind_of?(PG::Result).should be_true
801
- first_result.result_status.should == PG::PGRES_SINGLE_TUPLE
802
- 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 )
803
1236
  end
1237
+
804
1238
  end
805
1239
 
806
- context "multinationalization support", :ruby_19 do
1240
+ context "multinationalization support" do
807
1241
 
808
1242
  describe "rubyforge #22925: m17n support" do
809
1243
  it "should return results in the same encoding as the client (iso-8859-1)" do
810
- out_string = nil
811
- @conn.transaction do |conn|
812
- conn.internal_encoding = 'iso8859-1'
813
- res = conn.exec("VALUES ('fantasia')", [], 0)
814
- out_string = res[0]['column1']
815
- end
816
- out_string.should == 'fantasia'
817
- 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 )
818
1249
  end
819
1250
 
820
1251
  it "should return results in the same encoding as the client (utf-8)" do
821
- out_string = nil
822
- @conn.transaction do |conn|
823
- conn.internal_encoding = 'utf-8'
824
- res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
825
- out_string = res[0]['column1']
826
- end
827
- out_string.should == '世界線航跡蔵'
828
- 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 )
829
1257
  end
830
1258
 
831
1259
  it "should return results in the same encoding as the client (EUC-JP)" do
832
- out_string = nil
833
- @conn.transaction do |conn|
834
- conn.internal_encoding = 'EUC-JP'
835
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
836
- res = conn.exec(stmt, [], 0)
837
- out_string = res[0]['column1']
838
- end
839
- out_string.should == '世界線航跡蔵'.encode('EUC-JP')
840
- 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 )
841
1266
  end
842
1267
 
843
1268
  it "returns the results in the correct encoding even if the client_encoding has " +
844
1269
  "changed since the results were fetched" do
845
- out_string = nil
846
- @conn.transaction do |conn|
847
- conn.internal_encoding = 'EUC-JP'
848
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
849
- res = conn.exec(stmt, [], 0)
850
- conn.internal_encoding = 'utf-8'
851
- out_string = res[0]['column1']
852
- end
853
- out_string.should == '世界線航跡蔵'.encode('EUC-JP')
854
- 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 )
855
1277
  end
856
1278
 
857
1279
  it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
858
1280
  @conn.exec "SET client_encoding TO SQL_ASCII"
859
- @conn.internal_encoding.should == Encoding::ASCII_8BIT
1281
+ expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
860
1282
  end
861
1283
 
862
- it "works around the unsupported JOHAB encoding by returning stuff in 'ASCII_8BIT'" do
863
- pending "figuring out how to create a string in the JOHAB encoding" do
864
- out_string = nil
865
- @conn.transaction do |conn|
866
- conn.exec( "set client_encoding = 'JOHAB';" )
867
- stmt = "VALUES ('foo')".encode('JOHAB')
868
- res = conn.exec( stmt, [], 0 )
869
- out_string = res[0]['column1']
870
- end
871
- out_string.should == 'foo'.encode( Encoding::ASCII_8BIT )
872
- out_string.encoding.should == Encoding::ASCII_8BIT
873
- 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 )
874
1298
  end
875
1299
 
876
1300
  it "uses the client encoding for escaped string" do
877
- original = "string to escape".force_encoding( "euc-jp" )
1301
+ original = "Möhre to 'scape".encode( "utf-16be" )
878
1302
  @conn.set_client_encoding( "euc_jp" )
879
1303
  escaped = @conn.escape( original )
880
- escaped.encoding.should == Encoding::EUC_JP
1304
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1305
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
881
1306
  end
882
1307
 
883
- it "escapes string as literal", :postgresql_90 do
884
- original = "string to\0 escape"
1308
+ it "uses the client encoding for escaped literal" do
1309
+ original = "Möhre to 'scape".encode( "utf-16be" )
1310
+ @conn.set_client_encoding( "euc_jp" )
885
1311
  escaped = @conn.escape_literal( original )
886
- 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) )
1314
+ end
1315
+
1316
+ it "uses the client encoding for escaped identifier" do
1317
+ original = "Möhre to 'scape".encode( "utf-16le" )
1318
+ @conn.set_client_encoding( "euc_jp" )
1319
+ escaped = @conn.escape_identifier( original )
1320
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1321
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1322
+ end
1323
+
1324
+ it "uses the client encoding for quote_ident" do
1325
+ original = "Möhre to 'scape".encode( "utf-16le" )
1326
+ @conn.set_client_encoding( "euc_jp" )
1327
+ escaped = @conn.quote_ident( original )
1328
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1329
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1330
+ end
1331
+
1332
+ it "uses the previous string encoding for escaped string" do
1333
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1334
+ @conn.set_client_encoding( "euc_jp" )
1335
+ escaped = described_class.escape( original )
1336
+ expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1337
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
1338
+ end
1339
+
1340
+ it "uses the previous string encoding for quote_ident" do
1341
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1342
+ @conn.set_client_encoding( "euc_jp" )
1343
+ escaped = described_class.quote_ident( original )
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']] )
887
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 )
1428
+ end
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/)
888
1488
  end
889
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("\"", "\"\"") + "\"" )
1494
+ end
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
890
1508
 
891
1509
  describe "Ruby 1.9.x default_internal encoding" do
892
1510
 
893
- 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
894
1512
  @conn.transaction do |txn_conn|
895
1513
  txn_conn.internal_encoding = Encoding::ISO8859_1
896
1514
  txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
@@ -899,13 +1517,14 @@ describe PG::Connection do
899
1517
 
900
1518
  begin
901
1519
  prev_encoding = Encoding.default_internal
902
- Encoding.default_internal = Encoding::UTF_8
1520
+ Encoding.default_internal = Encoding::ISO8859_2
903
1521
 
904
1522
  conn = PG.connect( @conninfo )
905
- conn.internal_encoding.should == Encoding::UTF_8
1523
+ expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
906
1524
  res = conn.exec( "SELECT foo FROM defaultinternaltest" )
907
- res[0]['foo'].encoding.should == Encoding::UTF_8
1525
+ expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
908
1526
  ensure
1527
+ conn.exec( "DROP TABLE defaultinternaltest" )
909
1528
  conn.finish if conn
910
1529
  Encoding.default_internal = prev_encoding
911
1530
  end
@@ -918,7 +1537,7 @@ describe PG::Connection do
918
1537
 
919
1538
  @conn.set_default_encoding
920
1539
 
921
- @conn.internal_encoding.should == Encoding::KOI8_R
1540
+ expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
922
1541
  ensure
923
1542
  Encoding.default_internal = prev_encoding
924
1543
  end
@@ -939,7 +1558,7 @@ describe PG::Connection do
939
1558
  query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
940
1559
  conn.exec( query )
941
1560
  rescue => err
942
- err.message.encoding.should == Encoding::ISO8859_15
1561
+ expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
943
1562
  else
944
1563
  fail "No exception raised?!"
945
1564
  end
@@ -948,7 +1567,22 @@ describe PG::Connection do
948
1567
  conn.finish if conn
949
1568
  end
950
1569
 
951
- 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
952
1586
  [:receiver, :processor].each do |kind|
953
1587
  notices = []
954
1588
  @conn.internal_encoding = 'utf-8'
@@ -966,19 +1600,18 @@ describe PG::Connection do
966
1600
  @conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
967
1601
  end
968
1602
 
969
- notices.length.should == 3
1603
+ expect( notices.length ).to eq( 3 )
970
1604
  notices.each do |notice|
971
- notice.should =~ /^NOTICE:.*世界線航跡蔵/
972
- notice.encoding.should == Encoding::UTF_8
1605
+ expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
1606
+ expect( notice.encoding ).to eq( Encoding::UTF_8 )
973
1607
  end
974
1608
  @conn.set_notice_receiver
975
1609
  @conn.set_notice_processor
976
1610
  end
977
1611
  end
978
1612
 
979
- 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
980
1614
  @conn.internal_encoding = 'utf-8'
981
- @conn.exec( 'ROLLBACK' )
982
1615
  @conn.exec( 'LISTEN "Möhre"' )
983
1616
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
984
1617
  event, pid, msg = nil
@@ -987,37 +1620,330 @@ describe PG::Connection do
987
1620
  end
988
1621
  @conn.exec( 'UNLISTEN "Möhre"' )
989
1622
 
990
- event.should == "Möhre"
991
- event.encoding.should == Encoding::UTF_8
992
- msg.should == '世界線航跡蔵'
993
- 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 )
994
1628
  end
995
1629
 
996
- it "returns properly encoded text from notifies", :postgresql_90 do
1630
+ it "returns properly encoded text from notifies", :without_transaction do
997
1631
  @conn.internal_encoding = 'utf-8'
998
- @conn.exec( 'ROLLBACK' )
999
1632
  @conn.exec( 'LISTEN "Möhre"' )
1000
1633
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1001
1634
  @conn.exec( 'UNLISTEN "Möhre"' )
1002
1635
 
1003
1636
  notification = @conn.notifies
1004
- notification[:relname].should == "Möhre"
1005
- notification[:relname].encoding.should == Encoding::UTF_8
1006
- notification[:extra].should == '世界線航跡蔵'
1007
- notification[:extra].encoding.should == Encoding::UTF_8
1008
- 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
1009
1642
  end
1010
1643
  end
1011
1644
 
1012
- context "OS thread support", :ruby_19 do
1013
- it "described_class#exec shouldn't block a second thread" do
1645
+ context "OS thread support" do
1646
+ it "Connection#exec shouldn't block a second thread" do
1014
1647
  t = Thread.new do
1015
1648
  @conn.exec( "select pg_sleep(1)" )
1016
1649
  end
1017
1650
 
1018
1651
  sleep 0.5
1019
- t.should be_alive()
1652
+ expect( t ).to be_alive()
1020
1653
  t.join
1021
1654
  end
1655
+
1656
+ it "Connection.new shouldn't block a second thread" do
1657
+ serv = nil
1658
+ t = Thread.new do
1659
+ serv = TCPServer.new( '127.0.0.1', 54320 )
1660
+ expect {
1661
+ described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
1662
+ }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
1663
+ end
1664
+
1665
+ sleep 0.5
1666
+ expect( t ).to be_alive()
1667
+ serv.close
1668
+ t.join
1669
+ end
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
1022
1948
  end
1023
1949
  end