pg 0.18.1 → 1.5.6

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