pg 0.18.4 → 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 +436 -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 +67 -17
  21. data/Rakefile +32 -144
  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 +119 -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 +82 -15
  34. data/ext/pg_binary_encoder.c +20 -19
  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 +606 -40
  41. data/ext/pg_text_encoder.c +250 -99
  42. data/ext/pg_tuple.c +569 -0
  43. data/ext/pg_type_map.c +55 -15
  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 +73 -34
  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 +79 -225
  102. metadata.gz.sig +0 -0
  103. data/ChangeLog +0 -5911
  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 -1544
  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 -697
  118. data/spec/pg_spec.rb +0 -50
@@ -1,1544 +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
- it "will raise a TypeError for invalid arguments to quote_ident" do
1215
- expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
1216
- expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
1217
- expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
1218
- end
1219
-
1220
- describe "Ruby 1.9.x default_internal encoding" do
1221
-
1222
- it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
1223
- @conn.transaction do |txn_conn|
1224
- txn_conn.internal_encoding = Encoding::ISO8859_1
1225
- txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
1226
- txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
1227
- end
1228
-
1229
- begin
1230
- prev_encoding = Encoding.default_internal
1231
- Encoding.default_internal = Encoding::UTF_8
1232
-
1233
- conn = PG.connect( @conninfo )
1234
- expect( conn.internal_encoding ).to eq( Encoding::UTF_8 )
1235
- res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1236
- expect( res[0]['foo'].encoding ).to eq( Encoding::UTF_8 )
1237
- ensure
1238
- conn.exec( "DROP TABLE defaultinternaltest" )
1239
- conn.finish if conn
1240
- Encoding.default_internal = prev_encoding
1241
- end
1242
- end
1243
-
1244
- it "allows users of the async interface to set the client_encoding to the default_internal" do
1245
- begin
1246
- prev_encoding = Encoding.default_internal
1247
- Encoding.default_internal = Encoding::KOI8_R
1248
-
1249
- @conn.set_default_encoding
1250
-
1251
- expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
1252
- ensure
1253
- Encoding.default_internal = prev_encoding
1254
- end
1255
- end
1256
-
1257
- end
1258
-
1259
-
1260
- it "encodes exception messages with the connection's encoding (#96)", :without_transaction do
1261
- # Use a new connection so the client_encoding isn't set outside of this example
1262
- conn = PG.connect( @conninfo )
1263
- conn.client_encoding = 'iso-8859-15'
1264
-
1265
- conn.transaction do
1266
- conn.exec "CREATE TABLE foo (bar TEXT)"
1267
-
1268
- begin
1269
- query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
1270
- conn.exec( query )
1271
- rescue => err
1272
- expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
1273
- else
1274
- fail "No exception raised?!"
1275
- end
1276
- end
1277
-
1278
- conn.finish if conn
1279
- end
1280
-
1281
- it "handles clearing result in or after set_notice_receiver", :postgresql_90 do
1282
- r = nil
1283
- @conn.set_notice_receiver do |result|
1284
- r = result
1285
- expect( r.cleared? ).to eq(false)
1286
- end
1287
- @conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
1288
- sleep 0.2
1289
- expect( r ).to be_a( PG::Result )
1290
- expect( r.cleared? ).to eq(true)
1291
- expect( r.autoclear? ).to eq(true)
1292
- r.clear
1293
- @conn.set_notice_receiver
1294
- end
1295
-
1296
- it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1297
- [:receiver, :processor].each do |kind|
1298
- notices = []
1299
- @conn.internal_encoding = 'utf-8'
1300
- if kind == :processor
1301
- @conn.set_notice_processor do |msg|
1302
- notices << msg
1303
- end
1304
- else
1305
- @conn.set_notice_receiver do |result|
1306
- notices << result.error_message
1307
- end
1308
- end
1309
-
1310
- 3.times do
1311
- @conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
1312
- end
1313
-
1314
- expect( notices.length ).to eq( 3 )
1315
- notices.each do |notice|
1316
- expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
1317
- expect( notice.encoding ).to eq( Encoding::UTF_8 )
1318
- end
1319
- @conn.set_notice_receiver
1320
- @conn.set_notice_processor
1321
- end
1322
- end
1323
-
1324
- it "receives properly encoded text from wait_for_notify", :postgresql_90 do
1325
- @conn.internal_encoding = 'utf-8'
1326
- @conn.exec( 'ROLLBACK' )
1327
- @conn.exec( 'LISTEN "Möhre"' )
1328
- @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1329
- event, pid, msg = nil
1330
- @conn.wait_for_notify( 10 ) do |*args|
1331
- event, pid, msg = *args
1332
- end
1333
- @conn.exec( 'UNLISTEN "Möhre"' )
1334
-
1335
- expect( event ).to eq( "Möhre" )
1336
- expect( event.encoding ).to eq( Encoding::UTF_8 )
1337
- expect( msg ).to eq( '世界線航跡蔵' )
1338
- expect( msg.encoding ).to eq( Encoding::UTF_8 )
1339
- end
1340
-
1341
- it "returns properly encoded text from notifies", :postgresql_90 do
1342
- @conn.internal_encoding = 'utf-8'
1343
- @conn.exec( 'ROLLBACK' )
1344
- @conn.exec( 'LISTEN "Möhre"' )
1345
- @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1346
- @conn.exec( 'UNLISTEN "Möhre"' )
1347
-
1348
- notification = @conn.notifies
1349
- expect( notification[:relname] ).to eq( "Möhre" )
1350
- expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
1351
- expect( notification[:extra] ).to eq( '世界線航跡蔵' )
1352
- expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
1353
- expect( notification[:be_pid] ).to be > 0
1354
- end
1355
- end
1356
-
1357
- context "OS thread support", :ruby_19 do
1358
- it "Connection#exec shouldn't block a second thread" do
1359
- t = Thread.new do
1360
- @conn.exec( "select pg_sleep(1)" )
1361
- end
1362
-
1363
- sleep 0.5
1364
- expect( t ).to be_alive()
1365
- t.join
1366
- end
1367
-
1368
- it "Connection.new shouldn't block a second thread" do
1369
- serv = nil
1370
- t = Thread.new do
1371
- serv = TCPServer.new( '127.0.0.1', 54320 )
1372
- expect {
1373
- described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
1374
- }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
1375
- end
1376
-
1377
- sleep 0.5
1378
- expect( t ).to be_alive()
1379
- serv.close
1380
- t.join
1381
- end
1382
- end
1383
-
1384
- describe "type casting" do
1385
- it "should raise an error on invalid param mapping" do
1386
- expect{
1387
- @conn.exec_params( "SELECT 1", [], nil, :invalid )
1388
- }.to raise_error(TypeError)
1389
- end
1390
-
1391
- it "should return nil if no type mapping is set" do
1392
- expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
1393
- expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
1394
- end
1395
-
1396
- it "shouldn't type map params unless requested" do
1397
- expect{
1398
- @conn.exec_params( "SELECT $1", [5] )
1399
- }.to raise_error(PG::IndeterminateDatatype)
1400
- end
1401
-
1402
- it "should raise an error on invalid encoder to put_copy_data" do
1403
- expect{
1404
- @conn.put_copy_data [1], :invalid
1405
- }.to raise_error(TypeError)
1406
- end
1407
-
1408
- it "can type cast parameters to put_copy_data with explicit encoder" do
1409
- tm = PG::TypeMapByColumn.new [nil]
1410
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1411
-
1412
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1413
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1414
- @conn.put_copy_data [1], row_encoder
1415
- @conn.put_copy_data ["2"], row_encoder
1416
- end
1417
-
1418
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1419
- @conn.put_copy_data [3]
1420
- @conn.put_copy_data ["4"]
1421
- end
1422
-
1423
- res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
1424
- expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
1425
- end
1426
-
1427
- context "with default query type map" do
1428
- before :each do
1429
- @conn2 = described_class.new(@conninfo)
1430
- tm = PG::TypeMapByClass.new
1431
- tm[Integer] = PG::TextEncoder::Integer.new oid: 20
1432
- @conn2.type_map_for_queries = tm
1433
-
1434
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1435
- @conn2.encoder_for_put_copy_data = row_encoder
1436
- end
1437
- after :each do
1438
- @conn2.close
1439
- end
1440
-
1441
- it "should respect a type mapping for params and it's OID and format code" do
1442
- res = @conn2.exec_params( "SELECT $1", [5] )
1443
- expect( res.values ).to eq( [["5"]] )
1444
- expect( res.ftype(0) ).to eq( 20 )
1445
- end
1446
-
1447
- it "should return the current type mapping" do
1448
- expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
1449
- end
1450
-
1451
- it "should work with arbitrary number of params in conjunction with type casting" do
1452
- begin
1453
- 3.step( 12, 0.2 ) do |exp|
1454
- num_params = (2 ** exp).to_i
1455
- sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
1456
- params = num_params.times.to_a
1457
- res = @conn2.exec_params( "SELECT #{sql}", params )
1458
- expect( res.nfields ).to eq( num_params )
1459
- expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
1460
- end
1461
- rescue PG::ProgramLimitExceeded
1462
- # Stop silently as soon the server complains about too many params
1463
- end
1464
- end
1465
-
1466
- it "can process #copy_data input queries with row encoder" do
1467
- @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1468
- res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1469
- @conn2.put_copy_data [1]
1470
- @conn2.put_copy_data ["2"]
1471
- end
1472
-
1473
- res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1474
- expect( res.values ).to eq( [["1"], ["2"]] )
1475
- end
1476
- end
1477
-
1478
- context "with default result type map" do
1479
- before :each do
1480
- @conn2 = described_class.new(@conninfo)
1481
- tm = PG::TypeMapByOid.new
1482
- tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
1483
- @conn2.type_map_for_results = tm
1484
-
1485
- row_decoder = PG::TextDecoder::CopyRow.new
1486
- @conn2.decoder_for_get_copy_data = row_decoder
1487
- end
1488
- after :each do
1489
- @conn2.close
1490
- end
1491
-
1492
- it "should respect a type mapping for result" do
1493
- res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
1494
- expect( res.values ).to eq( [[5]] )
1495
- end
1496
-
1497
- it "should return the current type mapping" do
1498
- expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
1499
- end
1500
-
1501
- it "should work with arbitrary number of params in conjunction with type casting" do
1502
- begin
1503
- 3.step( 12, 0.2 ) do |exp|
1504
- num_params = (2 ** exp).to_i
1505
- sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
1506
- params = num_params.times.to_a
1507
- res = @conn2.exec_params( "SELECT #{sql}", params )
1508
- expect( res.nfields ).to eq( num_params )
1509
- expect( res.values ).to eq( [num_params.times.to_a] )
1510
- end
1511
- rescue PG::ProgramLimitExceeded
1512
- # Stop silently as soon the server complains about too many params
1513
- end
1514
- end
1515
-
1516
- it "can process #copy_data output with row decoder" do
1517
- rows = []
1518
- res2 = @conn2.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
1519
- while row=@conn2.get_copy_data
1520
- rows << row
1521
- end
1522
- end
1523
- expect( rows ).to eq( [["1"], ["2"]] )
1524
- end
1525
-
1526
- it "can type cast #copy_data output with explicit decoder" do
1527
- tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
1528
- row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
1529
- rows = []
1530
- @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
1531
- while row=@conn.get_copy_data
1532
- rows << row
1533
- end
1534
- end
1535
- @conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
1536
- while row=@conn.get_copy_data( false, row_decoder )
1537
- rows << row
1538
- end
1539
- end
1540
- expect( rows ).to eq( [[1], [2], [3], [4]] )
1541
- end
1542
- end
1543
- end
1544
- end