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