pg 0.18.4 → 1.2.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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/BSDL +2 -2
- data/ChangeLog +0 -5911
- data/History.rdoc +240 -0
- data/Manifest.txt +8 -20
- data/README-Windows.rdoc +4 -4
- data/README.ja.rdoc +1 -2
- data/README.rdoc +64 -15
- data/Rakefile +20 -21
- data/Rakefile.cross +67 -69
- data/ext/errorcodes.def +101 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +33 -2
- data/ext/extconf.rb +26 -36
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +27 -39
- data/ext/pg.c +156 -145
- data/ext/pg.h +74 -98
- data/ext/pg_binary_decoder.c +82 -15
- data/ext/pg_binary_encoder.c +20 -19
- data/ext/pg_coder.c +103 -21
- data/ext/pg_connection.c +917 -523
- data/ext/pg_copy_coder.c +50 -12
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +590 -208
- data/ext/pg_text_decoder.c +606 -40
- data/ext/pg_text_encoder.c +245 -94
- data/ext/pg_tuple.c +549 -0
- data/ext/pg_type_map.c +14 -7
- data/ext/pg_type_map_all_strings.c +4 -4
- data/ext/pg_type_map_by_class.c +9 -4
- data/ext/pg_type_map_by_column.c +7 -6
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +3 -2
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/ext/{util.c → pg_util.c} +10 -10
- data/ext/{util.h → pg_util.h} +2 -2
- data/lib/pg.rb +23 -13
- data/lib/pg/basic_type_mapping.rb +155 -32
- data/lib/pg/binary_decoder.rb +23 -0
- data/lib/pg/coder.rb +23 -2
- data/lib/pg/connection.rb +73 -13
- data/lib/pg/constants.rb +2 -1
- data/lib/pg/exceptions.rb +2 -1
- data/lib/pg/result.rb +24 -7
- data/lib/pg/text_decoder.rb +24 -22
- data/lib/pg/text_encoder.rb +40 -8
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +3 -2
- data/spec/helpers.rb +61 -36
- data/spec/pg/basic_type_mapping_spec.rb +415 -36
- data/spec/pg/connection_spec.rb +732 -327
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +253 -21
- data/spec/pg/tuple_spec.rb +333 -0
- data/spec/pg/type_map_by_class_spec.rb +4 -4
- data/spec/pg/type_map_by_column_spec.rb +6 -2
- data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
- data/spec/pg/type_map_by_oid_spec.rb +3 -3
- data/spec/pg/type_map_in_ruby_spec.rb +1 -1
- data/spec/pg/type_map_spec.rb +1 -1
- data/spec/pg/type_spec.rb +446 -20
- data/spec/pg_spec.rb +2 -2
- metadata +63 -72
- metadata.gz.sig +0 -0
- data/sample/array_insert.rb +0 -20
- data/sample/async_api.rb +0 -106
- data/sample/async_copyto.rb +0 -39
- data/sample/async_mixed.rb +0 -56
- data/sample/check_conn.rb +0 -21
- data/sample/copyfrom.rb +0 -81
- data/sample/copyto.rb +0 -19
- data/sample/cursor.rb +0 -21
- data/sample/disk_usage_report.rb +0 -186
- data/sample/issue-119.rb +0 -94
- data/sample/losample.rb +0 -69
- data/sample/minimal-testcase.rb +0 -17
- data/sample/notify_wait.rb +0 -72
- data/sample/pg_statistics.rb +0 -294
- data/sample/replication_monitor.rb +0 -231
- data/sample/test_binary_values.rb +0 -33
- data/sample/wal_shipper.rb +0 -434
- data/sample/warehouse_partitions.rb +0 -320
data/spec/pg/connection_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- rspec -*-
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
4
|
require_relative '../helpers'
|
@@ -118,6 +118,19 @@ describe PG::Connection do
|
|
118
118
|
expect( described_class.parse_connect_args ).to eq( '' )
|
119
119
|
end
|
120
120
|
|
121
|
+
it "connects successfully with connection string" do
|
122
|
+
conninfo_with_colon_in_password = "host=localhost user=a port=555 dbname=test password=a:a"
|
123
|
+
|
124
|
+
string = described_class.parse_connect_args( conninfo_with_colon_in_password )
|
125
|
+
|
126
|
+
expect( string ).to be_a( String )
|
127
|
+
expect( string ).to match( %r{(^|\s)user=a} )
|
128
|
+
expect( string ).to match( %r{(^|\s)password=a:a} )
|
129
|
+
expect( string ).to match( %r{(^|\s)host=localhost} )
|
130
|
+
expect( string ).to match( %r{(^|\s)port=555} )
|
131
|
+
expect( string ).to match( %r{(^|\s)dbname=test} )
|
132
|
+
end
|
133
|
+
|
121
134
|
it "connects successfully with connection string" do
|
122
135
|
tmpconn = described_class.connect( @conninfo )
|
123
136
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
@@ -139,7 +152,7 @@ describe PG::Connection do
|
|
139
152
|
tmpconn.finish
|
140
153
|
end
|
141
154
|
|
142
|
-
it "connects using a hash of optional connection parameters"
|
155
|
+
it "connects using a hash of optional connection parameters" do
|
143
156
|
tmpconn = described_class.connect(
|
144
157
|
:host => 'localhost',
|
145
158
|
:port => @port,
|
@@ -160,56 +173,94 @@ describe PG::Connection do
|
|
160
173
|
end
|
161
174
|
end
|
162
175
|
|
163
|
-
it "can connect asynchronously"
|
176
|
+
it "can connect asynchronously" do
|
164
177
|
tmpconn = described_class.connect_start( @conninfo )
|
165
178
|
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
179
|
|
180
|
+
wait_for_polling_ok(tmpconn)
|
181
181
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
182
182
|
tmpconn.finish
|
183
183
|
end
|
184
184
|
|
185
|
-
it "can connect asynchronously for the duration of a block"
|
185
|
+
it "can connect asynchronously for the duration of a block" do
|
186
186
|
conn = nil
|
187
187
|
|
188
188
|
described_class.connect_start(@conninfo) do |tmpconn|
|
189
189
|
expect( tmpconn ).to be_a( described_class )
|
190
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
191
|
|
192
|
+
wait_for_polling_ok(tmpconn)
|
207
193
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
208
194
|
end
|
209
195
|
|
210
196
|
expect( conn ).to be_finished()
|
211
197
|
end
|
212
198
|
|
199
|
+
context "with async established connection" do
|
200
|
+
before :each do
|
201
|
+
@conn2 = described_class.connect_start( @conninfo )
|
202
|
+
wait_for_polling_ok(@conn2)
|
203
|
+
expect( @conn2 ).to still_be_usable
|
204
|
+
end
|
205
|
+
|
206
|
+
after :each do
|
207
|
+
expect( @conn2 ).to still_be_usable
|
208
|
+
@conn2.close
|
209
|
+
end
|
210
|
+
|
211
|
+
it "conn.send_query and IO.select work" do
|
212
|
+
@conn2.send_query("SELECT 1")
|
213
|
+
res = wait_for_query_result(@conn2)
|
214
|
+
expect( res.values ).to eq([["1"]])
|
215
|
+
end
|
216
|
+
|
217
|
+
it "conn.send_query and conn.block work" do
|
218
|
+
@conn2.send_query("SELECT 2")
|
219
|
+
@conn2.block
|
220
|
+
res = @conn2.get_last_result
|
221
|
+
expect( res.values ).to eq([["2"]])
|
222
|
+
end
|
223
|
+
|
224
|
+
it "conn.async_query works" do
|
225
|
+
res = @conn2.async_query("SELECT 3")
|
226
|
+
expect( res.values ).to eq([["3"]])
|
227
|
+
expect( @conn2 ).to still_be_usable
|
228
|
+
|
229
|
+
res = @conn2.query("SELECT 4")
|
230
|
+
end
|
231
|
+
|
232
|
+
it "can use conn.reset_start to restart the connection" do
|
233
|
+
ios = IO.pipe
|
234
|
+
conn = described_class.connect_start( @conninfo )
|
235
|
+
wait_for_polling_ok(conn)
|
236
|
+
|
237
|
+
# Close the two pipe file descriptors, so that the file descriptor of
|
238
|
+
# newly established connection is probably distinct from the previous one.
|
239
|
+
ios.each(&:close)
|
240
|
+
conn.reset_start
|
241
|
+
wait_for_polling_ok(conn, :reset_poll)
|
242
|
+
|
243
|
+
# The new connection should work even when the file descriptor has changed.
|
244
|
+
conn.send_query("SELECT 1")
|
245
|
+
res = wait_for_query_result(conn)
|
246
|
+
expect( res.values ).to eq([["1"]])
|
247
|
+
|
248
|
+
conn.close
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should properly close a socket IO when GC'ed" do
|
252
|
+
# This results in
|
253
|
+
# Errno::ENOTSOCK: An operation was attempted on something that is not a socket.
|
254
|
+
# on Windows when rb_w32_unwrap_io_handle() isn't called in pgconn_gc_free().
|
255
|
+
5.times do
|
256
|
+
conn = described_class.connect( @conninfo )
|
257
|
+
conn.socket_io.close
|
258
|
+
end
|
259
|
+
GC.start
|
260
|
+
IO.pipe.each(&:close)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
213
264
|
it "raises proper error when sending fails" do
|
214
265
|
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
215
266
|
expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
|
@@ -219,7 +270,7 @@ describe PG::Connection do
|
|
219
270
|
described_class.connect(@conninfo).finish
|
220
271
|
sleep 0.5
|
221
272
|
res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
|
222
|
-
WHERE usename IS NOT NULL])
|
273
|
+
WHERE usename IS NOT NULL AND application_name != ''])
|
223
274
|
# there's still the global @conn, but should be no more
|
224
275
|
expect( res[0]['n'] ).to eq( '1' )
|
225
276
|
end
|
@@ -228,7 +279,7 @@ describe PG::Connection do
|
|
228
279
|
expect( @conn.db ).to eq( "test" )
|
229
280
|
expect( @conn.user ).to be_a_kind_of( String )
|
230
281
|
expect( @conn.pass ).to eq( "" )
|
231
|
-
expect( @conn.port ).to eq(
|
282
|
+
expect( @conn.port ).to eq( @port )
|
232
283
|
expect( @conn.tty ).to eq( "" )
|
233
284
|
expect( @conn.options ).to eq( "" )
|
234
285
|
end
|
@@ -237,7 +288,20 @@ describe PG::Connection do
|
|
237
288
|
expect( @conn.host ).to eq( "localhost" )
|
238
289
|
end
|
239
290
|
|
240
|
-
|
291
|
+
it "can set error verbosity" do
|
292
|
+
old = @conn.set_error_verbosity( PG::PQERRORS_TERSE )
|
293
|
+
new = @conn.set_error_verbosity( old )
|
294
|
+
expect( new ).to eq( PG::PQERRORS_TERSE )
|
295
|
+
end
|
296
|
+
|
297
|
+
it "can set error context visibility", :postgresql_96 do
|
298
|
+
old = @conn.set_error_context_visibility( PG::PQSHOW_CONTEXT_NEVER )
|
299
|
+
new = @conn.set_error_context_visibility( old )
|
300
|
+
expect( new ).to eq( PG::PQSHOW_CONTEXT_NEVER )
|
301
|
+
end
|
302
|
+
|
303
|
+
let(:expected_trace_output) do
|
304
|
+
%{
|
241
305
|
To backend> Msg Q
|
242
306
|
To backend> "SELECT 1 AS one"
|
243
307
|
To backend> Msg complete, length 21
|
@@ -265,6 +329,7 @@ describe PG::Connection do
|
|
265
329
|
From backend (#4)> 5
|
266
330
|
From backend> T
|
267
331
|
}.gsub( /^\t{2}/, '' ).lstrip
|
332
|
+
end
|
268
333
|
|
269
334
|
it "trace and untrace client-server communication", :unix do
|
270
335
|
# be careful to explicitly close files so that the
|
@@ -275,23 +340,20 @@ describe PG::Connection do
|
|
275
340
|
@conn.trace( trace_io )
|
276
341
|
trace_io.close
|
277
342
|
|
278
|
-
|
343
|
+
@conn.exec("SELECT 1 AS one")
|
279
344
|
@conn.untrace
|
280
345
|
|
281
|
-
|
346
|
+
@conn.exec("SELECT 2 AS two")
|
282
347
|
|
283
348
|
trace_data = trace_file.read
|
284
349
|
|
285
|
-
|
286
|
-
#
|
287
|
-
#
|
288
|
-
#
|
289
|
-
# +From backend (#4)>
|
290
|
-
#
|
291
|
-
|
292
|
-
expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
|
293
|
-
expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
|
294
|
-
end
|
350
|
+
# For async_exec the output will be different:
|
351
|
+
# From backend> Z
|
352
|
+
# From backend (#4)> 5
|
353
|
+
# +From backend> Z
|
354
|
+
# +From backend (#4)> 5
|
355
|
+
# From backend> T
|
356
|
+
trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
|
295
357
|
|
296
358
|
expect( trace_data ).to eq( expected_trace_output )
|
297
359
|
end
|
@@ -308,8 +370,6 @@ describe PG::Connection do
|
|
308
370
|
end
|
309
371
|
|
310
372
|
it "can stop a thread that runs a blocking query with async_exec" do
|
311
|
-
pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
|
312
|
-
|
313
373
|
start = Time.now
|
314
374
|
t = Thread.new do
|
315
375
|
@conn.async_exec( 'select pg_sleep(10)' )
|
@@ -323,24 +383,16 @@ describe PG::Connection do
|
|
323
383
|
|
324
384
|
it "should work together with signal handlers", :unix do
|
325
385
|
signal_received = false
|
326
|
-
trap '
|
386
|
+
trap 'USR2' do
|
327
387
|
signal_received = true
|
328
388
|
end
|
329
389
|
|
330
390
|
Thread.new do
|
331
391
|
sleep 0.1
|
332
|
-
Process.kill("
|
392
|
+
Process.kill("USR2", Process.pid)
|
333
393
|
end
|
334
394
|
@conn.exec("select pg_sleep(0.3)")
|
335
395
|
expect( signal_received ).to be_truthy
|
336
|
-
|
337
|
-
signal_received = false
|
338
|
-
Thread.new do
|
339
|
-
sleep 0.1
|
340
|
-
Process.kill("USR1", Process.pid)
|
341
|
-
end
|
342
|
-
@conn.async_exec("select pg_sleep(0.3)")
|
343
|
-
expect( signal_received ).to be_truthy
|
344
396
|
end
|
345
397
|
|
346
398
|
|
@@ -388,22 +440,11 @@ describe PG::Connection do
|
|
388
440
|
end
|
389
441
|
end
|
390
442
|
|
391
|
-
|
392
|
-
it "supports parameters passed to #exec (backward compatibility)" do
|
393
|
-
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
394
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
|
395
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
|
396
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
397
|
-
|
398
|
-
res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
|
399
|
-
expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
|
400
|
-
end
|
401
|
-
|
402
443
|
it "supports explicitly calling #exec_params" do
|
403
444
|
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
404
|
-
@conn.
|
405
|
-
@conn.
|
406
|
-
@conn.
|
445
|
+
@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
|
446
|
+
@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
|
447
|
+
@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
407
448
|
|
408
449
|
res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
|
409
450
|
expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
|
@@ -524,7 +565,7 @@ describe PG::Connection do
|
|
524
565
|
@conn.exec( 'UNLISTEN woo' )
|
525
566
|
end
|
526
567
|
|
527
|
-
it "can receive notices while waiting for NOTIFY without exceeding the timeout"
|
568
|
+
it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
|
528
569
|
notices = []
|
529
570
|
@conn.set_notice_processor do |msg|
|
530
571
|
notices << [msg, Time.now]
|
@@ -534,7 +575,7 @@ describe PG::Connection do
|
|
534
575
|
expect( @conn.wait_for_notify( 1 ) ).to be_nil
|
535
576
|
expect( notices.first ).to_not be_nil
|
536
577
|
et = Time.now
|
537
|
-
expect( (et - notices.first[1]) ).to be >= 0.
|
578
|
+
expect( (et - notices.first[1]) ).to be >= 0.3
|
538
579
|
expect( (et - st) ).to be >= 0.9
|
539
580
|
expect( (et - st) ).to be < 1.4
|
540
581
|
end
|
@@ -586,7 +627,7 @@ describe PG::Connection do
|
|
586
627
|
expect( @conn ).to still_be_usable
|
587
628
|
end
|
588
629
|
|
589
|
-
it "can handle server errors in #copy_data for output"
|
630
|
+
it "can handle server errors in #copy_data for output" do
|
590
631
|
@conn.exec "ROLLBACK"
|
591
632
|
@conn.transaction do
|
592
633
|
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
@@ -638,7 +679,32 @@ describe PG::Connection do
|
|
638
679
|
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
639
680
|
@conn.put_copy_data "xyz\n"
|
640
681
|
end
|
641
|
-
}.to raise_error(PG::Error, /invalid input syntax for integer/)
|
682
|
+
}.to raise_error(PG::Error, /invalid input syntax for .*integer/)
|
683
|
+
end
|
684
|
+
expect( @conn ).to still_be_usable
|
685
|
+
end
|
686
|
+
|
687
|
+
it "gracefully handle SQL statements while in #copy_data for input" do
|
688
|
+
@conn.exec "ROLLBACK"
|
689
|
+
@conn.transaction do
|
690
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
|
691
|
+
expect {
|
692
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
693
|
+
@conn.exec "SELECT 1"
|
694
|
+
end
|
695
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
696
|
+
end
|
697
|
+
expect( @conn ).to still_be_usable
|
698
|
+
end
|
699
|
+
|
700
|
+
it "gracefully handle SQL statements while in #copy_data for output" do
|
701
|
+
@conn.exec "ROLLBACK"
|
702
|
+
@conn.transaction do
|
703
|
+
expect {
|
704
|
+
@conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
|
705
|
+
@conn.exec "SELECT 3"
|
706
|
+
end
|
707
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
642
708
|
end
|
643
709
|
expect( @conn ).to still_be_usable
|
644
710
|
end
|
@@ -684,18 +750,13 @@ describe PG::Connection do
|
|
684
750
|
end
|
685
751
|
|
686
752
|
it "described_class#block should allow a timeout" do
|
687
|
-
@conn.send_query( "select pg_sleep(
|
753
|
+
@conn.send_query( "select pg_sleep(1)" )
|
688
754
|
|
689
755
|
start = Time.now
|
690
|
-
@conn.block( 0.
|
756
|
+
@conn.block( 0.3 )
|
691
757
|
finish = Time.now
|
692
758
|
|
693
|
-
expect( (finish - start) ).to be_within( 0.
|
694
|
-
end
|
695
|
-
|
696
|
-
|
697
|
-
it "can encrypt a string given a password and username" do
|
698
|
-
expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
759
|
+
expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
|
699
760
|
end
|
700
761
|
|
701
762
|
it "can return the default connection options" do
|
@@ -708,7 +769,7 @@ describe PG::Connection do
|
|
708
769
|
it "can return the default connection options as a Hash" do
|
709
770
|
expect( described_class.conndefaults_hash ).to be_a( Hash )
|
710
771
|
expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
|
711
|
-
expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
|
772
|
+
expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
|
712
773
|
expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
|
713
774
|
end
|
714
775
|
|
@@ -725,6 +786,25 @@ describe PG::Connection do
|
|
725
786
|
expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
|
726
787
|
end
|
727
788
|
|
789
|
+
describe "connection information related to SSL" do
|
790
|
+
|
791
|
+
it "can retrieve connection's ssl state", :postgresql_95 do
|
792
|
+
expect( @conn.ssl_in_use? ).to be false
|
793
|
+
end
|
794
|
+
|
795
|
+
it "can retrieve connection's ssl attribute_names", :postgresql_95 do
|
796
|
+
expect( @conn.ssl_attribute_names ).to be_a(Array)
|
797
|
+
end
|
798
|
+
|
799
|
+
it "can retrieve a single ssl connection attribute", :postgresql_95 do
|
800
|
+
expect( @conn.ssl_attribute('dbname') ).to eq( nil )
|
801
|
+
end
|
802
|
+
|
803
|
+
it "can retrieve all connection's ssl attributes", :postgresql_95 do
|
804
|
+
expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
728
808
|
|
729
809
|
it "honors the connect_timeout connection parameter", :postgresql_93 do
|
730
810
|
conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
|
@@ -735,18 +815,52 @@ describe PG::Connection do
|
|
735
815
|
end
|
736
816
|
end
|
737
817
|
|
818
|
+
describe "deprecated password encryption method" do
|
819
|
+
it "can encrypt password for a given user" do
|
820
|
+
expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
821
|
+
end
|
738
822
|
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
823
|
+
it "raises an appropriate error if either of the required arguments is not valid" do
|
824
|
+
expect {
|
825
|
+
described_class.encrypt_password( nil, nil )
|
826
|
+
}.to raise_error( TypeError )
|
827
|
+
expect {
|
828
|
+
described_class.encrypt_password( "postgres", nil )
|
829
|
+
}.to raise_error( TypeError )
|
830
|
+
expect {
|
831
|
+
described_class.encrypt_password( nil, "postgres" )
|
832
|
+
}.to raise_error( TypeError )
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
describe "password encryption method", :postgresql_10 do
|
837
|
+
it "can encrypt without algorithm" do
|
838
|
+
expect( @conn.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
839
|
+
expect( @conn.encrypt_password("postgres", "postgres", nil) ).to match( /\S+/ )
|
840
|
+
end
|
841
|
+
|
842
|
+
it "can encrypt with algorithm" do
|
843
|
+
expect( @conn.encrypt_password("postgres", "postgres", "md5") ).to match( /md5\S+/i )
|
844
|
+
expect( @conn.encrypt_password("postgres", "postgres", "scram-sha-256") ).to match( /SCRAM-SHA-256\S+/i )
|
845
|
+
end
|
846
|
+
|
847
|
+
it "raises an appropriate error if either of the required arguments is not valid" do
|
848
|
+
expect {
|
849
|
+
@conn.encrypt_password( nil, nil )
|
850
|
+
}.to raise_error( TypeError )
|
851
|
+
expect {
|
852
|
+
@conn.encrypt_password( "postgres", nil )
|
853
|
+
}.to raise_error( TypeError )
|
854
|
+
expect {
|
855
|
+
@conn.encrypt_password( nil, "postgres" )
|
856
|
+
}.to raise_error( TypeError )
|
857
|
+
expect {
|
858
|
+
@conn.encrypt_password( "postgres", "postgres", :invalid )
|
859
|
+
}.to raise_error( TypeError )
|
860
|
+
expect {
|
861
|
+
@conn.encrypt_password( "postgres", "postgres", "invalid" )
|
862
|
+
}.to raise_error( PG::Error, /unrecognized/ )
|
863
|
+
end
|
750
864
|
end
|
751
865
|
|
752
866
|
|
@@ -788,7 +902,7 @@ describe PG::Connection do
|
|
788
902
|
end
|
789
903
|
|
790
904
|
|
791
|
-
it "
|
905
|
+
it "handles server close while asynchronous connect" do
|
792
906
|
serv = TCPServer.new( '127.0.0.1', 54320 )
|
793
907
|
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
794
908
|
expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
|
@@ -800,11 +914,29 @@ describe PG::Connection do
|
|
800
914
|
expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
|
801
915
|
end
|
802
916
|
|
803
|
-
it "discards previous results
|
917
|
+
it "discards previous results at #discard_results" do
|
918
|
+
@conn.send_query( "select 1" )
|
919
|
+
@conn.discard_results
|
920
|
+
@conn.send_query( "select 41 as one" )
|
921
|
+
res = @conn.get_last_result
|
922
|
+
expect( res.to_a ).to eq( [{ 'one' => '41' }] )
|
923
|
+
end
|
924
|
+
|
925
|
+
it "discards previous results (if any) before waiting on #exec" do
|
926
|
+
@conn.send_query( "select 1" )
|
927
|
+
res = @conn.exec( "select 42 as one" )
|
928
|
+
expect( res.to_a ).to eq( [{ 'one' => '42' }] )
|
929
|
+
end
|
930
|
+
|
931
|
+
it "discards previous errors before waiting on #exec", :without_transaction do
|
932
|
+
@conn.send_query( "ERROR" )
|
933
|
+
res = @conn.exec( "select 43 as one" )
|
934
|
+
expect( res.to_a ).to eq( [{ 'one' => '43' }] )
|
935
|
+
end
|
804
936
|
|
805
|
-
it "calls the block if one is provided to #
|
937
|
+
it "calls the block if one is provided to #exec" do
|
806
938
|
result = nil
|
807
|
-
@conn.
|
939
|
+
@conn.exec( "select 47 as one" ) do |pg_res|
|
808
940
|
result = pg_res[0]
|
809
941
|
end
|
810
942
|
expect( result ).to eq( { 'one' => '47' } )
|
@@ -817,7 +949,21 @@ describe PG::Connection do
|
|
817
949
|
expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
818
950
|
end
|
819
951
|
|
820
|
-
it "
|
952
|
+
it "can use conn.reset to restart the connection" do
|
953
|
+
ios = IO.pipe
|
954
|
+
conn = PG.connect( @conninfo )
|
955
|
+
|
956
|
+
# Close the two pipe file descriptors, so that the file descriptor of
|
957
|
+
# newly established connection is probably distinct from the previous one.
|
958
|
+
ios.each(&:close)
|
959
|
+
conn.reset
|
960
|
+
|
961
|
+
# The new connection should work even when the file descriptor has changed.
|
962
|
+
expect( conn.exec("SELECT 1").values ).to eq([["1"]])
|
963
|
+
conn.close
|
964
|
+
end
|
965
|
+
|
966
|
+
it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction do
|
821
967
|
conn = PG.connect( @conninfo )
|
822
968
|
io = conn.socket_io
|
823
969
|
conn.finish
|
@@ -825,7 +971,7 @@ describe PG::Connection do
|
|
825
971
|
expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
826
972
|
end
|
827
973
|
|
828
|
-
it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction
|
974
|
+
it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction do
|
829
975
|
conn = PG.connect( @conninfo )
|
830
976
|
io = conn.socket_io
|
831
977
|
conn.reset
|
@@ -845,155 +991,147 @@ describe PG::Connection do
|
|
845
991
|
expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
|
846
992
|
end
|
847
993
|
|
848
|
-
|
994
|
+
it "sets the fallback_application_name on new connections" do
|
995
|
+
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
849
996
|
|
850
|
-
|
851
|
-
|
852
|
-
|
997
|
+
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
998
|
+
expect( conn_name ).to include( $0[0..10] )
|
999
|
+
expect( conn_name ).to include( $0[-10..-1] )
|
1000
|
+
expect( conn_name.length ).to be <= 64
|
1001
|
+
end
|
853
1002
|
|
854
|
-
|
1003
|
+
it "sets a shortened fallback_application_name on new connections" do
|
1004
|
+
old_0 = $0
|
1005
|
+
begin
|
1006
|
+
$0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
|
855
1007
|
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
856
|
-
|
857
1008
|
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
858
1009
|
expect( conn_name ).to include( $0[0..10] )
|
859
1010
|
expect( conn_name ).to include( $0[-10..-1] )
|
860
1011
|
expect( conn_name.length ).to be <= 64
|
1012
|
+
ensure
|
1013
|
+
$0 = old_0
|
861
1014
|
end
|
1015
|
+
end
|
862
1016
|
|
863
|
-
|
864
|
-
|
865
|
-
begin
|
866
|
-
$0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
|
867
|
-
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
868
|
-
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
869
|
-
expect( conn_name ).to include( $0[0..10] )
|
870
|
-
expect( conn_name ).to include( $0[-10..-1] )
|
871
|
-
expect( conn_name.length ).to be <= 64
|
872
|
-
ensure
|
873
|
-
$0 = old_0
|
874
|
-
end
|
875
|
-
end
|
876
|
-
|
877
|
-
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
878
|
-
"any number of arguments" do
|
879
|
-
|
880
|
-
@conn.exec( 'ROLLBACK' )
|
881
|
-
@conn.exec( 'LISTEN knees' )
|
1017
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1018
|
+
"any number of arguments" do
|
882
1019
|
|
883
|
-
|
884
|
-
|
885
|
-
conn.finish
|
1020
|
+
@conn.exec( 'ROLLBACK' )
|
1021
|
+
@conn.exec( 'LISTEN knees' )
|
886
1022
|
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
end
|
891
|
-
@conn.exec( 'UNLISTEN knees' )
|
1023
|
+
conn = described_class.connect( @conninfo )
|
1024
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1025
|
+
conn.finish
|
892
1026
|
|
893
|
-
|
894
|
-
|
895
|
-
|
1027
|
+
event, pid, msg = nil
|
1028
|
+
@conn.wait_for_notify( 10 ) do |*args|
|
1029
|
+
event, pid, msg = *args
|
896
1030
|
end
|
1031
|
+
@conn.exec( 'UNLISTEN knees' )
|
897
1032
|
|
898
|
-
|
899
|
-
|
900
|
-
|
1033
|
+
expect( event ).to eq( 'knees' )
|
1034
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1035
|
+
expect( msg ).to eq( 'skirt and boots' )
|
1036
|
+
end
|
901
1037
|
|
902
|
-
|
903
|
-
|
904
|
-
|
1038
|
+
it "accepts nil as the timeout in #wait_for_notify " do
|
1039
|
+
@conn.exec( 'ROLLBACK' )
|
1040
|
+
@conn.exec( 'LISTEN knees' )
|
905
1041
|
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
end
|
910
|
-
@conn.exec( 'UNLISTEN knees' )
|
1042
|
+
conn = described_class.connect( @conninfo )
|
1043
|
+
conn.exec( %Q{NOTIFY knees} )
|
1044
|
+
conn.finish
|
911
1045
|
|
912
|
-
|
913
|
-
|
1046
|
+
event, pid = nil
|
1047
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1048
|
+
event, pid = *args
|
914
1049
|
end
|
1050
|
+
@conn.exec( 'UNLISTEN knees' )
|
915
1051
|
|
916
|
-
|
917
|
-
|
918
|
-
|
1052
|
+
expect( event ).to eq( 'knees' )
|
1053
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1054
|
+
end
|
919
1055
|
|
920
|
-
|
921
|
-
|
922
|
-
|
1056
|
+
it "sends nil as the payload if the notification wasn't given one" do
|
1057
|
+
@conn.exec( 'ROLLBACK' )
|
1058
|
+
@conn.exec( 'LISTEN knees' )
|
923
1059
|
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
end
|
928
|
-
@conn.exec( 'UNLISTEN knees' )
|
1060
|
+
conn = described_class.connect( @conninfo )
|
1061
|
+
conn.exec( %Q{NOTIFY knees} )
|
1062
|
+
conn.finish
|
929
1063
|
|
930
|
-
|
1064
|
+
payload = :notnil
|
1065
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1066
|
+
payload = args[ 2 ]
|
931
1067
|
end
|
1068
|
+
@conn.exec( 'UNLISTEN knees' )
|
932
1069
|
|
933
|
-
|
934
|
-
|
1070
|
+
expect( payload ).to be_nil()
|
1071
|
+
end
|
935
1072
|
|
936
|
-
|
937
|
-
|
1073
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1074
|
+
"two arguments" do
|
938
1075
|
|
939
|
-
|
940
|
-
|
941
|
-
conn.finish
|
1076
|
+
@conn.exec( 'ROLLBACK' )
|
1077
|
+
@conn.exec( 'LISTEN knees' )
|
942
1078
|
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
end
|
947
|
-
@conn.exec( 'UNLISTEN knees' )
|
1079
|
+
conn = described_class.connect( @conninfo )
|
1080
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1081
|
+
conn.finish
|
948
1082
|
|
949
|
-
|
950
|
-
|
951
|
-
|
1083
|
+
event, pid, msg = nil
|
1084
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2|
|
1085
|
+
event, pid, msg = arg1, arg2
|
952
1086
|
end
|
1087
|
+
@conn.exec( 'UNLISTEN knees' )
|
953
1088
|
|
954
|
-
|
955
|
-
|
1089
|
+
expect( event ).to eq( 'knees' )
|
1090
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1091
|
+
expect( msg ).to be_nil()
|
1092
|
+
end
|
956
1093
|
|
957
|
-
|
958
|
-
|
1094
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it " +
|
1095
|
+
"doesn't accept arguments" do
|
959
1096
|
|
960
|
-
|
961
|
-
|
962
|
-
conn.finish
|
1097
|
+
@conn.exec( 'ROLLBACK' )
|
1098
|
+
@conn.exec( 'LISTEN knees' )
|
963
1099
|
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
end
|
968
|
-
@conn.exec( 'UNLISTEN knees' )
|
1100
|
+
conn = described_class.connect( @conninfo )
|
1101
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1102
|
+
conn.finish
|
969
1103
|
|
970
|
-
|
1104
|
+
notification_received = false
|
1105
|
+
@conn.wait_for_notify( 10 ) do
|
1106
|
+
notification_received = true
|
971
1107
|
end
|
1108
|
+
@conn.exec( 'UNLISTEN knees' )
|
972
1109
|
|
973
|
-
|
974
|
-
|
1110
|
+
expect( notification_received ).to be_truthy()
|
1111
|
+
end
|
975
1112
|
|
976
|
-
|
977
|
-
|
1113
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1114
|
+
"three arguments" do
|
978
1115
|
|
979
|
-
|
980
|
-
|
981
|
-
conn.finish
|
1116
|
+
@conn.exec( 'ROLLBACK' )
|
1117
|
+
@conn.exec( 'LISTEN knees' )
|
982
1118
|
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
end
|
987
|
-
@conn.exec( 'UNLISTEN knees' )
|
1119
|
+
conn = described_class.connect( @conninfo )
|
1120
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1121
|
+
conn.finish
|
988
1122
|
|
989
|
-
|
990
|
-
|
991
|
-
|
1123
|
+
event, pid, msg = nil
|
1124
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
|
1125
|
+
event, pid, msg = arg1, arg2, arg3
|
992
1126
|
end
|
1127
|
+
@conn.exec( 'UNLISTEN knees' )
|
993
1128
|
|
1129
|
+
expect( event ).to eq( 'knees' )
|
1130
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1131
|
+
expect( msg ).to eq( 'skirt and boots' )
|
994
1132
|
end
|
995
1133
|
|
996
|
-
context "
|
1134
|
+
context "server ping", :without_transaction do
|
997
1135
|
|
998
1136
|
it "pings successfully with connection string" do
|
999
1137
|
ping = described_class.ping(@conninfo)
|
@@ -1021,125 +1159,119 @@ describe PG::Connection do
|
|
1021
1159
|
expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
|
1022
1160
|
end
|
1023
1161
|
|
1024
|
-
it "returns
|
1162
|
+
it "returns error when ping connection arguments are wrong" do
|
1025
1163
|
ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
|
1026
|
-
expect( ping ).
|
1164
|
+
expect( ping ).to_not eq( PG::PQPING_OK )
|
1027
1165
|
end
|
1028
1166
|
|
1167
|
+
it "returns correct response when ping connection arguments are wrong" do
|
1168
|
+
ping = described_class.ping(
|
1169
|
+
:host => 'localhost',
|
1170
|
+
:invalid_option => 9999,
|
1171
|
+
:dbname => :test)
|
1172
|
+
expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
|
1173
|
+
end
|
1029
1174
|
|
1030
1175
|
end
|
1031
1176
|
|
1032
|
-
|
1033
|
-
describe "set_single_row_mode" do
|
1177
|
+
describe "set_single_row_mode" do
|
1034
1178
|
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1179
|
+
it "raises an error when called at the wrong time" do
|
1180
|
+
expect {
|
1181
|
+
@conn.set_single_row_mode
|
1182
|
+
}.to raise_error(PG::Error)
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
it "should work in single row mode" do
|
1186
|
+
@conn.send_query( "SELECT generate_series(1,10)" )
|
1187
|
+
@conn.set_single_row_mode
|
1188
|
+
|
1189
|
+
results = []
|
1190
|
+
loop do
|
1191
|
+
@conn.block
|
1192
|
+
res = @conn.get_result or break
|
1193
|
+
results << res
|
1039
1194
|
end
|
1195
|
+
expect( results.length ).to eq( 11 )
|
1196
|
+
results[0..-2].each do |res|
|
1197
|
+
expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1198
|
+
values = res.field_values('generate_series')
|
1199
|
+
expect( values.length ).to eq( 1 )
|
1200
|
+
expect( values.first.to_i ).to be > 0
|
1201
|
+
end
|
1202
|
+
expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
|
1203
|
+
expect( results.last.ntuples ).to eq( 0 )
|
1204
|
+
end
|
1040
1205
|
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1206
|
+
it "should receive rows before entire query is finished" do
|
1207
|
+
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
|
1208
|
+
@conn.set_single_row_mode
|
1044
1209
|
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
expect( results.length ).to eq( 11 )
|
1052
|
-
results[0..-2].each do |res|
|
1053
|
-
expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1054
|
-
values = res.field_values('generate_series')
|
1055
|
-
expect( values.length ).to eq( 1 )
|
1056
|
-
expect( values.first.to_i ).to be > 0
|
1057
|
-
end
|
1058
|
-
expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
|
1059
|
-
expect( results.last.ntuples ).to eq( 0 )
|
1210
|
+
start_time = Time.now
|
1211
|
+
first_row_time = nil
|
1212
|
+
loop do
|
1213
|
+
res = @conn.get_result or break
|
1214
|
+
res.check
|
1215
|
+
first_row_time = Time.now unless first_row_time
|
1060
1216
|
end
|
1217
|
+
expect( (Time.now - start_time) ).to be >= 0.9
|
1218
|
+
expect( (first_row_time - start_time) ).to be < 0.9
|
1219
|
+
end
|
1061
1220
|
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1221
|
+
it "should receive rows before entire query fails" do
|
1222
|
+
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
1223
|
+
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
|
1224
|
+
@conn.set_single_row_mode
|
1065
1225
|
|
1066
|
-
|
1067
|
-
|
1226
|
+
first_result = nil
|
1227
|
+
expect do
|
1068
1228
|
loop do
|
1069
1229
|
res = @conn.get_result or break
|
1070
1230
|
res.check
|
1071
|
-
|
1231
|
+
first_result ||= res
|
1072
1232
|
end
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
it "should receive rows before entire query fails" do
|
1078
|
-
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
1079
|
-
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
|
1080
|
-
@conn.set_single_row_mode
|
1081
|
-
|
1082
|
-
first_result = nil
|
1083
|
-
expect do
|
1084
|
-
loop do
|
1085
|
-
res = @conn.get_result or break
|
1086
|
-
res.check
|
1087
|
-
first_result ||= res
|
1088
|
-
end
|
1089
|
-
end.to raise_error(PG::Error)
|
1090
|
-
expect( first_result.kind_of?(PG::Result) ).to be_truthy
|
1091
|
-
expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1092
|
-
end
|
1233
|
+
end.to raise_error(PG::Error)
|
1234
|
+
expect( first_result.kind_of?(PG::Result) ).to be_truthy
|
1235
|
+
expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1093
1236
|
end
|
1237
|
+
|
1094
1238
|
end
|
1095
1239
|
|
1096
|
-
context "multinationalization support"
|
1240
|
+
context "multinationalization support" do
|
1097
1241
|
|
1098
1242
|
describe "rubyforge #22925: m17n support" do
|
1099
1243
|
it "should return results in the same encoding as the client (iso-8859-1)" do
|
1100
|
-
|
1101
|
-
@conn.
|
1102
|
-
|
1103
|
-
res = conn.exec("VALUES ('fantasia')", [], 0)
|
1104
|
-
out_string = res[0]['column1']
|
1105
|
-
end
|
1244
|
+
@conn.internal_encoding = 'iso8859-1'
|
1245
|
+
res = @conn.exec_params("VALUES ('fantasia')", [], 0)
|
1246
|
+
out_string = res[0]['column1']
|
1106
1247
|
expect( out_string ).to eq( 'fantasia' )
|
1107
1248
|
expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
|
1108
1249
|
end
|
1109
1250
|
|
1110
1251
|
it "should return results in the same encoding as the client (utf-8)" do
|
1111
|
-
|
1112
|
-
@conn.
|
1113
|
-
|
1114
|
-
res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
|
1115
|
-
out_string = res[0]['column1']
|
1116
|
-
end
|
1252
|
+
@conn.internal_encoding = 'utf-8'
|
1253
|
+
res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
|
1254
|
+
out_string = res[0]['column1']
|
1117
1255
|
expect( out_string ).to eq( '世界線航跡蔵' )
|
1118
1256
|
expect( out_string.encoding ).to eq( Encoding::UTF_8 )
|
1119
1257
|
end
|
1120
1258
|
|
1121
1259
|
it "should return results in the same encoding as the client (EUC-JP)" do
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
res = conn.exec(stmt, [], 0)
|
1127
|
-
out_string = res[0]['column1']
|
1128
|
-
end
|
1260
|
+
@conn.internal_encoding = 'EUC-JP'
|
1261
|
+
stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
|
1262
|
+
res = @conn.exec_params(stmt, [], 0)
|
1263
|
+
out_string = res[0]['column1']
|
1129
1264
|
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
1130
1265
|
expect( out_string.encoding ).to eq( Encoding::EUC_JP )
|
1131
1266
|
end
|
1132
1267
|
|
1133
1268
|
it "returns the results in the correct encoding even if the client_encoding has " +
|
1134
1269
|
"changed since the results were fetched" do
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
conn.internal_encoding = 'utf-8'
|
1141
|
-
out_string = res[0]['column1']
|
1142
|
-
end
|
1270
|
+
@conn.internal_encoding = 'EUC-JP'
|
1271
|
+
stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
|
1272
|
+
res = @conn.exec_params(stmt, [], 0)
|
1273
|
+
@conn.internal_encoding = 'utf-8'
|
1274
|
+
out_string = res[0]['column1']
|
1143
1275
|
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
1144
1276
|
expect( out_string.encoding ).to eq( Encoding::EUC_JP )
|
1145
1277
|
end
|
@@ -1149,58 +1281,215 @@ describe PG::Connection do
|
|
1149
1281
|
expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
|
1150
1282
|
end
|
1151
1283
|
|
1284
|
+
it "the connection should use JOHAB dummy encoding when it's set to JOHAB" do
|
1285
|
+
@conn.set_client_encoding "JOHAB"
|
1286
|
+
val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
|
1287
|
+
expect( val.encoding.name ).to eq( "JOHAB" )
|
1288
|
+
expect( val.unpack("H*")[0] ).to eq( "dc65" )
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
it "can retrieve server encoding as text" do
|
1292
|
+
enc = @conn.parameter_status "server_encoding"
|
1293
|
+
expect( enc ).to eq( "UTF8" )
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
it "can retrieve server encoding as ruby encoding" do
|
1297
|
+
expect( @conn.external_encoding ).to eq( Encoding::UTF_8 )
|
1298
|
+
end
|
1299
|
+
|
1152
1300
|
it "uses the client encoding for escaped string" do
|
1153
|
-
original = "
|
1301
|
+
original = "Möhre to 'scape".encode( "utf-16be" )
|
1154
1302
|
@conn.set_client_encoding( "euc_jp" )
|
1155
1303
|
escaped = @conn.escape( original )
|
1156
1304
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1157
|
-
expect( escaped ).to eq( "
|
1305
|
+
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
|
1158
1306
|
end
|
1159
1307
|
|
1160
|
-
it "uses the client encoding for escaped literal"
|
1161
|
-
original = "
|
1308
|
+
it "uses the client encoding for escaped literal" do
|
1309
|
+
original = "Möhre to 'scape".encode( "utf-16be" )
|
1162
1310
|
@conn.set_client_encoding( "euc_jp" )
|
1163
1311
|
escaped = @conn.escape_literal( original )
|
1164
1312
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1165
|
-
expect( escaped ).to eq( "'
|
1313
|
+
expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
|
1166
1314
|
end
|
1167
1315
|
|
1168
|
-
it "uses the client encoding for escaped identifier"
|
1169
|
-
original = "
|
1316
|
+
it "uses the client encoding for escaped identifier" do
|
1317
|
+
original = "Möhre to 'scape".encode( "utf-16le" )
|
1170
1318
|
@conn.set_client_encoding( "euc_jp" )
|
1171
1319
|
escaped = @conn.escape_identifier( original )
|
1172
1320
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1173
|
-
expect( escaped ).to eq( "\"
|
1321
|
+
expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
|
1174
1322
|
end
|
1175
1323
|
|
1176
1324
|
it "uses the client encoding for quote_ident" do
|
1177
|
-
original = "
|
1325
|
+
original = "Möhre to 'scape".encode( "utf-16le" )
|
1178
1326
|
@conn.set_client_encoding( "euc_jp" )
|
1179
1327
|
escaped = @conn.quote_ident( original )
|
1180
1328
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1181
|
-
expect( escaped ).to eq( "\"
|
1329
|
+
expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
|
1182
1330
|
end
|
1183
1331
|
|
1184
1332
|
it "uses the previous string encoding for escaped string" do
|
1185
|
-
original = "
|
1333
|
+
original = "Möhre to 'scape".encode( "iso-8859-1" )
|
1186
1334
|
@conn.set_client_encoding( "euc_jp" )
|
1187
1335
|
escaped = described_class.escape( original )
|
1188
1336
|
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1189
|
-
expect( escaped ).to eq( "
|
1337
|
+
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
|
1190
1338
|
end
|
1191
1339
|
|
1192
1340
|
it "uses the previous string encoding for quote_ident" do
|
1193
|
-
original = "
|
1341
|
+
original = "Möhre to 'scape".encode( "iso-8859-1" )
|
1194
1342
|
@conn.set_client_encoding( "euc_jp" )
|
1195
1343
|
escaped = described_class.quote_ident( original )
|
1196
1344
|
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1197
|
-
expect( escaped ).to eq( "\"
|
1345
|
+
expect( escaped.encode ).to eq( "\"Möhre to 'scape\"".encode(Encoding::ISO8859_1) )
|
1346
|
+
end
|
1347
|
+
|
1348
|
+
it "raises appropriate error if set_client_encoding is called with invalid arguments" do
|
1349
|
+
expect { @conn.set_client_encoding( "invalid" ) }.to raise_error(PG::Error, /invalid value/)
|
1350
|
+
expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
|
1351
|
+
expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
|
1352
|
+
end
|
1353
|
+
|
1354
|
+
it "can use an encoding with high index for client encoding" do
|
1355
|
+
# Allocate a lot of encoding indices, so that MRI's ENCODING_INLINE_MAX is exceeded
|
1356
|
+
unless Encoding.name_list.include?("pgtest-0")
|
1357
|
+
256.times do |eidx|
|
1358
|
+
Encoding::UTF_8.replicate("pgtest-#{eidx}")
|
1359
|
+
end
|
1360
|
+
end
|
1361
|
+
|
1362
|
+
# Now allocate the JOHAB encoding with an unusual high index
|
1363
|
+
@conn.set_client_encoding "JOHAB"
|
1364
|
+
val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
|
1365
|
+
expect( val.encoding.name ).to eq( "JOHAB" )
|
1366
|
+
end
|
1367
|
+
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
describe "respect and convert character encoding of input strings" do
|
1371
|
+
before :each do
|
1372
|
+
@conn.internal_encoding = __ENCODING__
|
1198
1373
|
end
|
1374
|
+
|
1375
|
+
it "should convert query string and parameters to #exec_params" do
|
1376
|
+
r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
|
1377
|
+
['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
|
1378
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1379
|
+
end
|
1380
|
+
|
1381
|
+
it "should convert query string to #exec" do
|
1382
|
+
r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
|
1383
|
+
expect( r.values ).to eq( [['grün']] )
|
1384
|
+
end
|
1385
|
+
|
1386
|
+
it "should convert strings and parameters to #prepare and #exec_prepared" do
|
1387
|
+
@conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
|
1388
|
+
r = @conn.exec_prepared("weiß1".encode("utf-32le"),
|
1389
|
+
['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
|
1390
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
it "should convert strings to #describe_prepared" do
|
1394
|
+
@conn.prepare("weiß2", "VALUES(123)")
|
1395
|
+
r = @conn.describe_prepared("weiß2".encode("utf-16be"))
|
1396
|
+
expect( r.nfields ).to eq( 1 )
|
1397
|
+
end
|
1398
|
+
|
1399
|
+
it "should convert strings to #describe_portal" do
|
1400
|
+
@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
|
1401
|
+
r = @conn.describe_portal("cörsör".encode("utf-16le"))
|
1402
|
+
expect( r.nfields ).to eq( 3 )
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
it "should convert query string to #send_query" do
|
1406
|
+
@conn.send_query("VALUES('grün')".encode("utf-16be"))
|
1407
|
+
expect( @conn.get_last_result.values ).to eq( [['grün']] )
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
it "should convert query string and parameters to #send_query_params" do
|
1411
|
+
@conn.send_query_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
|
1412
|
+
['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
|
1413
|
+
expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1414
|
+
end
|
1415
|
+
|
1416
|
+
it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
|
1417
|
+
@conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
|
1418
|
+
@conn.get_last_result
|
1419
|
+
@conn.send_query_prepared("weiß3".encode("utf-32le"),
|
1420
|
+
['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
|
1421
|
+
expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1422
|
+
end
|
1423
|
+
|
1424
|
+
it "should convert strings to #send_describe_prepared" do
|
1425
|
+
@conn.prepare("weiß4", "VALUES(123)")
|
1426
|
+
@conn.send_describe_prepared("weiß4".encode("utf-16be"))
|
1427
|
+
expect( @conn.get_last_result.nfields ).to eq( 1 )
|
1428
|
+
end
|
1429
|
+
|
1430
|
+
it "should convert strings to #send_describe_portal" do
|
1431
|
+
@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
|
1432
|
+
@conn.send_describe_portal("cörsör".encode("utf-16le"))
|
1433
|
+
expect( @conn.get_last_result.nfields ).to eq( 3 )
|
1434
|
+
end
|
1435
|
+
|
1436
|
+
it "should convert error string to #put_copy_end" do
|
1437
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1438
|
+
@conn.exec( "COPY copytable FROM STDIN" )
|
1439
|
+
@conn.put_copy_end("grün".encode("utf-16be"))
|
1440
|
+
expect( @conn.get_result.error_message ).to match(/grün/)
|
1441
|
+
@conn.get_result
|
1442
|
+
end
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
it "rejects command strings with zero bytes" do
|
1446
|
+
expect{ @conn.exec( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1447
|
+
expect{ @conn.exec_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
|
1448
|
+
expect{ @conn.prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
|
1449
|
+
expect{ @conn.prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1450
|
+
expect{ @conn.exec_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
|
1451
|
+
expect{ @conn.describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1452
|
+
expect{ @conn.describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1453
|
+
expect{ @conn.send_query( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1454
|
+
expect{ @conn.send_query_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
|
1455
|
+
expect{ @conn.send_prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
|
1456
|
+
expect{ @conn.send_prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1457
|
+
expect{ @conn.send_query_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
|
1458
|
+
expect{ @conn.send_describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1459
|
+
expect{ @conn.send_describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1460
|
+
end
|
1461
|
+
|
1462
|
+
it "rejects query params with zero bytes" do
|
1463
|
+
expect{ @conn.exec_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
|
1464
|
+
expect{ @conn.exec_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
|
1465
|
+
expect{ @conn.send_query_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
|
1466
|
+
expect{ @conn.send_query_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
it "rejects string with zero bytes in escape" do
|
1470
|
+
expect{ @conn.escape( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
it "rejects string with zero bytes in escape_literal" do
|
1474
|
+
expect{ @conn.escape_literal( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
it "rejects string with zero bytes in escape_identifier" do
|
1478
|
+
expect{ @conn.escape_identifier( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
it "rejects string with zero bytes in quote_ident" do
|
1482
|
+
expect{ described_class.quote_ident( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
it "rejects Array with string with zero bytes" do
|
1486
|
+
original = ["xyz", "2\x00"]
|
1487
|
+
expect{ described_class.quote_ident( original ) }.to raise_error(ArgumentError, /null byte/)
|
1199
1488
|
end
|
1200
1489
|
|
1201
1490
|
it "can quote bigger strings with quote_ident" do
|
1202
1491
|
original = "'01234567\"" * 100
|
1203
|
-
escaped = described_class.quote_ident( original
|
1492
|
+
escaped = described_class.quote_ident( original )
|
1204
1493
|
expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
|
1205
1494
|
end
|
1206
1495
|
|
@@ -1219,7 +1508,7 @@ describe PG::Connection do
|
|
1219
1508
|
|
1220
1509
|
describe "Ruby 1.9.x default_internal encoding" do
|
1221
1510
|
|
1222
|
-
it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
|
1511
|
+
it "honors the Encoding.default_internal if it's set and the synchronous interface is used", :without_transaction do
|
1223
1512
|
@conn.transaction do |txn_conn|
|
1224
1513
|
txn_conn.internal_encoding = Encoding::ISO8859_1
|
1225
1514
|
txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
|
@@ -1228,12 +1517,12 @@ describe PG::Connection do
|
|
1228
1517
|
|
1229
1518
|
begin
|
1230
1519
|
prev_encoding = Encoding.default_internal
|
1231
|
-
Encoding.default_internal = Encoding::
|
1520
|
+
Encoding.default_internal = Encoding::ISO8859_2
|
1232
1521
|
|
1233
1522
|
conn = PG.connect( @conninfo )
|
1234
|
-
expect( conn.internal_encoding ).to eq( Encoding::
|
1523
|
+
expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
|
1235
1524
|
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
1236
|
-
expect( res[0]['foo'].encoding ).to eq( Encoding::
|
1525
|
+
expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
|
1237
1526
|
ensure
|
1238
1527
|
conn.exec( "DROP TABLE defaultinternaltest" )
|
1239
1528
|
conn.finish if conn
|
@@ -1278,7 +1567,7 @@ describe PG::Connection do
|
|
1278
1567
|
conn.finish if conn
|
1279
1568
|
end
|
1280
1569
|
|
1281
|
-
it "handles clearing result in or after set_notice_receiver"
|
1570
|
+
it "handles clearing result in or after set_notice_receiver" do
|
1282
1571
|
r = nil
|
1283
1572
|
@conn.set_notice_receiver do |result|
|
1284
1573
|
r = result
|
@@ -1293,7 +1582,7 @@ describe PG::Connection do
|
|
1293
1582
|
@conn.set_notice_receiver
|
1294
1583
|
end
|
1295
1584
|
|
1296
|
-
it "receives properly encoded messages in the notice callbacks"
|
1585
|
+
it "receives properly encoded messages in the notice callbacks" do
|
1297
1586
|
[:receiver, :processor].each do |kind|
|
1298
1587
|
notices = []
|
1299
1588
|
@conn.internal_encoding = 'utf-8'
|
@@ -1321,9 +1610,8 @@ describe PG::Connection do
|
|
1321
1610
|
end
|
1322
1611
|
end
|
1323
1612
|
|
1324
|
-
it "receives properly encoded text from wait_for_notify", :
|
1613
|
+
it "receives properly encoded text from wait_for_notify", :without_transaction do
|
1325
1614
|
@conn.internal_encoding = 'utf-8'
|
1326
|
-
@conn.exec( 'ROLLBACK' )
|
1327
1615
|
@conn.exec( 'LISTEN "Möhre"' )
|
1328
1616
|
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1329
1617
|
event, pid, msg = nil
|
@@ -1334,13 +1622,13 @@ describe PG::Connection do
|
|
1334
1622
|
|
1335
1623
|
expect( event ).to eq( "Möhre" )
|
1336
1624
|
expect( event.encoding ).to eq( Encoding::UTF_8 )
|
1625
|
+
expect( pid ).to be_a_kind_of(Integer)
|
1337
1626
|
expect( msg ).to eq( '世界線航跡蔵' )
|
1338
1627
|
expect( msg.encoding ).to eq( Encoding::UTF_8 )
|
1339
1628
|
end
|
1340
1629
|
|
1341
|
-
it "returns properly encoded text from notifies", :
|
1630
|
+
it "returns properly encoded text from notifies", :without_transaction do
|
1342
1631
|
@conn.internal_encoding = 'utf-8'
|
1343
|
-
@conn.exec( 'ROLLBACK' )
|
1344
1632
|
@conn.exec( 'LISTEN "Möhre"' )
|
1345
1633
|
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1346
1634
|
@conn.exec( 'UNLISTEN "Möhre"' )
|
@@ -1354,7 +1642,7 @@ describe PG::Connection do
|
|
1354
1642
|
end
|
1355
1643
|
end
|
1356
1644
|
|
1357
|
-
context "OS thread support"
|
1645
|
+
context "OS thread support" do
|
1358
1646
|
it "Connection#exec shouldn't block a second thread" do
|
1359
1647
|
t = Thread.new do
|
1360
1648
|
@conn.exec( "select pg_sleep(1)" )
|
@@ -1394,9 +1682,14 @@ describe PG::Connection do
|
|
1394
1682
|
end
|
1395
1683
|
|
1396
1684
|
it "shouldn't type map params unless requested" do
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1685
|
+
if @conn.server_version < 100000
|
1686
|
+
expect{
|
1687
|
+
@conn.exec_params( "SELECT $1", [5] )
|
1688
|
+
}.to raise_error(PG::IndeterminateDatatype)
|
1689
|
+
else
|
1690
|
+
# PostgreSQL-10 maps to TEXT type (OID 25)
|
1691
|
+
expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
|
1692
|
+
end
|
1400
1693
|
end
|
1401
1694
|
|
1402
1695
|
it "should raise an error on invalid encoder to put_copy_data" do
|
@@ -1410,12 +1703,12 @@ describe PG::Connection do
|
|
1410
1703
|
row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
1411
1704
|
|
1412
1705
|
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1413
|
-
|
1706
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1414
1707
|
@conn.put_copy_data [1], row_encoder
|
1415
1708
|
@conn.put_copy_data ["2"], row_encoder
|
1416
1709
|
end
|
1417
1710
|
|
1418
|
-
|
1711
|
+
@conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
|
1419
1712
|
@conn.put_copy_data [3]
|
1420
1713
|
@conn.put_copy_data ["4"]
|
1421
1714
|
end
|
@@ -1463,15 +1756,15 @@ describe PG::Connection do
|
|
1463
1756
|
end
|
1464
1757
|
end
|
1465
1758
|
|
1466
|
-
it "can process #copy_data input queries with row encoder" do
|
1759
|
+
it "can process #copy_data input queries with row encoder and respects character encoding" do
|
1467
1760
|
@conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1468
|
-
|
1761
|
+
@conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1469
1762
|
@conn2.put_copy_data [1]
|
1470
|
-
@conn2.put_copy_data ["
|
1763
|
+
@conn2.put_copy_data ["Möhre".encode("utf-16le")]
|
1471
1764
|
end
|
1472
1765
|
|
1473
1766
|
res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1474
|
-
expect( res.values ).to eq( [["1"], ["
|
1767
|
+
expect( res.values ).to eq( [["1"], ["Möhre"]] )
|
1475
1768
|
end
|
1476
1769
|
end
|
1477
1770
|
|
@@ -1513,14 +1806,16 @@ describe PG::Connection do
|
|
1513
1806
|
end
|
1514
1807
|
end
|
1515
1808
|
|
1516
|
-
it "can process #copy_data output with row decoder" do
|
1809
|
+
it "can process #copy_data output with row decoder and respects character encoding" do
|
1810
|
+
@conn2.internal_encoding = Encoding::ISO8859_1
|
1517
1811
|
rows = []
|
1518
|
-
|
1812
|
+
@conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
|
1519
1813
|
while row=@conn2.get_copy_data
|
1520
1814
|
rows << row
|
1521
1815
|
end
|
1522
1816
|
end
|
1523
|
-
expect( rows ).to eq(
|
1817
|
+
expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
|
1818
|
+
expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
|
1524
1819
|
end
|
1525
1820
|
|
1526
1821
|
it "can type cast #copy_data output with explicit decoder" do
|
@@ -1541,4 +1836,114 @@ describe PG::Connection do
|
|
1541
1836
|
end
|
1542
1837
|
end
|
1543
1838
|
end
|
1839
|
+
|
1840
|
+
describe :field_name_type do
|
1841
|
+
before :each do
|
1842
|
+
@conn2 = PG.connect(@conninfo)
|
1843
|
+
end
|
1844
|
+
after :each do
|
1845
|
+
@conn2.close
|
1846
|
+
end
|
1847
|
+
|
1848
|
+
it "uses string field names per default" do
|
1849
|
+
expect(@conn2.field_name_type).to eq(:string)
|
1850
|
+
end
|
1851
|
+
|
1852
|
+
it "can set string field names" do
|
1853
|
+
@conn2.field_name_type = :string
|
1854
|
+
expect(@conn2.field_name_type).to eq(:string)
|
1855
|
+
res = @conn2.exec("SELECT 1 as az")
|
1856
|
+
expect(res.field_name_type).to eq(:string)
|
1857
|
+
expect(res.fields).to eq(["az"])
|
1858
|
+
end
|
1859
|
+
|
1860
|
+
it "can set symbol field names" do
|
1861
|
+
@conn2.field_name_type = :symbol
|
1862
|
+
expect(@conn2.field_name_type).to eq(:symbol)
|
1863
|
+
res = @conn2.exec("SELECT 1 as az")
|
1864
|
+
expect(res.field_name_type).to eq(:symbol)
|
1865
|
+
expect(res.fields).to eq([:az])
|
1866
|
+
end
|
1867
|
+
|
1868
|
+
it "can't set invalid values" do
|
1869
|
+
expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
|
1870
|
+
expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
|
1871
|
+
end
|
1872
|
+
end
|
1873
|
+
|
1874
|
+
describe "deprecated forms of methods" do
|
1875
|
+
if PG::VERSION < "2"
|
1876
|
+
it "should forward exec to exec_params" do
|
1877
|
+
res = @conn.exec("VALUES($1::INT)", [7]).values
|
1878
|
+
expect(res).to eq( [["7"]] )
|
1879
|
+
res = @conn.exec("VALUES($1::INT)", [7], 1).values
|
1880
|
+
expect(res).to eq( [[[7].pack("N")]] )
|
1881
|
+
res = @conn.exec("VALUES(8)", [], 1).values
|
1882
|
+
expect(res).to eq( [[[8].pack("N")]] )
|
1883
|
+
end
|
1884
|
+
|
1885
|
+
it "should forward exec_params to exec" do
|
1886
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)").values
|
1887
|
+
expect(res).to eq( [["4"]] )
|
1888
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)", nil).values
|
1889
|
+
expect(res).to eq( [["4"]] )
|
1890
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil).values
|
1891
|
+
expect(res).to eq( [["4"]] )
|
1892
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, 1).values
|
1893
|
+
expect(res).to eq( [["4"]] )
|
1894
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil).values
|
1895
|
+
expect(res).to eq( [["4"]] )
|
1896
|
+
expect{
|
1897
|
+
@conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil, nil).values
|
1898
|
+
}.to raise_error(ArgumentError)
|
1899
|
+
end
|
1900
|
+
|
1901
|
+
it "should forward send_query to send_query_params" do
|
1902
|
+
@conn.send_query("VALUES($1)", [5])
|
1903
|
+
expect(@conn.get_last_result.values).to eq( [["5"]] )
|
1904
|
+
end
|
1905
|
+
|
1906
|
+
it "should respond_to socket", :unix do
|
1907
|
+
expect( @conn.socket ).to eq( @conn.socket_io.fileno )
|
1908
|
+
end
|
1909
|
+
else
|
1910
|
+
# Method forwarding removed by PG::VERSION >= "2"
|
1911
|
+
it "shouldn't forward exec to exec_params" do
|
1912
|
+
expect do
|
1913
|
+
@conn.exec("VALUES($1::INT)", [7])
|
1914
|
+
end.to raise_error(ArgumentError)
|
1915
|
+
end
|
1916
|
+
|
1917
|
+
it "shouldn't forward exec_params to exec" do
|
1918
|
+
expect do
|
1919
|
+
@conn.exec_params("VALUES(3); VALUES(4)")
|
1920
|
+
end.to raise_error(ArgumentError)
|
1921
|
+
end
|
1922
|
+
|
1923
|
+
it "shouldn't forward send_query to send_query_params" do
|
1924
|
+
expect do
|
1925
|
+
@conn.send_query("VALUES($1)", [5])
|
1926
|
+
end.to raise_error(ArgumentError)
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
it "shouldn't forward async_exec_params to async_exec" do
|
1930
|
+
expect do
|
1931
|
+
@conn.async_exec_params("VALUES(1)")
|
1932
|
+
end.to raise_error(ArgumentError)
|
1933
|
+
end
|
1934
|
+
|
1935
|
+
it "shouldn't respond_to socket" do
|
1936
|
+
expect do
|
1937
|
+
@conn.socket
|
1938
|
+
end.to raise_error(ArgumentError)
|
1939
|
+
end
|
1940
|
+
end
|
1941
|
+
|
1942
|
+
it "shouldn't forward send_query_params to send_query" do
|
1943
|
+
expect{ @conn.send_query_params("VALUES(4)").values }
|
1944
|
+
.to raise_error(ArgumentError)
|
1945
|
+
expect{ @conn.send_query_params("VALUES(4)", nil).values }
|
1946
|
+
.to raise_error(TypeError)
|
1947
|
+
end
|
1948
|
+
end
|
1544
1949
|
end
|