pg 0.18.3 → 1.4.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 (118) hide show
  1. checksums.yaml +5 -5
  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/BSDL +2 -2
  15. data/Gemfile +14 -0
  16. data/History.rdoc +448 -4
  17. data/Manifest.txt +8 -21
  18. data/README-Windows.rdoc +4 -4
  19. data/README.ja.rdoc +1 -2
  20. data/README.rdoc +69 -17
  21. data/Rakefile +33 -135
  22. data/Rakefile.cross +70 -69
  23. data/certs/ged.pem +24 -0
  24. data/certs/larskanis-2022.pem +26 -0
  25. data/ext/errorcodes.def +109 -0
  26. data/ext/errorcodes.rb +1 -1
  27. data/ext/errorcodes.txt +35 -2
  28. data/ext/extconf.rb +120 -54
  29. data/ext/gvl_wrappers.c +8 -0
  30. data/ext/gvl_wrappers.h +44 -33
  31. data/ext/pg.c +213 -171
  32. data/ext/pg.h +92 -98
  33. data/ext/pg_binary_decoder.c +84 -15
  34. data/ext/pg_binary_encoder.c +24 -21
  35. data/ext/pg_coder.c +175 -39
  36. data/ext/pg_connection.c +1730 -1135
  37. data/ext/pg_copy_coder.c +94 -27
  38. data/ext/pg_record_coder.c +521 -0
  39. data/ext/pg_result.c +640 -221
  40. data/ext/pg_text_decoder.c +608 -40
  41. data/ext/pg_text_encoder.c +253 -99
  42. data/ext/pg_tuple.c +569 -0
  43. data/ext/pg_type_map.c +61 -21
  44. data/ext/pg_type_map_all_strings.c +19 -5
  45. data/ext/pg_type_map_by_class.c +54 -24
  46. data/ext/pg_type_map_by_column.c +79 -40
  47. data/ext/pg_type_map_by_mri_type.c +48 -19
  48. data/ext/pg_type_map_by_oid.c +55 -25
  49. data/ext/pg_type_map_in_ruby.c +51 -20
  50. data/ext/{util.c → pg_util.c} +12 -12
  51. data/ext/{util.h → pg_util.h} +2 -2
  52. data/lib/pg/basic_type_map_based_on_result.rb +47 -0
  53. data/lib/pg/basic_type_map_for_queries.rb +193 -0
  54. data/lib/pg/basic_type_map_for_results.rb +81 -0
  55. data/lib/pg/basic_type_registry.rb +301 -0
  56. data/lib/pg/binary_decoder.rb +23 -0
  57. data/lib/pg/coder.rb +24 -3
  58. data/lib/pg/connection.rb +723 -65
  59. data/lib/pg/constants.rb +2 -1
  60. data/lib/pg/exceptions.rb +9 -2
  61. data/lib/pg/result.rb +24 -7
  62. data/lib/pg/text_decoder.rb +24 -22
  63. data/lib/pg/text_encoder.rb +40 -8
  64. data/lib/pg/tuple.rb +30 -0
  65. data/lib/pg/type_map_by_column.rb +3 -2
  66. data/lib/pg/version.rb +4 -0
  67. data/lib/pg.rb +61 -36
  68. data/misc/openssl-pg-segfault.rb +31 -0
  69. data/misc/postgres/History.txt +9 -0
  70. data/misc/postgres/Manifest.txt +5 -0
  71. data/misc/postgres/README.txt +21 -0
  72. data/misc/postgres/Rakefile +21 -0
  73. data/misc/postgres/lib/postgres.rb +16 -0
  74. data/misc/ruby-pg/History.txt +9 -0
  75. data/misc/ruby-pg/Manifest.txt +5 -0
  76. data/misc/ruby-pg/README.txt +21 -0
  77. data/misc/ruby-pg/Rakefile +21 -0
  78. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  79. data/pg.gemspec +32 -0
  80. data/rakelib/task_extension.rb +46 -0
  81. data/sample/array_insert.rb +1 -1
  82. data/sample/async_api.rb +4 -8
  83. data/sample/async_copyto.rb +1 -1
  84. data/sample/async_mixed.rb +1 -1
  85. data/sample/check_conn.rb +1 -1
  86. data/sample/copydata.rb +71 -0
  87. data/sample/copyfrom.rb +1 -1
  88. data/sample/copyto.rb +1 -1
  89. data/sample/cursor.rb +1 -1
  90. data/sample/disk_usage_report.rb +6 -15
  91. data/sample/issue-119.rb +2 -2
  92. data/sample/losample.rb +1 -1
  93. data/sample/minimal-testcase.rb +2 -2
  94. data/sample/notify_wait.rb +1 -1
  95. data/sample/pg_statistics.rb +6 -15
  96. data/sample/replication_monitor.rb +9 -18
  97. data/sample/test_binary_values.rb +1 -1
  98. data/sample/wal_shipper.rb +2 -2
  99. data/sample/warehouse_partitions.rb +8 -17
  100. data.tar.gz.sig +0 -0
  101. metadata +80 -230
  102. metadata.gz.sig +0 -0
  103. data/ChangeLog +0 -5804
  104. data/lib/pg/basic_type_mapping.rb +0 -399
  105. data/spec/data/expected_trace.out +0 -26
  106. data/spec/data/random_binary_data +0 -0
  107. data/spec/helpers.rb +0 -355
  108. data/spec/pg/basic_type_mapping_spec.rb +0 -251
  109. data/spec/pg/connection_spec.rb +0 -1538
  110. data/spec/pg/result_spec.rb +0 -449
  111. data/spec/pg/type_map_by_class_spec.rb +0 -138
  112. data/spec/pg/type_map_by_column_spec.rb +0 -222
  113. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  114. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  115. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  116. data/spec/pg/type_map_spec.rb +0 -22
  117. data/spec/pg/type_spec.rb +0 -690
  118. data/spec/pg_spec.rb +0 -50
@@ -1,1538 +0,0 @@
1
- #!/usr/bin/env 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
- tmpconn = described_class.connect( @conninfo )
123
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
124
- tmpconn.finish
125
- end
126
-
127
- it "connects using 7 arguments converted to strings" do
128
- tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
129
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
130
- tmpconn.finish
131
- end
132
-
133
- it "connects using a hash of connection parameters" do
134
- tmpconn = described_class.connect(
135
- :host => 'localhost',
136
- :port => @port,
137
- :dbname => :test)
138
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
139
- tmpconn.finish
140
- end
141
-
142
- it "connects using a hash of optional connection parameters", :postgresql_90 do
143
- tmpconn = described_class.connect(
144
- :host => 'localhost',
145
- :port => @port,
146
- :dbname => :test,
147
- :keepalives => 1)
148
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
149
- tmpconn.finish
150
- end
151
-
152
- it "raises an exception when connecting with an invalid number of arguments" do
153
- expect {
154
- described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'the-extra-arg' )
155
- }.to raise_error do |error|
156
- expect( error ).to be_an( ArgumentError )
157
- expect( error.message ).to match( /extra positional parameter/i )
158
- expect( error.message ).to match( /8/ )
159
- expect( error.message ).to match( /the-extra-arg/ )
160
- end
161
- end
162
-
163
- it "can connect asynchronously", :socket_io do
164
- tmpconn = described_class.connect_start( @conninfo )
165
- expect( tmpconn ).to be_a( described_class )
166
- socket = tmpconn.socket_io
167
- status = tmpconn.connect_poll
168
-
169
- while status != PG::PGRES_POLLING_OK
170
- if status == PG::PGRES_POLLING_READING
171
- select( [socket], [], [], 5.0 ) or
172
- raise "Asynchronous connection timed out!"
173
-
174
- elsif status == PG::PGRES_POLLING_WRITING
175
- select( [], [socket], [], 5.0 ) or
176
- raise "Asynchronous connection timed out!"
177
- end
178
- status = tmpconn.connect_poll
179
- end
180
-
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
- socket = tmpconn.socket_io
192
- status = tmpconn.connect_poll
193
-
194
- while status != PG::PGRES_POLLING_OK
195
- if status == PG::PGRES_POLLING_READING
196
- if(not select([socket],[],[],5.0))
197
- raise "Asynchronous connection timed out!"
198
- end
199
- elsif(status == PG::PGRES_POLLING_WRITING)
200
- if(not select([],[socket],[],5.0))
201
- raise "Asynchronous connection timed out!"
202
- end
203
- end
204
- status = tmpconn.connect_poll
205
- end
206
-
207
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
208
- end
209
-
210
- expect( conn ).to be_finished()
211
- end
212
-
213
- it "raises proper error when sending fails" do
214
- conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
215
- expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
216
- end
217
-
218
- it "doesn't leave stale server connections after finish" do
219
- described_class.connect(@conninfo).finish
220
- sleep 0.5
221
- res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
222
- WHERE usename IS NOT NULL])
223
- # there's still the global @conn, but should be no more
224
- expect( res[0]['n'] ).to eq( '1' )
225
- end
226
-
227
- it "can retrieve it's connection parameters for the established connection" do
228
- expect( @conn.db ).to eq( "test" )
229
- expect( @conn.user ).to be_a_kind_of( String )
230
- expect( @conn.pass ).to eq( "" )
231
- expect( @conn.port ).to eq( 54321 )
232
- expect( @conn.tty ).to eq( "" )
233
- expect( @conn.options ).to eq( "" )
234
- end
235
- it "can retrieve it's connection parameters for the established connection",
236
- skip: RUBY_PLATFORM=~/x64-mingw/ ? "host segfaults on Windows-x64" : false do
237
- expect( @conn.host ).to eq( "localhost" )
238
- end
239
-
240
- EXPECTED_TRACE_OUTPUT = %{
241
- To backend> Msg Q
242
- To backend> "SELECT 1 AS one"
243
- To backend> Msg complete, length 21
244
- From backend> T
245
- From backend (#4)> 28
246
- From backend (#2)> 1
247
- From backend> "one"
248
- From backend (#4)> 0
249
- From backend (#2)> 0
250
- From backend (#4)> 23
251
- From backend (#2)> 4
252
- From backend (#4)> -1
253
- From backend (#2)> 0
254
- From backend> D
255
- From backend (#4)> 11
256
- From backend (#2)> 1
257
- From backend (#4)> 1
258
- From backend (1)> 1
259
- From backend> C
260
- From backend (#4)> 13
261
- From backend> "SELECT 1"
262
- From backend> Z
263
- From backend (#4)> 5
264
- From backend> Z
265
- From backend (#4)> 5
266
- From backend> T
267
- }.gsub( /^\t{2}/, '' ).lstrip
268
-
269
- it "trace and untrace client-server communication", :unix do
270
- # be careful to explicitly close files so that the
271
- # directory can be removed and we don't have to wait for
272
- # the GC to run.
273
- trace_file = TEST_DIRECTORY + "test_trace.out"
274
- trace_io = trace_file.open( 'w', 0600 )
275
- @conn.trace( trace_io )
276
- trace_io.close
277
-
278
- res = @conn.exec("SELECT 1 AS one")
279
- @conn.untrace
280
-
281
- res = @conn.exec("SELECT 2 AS two")
282
-
283
- trace_data = trace_file.read
284
-
285
- expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
286
- # For PostgreSQL < 9.0, the output will be different:
287
- # -From backend (#4)> 13
288
- # -From backend> "SELECT 1"
289
- # +From backend (#4)> 11
290
- # +From backend> "SELECT"
291
- if @conn.server_version < 90000
292
- expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
293
- expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
294
- end
295
-
296
- expect( trace_data ).to eq( expected_trace_output )
297
- end
298
-
299
- it "allows a query to be cancelled" do
300
- error = false
301
- @conn.send_query("SELECT pg_sleep(1000)")
302
- @conn.cancel
303
- tmpres = @conn.get_result
304
- if(tmpres.result_status != PG::PGRES_TUPLES_OK)
305
- error = true
306
- end
307
- expect( error ).to eq( true )
308
- end
309
-
310
- it "can stop a thread that runs a blocking query with async_exec" do
311
- pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
312
-
313
- start = Time.now
314
- t = Thread.new do
315
- @conn.async_exec( 'select pg_sleep(10)' )
316
- end
317
- sleep 0.1
318
-
319
- t.kill
320
- t.join
321
- expect( (Time.now - start) ).to be < 10
322
- end
323
-
324
- it "should work together with signal handlers", :unix do
325
- signal_received = false
326
- trap 'USR1' do
327
- signal_received = true
328
- end
329
-
330
- Thread.new do
331
- sleep 0.1
332
- Process.kill("USR1", Process.pid)
333
- end
334
- @conn.exec("select pg_sleep(0.3)")
335
- expect( signal_received ).to be_truthy
336
-
337
- signal_received = false
338
- Thread.new do
339
- sleep 0.1
340
- Process.kill("USR1", Process.pid)
341
- end
342
- @conn.async_exec("select pg_sleep(0.3)")
343
- expect( signal_received ).to be_truthy
344
- end
345
-
346
-
347
- it "automatically rolls back a transaction started with Connection#transaction if an exception " +
348
- "is raised" do
349
- # abort the per-example transaction so we can test our own
350
- @conn.exec( 'ROLLBACK' )
351
-
352
- res = nil
353
- @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
354
-
355
- begin
356
- expect {
357
- res = @conn.transaction do
358
- @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
359
- raise "Oh noes! All pie is gone!"
360
- end
361
- }.to raise_exception( RuntimeError, /all pie is gone/i )
362
-
363
- res = @conn.exec( "SELECT * FROM pie" )
364
- expect( res.ntuples ).to eq( 0 )
365
- ensure
366
- @conn.exec( "DROP TABLE pie" )
367
- end
368
- end
369
-
370
- it "returns the block result from Connection#transaction" do
371
- # abort the per-example transaction so we can test our own
372
- @conn.exec( 'ROLLBACK' )
373
-
374
- res = @conn.transaction do
375
- "transaction result"
376
- end
377
- expect( res ).to eq( "transaction result" )
378
- end
379
-
380
- it "not read past the end of a large object" do
381
- @conn.transaction do
382
- oid = @conn.lo_create( 0 )
383
- fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
384
- @conn.lo_write( fd, "foobar" )
385
- expect( @conn.lo_read( fd, 10 ) ).to be_nil()
386
- @conn.lo_lseek( fd, 0, PG::SEEK_SET )
387
- expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
388
- end
389
- end
390
-
391
-
392
- it "supports parameters passed to #exec (backward compatibility)" do
393
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
394
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
395
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
396
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
397
-
398
- res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
399
- expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
400
- end
401
-
402
- it "supports explicitly calling #exec_params" do
403
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
404
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
405
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
406
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
407
-
408
- res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
409
- expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
410
- end
411
-
412
- it "supports hash form parameters for #exec_params" do
413
- hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
414
- hash_param_nil = { value: nil, type: 17, format: 1 }
415
- res = @conn.exec_params( "SELECT $1, $2",
416
- [ hash_param_bin, hash_param_nil ] )
417
- expect( res.values ).to eq( [["\\x00ff", nil]] )
418
- expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
419
- end
420
-
421
- it "should work with arbitrary number of params" do
422
- begin
423
- 3.step( 12, 0.2 ) do |exp|
424
- num_params = (2 ** exp).to_i
425
- sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
426
- params = num_params.times.to_a
427
- res = @conn.exec_params( "SELECT #{sql}", params )
428
- expect( res.nfields ).to eq( num_params )
429
- expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
430
- end
431
- rescue PG::ProgramLimitExceeded
432
- # Stop silently if the server complains about too many params
433
- end
434
- end
435
-
436
- it "can wait for NOTIFY events" do
437
- @conn.exec( 'ROLLBACK' )
438
- @conn.exec( 'LISTEN woo' )
439
-
440
- t = Thread.new do
441
- begin
442
- conn = described_class.connect( @conninfo )
443
- sleep 1
444
- conn.async_exec( 'NOTIFY woo' )
445
- ensure
446
- conn.finish
447
- end
448
- end
449
-
450
- expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
451
- @conn.exec( 'UNLISTEN woo' )
452
-
453
- t.join
454
- end
455
-
456
- it "calls a block for NOTIFY events if one is given" do
457
- @conn.exec( 'ROLLBACK' )
458
- @conn.exec( 'LISTEN woo' )
459
-
460
- t = Thread.new do
461
- begin
462
- conn = described_class.connect( @conninfo )
463
- sleep 1
464
- conn.async_exec( 'NOTIFY woo' )
465
- ensure
466
- conn.finish
467
- end
468
- end
469
-
470
- eventpid = event = nil
471
- @conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
472
- expect( event ).to eq( 'woo' )
473
- expect( eventpid ).to be_an( Integer )
474
-
475
- @conn.exec( 'UNLISTEN woo' )
476
-
477
- t.join
478
- end
479
-
480
- it "doesn't collapse sequential notifications" do
481
- @conn.exec( 'ROLLBACK' )
482
- @conn.exec( 'LISTEN woo' )
483
- @conn.exec( 'LISTEN war' )
484
- @conn.exec( 'LISTEN woz' )
485
-
486
- begin
487
- conn = described_class.connect( @conninfo )
488
- conn.exec( 'NOTIFY woo' )
489
- conn.exec( 'NOTIFY war' )
490
- conn.exec( 'NOTIFY woz' )
491
- ensure
492
- conn.finish
493
- end
494
-
495
- channels = []
496
- 3.times do
497
- channels << @conn.wait_for_notify( 2 )
498
- end
499
-
500
- expect( channels.size ).to eq( 3 )
501
- expect( channels ).to include( 'woo', 'war', 'woz' )
502
-
503
- @conn.exec( 'UNLISTEN woz' )
504
- @conn.exec( 'UNLISTEN war' )
505
- @conn.exec( 'UNLISTEN woo' )
506
- end
507
-
508
- it "returns notifications which are already in the queue before wait_for_notify is called " +
509
- "without waiting for the socket to become readable" do
510
- @conn.exec( 'ROLLBACK' )
511
- @conn.exec( 'LISTEN woo' )
512
-
513
- begin
514
- conn = described_class.connect( @conninfo )
515
- conn.exec( 'NOTIFY woo' )
516
- ensure
517
- conn.finish
518
- end
519
-
520
- # Cause the notification to buffer, but not be read yet
521
- @conn.exec( 'SELECT 1' )
522
-
523
- expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
524
- @conn.exec( 'UNLISTEN woo' )
525
- end
526
-
527
- it "can receive notices while waiting for NOTIFY without exceeding the timeout", :postgresql_90 do
528
- notices = []
529
- @conn.set_notice_processor do |msg|
530
- notices << [msg, Time.now]
531
- end
532
- st = Time.now
533
- @conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
534
- expect( @conn.wait_for_notify( 1 ) ).to be_nil
535
- expect( notices.first ).to_not be_nil
536
- et = Time.now
537
- expect( (et - notices.first[1]) ).to be >= 0.4
538
- expect( (et - st) ).to be >= 0.9
539
- expect( (et - st) ).to be < 1.4
540
- end
541
-
542
- it "yields the result if block is given to exec" do
543
- rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
544
- values = []
545
- expect( result ).to be_kind_of( PG::Result )
546
- expect( result.ntuples ).to eq( 2 )
547
- result.each do |tuple|
548
- values << tuple['a']
549
- end
550
- values
551
- end
552
-
553
- expect( rval.size ).to eq( 2 )
554
- expect( rval ).to include( '5678', '1234' )
555
- end
556
-
557
- it "can process #copy_data output queries" do
558
- rows = []
559
- res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
560
- expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
561
- expect( res.nfields ).to eq( 1 )
562
- while row=@conn.get_copy_data
563
- rows << row
564
- end
565
- end
566
- expect( rows ).to eq( ["1\n", "2\n"] )
567
- expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
568
- expect( @conn ).to still_be_usable
569
- end
570
-
571
- it "can handle incomplete #copy_data output queries" do
572
- expect {
573
- @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
574
- @conn.get_copy_data
575
- end
576
- }.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
577
- expect( @conn ).to still_be_usable
578
- end
579
-
580
- it "can handle client errors in #copy_data for output" do
581
- expect {
582
- @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do
583
- raise "boom"
584
- end
585
- }.to raise_error(RuntimeError, "boom")
586
- expect( @conn ).to still_be_usable
587
- end
588
-
589
- it "can handle server errors in #copy_data for output", :postgresql_90 do
590
- @conn.exec "ROLLBACK"
591
- @conn.transaction do
592
- @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
593
- expect {
594
- @conn.copy_data( "COPY (SELECT errfunc()) TO STDOUT" ) do |res|
595
- while @conn.get_copy_data
596
- end
597
- end
598
- }.to raise_error(PG::Error, /test-error/)
599
- end
600
- expect( @conn ).to still_be_usable
601
- end
602
-
603
- it "can process #copy_data input queries" do
604
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
605
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
606
- expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
607
- expect( res.nfields ).to eq( 1 )
608
- @conn.put_copy_data "1\n"
609
- @conn.put_copy_data "2\n"
610
- end
611
- expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
612
-
613
- expect( @conn ).to still_be_usable
614
-
615
- res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
616
- expect( res.values ).to eq( [["1"], ["2"]] )
617
- end
618
-
619
- it "can handle client errors in #copy_data for input" do
620
- @conn.exec "ROLLBACK"
621
- @conn.transaction do
622
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
623
- expect {
624
- @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
625
- raise "boom"
626
- end
627
- }.to raise_error(RuntimeError, "boom")
628
- end
629
-
630
- expect( @conn ).to still_be_usable
631
- end
632
-
633
- it "can handle server errors in #copy_data for input" do
634
- @conn.exec "ROLLBACK"
635
- @conn.transaction do
636
- @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
637
- expect {
638
- @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
639
- @conn.put_copy_data "xyz\n"
640
- end
641
- }.to raise_error(PG::Error, /invalid input syntax for integer/)
642
- end
643
- expect( @conn ).to still_be_usable
644
- end
645
-
646
- it "should raise an error for non copy statements in #copy_data" do
647
- expect {
648
- @conn.copy_data( "SELECT 1" ){}
649
- }.to raise_error(ArgumentError, /no COPY/)
650
-
651
- expect( @conn ).to still_be_usable
652
- end
653
-
654
- it "correctly finishes COPY queries passed to #async_exec" do
655
- @conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
656
-
657
- results = []
658
- begin
659
- data = @conn.get_copy_data( true )
660
- if false == data
661
- @conn.block( 2.0 )
662
- data = @conn.get_copy_data( true )
663
- end
664
- results << data if data
665
- end until data.nil?
666
-
667
- expect( results.size ).to eq( 2 )
668
- expect( results ).to include( "1\n", "2\n" )
669
- end
670
-
671
-
672
- it "described_class#block shouldn't block a second thread" do
673
- start = Time.now
674
- t = Thread.new do
675
- @conn.send_query( "select pg_sleep(3)" )
676
- @conn.block
677
- end
678
-
679
- sleep 0.5
680
- expect( t ).to be_alive()
681
- @conn.cancel
682
- t.join
683
- expect( (Time.now - start) ).to be < 3
684
- end
685
-
686
- it "described_class#block should allow a timeout" do
687
- @conn.send_query( "select pg_sleep(3)" )
688
-
689
- start = Time.now
690
- @conn.block( 0.1 )
691
- finish = Time.now
692
-
693
- expect( (finish - start) ).to be_within( 0.05 ).of( 0.1 )
694
- end
695
-
696
-
697
- it "can encrypt a string given a password and username" do
698
- expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
699
- end
700
-
701
- it "can return the default connection options" do
702
- expect( described_class.conndefaults ).to be_a( Array )
703
- expect( described_class.conndefaults ).to all( be_a(Hash) )
704
- expect( described_class.conndefaults[0] ).to include( :keyword, :label, :dispchar, :dispsize )
705
- expect( @conn.conndefaults ).to eq( described_class.conndefaults )
706
- end
707
-
708
- it "can return the default connection options as a Hash" do
709
- expect( described_class.conndefaults_hash ).to be_a( Hash )
710
- expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
711
- expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
712
- expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
713
- end
714
-
715
- it "can return the connection's connection options", :postgresql_93 do
716
- expect( @conn.conninfo ).to be_a( Array )
717
- expect( @conn.conninfo ).to all( be_a(Hash) )
718
- expect( @conn.conninfo[0] ).to include( :keyword, :label, :dispchar, :dispsize )
719
- end
720
-
721
-
722
- it "can return the connection's connection options as a Hash", :postgresql_93 do
723
- expect( @conn.conninfo_hash ).to be_a( Hash )
724
- expect( @conn.conninfo_hash ).to include( :user, :password, :connect_timeout, :dbname, :host )
725
- expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
726
- end
727
-
728
-
729
- it "honors the connect_timeout connection parameter", :postgresql_93 do
730
- conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
731
- begin
732
- expect( conn.conninfo_hash[:connect_timeout] ).to eq( "11" )
733
- ensure
734
- conn.finish
735
- end
736
- end
737
-
738
-
739
- it "raises an appropriate error if either of the required arguments for encrypt_password " +
740
- "is not valid" do
741
- expect {
742
- described_class.encrypt_password( nil, nil )
743
- }.to raise_error( TypeError )
744
- expect {
745
- described_class.encrypt_password( "postgres", nil )
746
- }.to raise_error( TypeError )
747
- expect {
748
- described_class.encrypt_password( nil, "postgres" )
749
- }.to raise_error( TypeError )
750
- end
751
-
752
-
753
- it "allows fetching a column of values from a result by column number" do
754
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
755
- expect( res.column_values( 0 ) ).to eq( %w[1 2 3] )
756
- expect( res.column_values( 1 ) ).to eq( %w[2 3 4] )
757
- end
758
-
759
-
760
- it "allows fetching a column of values from a result by field name" do
761
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
762
- expect( res.field_values( 'column1' ) ).to eq( %w[1 2 3] )
763
- expect( res.field_values( 'column2' ) ).to eq( %w[2 3 4] )
764
- end
765
-
766
-
767
- it "raises an error if selecting an invalid column index" do
768
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
769
- expect {
770
- res.column_values( 20 )
771
- }.to raise_error( IndexError )
772
- end
773
-
774
-
775
- it "raises an error if selecting an invalid field name" do
776
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
777
- expect {
778
- res.field_values( 'hUUuurrg' )
779
- }.to raise_error( IndexError )
780
- end
781
-
782
-
783
- it "raises an error if column index is not a number" do
784
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
785
- expect {
786
- res.column_values( 'hUUuurrg' )
787
- }.to raise_error( TypeError )
788
- end
789
-
790
-
791
- it "can connect asynchronously", :socket_io do
792
- serv = TCPServer.new( '127.0.0.1', 54320 )
793
- conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
794
- expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
795
- select( nil, [conn.socket_io], nil, 0.2 )
796
- serv.close
797
- if conn.connect_poll == PG::PGRES_POLLING_READING
798
- select( [conn.socket_io], nil, nil, 0.2 )
799
- end
800
- expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
801
- end
802
-
803
- it "discards previous results (if any) before waiting on an #async_exec"
804
-
805
- it "calls the block if one is provided to #async_exec" do
806
- result = nil
807
- @conn.async_exec( "select 47 as one" ) do |pg_res|
808
- result = pg_res[0]
809
- end
810
- expect( result ).to eq( { 'one' => '47' } )
811
- end
812
-
813
- it "raises a rescue-able error if #finish is called twice", :without_transaction do
814
- conn = PG.connect( @conninfo )
815
-
816
- conn.finish
817
- expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
818
- end
819
-
820
- it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
821
- conn = PG.connect( @conninfo )
822
- io = conn.socket_io
823
- conn.finish
824
- expect( io ).to be_closed()
825
- expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
826
- end
827
-
828
- it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
829
- conn = PG.connect( @conninfo )
830
- io = conn.socket_io
831
- conn.reset
832
- expect( io ).to be_closed()
833
- expect( conn.socket_io ).to_not equal( io )
834
- conn.finish
835
- end
836
-
837
- it "block should raise ConnectionBad for a closed connection" do
838
- serv = TCPServer.new( '127.0.0.1', 54320 )
839
- conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
840
- while [PG::CONNECTION_STARTED, PG::CONNECTION_MADE].include?(conn.connect_poll)
841
- sleep 0.1
842
- end
843
- serv.close
844
- expect{ conn.block }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
845
- expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
846
- end
847
-
848
- context "under PostgreSQL 9", :postgresql_90 do
849
-
850
- before( :each ) do
851
- pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
852
- end
853
-
854
- it "sets the fallback_application_name on new connections" do
855
- conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
856
-
857
- conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
858
- expect( conn_name ).to include( $0[0..10] )
859
- expect( conn_name ).to include( $0[-10..-1] )
860
- expect( conn_name.length ).to be <= 64
861
- end
862
-
863
- it "sets a shortened fallback_application_name on new connections" do
864
- old_0 = $0
865
- begin
866
- $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
867
- conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
868
- conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
869
- expect( conn_name ).to include( $0[0..10] )
870
- expect( conn_name ).to include( $0[-10..-1] )
871
- expect( conn_name.length ).to be <= 64
872
- ensure
873
- $0 = old_0
874
- end
875
- end
876
-
877
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
878
- "any number of arguments" do
879
-
880
- @conn.exec( 'ROLLBACK' )
881
- @conn.exec( 'LISTEN knees' )
882
-
883
- conn = described_class.connect( @conninfo )
884
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
885
- conn.finish
886
-
887
- event, pid, msg = nil
888
- @conn.wait_for_notify( 10 ) do |*args|
889
- event, pid, msg = *args
890
- end
891
- @conn.exec( 'UNLISTEN knees' )
892
-
893
- expect( event ).to eq( 'knees' )
894
- expect( pid ).to be_a_kind_of( Integer )
895
- expect( msg ).to eq( 'skirt and boots' )
896
- end
897
-
898
- it "accepts nil as the timeout in #wait_for_notify " do
899
- @conn.exec( 'ROLLBACK' )
900
- @conn.exec( 'LISTEN knees' )
901
-
902
- conn = described_class.connect( @conninfo )
903
- conn.exec( %Q{NOTIFY knees} )
904
- conn.finish
905
-
906
- event, pid = nil
907
- @conn.wait_for_notify( nil ) do |*args|
908
- event, pid = *args
909
- end
910
- @conn.exec( 'UNLISTEN knees' )
911
-
912
- expect( event ).to eq( 'knees' )
913
- expect( pid ).to be_a_kind_of( Integer )
914
- end
915
-
916
- it "sends nil as the payload if the notification wasn't given one" do
917
- @conn.exec( 'ROLLBACK' )
918
- @conn.exec( 'LISTEN knees' )
919
-
920
- conn = described_class.connect( @conninfo )
921
- conn.exec( %Q{NOTIFY knees} )
922
- conn.finish
923
-
924
- payload = :notnil
925
- @conn.wait_for_notify( nil ) do |*args|
926
- payload = args[ 2 ]
927
- end
928
- @conn.exec( 'UNLISTEN knees' )
929
-
930
- expect( payload ).to be_nil()
931
- end
932
-
933
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
934
- "two arguments" do
935
-
936
- @conn.exec( 'ROLLBACK' )
937
- @conn.exec( 'LISTEN knees' )
938
-
939
- conn = described_class.connect( @conninfo )
940
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
941
- conn.finish
942
-
943
- event, pid, msg = nil
944
- @conn.wait_for_notify( 10 ) do |arg1, arg2|
945
- event, pid, msg = arg1, arg2
946
- end
947
- @conn.exec( 'UNLISTEN knees' )
948
-
949
- expect( event ).to eq( 'knees' )
950
- expect( pid ).to be_a_kind_of( Integer )
951
- expect( msg ).to be_nil()
952
- end
953
-
954
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
955
- "doesn't accept arguments" do
956
-
957
- @conn.exec( 'ROLLBACK' )
958
- @conn.exec( 'LISTEN knees' )
959
-
960
- conn = described_class.connect( @conninfo )
961
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
962
- conn.finish
963
-
964
- notification_received = false
965
- @conn.wait_for_notify( 10 ) do
966
- notification_received = true
967
- end
968
- @conn.exec( 'UNLISTEN knees' )
969
-
970
- expect( notification_received ).to be_truthy()
971
- end
972
-
973
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
974
- "three arguments" do
975
-
976
- @conn.exec( 'ROLLBACK' )
977
- @conn.exec( 'LISTEN knees' )
978
-
979
- conn = described_class.connect( @conninfo )
980
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
981
- conn.finish
982
-
983
- event, pid, msg = nil
984
- @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
985
- event, pid, msg = arg1, arg2, arg3
986
- end
987
- @conn.exec( 'UNLISTEN knees' )
988
-
989
- expect( event ).to eq( 'knees' )
990
- expect( pid ).to be_a_kind_of( Integer )
991
- expect( msg ).to eq( 'skirt and boots' )
992
- end
993
-
994
- end
995
-
996
- context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
997
-
998
- it "pings successfully with connection string" do
999
- ping = described_class.ping(@conninfo)
1000
- expect( ping ).to eq( PG::PQPING_OK )
1001
- end
1002
-
1003
- it "pings using 7 arguments converted to strings" do
1004
- ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
1005
- expect( ping ).to eq( PG::PQPING_OK )
1006
- end
1007
-
1008
- it "pings using a hash of connection parameters" do
1009
- ping = described_class.ping(
1010
- :host => 'localhost',
1011
- :port => @port,
1012
- :dbname => :test)
1013
- expect( ping ).to eq( PG::PQPING_OK )
1014
- end
1015
-
1016
- it "returns correct response when ping connection cannot be established" do
1017
- ping = described_class.ping(
1018
- :host => 'localhost',
1019
- :port => 9999,
1020
- :dbname => :test)
1021
- expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
1022
- end
1023
-
1024
- it "returns correct response when ping connection arguments are wrong" do
1025
- ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
1026
- expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1027
- end
1028
-
1029
-
1030
- end
1031
-
1032
- context "under PostgreSQL 9.2 client library", :postgresql_92 do
1033
- describe "set_single_row_mode" do
1034
-
1035
- it "raises an error when called at the wrong time" do
1036
- expect {
1037
- @conn.set_single_row_mode
1038
- }.to raise_error(PG::Error)
1039
- end
1040
-
1041
- it "should work in single row mode" do
1042
- @conn.send_query( "SELECT generate_series(1,10)" )
1043
- @conn.set_single_row_mode
1044
-
1045
- results = []
1046
- loop do
1047
- @conn.block
1048
- res = @conn.get_result or break
1049
- results << res
1050
- end
1051
- expect( results.length ).to eq( 11 )
1052
- results[0..-2].each do |res|
1053
- expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1054
- values = res.field_values('generate_series')
1055
- expect( values.length ).to eq( 1 )
1056
- expect( values.first.to_i ).to be > 0
1057
- end
1058
- expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
1059
- expect( results.last.ntuples ).to eq( 0 )
1060
- end
1061
-
1062
- it "should receive rows before entire query is finished" do
1063
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
1064
- @conn.set_single_row_mode
1065
-
1066
- start_time = Time.now
1067
- first_row_time = nil
1068
- loop do
1069
- res = @conn.get_result or break
1070
- res.check
1071
- first_row_time = Time.now unless first_row_time
1072
- end
1073
- expect( (Time.now - start_time) ).to be >= 1.0
1074
- expect( (first_row_time - start_time) ).to be < 1.0
1075
- end
1076
-
1077
- it "should receive rows before entire query fails" do
1078
- @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
1079
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
1080
- @conn.set_single_row_mode
1081
-
1082
- first_result = nil
1083
- expect do
1084
- loop do
1085
- res = @conn.get_result or break
1086
- res.check
1087
- first_result ||= res
1088
- end
1089
- end.to raise_error(PG::Error)
1090
- expect( first_result.kind_of?(PG::Result) ).to be_truthy
1091
- expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1092
- end
1093
- end
1094
- end
1095
-
1096
- context "multinationalization support", :ruby_19 do
1097
-
1098
- describe "rubyforge #22925: m17n support" do
1099
- it "should return results in the same encoding as the client (iso-8859-1)" do
1100
- out_string = nil
1101
- @conn.transaction do |conn|
1102
- conn.internal_encoding = 'iso8859-1'
1103
- res = conn.exec("VALUES ('fantasia')", [], 0)
1104
- out_string = res[0]['column1']
1105
- end
1106
- expect( out_string ).to eq( 'fantasia' )
1107
- expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
1108
- end
1109
-
1110
- it "should return results in the same encoding as the client (utf-8)" do
1111
- out_string = nil
1112
- @conn.transaction do |conn|
1113
- conn.internal_encoding = 'utf-8'
1114
- res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
1115
- out_string = res[0]['column1']
1116
- end
1117
- expect( out_string ).to eq( '世界線航跡蔵' )
1118
- expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1119
- end
1120
-
1121
- it "should return results in the same encoding as the client (EUC-JP)" do
1122
- out_string = nil
1123
- @conn.transaction do |conn|
1124
- conn.internal_encoding = 'EUC-JP'
1125
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1126
- res = conn.exec(stmt, [], 0)
1127
- out_string = res[0]['column1']
1128
- end
1129
- expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1130
- expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1131
- end
1132
-
1133
- it "returns the results in the correct encoding even if the client_encoding has " +
1134
- "changed since the results were fetched" do
1135
- out_string = nil
1136
- @conn.transaction do |conn|
1137
- conn.internal_encoding = 'EUC-JP'
1138
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1139
- res = conn.exec(stmt, [], 0)
1140
- conn.internal_encoding = 'utf-8'
1141
- out_string = res[0]['column1']
1142
- end
1143
- expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1144
- expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1145
- end
1146
-
1147
- it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
1148
- @conn.exec "SET client_encoding TO SQL_ASCII"
1149
- expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1150
- end
1151
-
1152
- it "uses the client encoding for escaped string" do
1153
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1154
- @conn.set_client_encoding( "euc_jp" )
1155
- escaped = @conn.escape( original )
1156
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1157
- expect( escaped ).to eq( "string to" )
1158
- end
1159
-
1160
- it "uses the client encoding for escaped literal", :postgresql_90 do
1161
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1162
- @conn.set_client_encoding( "euc_jp" )
1163
- escaped = @conn.escape_literal( original )
1164
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1165
- expect( escaped ).to eq( "'string to'" )
1166
- end
1167
-
1168
- it "uses the client encoding for escaped identifier", :postgresql_90 do
1169
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1170
- @conn.set_client_encoding( "euc_jp" )
1171
- escaped = @conn.escape_identifier( original )
1172
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1173
- expect( escaped ).to eq( "\"string to\"" )
1174
- end
1175
-
1176
- it "uses the client encoding for quote_ident" do
1177
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1178
- @conn.set_client_encoding( "euc_jp" )
1179
- escaped = @conn.quote_ident( original )
1180
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1181
- expect( escaped ).to eq( "\"string to\"" )
1182
- end
1183
-
1184
- it "uses the previous string encoding for escaped string" do
1185
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1186
- @conn.set_client_encoding( "euc_jp" )
1187
- escaped = described_class.escape( original )
1188
- expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1189
- expect( escaped ).to eq( "string to" )
1190
- end
1191
-
1192
- it "uses the previous string encoding for quote_ident" do
1193
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1194
- @conn.set_client_encoding( "euc_jp" )
1195
- escaped = described_class.quote_ident( original )
1196
- expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1197
- expect( escaped ).to eq( "\"string to\"" )
1198
- end
1199
- end
1200
-
1201
- it "can quote bigger strings with quote_ident" do
1202
- original = "'01234567\"" * 100
1203
- escaped = described_class.quote_ident( original + "\0afterzero" )
1204
- expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
1205
- end
1206
-
1207
- it "can quote Arrays with quote_ident" do
1208
- original = "'01234567\""
1209
- escaped = described_class.quote_ident( [original]*3 )
1210
- expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
1211
- expect( escaped ).to eq( expected.join(".") )
1212
- end
1213
-
1214
- describe "Ruby 1.9.x default_internal encoding" do
1215
-
1216
- it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
1217
- @conn.transaction do |txn_conn|
1218
- txn_conn.internal_encoding = Encoding::ISO8859_1
1219
- txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
1220
- txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
1221
- end
1222
-
1223
- begin
1224
- prev_encoding = Encoding.default_internal
1225
- Encoding.default_internal = Encoding::UTF_8
1226
-
1227
- conn = PG.connect( @conninfo )
1228
- expect( conn.internal_encoding ).to eq( Encoding::UTF_8 )
1229
- res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1230
- expect( res[0]['foo'].encoding ).to eq( Encoding::UTF_8 )
1231
- ensure
1232
- conn.exec( "DROP TABLE defaultinternaltest" )
1233
- conn.finish if conn
1234
- Encoding.default_internal = prev_encoding
1235
- end
1236
- end
1237
-
1238
- it "allows users of the async interface to set the client_encoding to the default_internal" do
1239
- begin
1240
- prev_encoding = Encoding.default_internal
1241
- Encoding.default_internal = Encoding::KOI8_R
1242
-
1243
- @conn.set_default_encoding
1244
-
1245
- expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
1246
- ensure
1247
- Encoding.default_internal = prev_encoding
1248
- end
1249
- end
1250
-
1251
- end
1252
-
1253
-
1254
- it "encodes exception messages with the connection's encoding (#96)", :without_transaction do
1255
- # Use a new connection so the client_encoding isn't set outside of this example
1256
- conn = PG.connect( @conninfo )
1257
- conn.client_encoding = 'iso-8859-15'
1258
-
1259
- conn.transaction do
1260
- conn.exec "CREATE TABLE foo (bar TEXT)"
1261
-
1262
- begin
1263
- query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
1264
- conn.exec( query )
1265
- rescue => err
1266
- expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
1267
- else
1268
- fail "No exception raised?!"
1269
- end
1270
- end
1271
-
1272
- conn.finish if conn
1273
- end
1274
-
1275
- it "handles clearing result in or after set_notice_receiver", :postgresql_90 do
1276
- r = nil
1277
- @conn.set_notice_receiver do |result|
1278
- r = result
1279
- expect( r.cleared? ).to eq(false)
1280
- end
1281
- @conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
1282
- sleep 0.2
1283
- expect( r ).to be_a( PG::Result )
1284
- expect( r.cleared? ).to eq(true)
1285
- expect( r.autoclear? ).to eq(true)
1286
- r.clear
1287
- @conn.set_notice_receiver
1288
- end
1289
-
1290
- it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1291
- [:receiver, :processor].each do |kind|
1292
- notices = []
1293
- @conn.internal_encoding = 'utf-8'
1294
- if kind == :processor
1295
- @conn.set_notice_processor do |msg|
1296
- notices << msg
1297
- end
1298
- else
1299
- @conn.set_notice_receiver do |result|
1300
- notices << result.error_message
1301
- end
1302
- end
1303
-
1304
- 3.times do
1305
- @conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
1306
- end
1307
-
1308
- expect( notices.length ).to eq( 3 )
1309
- notices.each do |notice|
1310
- expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
1311
- expect( notice.encoding ).to eq( Encoding::UTF_8 )
1312
- end
1313
- @conn.set_notice_receiver
1314
- @conn.set_notice_processor
1315
- end
1316
- end
1317
-
1318
- it "receives properly encoded text from wait_for_notify", :postgresql_90 do
1319
- @conn.internal_encoding = 'utf-8'
1320
- @conn.exec( 'ROLLBACK' )
1321
- @conn.exec( 'LISTEN "Möhre"' )
1322
- @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1323
- event, pid, msg = nil
1324
- @conn.wait_for_notify( 10 ) do |*args|
1325
- event, pid, msg = *args
1326
- end
1327
- @conn.exec( 'UNLISTEN "Möhre"' )
1328
-
1329
- expect( event ).to eq( "Möhre" )
1330
- expect( event.encoding ).to eq( Encoding::UTF_8 )
1331
- expect( msg ).to eq( '世界線航跡蔵' )
1332
- expect( msg.encoding ).to eq( Encoding::UTF_8 )
1333
- end
1334
-
1335
- it "returns properly encoded text from notifies", :postgresql_90 do
1336
- @conn.internal_encoding = 'utf-8'
1337
- @conn.exec( 'ROLLBACK' )
1338
- @conn.exec( 'LISTEN "Möhre"' )
1339
- @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1340
- @conn.exec( 'UNLISTEN "Möhre"' )
1341
-
1342
- notification = @conn.notifies
1343
- expect( notification[:relname] ).to eq( "Möhre" )
1344
- expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
1345
- expect( notification[:extra] ).to eq( '世界線航跡蔵' )
1346
- expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
1347
- expect( notification[:be_pid] ).to be > 0
1348
- end
1349
- end
1350
-
1351
- context "OS thread support", :ruby_19 do
1352
- it "Connection#exec shouldn't block a second thread" do
1353
- t = Thread.new do
1354
- @conn.exec( "select pg_sleep(1)" )
1355
- end
1356
-
1357
- sleep 0.5
1358
- expect( t ).to be_alive()
1359
- t.join
1360
- end
1361
-
1362
- it "Connection.new shouldn't block a second thread" do
1363
- serv = nil
1364
- t = Thread.new do
1365
- serv = TCPServer.new( '127.0.0.1', 54320 )
1366
- expect {
1367
- described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
1368
- }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
1369
- end
1370
-
1371
- sleep 0.5
1372
- expect( t ).to be_alive()
1373
- serv.close
1374
- t.join
1375
- end
1376
- end
1377
-
1378
- describe "type casting" do
1379
- it "should raise an error on invalid param mapping" do
1380
- expect{
1381
- @conn.exec_params( "SELECT 1", [], nil, :invalid )
1382
- }.to raise_error(TypeError)
1383
- end
1384
-
1385
- it "should return nil if no type mapping is set" do
1386
- expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
1387
- expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
1388
- end
1389
-
1390
- it "shouldn't type map params unless requested" do
1391
- expect{
1392
- @conn.exec_params( "SELECT $1", [5] )
1393
- }.to raise_error(PG::IndeterminateDatatype)
1394
- end
1395
-
1396
- it "should raise an error on invalid encoder to put_copy_data" do
1397
- expect{
1398
- @conn.put_copy_data [1], :invalid
1399
- }.to raise_error(TypeError)
1400
- end
1401
-
1402
- it "can type cast parameters to put_copy_data with explicit encoder" do
1403
- tm = PG::TypeMapByColumn.new [nil]
1404
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1405
-
1406
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1407
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1408
- @conn.put_copy_data [1], row_encoder
1409
- @conn.put_copy_data ["2"], row_encoder
1410
- end
1411
-
1412
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1413
- @conn.put_copy_data [3]
1414
- @conn.put_copy_data ["4"]
1415
- end
1416
-
1417
- res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
1418
- expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
1419
- end
1420
-
1421
- context "with default query type map" do
1422
- before :each do
1423
- @conn2 = described_class.new(@conninfo)
1424
- tm = PG::TypeMapByClass.new
1425
- tm[Integer] = PG::TextEncoder::Integer.new oid: 20
1426
- @conn2.type_map_for_queries = tm
1427
-
1428
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1429
- @conn2.encoder_for_put_copy_data = row_encoder
1430
- end
1431
- after :each do
1432
- @conn2.close
1433
- end
1434
-
1435
- it "should respect a type mapping for params and it's OID and format code" do
1436
- res = @conn2.exec_params( "SELECT $1", [5] )
1437
- expect( res.values ).to eq( [["5"]] )
1438
- expect( res.ftype(0) ).to eq( 20 )
1439
- end
1440
-
1441
- it "should return the current type mapping" do
1442
- expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
1443
- end
1444
-
1445
- it "should work with arbitrary number of params in conjunction with type casting" do
1446
- begin
1447
- 3.step( 12, 0.2 ) do |exp|
1448
- num_params = (2 ** exp).to_i
1449
- sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
1450
- params = num_params.times.to_a
1451
- res = @conn2.exec_params( "SELECT #{sql}", params )
1452
- expect( res.nfields ).to eq( num_params )
1453
- expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
1454
- end
1455
- rescue PG::ProgramLimitExceeded
1456
- # Stop silently as soon the server complains about too many params
1457
- end
1458
- end
1459
-
1460
- it "can process #copy_data input queries with row encoder" do
1461
- @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1462
- res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1463
- @conn2.put_copy_data [1]
1464
- @conn2.put_copy_data ["2"]
1465
- end
1466
-
1467
- res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1468
- expect( res.values ).to eq( [["1"], ["2"]] )
1469
- end
1470
- end
1471
-
1472
- context "with default result type map" do
1473
- before :each do
1474
- @conn2 = described_class.new(@conninfo)
1475
- tm = PG::TypeMapByOid.new
1476
- tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
1477
- @conn2.type_map_for_results = tm
1478
-
1479
- row_decoder = PG::TextDecoder::CopyRow.new
1480
- @conn2.decoder_for_get_copy_data = row_decoder
1481
- end
1482
- after :each do
1483
- @conn2.close
1484
- end
1485
-
1486
- it "should respect a type mapping for result" do
1487
- res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
1488
- expect( res.values ).to eq( [[5]] )
1489
- end
1490
-
1491
- it "should return the current type mapping" do
1492
- expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
1493
- end
1494
-
1495
- it "should work with arbitrary number of params in conjunction with type casting" do
1496
- begin
1497
- 3.step( 12, 0.2 ) do |exp|
1498
- num_params = (2 ** exp).to_i
1499
- sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
1500
- params = num_params.times.to_a
1501
- res = @conn2.exec_params( "SELECT #{sql}", params )
1502
- expect( res.nfields ).to eq( num_params )
1503
- expect( res.values ).to eq( [num_params.times.to_a] )
1504
- end
1505
- rescue PG::ProgramLimitExceeded
1506
- # Stop silently as soon the server complains about too many params
1507
- end
1508
- end
1509
-
1510
- it "can process #copy_data output with row decoder" do
1511
- rows = []
1512
- res2 = @conn2.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
1513
- while row=@conn2.get_copy_data
1514
- rows << row
1515
- end
1516
- end
1517
- expect( rows ).to eq( [["1"], ["2"]] )
1518
- end
1519
-
1520
- it "can type cast #copy_data output with explicit decoder" do
1521
- tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
1522
- row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
1523
- rows = []
1524
- @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
1525
- while row=@conn.get_copy_data
1526
- rows << row
1527
- end
1528
- end
1529
- @conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
1530
- while row=@conn.get_copy_data( false, row_decoder )
1531
- rows << row
1532
- end
1533
- end
1534
- expect( rows ).to eq( [[1], [2], [3], [4]] )
1535
- end
1536
- end
1537
- end
1538
- end