pg 1.2.1-x64-mingw32 → 1.3.0.rc2-x64-mingw32

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 (112) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.appveyor.yml +36 -0
  4. data/.gems +6 -0
  5. data/.github/workflows/binary-gems.yml +85 -0
  6. data/.github/workflows/source-gem.yml +130 -0
  7. data/.gitignore +13 -0
  8. data/.hgsigs +34 -0
  9. data/.hgtags +41 -0
  10. data/.irbrc +23 -0
  11. data/.pryrc +23 -0
  12. data/.tm_properties +21 -0
  13. data/.travis.yml +49 -0
  14. data/Gemfile +14 -0
  15. data/History.rdoc +93 -7
  16. data/Manifest.txt +0 -1
  17. data/README.rdoc +8 -7
  18. data/Rakefile +31 -140
  19. data/Rakefile.cross +55 -56
  20. data/certs/ged.pem +24 -0
  21. data/ext/errorcodes.def +8 -0
  22. data/ext/errorcodes.txt +3 -1
  23. data/ext/extconf.rb +90 -19
  24. data/ext/gvl_wrappers.c +4 -0
  25. data/ext/gvl_wrappers.h +23 -0
  26. data/ext/pg.c +59 -4
  27. data/ext/pg.h +18 -0
  28. data/ext/pg_coder.c +90 -24
  29. data/ext/pg_connection.c +606 -533
  30. data/ext/pg_copy_coder.c +45 -15
  31. data/ext/pg_record_coder.c +38 -9
  32. data/ext/pg_result.c +61 -31
  33. data/ext/pg_text_decoder.c +1 -1
  34. data/ext/pg_text_encoder.c +6 -6
  35. data/ext/pg_tuple.c +47 -21
  36. data/ext/pg_type_map.c +41 -8
  37. data/ext/pg_type_map_all_strings.c +14 -1
  38. data/ext/pg_type_map_by_class.c +50 -21
  39. data/ext/pg_type_map_by_column.c +64 -28
  40. data/ext/pg_type_map_by_mri_type.c +47 -18
  41. data/ext/pg_type_map_by_oid.c +52 -23
  42. data/ext/pg_type_map_in_ruby.c +50 -19
  43. data/ext/pg_util.c +2 -2
  44. data/lib/2.5/pg_ext.so +0 -0
  45. data/lib/2.6/pg_ext.so +0 -0
  46. data/lib/2.7/pg_ext.so +0 -0
  47. data/lib/3.0/pg_ext.so +0 -0
  48. data/lib/pg/basic_type_map_based_on_result.rb +47 -0
  49. data/lib/pg/basic_type_map_for_queries.rb +193 -0
  50. data/lib/pg/basic_type_map_for_results.rb +81 -0
  51. data/lib/pg/basic_type_registry.rb +296 -0
  52. data/lib/pg/coder.rb +1 -1
  53. data/lib/pg/connection.rb +579 -57
  54. data/lib/pg/version.rb +4 -0
  55. data/lib/pg.rb +38 -24
  56. data/lib/x64-mingw32/libpq.dll +0 -0
  57. data/misc/openssl-pg-segfault.rb +31 -0
  58. data/misc/postgres/History.txt +9 -0
  59. data/misc/postgres/Manifest.txt +5 -0
  60. data/misc/postgres/README.txt +21 -0
  61. data/misc/postgres/Rakefile +21 -0
  62. data/misc/postgres/lib/postgres.rb +16 -0
  63. data/misc/ruby-pg/History.txt +9 -0
  64. data/misc/ruby-pg/Manifest.txt +5 -0
  65. data/misc/ruby-pg/README.txt +21 -0
  66. data/misc/ruby-pg/Rakefile +21 -0
  67. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  68. data/pg.gemspec +32 -0
  69. data/sample/array_insert.rb +20 -0
  70. data/sample/async_api.rb +106 -0
  71. data/sample/async_copyto.rb +39 -0
  72. data/sample/async_mixed.rb +56 -0
  73. data/sample/check_conn.rb +21 -0
  74. data/sample/copydata.rb +71 -0
  75. data/sample/copyfrom.rb +81 -0
  76. data/sample/copyto.rb +19 -0
  77. data/sample/cursor.rb +21 -0
  78. data/sample/disk_usage_report.rb +177 -0
  79. data/sample/issue-119.rb +94 -0
  80. data/sample/losample.rb +69 -0
  81. data/sample/minimal-testcase.rb +17 -0
  82. data/sample/notify_wait.rb +72 -0
  83. data/sample/pg_statistics.rb +285 -0
  84. data/sample/replication_monitor.rb +222 -0
  85. data/sample/test_binary_values.rb +33 -0
  86. data/sample/wal_shipper.rb +434 -0
  87. data/sample/warehouse_partitions.rb +311 -0
  88. data.tar.gz.sig +0 -0
  89. metadata +94 -235
  90. metadata.gz.sig +0 -0
  91. data/ChangeLog +0 -0
  92. data/lib/2.2/pg_ext.so +0 -0
  93. data/lib/2.3/pg_ext.so +0 -0
  94. data/lib/2.4/pg_ext.so +0 -0
  95. data/lib/libpq.dll +0 -0
  96. data/lib/pg/basic_type_mapping.rb +0 -522
  97. data/spec/data/expected_trace.out +0 -26
  98. data/spec/data/random_binary_data +0 -0
  99. data/spec/helpers.rb +0 -382
  100. data/spec/pg/basic_type_mapping_spec.rb +0 -645
  101. data/spec/pg/connection_spec.rb +0 -1911
  102. data/spec/pg/connection_sync_spec.rb +0 -41
  103. data/spec/pg/result_spec.rb +0 -681
  104. data/spec/pg/tuple_spec.rb +0 -333
  105. data/spec/pg/type_map_by_class_spec.rb +0 -138
  106. data/spec/pg/type_map_by_column_spec.rb +0 -226
  107. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  108. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  109. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  110. data/spec/pg/type_map_spec.rb +0 -22
  111. data/spec/pg/type_spec.rb +0 -1123
  112. data/spec/pg_spec.rb +0 -50
@@ -1,1911 +0,0 @@
1
- # -*- rspec -*-
2
- #encoding: utf-8
3
-
4
- require_relative '../helpers'
5
-
6
- require 'timeout'
7
- require 'socket'
8
- require 'pg'
9
-
10
- describe PG::Connection do
11
-
12
- it "can create a connection option string from a Hash of options" do
13
- optstring = described_class.parse_connect_args(
14
- :host => 'pgsql.example.com',
15
- :dbname => 'db01',
16
- 'sslmode' => 'require'
17
- )
18
-
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'/ )
23
- end
24
-
25
- it "can create a connection option string from positional parameters" do
26
- optstring = described_class.parse_connect_args( 'pgsql.example.com', nil, '-c geqo=off', nil,
27
- 'sales' )
28
-
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'/ )
33
-
34
- expect( optstring ).to_not match( /port=/ )
35
- expect( optstring ).to_not match( /tty=/ )
36
- end
37
-
38
- it "can create a connection option string from a mix of positional and hash parameters" do
39
- optstring = described_class.parse_connect_args( 'pgsql.example.com',
40
- :dbname => 'licensing', :user => 'jrandom' )
41
-
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'/ )
54
- end
55
-
56
- it "escapes single quotes and backslashes in connection parameters" do
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
-
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
-
117
- it "connects with defaults if no connection parameters are given" do
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} )
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 )
137
- tmpconn.finish
138
- end
139
-
140
- it "connects using 7 arguments converted to strings" do
141
- tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
142
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
143
- tmpconn.finish
144
- end
145
-
146
- it "connects using a hash of connection parameters" do
147
- tmpconn = described_class.connect(
148
- :host => 'localhost',
149
- :port => @port,
150
- :dbname => :test)
151
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
152
- tmpconn.finish
153
- end
154
-
155
- it "connects using a hash of optional connection parameters" do
156
- tmpconn = described_class.connect(
157
- :host => 'localhost',
158
- :port => @port,
159
- :dbname => :test,
160
- :keepalives => 1)
161
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
162
- tmpconn.finish
163
- end
164
-
165
- it "raises an exception when connecting with an invalid number of arguments" do
166
- expect {
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
174
- end
175
-
176
- it "can connect asynchronously", :socket_io do
177
- tmpconn = described_class.connect_start( @conninfo )
178
- expect( tmpconn ).to be_a( described_class )
179
-
180
- wait_for_polling_ok(tmpconn)
181
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
182
- tmpconn.finish
183
- end
184
-
185
- it "can connect asynchronously for the duration of a block", :socket_io do
186
- conn = nil
187
-
188
- described_class.connect_start(@conninfo) do |tmpconn|
189
- expect( tmpconn ).to be_a( described_class )
190
- conn = tmpconn
191
-
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", :socket_io 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
216
-
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/)
267
- end
268
-
269
- it "doesn't leave stale server connections after finish" do
270
- described_class.connect(@conninfo).finish
271
- sleep 0.5
272
- res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
273
- WHERE usename IS NOT NULL AND application_name != ''])
274
- # there's still the global @conn, but should be no more
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" )
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
296
-
297
- it "can set error context visibility", :postgresql_96 do
298
- old = @conn.set_error_context_visibility( PG::PQSHOW_CONTEXT_NEVER )
299
- new = @conn.set_error_context_visibility( old )
300
- expect( new ).to eq( PG::PQSHOW_CONTEXT_NEVER )
301
- end
302
-
303
- let(:expected_trace_output) do
304
- %{
305
- To backend> Msg Q
306
- To backend> "SELECT 1 AS one"
307
- To backend> Msg complete, length 21
308
- From backend> T
309
- From backend (#4)> 28
310
- From backend (#2)> 1
311
- From backend> "one"
312
- From backend (#4)> 0
313
- From backend (#2)> 0
314
- From backend (#4)> 23
315
- From backend (#2)> 4
316
- From backend (#4)> -1
317
- From backend (#2)> 0
318
- From backend> D
319
- From backend (#4)> 11
320
- From backend (#2)> 1
321
- From backend (#4)> 1
322
- From backend (1)> 1
323
- From backend> C
324
- From backend (#4)> 13
325
- From backend> "SELECT 1"
326
- From backend> Z
327
- From backend (#4)> 5
328
- From backend> Z
329
- From backend (#4)> 5
330
- From backend> T
331
- }.gsub( /^\t{2}/, '' ).lstrip
332
- end
333
-
334
- it "trace and untrace client-server communication", :unix do
335
- # be careful to explicitly close files so that the
336
- # directory can be removed and we don't have to wait for
337
- # the GC to run.
338
- trace_file = TEST_DIRECTORY + "test_trace.out"
339
- trace_io = trace_file.open( 'w', 0600 )
340
- @conn.trace( trace_io )
341
- trace_io.close
342
-
343
- res = @conn.exec("SELECT 1 AS one")
344
- @conn.untrace
345
-
346
- res = @conn.exec("SELECT 2 AS two")
347
-
348
- trace_data = trace_file.read
349
-
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' )
357
-
358
- expect( trace_data ).to eq( expected_trace_output )
359
- end
360
-
361
- it "allows a query to be cancelled" do
362
- error = false
363
- @conn.send_query("SELECT pg_sleep(1000)")
364
- @conn.cancel
365
- tmpres = @conn.get_result
366
- if(tmpres.result_status != PG::PGRES_TUPLES_OK)
367
- error = true
368
- end
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
382
- end
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
-
399
- it "automatically rolls back a transaction started with Connection#transaction if an exception " +
400
- "is raised" do
401
- # abort the per-example transaction so we can test our own
402
- @conn.exec( 'ROLLBACK' )
403
-
404
- res = nil
405
- @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
406
-
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 )
414
-
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" )
430
- end
431
-
432
- it "not read past the end of a large object" do
433
- @conn.transaction do
434
- oid = @conn.lo_create( 0 )
435
- fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
436
- @conn.lo_write( fd, "foobar" )
437
- expect( @conn.lo_read( fd, 10 ) ).to be_nil()
438
- @conn.lo_lseek( fd, 0, PG::SEEK_SET )
439
- expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
440
- end
441
- end
442
-
443
- it "supports explicitly calling #exec_params" do
444
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
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] )
448
-
449
- res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
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'] )
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
476
-
477
- it "can wait for NOTIFY events" do
478
- @conn.exec( 'ROLLBACK' )
479
- @conn.exec( 'LISTEN woo' )
480
-
481
- t = Thread.new do
482
- begin
483
- conn = described_class.connect( @conninfo )
484
- sleep 1
485
- conn.async_exec( 'NOTIFY woo' )
486
- ensure
487
- conn.finish
488
- end
489
- end
490
-
491
- expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
492
- @conn.exec( 'UNLISTEN woo' )
493
-
494
- t.join
495
- end
496
-
497
- it "calls a block for NOTIFY events if one is given" do
498
- @conn.exec( 'ROLLBACK' )
499
- @conn.exec( 'LISTEN woo' )
500
-
501
- t = Thread.new do
502
- begin
503
- conn = described_class.connect( @conninfo )
504
- sleep 1
505
- conn.async_exec( 'NOTIFY woo' )
506
- ensure
507
- conn.finish
508
- end
509
- end
510
-
511
- eventpid = event = nil
512
- @conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
513
- expect( event ).to eq( 'woo' )
514
- expect( eventpid ).to be_an( Integer )
515
-
516
- @conn.exec( 'UNLISTEN woo' )
517
-
518
- t.join
519
- end
520
-
521
- it "doesn't collapse sequential notifications" do
522
- @conn.exec( 'ROLLBACK' )
523
- @conn.exec( 'LISTEN woo' )
524
- @conn.exec( 'LISTEN war' )
525
- @conn.exec( 'LISTEN woz' )
526
-
527
- begin
528
- conn = described_class.connect( @conninfo )
529
- conn.exec( 'NOTIFY woo' )
530
- conn.exec( 'NOTIFY war' )
531
- conn.exec( 'NOTIFY woz' )
532
- ensure
533
- conn.finish
534
- end
535
-
536
- channels = []
537
- 3.times do
538
- channels << @conn.wait_for_notify( 2 )
539
- end
540
-
541
- expect( channels.size ).to eq( 3 )
542
- expect( channels ).to include( 'woo', 'war', 'woz' )
543
-
544
- @conn.exec( 'UNLISTEN woz' )
545
- @conn.exec( 'UNLISTEN war' )
546
- @conn.exec( 'UNLISTEN woo' )
547
- end
548
-
549
- it "returns notifications which are already in the queue before wait_for_notify is called " +
550
- "without waiting for the socket to become readable" do
551
- @conn.exec( 'ROLLBACK' )
552
- @conn.exec( 'LISTEN woo' )
553
-
554
- begin
555
- conn = described_class.connect( @conninfo )
556
- conn.exec( 'NOTIFY woo' )
557
- ensure
558
- conn.finish
559
- end
560
-
561
- # Cause the notification to buffer, but not be read yet
562
- @conn.exec( 'SELECT 1' )
563
-
564
- expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
565
- @conn.exec( 'UNLISTEN woo' )
566
- end
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
-
583
- it "yields the result if block is given to exec" do
584
- rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
585
- values = []
586
- expect( result ).to be_kind_of( PG::Result )
587
- expect( result.ntuples ).to eq( 2 )
588
- result.each do |tuple|
589
- values << tuple['a']
590
- end
591
- values
592
- end
593
-
594
- expect( rval.size ).to eq( 2 )
595
- expect( rval ).to include( '5678', '1234' )
596
- end
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
719
-
720
- it "correctly finishes COPY queries passed to #async_exec" do
721
- @conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
722
-
723
- results = []
724
- begin
725
- data = @conn.get_copy_data( true )
726
- if false == data
727
- @conn.block( 2.0 )
728
- data = @conn.get_copy_data( true )
729
- end
730
- results << data if data
731
- end until data.nil?
732
-
733
- expect( results.size ).to eq( 2 )
734
- expect( results ).to include( "1\n", "2\n" )
735
- end
736
-
737
-
738
- it "described_class#block shouldn't block a second thread" do
739
- start = Time.now
740
- t = Thread.new do
741
- @conn.send_query( "select pg_sleep(3)" )
742
- @conn.block
743
- end
744
-
745
- sleep 0.5
746
- expect( t ).to be_alive()
747
- @conn.cancel
748
- t.join
749
- expect( (Time.now - start) ).to be < 3
750
- end
751
-
752
- it "described_class#block should allow a timeout" do
753
- @conn.send_query( "select pg_sleep(1)" )
754
-
755
- start = Time.now
756
- @conn.block( 0.3 )
757
- finish = Time.now
758
-
759
- expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
760
- end
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
768
-
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 )
774
- end
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
781
-
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
864
- end
865
-
866
-
867
- it "allows fetching a column of values from a result by column number" do
868
- res = @conn.exec( 'VALUES (1,2),(2,3),(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] )
871
- end
872
-
873
-
874
- it "allows fetching a column of values from a result by field name" do
875
- res = @conn.exec( 'VALUES (1,2),(2,3),(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] )
878
- end
879
-
880
-
881
- it "raises an error if selecting an invalid column index" do
882
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
883
- expect {
884
- res.column_values( 20 )
885
- }.to raise_error( IndexError )
886
- end
887
-
888
-
889
- it "raises an error if selecting an invalid field name" do
890
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
891
- expect {
892
- res.field_values( 'hUUuurrg' )
893
- }.to raise_error( IndexError )
894
- end
895
-
896
-
897
- it "raises an error if column index is not a number" do
898
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
899
- expect {
900
- res.column_values( 'hUUuurrg' )
901
- }.to raise_error( TypeError )
902
- end
903
-
904
-
905
- it "handles server close while asynchronous connect", :socket_io do
906
- serv = TCPServer.new( '127.0.0.1', 54320 )
907
- conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
908
- expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
909
- select( nil, [conn.socket_io], nil, 0.2 )
910
- serv.close
911
- if conn.connect_poll == PG::PGRES_POLLING_READING
912
- select( [conn.socket_io], nil, nil, 0.2 )
913
- end
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' }] )
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
930
-
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
938
- result = nil
939
- @conn.exec( "select 47 as one" ) do |pg_res|
940
- result = pg_res[0]
941
- end
942
- expect( result ).to eq( { 'one' => '47' } )
943
- end
944
-
945
- it "raises a rescue-able error if #finish is called twice", :without_transaction do
946
- conn = PG.connect( @conninfo )
947
-
948
- conn.finish
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
964
- end
965
-
966
- it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
967
- conn = PG.connect( @conninfo )
968
- io = conn.socket_io
969
- conn.finish
970
- expect( io ).to be_closed()
971
- expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
972
- end
973
-
974
- it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
975
- conn = PG.connect( @conninfo )
976
- io = conn.socket_io
977
- conn.reset
978
- expect( io ).to be_closed()
979
- expect( conn.socket_io ).to_not equal( io )
980
- conn.finish
981
- end
982
-
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
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
993
-
994
- it "sets the fallback_application_name on new connections" do
995
- conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
996
-
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
1002
-
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
1016
-
1017
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1018
- "any number of arguments" do
1019
-
1020
- @conn.exec( 'ROLLBACK' )
1021
- @conn.exec( 'LISTEN knees' )
1022
-
1023
- conn = described_class.connect( @conninfo )
1024
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1025
- conn.finish
1026
-
1027
- event, pid, msg = nil
1028
- @conn.wait_for_notify( 10 ) do |*args|
1029
- event, pid, msg = *args
1030
- end
1031
- @conn.exec( 'UNLISTEN knees' )
1032
-
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
1037
-
1038
- it "accepts nil as the timeout in #wait_for_notify " do
1039
- @conn.exec( 'ROLLBACK' )
1040
- @conn.exec( 'LISTEN knees' )
1041
-
1042
- conn = described_class.connect( @conninfo )
1043
- conn.exec( %Q{NOTIFY knees} )
1044
- conn.finish
1045
-
1046
- event, pid = nil
1047
- @conn.wait_for_notify( nil ) do |*args|
1048
- event, pid = *args
1049
- end
1050
- @conn.exec( 'UNLISTEN knees' )
1051
-
1052
- expect( event ).to eq( 'knees' )
1053
- expect( pid ).to be_a_kind_of( Integer )
1054
- end
1055
-
1056
- it "sends nil as the payload if the notification wasn't given one" do
1057
- @conn.exec( 'ROLLBACK' )
1058
- @conn.exec( 'LISTEN knees' )
1059
-
1060
- conn = described_class.connect( @conninfo )
1061
- conn.exec( %Q{NOTIFY knees} )
1062
- conn.finish
1063
-
1064
- payload = :notnil
1065
- @conn.wait_for_notify( nil ) do |*args|
1066
- payload = args[ 2 ]
1067
- end
1068
- @conn.exec( 'UNLISTEN knees' )
1069
-
1070
- expect( payload ).to be_nil()
1071
- end
1072
-
1073
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1074
- "two arguments" do
1075
-
1076
- @conn.exec( 'ROLLBACK' )
1077
- @conn.exec( 'LISTEN knees' )
1078
-
1079
- conn = described_class.connect( @conninfo )
1080
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1081
- conn.finish
1082
-
1083
- event, pid, msg = nil
1084
- @conn.wait_for_notify( 10 ) do |arg1, arg2|
1085
- event, pid, msg = arg1, arg2
1086
- end
1087
- @conn.exec( 'UNLISTEN knees' )
1088
-
1089
- expect( event ).to eq( 'knees' )
1090
- expect( pid ).to be_a_kind_of( Integer )
1091
- expect( msg ).to be_nil()
1092
- end
1093
-
1094
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
1095
- "doesn't accept arguments" do
1096
-
1097
- @conn.exec( 'ROLLBACK' )
1098
- @conn.exec( 'LISTEN knees' )
1099
-
1100
- conn = described_class.connect( @conninfo )
1101
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1102
- conn.finish
1103
-
1104
- notification_received = false
1105
- @conn.wait_for_notify( 10 ) do
1106
- notification_received = true
1107
- end
1108
- @conn.exec( 'UNLISTEN knees' )
1109
-
1110
- expect( notification_received ).to be_truthy()
1111
- end
1112
-
1113
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1114
- "three arguments" do
1115
-
1116
- @conn.exec( 'ROLLBACK' )
1117
- @conn.exec( 'LISTEN knees' )
1118
-
1119
- conn = described_class.connect( @conninfo )
1120
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1121
- conn.finish
1122
-
1123
- event, pid, msg = nil
1124
- @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
1125
- event, pid, msg = arg1, arg2, arg3
1126
- end
1127
- @conn.exec( 'UNLISTEN knees' )
1128
-
1129
- expect( event ).to eq( 'knees' )
1130
- expect( pid ).to be_a_kind_of( Integer )
1131
- expect( msg ).to eq( 'skirt and boots' )
1132
- end
1133
-
1134
- context "server ping", :without_transaction do
1135
-
1136
- it "pings successfully with connection string" do
1137
- ping = described_class.ping(@conninfo)
1138
- expect( ping ).to eq( PG::PQPING_OK )
1139
- end
1140
-
1141
- it "pings using 7 arguments converted to strings" do
1142
- ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
1143
- expect( ping ).to eq( PG::PQPING_OK )
1144
- end
1145
-
1146
- it "pings using a hash of connection parameters" do
1147
- ping = described_class.ping(
1148
- :host => 'localhost',
1149
- :port => @port,
1150
- :dbname => :test)
1151
- expect( ping ).to eq( PG::PQPING_OK )
1152
- end
1153
-
1154
- it "returns correct response when ping connection cannot be established" do
1155
- ping = described_class.ping(
1156
- :host => 'localhost',
1157
- :port => 9999,
1158
- :dbname => :test)
1159
- expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
1160
- end
1161
-
1162
- it "returns error when ping connection arguments are wrong" do
1163
- ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
1164
- expect( ping ).to_not eq( PG::PQPING_OK )
1165
- end
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
1174
-
1175
- end
1176
-
1177
- describe "set_single_row_mode" do
1178
-
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
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
1205
-
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
1209
-
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
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
1220
-
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
1225
-
1226
- first_result = nil
1227
- expect do
1228
- loop do
1229
- res = @conn.get_result or break
1230
- res.check
1231
- first_result ||= res
1232
- 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 )
1236
- end
1237
-
1238
- end
1239
-
1240
- context "multinationalization support" do
1241
-
1242
- describe "rubyforge #22925: m17n support" do
1243
- it "should return results in the same encoding as the client (iso-8859-1)" do
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 )
1249
- end
1250
-
1251
- it "should return results in the same encoding as the client (utf-8)" do
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 )
1257
- end
1258
-
1259
- it "should return results in the same encoding as the client (EUC-JP)" do
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 )
1266
- end
1267
-
1268
- it "returns the results in the correct encoding even if the client_encoding has " +
1269
- "changed since the results were fetched" do
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 )
1277
- end
1278
-
1279
- it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
1280
- @conn.exec "SET client_encoding TO SQL_ASCII"
1281
- expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1282
- end
1283
-
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 )
1298
- end
1299
-
1300
- it "uses the client encoding for escaped string" do
1301
- original = "Möhre to 'scape".encode( "utf-16be" )
1302
- @conn.set_client_encoding( "euc_jp" )
1303
- escaped = @conn.escape( original )
1304
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1305
- expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
1306
- end
1307
-
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" )
1311
- escaped = @conn.escape_literal( original )
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']] )
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/)
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("\"", "\"\"") + "\"" )
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
1508
-
1509
- describe "Ruby 1.9.x default_internal encoding" do
1510
-
1511
- it "honors the Encoding.default_internal if it's set and the synchronous interface is used", :without_transaction do
1512
- @conn.transaction do |txn_conn|
1513
- txn_conn.internal_encoding = Encoding::ISO8859_1
1514
- txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
1515
- txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
1516
- end
1517
-
1518
- begin
1519
- prev_encoding = Encoding.default_internal
1520
- Encoding.default_internal = Encoding::ISO8859_2
1521
-
1522
- conn = PG.connect( @conninfo )
1523
- expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
1524
- res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1525
- expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
1526
- ensure
1527
- conn.exec( "DROP TABLE defaultinternaltest" )
1528
- conn.finish if conn
1529
- Encoding.default_internal = prev_encoding
1530
- end
1531
- end
1532
-
1533
- it "allows users of the async interface to set the client_encoding to the default_internal" do
1534
- begin
1535
- prev_encoding = Encoding.default_internal
1536
- Encoding.default_internal = Encoding::KOI8_R
1537
-
1538
- @conn.set_default_encoding
1539
-
1540
- expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
1541
- ensure
1542
- Encoding.default_internal = prev_encoding
1543
- end
1544
- end
1545
-
1546
- end
1547
-
1548
-
1549
- it "encodes exception messages with the connection's encoding (#96)", :without_transaction do
1550
- # Use a new connection so the client_encoding isn't set outside of this example
1551
- conn = PG.connect( @conninfo )
1552
- conn.client_encoding = 'iso-8859-15'
1553
-
1554
- conn.transaction do
1555
- conn.exec "CREATE TABLE foo (bar TEXT)"
1556
-
1557
- begin
1558
- query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
1559
- conn.exec( query )
1560
- rescue => err
1561
- expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
1562
- else
1563
- fail "No exception raised?!"
1564
- end
1565
- end
1566
-
1567
- conn.finish if conn
1568
- end
1569
-
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
1586
- [:receiver, :processor].each do |kind|
1587
- notices = []
1588
- @conn.internal_encoding = 'utf-8'
1589
- if kind == :processor
1590
- @conn.set_notice_processor do |msg|
1591
- notices << msg
1592
- end
1593
- else
1594
- @conn.set_notice_receiver do |result|
1595
- notices << result.error_message
1596
- end
1597
- end
1598
-
1599
- 3.times do
1600
- @conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
1601
- end
1602
-
1603
- expect( notices.length ).to eq( 3 )
1604
- notices.each do |notice|
1605
- expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
1606
- expect( notice.encoding ).to eq( Encoding::UTF_8 )
1607
- end
1608
- @conn.set_notice_receiver
1609
- @conn.set_notice_processor
1610
- end
1611
- end
1612
-
1613
- it "receives properly encoded text from wait_for_notify", :without_transaction do
1614
- @conn.internal_encoding = 'utf-8'
1615
- @conn.exec( 'LISTEN "Möhre"' )
1616
- @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1617
- event, pid, msg = nil
1618
- @conn.wait_for_notify( 10 ) do |*args|
1619
- event, pid, msg = *args
1620
- end
1621
- @conn.exec( 'UNLISTEN "Möhre"' )
1622
-
1623
- expect( event ).to eq( "Möhre" )
1624
- expect( event.encoding ).to eq( Encoding::UTF_8 )
1625
- expect( msg ).to eq( '世界線航跡蔵' )
1626
- expect( msg.encoding ).to eq( Encoding::UTF_8 )
1627
- end
1628
-
1629
- it "returns properly encoded text from notifies", :without_transaction do
1630
- @conn.internal_encoding = 'utf-8'
1631
- @conn.exec( 'LISTEN "Möhre"' )
1632
- @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1633
- @conn.exec( 'UNLISTEN "Möhre"' )
1634
-
1635
- notification = @conn.notifies
1636
- expect( notification[:relname] ).to eq( "Möhre" )
1637
- expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
1638
- expect( notification[:extra] ).to eq( '世界線航跡蔵' )
1639
- expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
1640
- expect( notification[:be_pid] ).to be > 0
1641
- end
1642
- end
1643
-
1644
- context "OS thread support" do
1645
- it "Connection#exec shouldn't block a second thread" do
1646
- t = Thread.new do
1647
- @conn.exec( "select pg_sleep(1)" )
1648
- end
1649
-
1650
- sleep 0.5
1651
- expect( t ).to be_alive()
1652
- t.join
1653
- end
1654
-
1655
- it "Connection.new shouldn't block a second thread" do
1656
- serv = nil
1657
- t = Thread.new do
1658
- serv = TCPServer.new( '127.0.0.1', 54320 )
1659
- expect {
1660
- described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
1661
- }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
1662
- end
1663
-
1664
- sleep 0.5
1665
- expect( t ).to be_alive()
1666
- serv.close
1667
- t.join
1668
- end
1669
- end
1670
-
1671
- describe "type casting" do
1672
- it "should raise an error on invalid param mapping" do
1673
- expect{
1674
- @conn.exec_params( "SELECT 1", [], nil, :invalid )
1675
- }.to raise_error(TypeError)
1676
- end
1677
-
1678
- it "should return nil if no type mapping is set" do
1679
- expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
1680
- expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
1681
- end
1682
-
1683
- it "shouldn't type map params unless requested" do
1684
- if @conn.server_version < 100000
1685
- expect{
1686
- @conn.exec_params( "SELECT $1", [5] )
1687
- }.to raise_error(PG::IndeterminateDatatype)
1688
- else
1689
- # PostgreSQL-10 maps to TEXT type (OID 25)
1690
- expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
1691
- end
1692
- end
1693
-
1694
- it "should raise an error on invalid encoder to put_copy_data" do
1695
- expect{
1696
- @conn.put_copy_data [1], :invalid
1697
- }.to raise_error(TypeError)
1698
- end
1699
-
1700
- it "can type cast parameters to put_copy_data with explicit encoder" do
1701
- tm = PG::TypeMapByColumn.new [nil]
1702
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1703
-
1704
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1705
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1706
- @conn.put_copy_data [1], row_encoder
1707
- @conn.put_copy_data ["2"], row_encoder
1708
- end
1709
-
1710
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1711
- @conn.put_copy_data [3]
1712
- @conn.put_copy_data ["4"]
1713
- end
1714
-
1715
- res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
1716
- expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
1717
- end
1718
-
1719
- context "with default query type map" do
1720
- before :each do
1721
- @conn2 = described_class.new(@conninfo)
1722
- tm = PG::TypeMapByClass.new
1723
- tm[Integer] = PG::TextEncoder::Integer.new oid: 20
1724
- @conn2.type_map_for_queries = tm
1725
-
1726
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1727
- @conn2.encoder_for_put_copy_data = row_encoder
1728
- end
1729
- after :each do
1730
- @conn2.close
1731
- end
1732
-
1733
- it "should respect a type mapping for params and it's OID and format code" do
1734
- res = @conn2.exec_params( "SELECT $1", [5] )
1735
- expect( res.values ).to eq( [["5"]] )
1736
- expect( res.ftype(0) ).to eq( 20 )
1737
- end
1738
-
1739
- it "should return the current type mapping" do
1740
- expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
1741
- end
1742
-
1743
- it "should work with arbitrary number of params in conjunction with type casting" do
1744
- begin
1745
- 3.step( 12, 0.2 ) do |exp|
1746
- num_params = (2 ** exp).to_i
1747
- sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
1748
- params = num_params.times.to_a
1749
- res = @conn2.exec_params( "SELECT #{sql}", params )
1750
- expect( res.nfields ).to eq( num_params )
1751
- expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
1752
- end
1753
- rescue PG::ProgramLimitExceeded
1754
- # Stop silently as soon the server complains about too many params
1755
- end
1756
- end
1757
-
1758
- it "can process #copy_data input queries with row encoder and respects character encoding" do
1759
- @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1760
- res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1761
- @conn2.put_copy_data [1]
1762
- @conn2.put_copy_data ["Möhre".encode("utf-16le")]
1763
- end
1764
-
1765
- res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1766
- expect( res.values ).to eq( [["1"], ["Möhre"]] )
1767
- end
1768
- end
1769
-
1770
- context "with default result type map" do
1771
- before :each do
1772
- @conn2 = described_class.new(@conninfo)
1773
- tm = PG::TypeMapByOid.new
1774
- tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
1775
- @conn2.type_map_for_results = tm
1776
-
1777
- row_decoder = PG::TextDecoder::CopyRow.new
1778
- @conn2.decoder_for_get_copy_data = row_decoder
1779
- end
1780
- after :each do
1781
- @conn2.close
1782
- end
1783
-
1784
- it "should respect a type mapping for result" do
1785
- res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
1786
- expect( res.values ).to eq( [[5]] )
1787
- end
1788
-
1789
- it "should return the current type mapping" do
1790
- expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
1791
- end
1792
-
1793
- it "should work with arbitrary number of params in conjunction with type casting" do
1794
- begin
1795
- 3.step( 12, 0.2 ) do |exp|
1796
- num_params = (2 ** exp).to_i
1797
- sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
1798
- params = num_params.times.to_a
1799
- res = @conn2.exec_params( "SELECT #{sql}", params )
1800
- expect( res.nfields ).to eq( num_params )
1801
- expect( res.values ).to eq( [num_params.times.to_a] )
1802
- end
1803
- rescue PG::ProgramLimitExceeded
1804
- # Stop silently as soon the server complains about too many params
1805
- end
1806
- end
1807
-
1808
- it "can process #copy_data output with row decoder and respects character encoding" do
1809
- @conn2.internal_encoding = Encoding::ISO8859_1
1810
- rows = []
1811
- res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
1812
- while row=@conn2.get_copy_data
1813
- rows << row
1814
- end
1815
- end
1816
- expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
1817
- expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
1818
- end
1819
-
1820
- it "can type cast #copy_data output with explicit decoder" do
1821
- tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
1822
- row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
1823
- rows = []
1824
- @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
1825
- while row=@conn.get_copy_data
1826
- rows << row
1827
- end
1828
- end
1829
- @conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
1830
- while row=@conn.get_copy_data( false, row_decoder )
1831
- rows << row
1832
- end
1833
- end
1834
- expect( rows ).to eq( [[1], [2], [3], [4]] )
1835
- end
1836
- end
1837
- end
1838
-
1839
- describe :field_name_type do
1840
- before :each do
1841
- @conn2 = PG.connect(@conninfo)
1842
- end
1843
- after :each do
1844
- @conn2.close
1845
- end
1846
-
1847
- it "uses string field names per default" do
1848
- expect(@conn2.field_name_type).to eq(:string)
1849
- end
1850
-
1851
- it "can set string field names" do
1852
- @conn2.field_name_type = :string
1853
- expect(@conn2.field_name_type).to eq(:string)
1854
- res = @conn2.exec("SELECT 1 as az")
1855
- expect(res.field_name_type).to eq(:string)
1856
- expect(res.fields).to eq(["az"])
1857
- end
1858
-
1859
- it "can set symbol field names" do
1860
- @conn2.field_name_type = :symbol
1861
- expect(@conn2.field_name_type).to eq(:symbol)
1862
- res = @conn2.exec("SELECT 1 as az")
1863
- expect(res.field_name_type).to eq(:symbol)
1864
- expect(res.fields).to eq([:az])
1865
- end
1866
-
1867
- it "can't set invalid values" do
1868
- expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
1869
- expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
1870
- end
1871
- end
1872
-
1873
- describe "deprecated forms of methods" do
1874
- it "should forward exec to exec_params" do
1875
- res = @conn.exec("VALUES($1::INT)", [7]).values
1876
- expect(res).to eq( [["7"]] )
1877
- res = @conn.exec("VALUES($1::INT)", [7], 1).values
1878
- expect(res).to eq( [[[7].pack("N")]] )
1879
- res = @conn.exec("VALUES(8)", [], 1).values
1880
- expect(res).to eq( [[[8].pack("N")]] )
1881
- end
1882
-
1883
- it "should forward exec_params to exec" do
1884
- res = @conn.exec_params("VALUES(3); VALUES(4)").values
1885
- expect(res).to eq( [["4"]] )
1886
- res = @conn.exec_params("VALUES(3); VALUES(4)", nil).values
1887
- expect(res).to eq( [["4"]] )
1888
- res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil).values
1889
- expect(res).to eq( [["4"]] )
1890
- res = @conn.exec_params("VALUES(3); VALUES(4)", nil, 1).values
1891
- expect(res).to eq( [["4"]] )
1892
- res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil).values
1893
- expect(res).to eq( [["4"]] )
1894
- expect{
1895
- @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil, nil).values
1896
- }.to raise_error(ArgumentError)
1897
- end
1898
-
1899
- it "should forward send_query to send_query_params" do
1900
- @conn.send_query("VALUES($1)", [5])
1901
- expect(@conn.get_last_result.values).to eq( [["5"]] )
1902
- end
1903
-
1904
- it "shouldn't forward send_query_params to send_query" do
1905
- expect{ @conn.send_query_params("VALUES(4)").values }
1906
- .to raise_error(ArgumentError)
1907
- expect{ @conn.send_query_params("VALUES(4)", nil).values }
1908
- .to raise_error(TypeError)
1909
- end
1910
- end
1911
- end