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