pg 0.18.0 → 1.1.4
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/BSDL +2 -2
- data/ChangeLog +1221 -4
- data/History.rdoc +200 -0
- data/Manifest.txt +5 -18
- data/README-Windows.rdoc +15 -26
- data/README.rdoc +27 -10
- data/Rakefile +33 -24
- data/Rakefile.cross +57 -39
- data/ext/errorcodes.def +37 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +16 -1
- data/ext/extconf.rb +29 -35
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +27 -39
- data/ext/pg.c +27 -53
- data/ext/pg.h +66 -83
- data/ext/pg_binary_decoder.c +75 -6
- data/ext/pg_binary_encoder.c +14 -12
- data/ext/pg_coder.c +83 -13
- data/ext/pg_connection.c +627 -351
- data/ext/pg_copy_coder.c +44 -9
- data/ext/pg_result.c +364 -134
- data/ext/pg_text_decoder.c +605 -46
- data/ext/pg_text_encoder.c +95 -76
- data/ext/pg_tuple.c +541 -0
- data/ext/pg_type_map.c +20 -13
- data/ext/pg_type_map_by_column.c +7 -7
- data/ext/pg_type_map_by_mri_type.c +2 -2
- data/ext/pg_type_map_in_ruby.c +4 -7
- data/ext/util.c +7 -7
- data/ext/util.h +3 -3
- data/lib/pg/basic_type_mapping.rb +105 -45
- data/lib/pg/binary_decoder.rb +22 -0
- data/lib/pg/coder.rb +1 -1
- data/lib/pg/connection.rb +109 -39
- data/lib/pg/constants.rb +1 -1
- data/lib/pg/exceptions.rb +1 -1
- data/lib/pg/result.rb +11 -6
- data/lib/pg/text_decoder.rb +25 -20
- data/lib/pg/text_encoder.rb +43 -1
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +1 -1
- data/lib/pg.rb +21 -11
- data/spec/helpers.rb +50 -25
- data/spec/pg/basic_type_mapping_spec.rb +287 -30
- data/spec/pg/connection_spec.rb +695 -282
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +59 -17
- data/spec/pg/tuple_spec.rb +280 -0
- data/spec/pg/type_map_by_class_spec.rb +3 -3
- data/spec/pg/type_map_by_column_spec.rb +1 -1
- data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
- data/spec/pg/type_map_by_oid_spec.rb +1 -1
- 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 +319 -35
- data/spec/pg_spec.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +68 -68
- 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'
|
@@ -45,6 +45,14 @@ describe PG::Connection do
|
|
45
45
|
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
46
46
|
end
|
47
47
|
|
48
|
+
it "can create a connection option string from an option string and a hash" do
|
49
|
+
optstring = described_class.parse_connect_args( 'dbname=original', :user => 'jrandom' )
|
50
|
+
|
51
|
+
expect( optstring ).to be_a( String )
|
52
|
+
expect( optstring ).to match( /(^|\s)dbname=original/ )
|
53
|
+
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
54
|
+
end
|
55
|
+
|
48
56
|
it "escapes single quotes and backslashes in connection parameters" do
|
49
57
|
expect(
|
50
58
|
described_class.parse_connect_args( "DB 'browser' \\" )
|
@@ -52,18 +60,85 @@ describe PG::Connection do
|
|
52
60
|
|
53
61
|
end
|
54
62
|
|
63
|
+
let(:uri) { 'postgresql://user:pass@pgsql.example.com:222/db01?sslmode=require' }
|
64
|
+
|
65
|
+
it "can connect using a URI" do
|
66
|
+
string = described_class.parse_connect_args( uri )
|
67
|
+
|
68
|
+
expect( string ).to be_a( String )
|
69
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
70
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
71
|
+
|
72
|
+
string = described_class.parse_connect_args( URI.parse(uri) )
|
73
|
+
|
74
|
+
expect( string ).to be_a( String )
|
75
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
76
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can create a connection URI from a URI and a hash" do
|
80
|
+
string = described_class.parse_connect_args( uri, :connect_timeout => 2 )
|
81
|
+
|
82
|
+
expect( string ).to be_a( String )
|
83
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
84
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
85
|
+
expect( string ).to match( %r{\?.*connect_timeout=2} )
|
86
|
+
|
87
|
+
string = described_class.parse_connect_args( uri,
|
88
|
+
:user => 'a',
|
89
|
+
:password => 'b',
|
90
|
+
:host => 'localhost',
|
91
|
+
:port => 555,
|
92
|
+
:dbname => 'x' )
|
93
|
+
|
94
|
+
expect( string ).to be_a( String )
|
95
|
+
expect( string ).to match( %r{^postgresql://\?} )
|
96
|
+
expect( string ).to match( %r{\?.*user=a} )
|
97
|
+
expect( string ).to match( %r{\?.*password=b} )
|
98
|
+
expect( string ).to match( %r{\?.*host=localhost} )
|
99
|
+
expect( string ).to match( %r{\?.*port=555} )
|
100
|
+
expect( string ).to match( %r{\?.*dbname=x} )
|
101
|
+
end
|
102
|
+
|
103
|
+
it "can create a connection URI with a non-standard domain socket directory" do
|
104
|
+
string = described_class.parse_connect_args( 'postgresql://%2Fvar%2Flib%2Fpostgresql/dbname' )
|
105
|
+
|
106
|
+
expect( string ).to be_a( String )
|
107
|
+
expect( string ).to match( %r{^postgresql://%2Fvar%2Flib%2Fpostgresql/dbname} )
|
108
|
+
|
109
|
+
string = described_class.
|
110
|
+
parse_connect_args( 'postgresql:///dbname', :host => '/var/lib/postgresql' )
|
111
|
+
|
112
|
+
expect( string ).to be_a( String )
|
113
|
+
expect( string ).to match( %r{^postgresql:///dbname\?} )
|
114
|
+
expect( string ).to match( %r{\?.*host=%2Fvar%2Flib%2Fpostgresql} )
|
115
|
+
end
|
116
|
+
|
55
117
|
it "connects with defaults if no connection parameters are given" do
|
56
118
|
expect( described_class.parse_connect_args ).to eq( '' )
|
57
119
|
end
|
58
120
|
|
59
121
|
it "connects successfully with connection string" do
|
60
|
-
|
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
|
+
|
134
|
+
it "connects successfully with connection string" do
|
135
|
+
tmpconn = described_class.connect( @conninfo )
|
61
136
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
62
137
|
tmpconn.finish
|
63
138
|
end
|
64
139
|
|
65
140
|
it "connects using 7 arguments converted to strings" do
|
66
|
-
tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
|
141
|
+
tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
|
67
142
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
68
143
|
tmpconn.finish
|
69
144
|
end
|
@@ -77,7 +152,7 @@ describe PG::Connection do
|
|
77
152
|
tmpconn.finish
|
78
153
|
end
|
79
154
|
|
80
|
-
it "connects using a hash of optional connection parameters"
|
155
|
+
it "connects using a hash of optional connection parameters" do
|
81
156
|
tmpconn = described_class.connect(
|
82
157
|
:host => 'localhost',
|
83
158
|
:port => @port,
|
@@ -89,28 +164,20 @@ describe PG::Connection do
|
|
89
164
|
|
90
165
|
it "raises an exception when connecting with an invalid number of arguments" do
|
91
166
|
expect {
|
92
|
-
described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
|
93
|
-
}.to raise_error
|
167
|
+
described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'the-extra-arg' )
|
168
|
+
}.to raise_error do |error|
|
169
|
+
expect( error ).to be_an( ArgumentError )
|
170
|
+
expect( error.message ).to match( /extra positional parameter/i )
|
171
|
+
expect( error.message ).to match( /8/ )
|
172
|
+
expect( error.message ).to match( /the-extra-arg/ )
|
173
|
+
end
|
94
174
|
end
|
95
175
|
|
96
176
|
it "can connect asynchronously", :socket_io do
|
97
177
|
tmpconn = described_class.connect_start( @conninfo )
|
98
178
|
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
179
|
|
180
|
+
wait_for_polling_ok(tmpconn)
|
114
181
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
115
182
|
tmpconn.finish
|
116
183
|
end
|
@@ -121,28 +188,79 @@ describe PG::Connection do
|
|
121
188
|
described_class.connect_start(@conninfo) do |tmpconn|
|
122
189
|
expect( tmpconn ).to be_a( described_class )
|
123
190
|
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
191
|
|
192
|
+
wait_for_polling_ok(tmpconn)
|
140
193
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
141
194
|
end
|
142
195
|
|
143
196
|
expect( conn ).to be_finished()
|
144
197
|
end
|
145
198
|
|
199
|
+
context "with async established connection", :socket_io 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
|
+
|
146
264
|
it "raises proper error when sending fails" do
|
147
265
|
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
148
266
|
expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
|
@@ -152,7 +270,7 @@ describe PG::Connection do
|
|
152
270
|
described_class.connect(@conninfo).finish
|
153
271
|
sleep 0.5
|
154
272
|
res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
|
155
|
-
WHERE usename IS NOT NULL])
|
273
|
+
WHERE usename IS NOT NULL AND application_name != ''])
|
156
274
|
# there's still the global @conn, but should be no more
|
157
275
|
expect( res[0]['n'] ).to eq( '1' )
|
158
276
|
end
|
@@ -161,13 +279,14 @@ describe PG::Connection do
|
|
161
279
|
expect( @conn.db ).to eq( "test" )
|
162
280
|
expect( @conn.user ).to be_a_kind_of( String )
|
163
281
|
expect( @conn.pass ).to eq( "" )
|
164
|
-
expect( @conn.
|
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 )
|
282
|
+
expect( @conn.port ).to eq( @port )
|
168
283
|
expect( @conn.tty ).to eq( "" )
|
169
284
|
expect( @conn.options ).to eq( "" )
|
170
285
|
end
|
286
|
+
it "can retrieve it's connection parameters for the established connection",
|
287
|
+
skip: RUBY_PLATFORM=~/x64-mingw/ ? "host segfaults on Windows-x64" : false do
|
288
|
+
expect( @conn.host ).to eq( "localhost" )
|
289
|
+
end
|
171
290
|
|
172
291
|
EXPECTED_TRACE_OUTPUT = %{
|
173
292
|
To backend> Msg Q
|
@@ -214,18 +333,15 @@ describe PG::Connection do
|
|
214
333
|
|
215
334
|
trace_data = trace_file.read
|
216
335
|
|
217
|
-
|
218
|
-
#
|
219
|
-
#
|
220
|
-
#
|
221
|
-
# +From backend (#4)>
|
222
|
-
#
|
223
|
-
|
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
|
336
|
+
# For async_exec the output will be different:
|
337
|
+
# From backend> Z
|
338
|
+
# From backend (#4)> 5
|
339
|
+
# +From backend> Z
|
340
|
+
# +From backend (#4)> 5
|
341
|
+
# From backend> T
|
342
|
+
trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
|
227
343
|
|
228
|
-
expect( trace_data ).to eq(
|
344
|
+
expect( trace_data ).to eq( EXPECTED_TRACE_OUTPUT )
|
229
345
|
end
|
230
346
|
|
231
347
|
it "allows a query to be cancelled" do
|
@@ -320,22 +436,11 @@ describe PG::Connection do
|
|
320
436
|
end
|
321
437
|
end
|
322
438
|
|
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
439
|
it "supports explicitly calling #exec_params" do
|
335
440
|
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
336
|
-
@conn.
|
337
|
-
@conn.
|
338
|
-
@conn.
|
441
|
+
@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
|
442
|
+
@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
|
443
|
+
@conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
339
444
|
|
340
445
|
res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
|
341
446
|
expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
|
@@ -456,7 +561,7 @@ describe PG::Connection do
|
|
456
561
|
@conn.exec( 'UNLISTEN woo' )
|
457
562
|
end
|
458
563
|
|
459
|
-
it "can receive notices while waiting for NOTIFY without exceeding the timeout"
|
564
|
+
it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
|
460
565
|
notices = []
|
461
566
|
@conn.set_notice_processor do |msg|
|
462
567
|
notices << [msg, Time.now]
|
@@ -518,7 +623,7 @@ describe PG::Connection do
|
|
518
623
|
expect( @conn ).to still_be_usable
|
519
624
|
end
|
520
625
|
|
521
|
-
it "can handle server errors in #copy_data for output"
|
626
|
+
it "can handle server errors in #copy_data for output" do
|
522
627
|
@conn.exec "ROLLBACK"
|
523
628
|
@conn.transaction do
|
524
629
|
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
@@ -575,6 +680,31 @@ describe PG::Connection do
|
|
575
680
|
expect( @conn ).to still_be_usable
|
576
681
|
end
|
577
682
|
|
683
|
+
it "gracefully handle SQL statements while in #copy_data for input" do
|
684
|
+
@conn.exec "ROLLBACK"
|
685
|
+
@conn.transaction do
|
686
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
|
687
|
+
expect {
|
688
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
689
|
+
@conn.exec "SELECT 1"
|
690
|
+
end
|
691
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
692
|
+
end
|
693
|
+
expect( @conn ).to still_be_usable
|
694
|
+
end
|
695
|
+
|
696
|
+
it "gracefully handle SQL statements while in #copy_data for output" do
|
697
|
+
@conn.exec "ROLLBACK"
|
698
|
+
@conn.transaction do
|
699
|
+
expect {
|
700
|
+
@conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
|
701
|
+
@conn.exec "SELECT 3"
|
702
|
+
end
|
703
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
704
|
+
end
|
705
|
+
expect( @conn ).to still_be_usable
|
706
|
+
end
|
707
|
+
|
578
708
|
it "should raise an error for non copy statements in #copy_data" do
|
579
709
|
expect {
|
580
710
|
@conn.copy_data( "SELECT 1" ){}
|
@@ -616,18 +746,13 @@ describe PG::Connection do
|
|
616
746
|
end
|
617
747
|
|
618
748
|
it "described_class#block should allow a timeout" do
|
619
|
-
@conn.send_query( "select pg_sleep(
|
749
|
+
@conn.send_query( "select pg_sleep(1)" )
|
620
750
|
|
621
751
|
start = Time.now
|
622
|
-
@conn.block( 0.
|
752
|
+
@conn.block( 0.3 )
|
623
753
|
finish = Time.now
|
624
754
|
|
625
|
-
expect( (finish - start) ).to be_within( 0.
|
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+/ )
|
755
|
+
expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
|
631
756
|
end
|
632
757
|
|
633
758
|
it "can return the default connection options" do
|
@@ -640,7 +765,7 @@ describe PG::Connection do
|
|
640
765
|
it "can return the default connection options as a Hash" do
|
641
766
|
expect( described_class.conndefaults_hash ).to be_a( Hash )
|
642
767
|
expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
|
643
|
-
expect(
|
768
|
+
expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
|
644
769
|
expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
|
645
770
|
end
|
646
771
|
|
@@ -657,6 +782,25 @@ describe PG::Connection do
|
|
657
782
|
expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
|
658
783
|
end
|
659
784
|
|
785
|
+
describe "connection information related to SSL" do
|
786
|
+
|
787
|
+
it "can retrieve connection's ssl state", :postgresql_95 do
|
788
|
+
expect( @conn.ssl_in_use? ).to be false
|
789
|
+
end
|
790
|
+
|
791
|
+
it "can retrieve connection's ssl attribute_names", :postgresql_95 do
|
792
|
+
expect( @conn.ssl_attribute_names ).to be_a(Array)
|
793
|
+
end
|
794
|
+
|
795
|
+
it "can retrieve a single ssl connection attribute", :postgresql_95 do
|
796
|
+
expect( @conn.ssl_attribute('dbname') ).to eq( nil )
|
797
|
+
end
|
798
|
+
|
799
|
+
it "can retrieve all connection's ssl attributes", :postgresql_95 do
|
800
|
+
expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
660
804
|
|
661
805
|
it "honors the connect_timeout connection parameter", :postgresql_93 do
|
662
806
|
conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
|
@@ -667,18 +811,52 @@ describe PG::Connection do
|
|
667
811
|
end
|
668
812
|
end
|
669
813
|
|
814
|
+
describe "deprecated password encryption method" do
|
815
|
+
it "can encrypt password for a given user" do
|
816
|
+
expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
817
|
+
end
|
670
818
|
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
819
|
+
it "raises an appropriate error if either of the required arguments is not valid" do
|
820
|
+
expect {
|
821
|
+
described_class.encrypt_password( nil, nil )
|
822
|
+
}.to raise_error( TypeError )
|
823
|
+
expect {
|
824
|
+
described_class.encrypt_password( "postgres", nil )
|
825
|
+
}.to raise_error( TypeError )
|
826
|
+
expect {
|
827
|
+
described_class.encrypt_password( nil, "postgres" )
|
828
|
+
}.to raise_error( TypeError )
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
describe "password encryption method", :postgresql_10 do
|
833
|
+
it "can encrypt without algorithm" do
|
834
|
+
expect( @conn.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
835
|
+
expect( @conn.encrypt_password("postgres", "postgres", nil) ).to match( /\S+/ )
|
836
|
+
end
|
837
|
+
|
838
|
+
it "can encrypt with algorithm" do
|
839
|
+
expect( @conn.encrypt_password("postgres", "postgres", "md5") ).to match( /md5\S+/i )
|
840
|
+
expect( @conn.encrypt_password("postgres", "postgres", "scram-sha-256") ).to match( /SCRAM-SHA-256\S+/i )
|
841
|
+
end
|
842
|
+
|
843
|
+
it "raises an appropriate error if either of the required arguments is not valid" do
|
844
|
+
expect {
|
845
|
+
@conn.encrypt_password( nil, nil )
|
846
|
+
}.to raise_error( TypeError )
|
847
|
+
expect {
|
848
|
+
@conn.encrypt_password( "postgres", nil )
|
849
|
+
}.to raise_error( TypeError )
|
850
|
+
expect {
|
851
|
+
@conn.encrypt_password( nil, "postgres" )
|
852
|
+
}.to raise_error( TypeError )
|
853
|
+
expect {
|
854
|
+
@conn.encrypt_password( "postgres", "postgres", :invalid )
|
855
|
+
}.to raise_error( TypeError )
|
856
|
+
expect {
|
857
|
+
@conn.encrypt_password( "postgres", "postgres", "invalid" )
|
858
|
+
}.to raise_error( PG::Error, /unrecognized/ )
|
859
|
+
end
|
682
860
|
end
|
683
861
|
|
684
862
|
|
@@ -720,7 +898,7 @@ describe PG::Connection do
|
|
720
898
|
end
|
721
899
|
|
722
900
|
|
723
|
-
it "
|
901
|
+
it "handles server close while asynchronous connect", :socket_io do
|
724
902
|
serv = TCPServer.new( '127.0.0.1', 54320 )
|
725
903
|
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
726
904
|
expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
|
@@ -732,11 +910,29 @@ describe PG::Connection do
|
|
732
910
|
expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
|
733
911
|
end
|
734
912
|
|
735
|
-
it "discards previous results
|
913
|
+
it "discards previous results at #discard_results" do
|
914
|
+
@conn.send_query( "select 1" )
|
915
|
+
@conn.discard_results
|
916
|
+
@conn.send_query( "select 41 as one" )
|
917
|
+
res = @conn.get_last_result
|
918
|
+
expect( res.to_a ).to eq( [{ 'one' => '41' }] )
|
919
|
+
end
|
920
|
+
|
921
|
+
it "discards previous results (if any) before waiting on #exec" do
|
922
|
+
@conn.send_query( "select 1" )
|
923
|
+
res = @conn.exec( "select 42 as one" )
|
924
|
+
expect( res.to_a ).to eq( [{ 'one' => '42' }] )
|
925
|
+
end
|
926
|
+
|
927
|
+
it "discards previous errors before waiting on #exec", :without_transaction do
|
928
|
+
@conn.send_query( "ERROR" )
|
929
|
+
res = @conn.exec( "select 43 as one" )
|
930
|
+
expect( res.to_a ).to eq( [{ 'one' => '43' }] )
|
931
|
+
end
|
736
932
|
|
737
|
-
it "calls the block if one is provided to #
|
933
|
+
it "calls the block if one is provided to #exec" do
|
738
934
|
result = nil
|
739
|
-
@conn.
|
935
|
+
@conn.exec( "select 47 as one" ) do |pg_res|
|
740
936
|
result = pg_res[0]
|
741
937
|
end
|
742
938
|
expect( result ).to eq( { 'one' => '47' } )
|
@@ -749,6 +945,20 @@ describe PG::Connection do
|
|
749
945
|
expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
750
946
|
end
|
751
947
|
|
948
|
+
it "can use conn.reset to restart the connection" do
|
949
|
+
ios = IO.pipe
|
950
|
+
conn = PG.connect( @conninfo )
|
951
|
+
|
952
|
+
# Close the two pipe file descriptors, so that the file descriptor of
|
953
|
+
# newly established connection is probably distinct from the previous one.
|
954
|
+
ios.each(&:close)
|
955
|
+
conn.reset
|
956
|
+
|
957
|
+
# The new connection should work even when the file descriptor has changed.
|
958
|
+
expect( conn.exec("SELECT 1").values ).to eq([["1"]])
|
959
|
+
conn.close
|
960
|
+
end
|
961
|
+
|
752
962
|
it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
|
753
963
|
conn = PG.connect( @conninfo )
|
754
964
|
io = conn.socket_io
|
@@ -777,155 +987,147 @@ describe PG::Connection do
|
|
777
987
|
expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
|
778
988
|
end
|
779
989
|
|
780
|
-
|
990
|
+
it "sets the fallback_application_name on new connections" do
|
991
|
+
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
781
992
|
|
782
|
-
|
783
|
-
|
784
|
-
|
993
|
+
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
994
|
+
expect( conn_name ).to include( $0[0..10] )
|
995
|
+
expect( conn_name ).to include( $0[-10..-1] )
|
996
|
+
expect( conn_name.length ).to be <= 64
|
997
|
+
end
|
785
998
|
|
786
|
-
|
999
|
+
it "sets a shortened fallback_application_name on new connections" do
|
1000
|
+
old_0 = $0
|
1001
|
+
begin
|
1002
|
+
$0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
|
787
1003
|
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
788
|
-
|
789
1004
|
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
790
1005
|
expect( conn_name ).to include( $0[0..10] )
|
791
1006
|
expect( conn_name ).to include( $0[-10..-1] )
|
792
1007
|
expect( conn_name.length ).to be <= 64
|
1008
|
+
ensure
|
1009
|
+
$0 = old_0
|
793
1010
|
end
|
1011
|
+
end
|
794
1012
|
|
795
|
-
|
796
|
-
|
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' )
|
1013
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1014
|
+
"any number of arguments" do
|
814
1015
|
|
815
|
-
|
816
|
-
|
817
|
-
conn.finish
|
1016
|
+
@conn.exec( 'ROLLBACK' )
|
1017
|
+
@conn.exec( 'LISTEN knees' )
|
818
1018
|
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
end
|
823
|
-
@conn.exec( 'UNLISTEN knees' )
|
1019
|
+
conn = described_class.connect( @conninfo )
|
1020
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1021
|
+
conn.finish
|
824
1022
|
|
825
|
-
|
826
|
-
|
827
|
-
|
1023
|
+
event, pid, msg = nil
|
1024
|
+
@conn.wait_for_notify( 10 ) do |*args|
|
1025
|
+
event, pid, msg = *args
|
828
1026
|
end
|
1027
|
+
@conn.exec( 'UNLISTEN knees' )
|
829
1028
|
|
830
|
-
|
831
|
-
|
832
|
-
|
1029
|
+
expect( event ).to eq( 'knees' )
|
1030
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1031
|
+
expect( msg ).to eq( 'skirt and boots' )
|
1032
|
+
end
|
833
1033
|
|
834
|
-
|
835
|
-
|
836
|
-
|
1034
|
+
it "accepts nil as the timeout in #wait_for_notify " do
|
1035
|
+
@conn.exec( 'ROLLBACK' )
|
1036
|
+
@conn.exec( 'LISTEN knees' )
|
837
1037
|
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
end
|
842
|
-
@conn.exec( 'UNLISTEN knees' )
|
1038
|
+
conn = described_class.connect( @conninfo )
|
1039
|
+
conn.exec( %Q{NOTIFY knees} )
|
1040
|
+
conn.finish
|
843
1041
|
|
844
|
-
|
845
|
-
|
1042
|
+
event, pid = nil
|
1043
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1044
|
+
event, pid = *args
|
846
1045
|
end
|
1046
|
+
@conn.exec( 'UNLISTEN knees' )
|
847
1047
|
|
848
|
-
|
849
|
-
|
850
|
-
|
1048
|
+
expect( event ).to eq( 'knees' )
|
1049
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1050
|
+
end
|
851
1051
|
|
852
|
-
|
853
|
-
|
854
|
-
|
1052
|
+
it "sends nil as the payload if the notification wasn't given one" do
|
1053
|
+
@conn.exec( 'ROLLBACK' )
|
1054
|
+
@conn.exec( 'LISTEN knees' )
|
855
1055
|
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
end
|
860
|
-
@conn.exec( 'UNLISTEN knees' )
|
1056
|
+
conn = described_class.connect( @conninfo )
|
1057
|
+
conn.exec( %Q{NOTIFY knees} )
|
1058
|
+
conn.finish
|
861
1059
|
|
862
|
-
|
1060
|
+
payload = :notnil
|
1061
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1062
|
+
payload = args[ 2 ]
|
863
1063
|
end
|
1064
|
+
@conn.exec( 'UNLISTEN knees' )
|
864
1065
|
|
865
|
-
|
866
|
-
|
1066
|
+
expect( payload ).to be_nil()
|
1067
|
+
end
|
867
1068
|
|
868
|
-
|
869
|
-
|
1069
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1070
|
+
"two arguments" do
|
870
1071
|
|
871
|
-
|
872
|
-
|
873
|
-
conn.finish
|
1072
|
+
@conn.exec( 'ROLLBACK' )
|
1073
|
+
@conn.exec( 'LISTEN knees' )
|
874
1074
|
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
end
|
879
|
-
@conn.exec( 'UNLISTEN knees' )
|
1075
|
+
conn = described_class.connect( @conninfo )
|
1076
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1077
|
+
conn.finish
|
880
1078
|
|
881
|
-
|
882
|
-
|
883
|
-
|
1079
|
+
event, pid, msg = nil
|
1080
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2|
|
1081
|
+
event, pid, msg = arg1, arg2
|
884
1082
|
end
|
1083
|
+
@conn.exec( 'UNLISTEN knees' )
|
885
1084
|
|
886
|
-
|
887
|
-
|
1085
|
+
expect( event ).to eq( 'knees' )
|
1086
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1087
|
+
expect( msg ).to be_nil()
|
1088
|
+
end
|
888
1089
|
|
889
|
-
|
890
|
-
|
1090
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it " +
|
1091
|
+
"doesn't accept arguments" do
|
891
1092
|
|
892
|
-
|
893
|
-
|
894
|
-
conn.finish
|
1093
|
+
@conn.exec( 'ROLLBACK' )
|
1094
|
+
@conn.exec( 'LISTEN knees' )
|
895
1095
|
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
end
|
900
|
-
@conn.exec( 'UNLISTEN knees' )
|
1096
|
+
conn = described_class.connect( @conninfo )
|
1097
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1098
|
+
conn.finish
|
901
1099
|
|
902
|
-
|
1100
|
+
notification_received = false
|
1101
|
+
@conn.wait_for_notify( 10 ) do
|
1102
|
+
notification_received = true
|
903
1103
|
end
|
1104
|
+
@conn.exec( 'UNLISTEN knees' )
|
904
1105
|
|
905
|
-
|
906
|
-
|
1106
|
+
expect( notification_received ).to be_truthy()
|
1107
|
+
end
|
907
1108
|
|
908
|
-
|
909
|
-
|
1109
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1110
|
+
"three arguments" do
|
910
1111
|
|
911
|
-
|
912
|
-
|
913
|
-
conn.finish
|
1112
|
+
@conn.exec( 'ROLLBACK' )
|
1113
|
+
@conn.exec( 'LISTEN knees' )
|
914
1114
|
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
end
|
919
|
-
@conn.exec( 'UNLISTEN knees' )
|
1115
|
+
conn = described_class.connect( @conninfo )
|
1116
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1117
|
+
conn.finish
|
920
1118
|
|
921
|
-
|
922
|
-
|
923
|
-
|
1119
|
+
event, pid, msg = nil
|
1120
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
|
1121
|
+
event, pid, msg = arg1, arg2, arg3
|
924
1122
|
end
|
1123
|
+
@conn.exec( 'UNLISTEN knees' )
|
925
1124
|
|
1125
|
+
expect( event ).to eq( 'knees' )
|
1126
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1127
|
+
expect( msg ).to eq( 'skirt and boots' )
|
926
1128
|
end
|
927
1129
|
|
928
|
-
context "
|
1130
|
+
context "server ping", :without_transaction do
|
929
1131
|
|
930
1132
|
it "pings successfully with connection string" do
|
931
1133
|
ping = described_class.ping(@conninfo)
|
@@ -953,76 +1155,82 @@ describe PG::Connection do
|
|
953
1155
|
expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
|
954
1156
|
end
|
955
1157
|
|
956
|
-
it "returns
|
1158
|
+
it "returns error when ping connection arguments are wrong" do
|
957
1159
|
ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
|
958
|
-
expect( ping ).
|
1160
|
+
expect( ping ).to_not eq( PG::PQPING_OK )
|
959
1161
|
end
|
960
1162
|
|
1163
|
+
it "returns correct response when ping connection arguments are wrong" do
|
1164
|
+
ping = described_class.ping(
|
1165
|
+
:host => 'localhost',
|
1166
|
+
:invalid_option => 9999,
|
1167
|
+
:dbname => :test)
|
1168
|
+
expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
|
1169
|
+
end
|
961
1170
|
|
962
1171
|
end
|
963
1172
|
|
964
|
-
|
965
|
-
describe "set_single_row_mode" do
|
1173
|
+
describe "set_single_row_mode" do
|
966
1174
|
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
1175
|
+
it "raises an error when called at the wrong time" do
|
1176
|
+
expect {
|
1177
|
+
@conn.set_single_row_mode
|
1178
|
+
}.to raise_error(PG::Error)
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
it "should work in single row mode" do
|
1182
|
+
@conn.send_query( "SELECT generate_series(1,10)" )
|
1183
|
+
@conn.set_single_row_mode
|
1184
|
+
|
1185
|
+
results = []
|
1186
|
+
loop do
|
1187
|
+
@conn.block
|
1188
|
+
res = @conn.get_result or break
|
1189
|
+
results << res
|
1190
|
+
end
|
1191
|
+
expect( results.length ).to eq( 11 )
|
1192
|
+
results[0..-2].each do |res|
|
1193
|
+
expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1194
|
+
values = res.field_values('generate_series')
|
1195
|
+
expect( values.length ).to eq( 1 )
|
1196
|
+
expect( values.first.to_i ).to be > 0
|
971
1197
|
end
|
1198
|
+
expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
|
1199
|
+
expect( results.last.ntuples ).to eq( 0 )
|
1200
|
+
end
|
972
1201
|
|
973
|
-
|
974
|
-
|
975
|
-
|
1202
|
+
it "should receive rows before entire query is finished" do
|
1203
|
+
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
|
1204
|
+
@conn.set_single_row_mode
|
976
1205
|
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
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 )
|
1206
|
+
start_time = Time.now
|
1207
|
+
first_row_time = nil
|
1208
|
+
loop do
|
1209
|
+
res = @conn.get_result or break
|
1210
|
+
res.check
|
1211
|
+
first_row_time = Time.now unless first_row_time
|
992
1212
|
end
|
1213
|
+
expect( (Time.now - start_time) ).to be >= 0.9
|
1214
|
+
expect( (first_row_time - start_time) ).to be < 0.9
|
1215
|
+
end
|
993
1216
|
|
994
|
-
|
995
|
-
|
996
|
-
|
1217
|
+
it "should receive rows before entire query fails" do
|
1218
|
+
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
1219
|
+
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
|
1220
|
+
@conn.set_single_row_mode
|
997
1221
|
|
998
|
-
|
999
|
-
|
1222
|
+
first_result = nil
|
1223
|
+
expect do
|
1000
1224
|
loop do
|
1001
1225
|
res = @conn.get_result or break
|
1002
1226
|
res.check
|
1003
|
-
|
1227
|
+
first_result ||= res
|
1004
1228
|
end
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
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
|
1229
|
+
end.to raise_error(PG::Error)
|
1230
|
+
expect( first_result.kind_of?(PG::Result) ).to be_truthy
|
1231
|
+
expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1025
1232
|
end
|
1233
|
+
|
1026
1234
|
end
|
1027
1235
|
|
1028
1236
|
context "multinationalization support", :ruby_19 do
|
@@ -1032,7 +1240,7 @@ describe PG::Connection do
|
|
1032
1240
|
out_string = nil
|
1033
1241
|
@conn.transaction do |conn|
|
1034
1242
|
conn.internal_encoding = 'iso8859-1'
|
1035
|
-
res = conn.
|
1243
|
+
res = conn.exec_params("VALUES ('fantasia')", [], 0)
|
1036
1244
|
out_string = res[0]['column1']
|
1037
1245
|
end
|
1038
1246
|
expect( out_string ).to eq( 'fantasia' )
|
@@ -1043,7 +1251,7 @@ describe PG::Connection do
|
|
1043
1251
|
out_string = nil
|
1044
1252
|
@conn.transaction do |conn|
|
1045
1253
|
conn.internal_encoding = 'utf-8'
|
1046
|
-
res = conn.
|
1254
|
+
res = conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
|
1047
1255
|
out_string = res[0]['column1']
|
1048
1256
|
end
|
1049
1257
|
expect( out_string ).to eq( '世界線航跡蔵' )
|
@@ -1055,7 +1263,7 @@ describe PG::Connection do
|
|
1055
1263
|
@conn.transaction do |conn|
|
1056
1264
|
conn.internal_encoding = 'EUC-JP'
|
1057
1265
|
stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
|
1058
|
-
res = conn.
|
1266
|
+
res = conn.exec_params(stmt, [], 0)
|
1059
1267
|
out_string = res[0]['column1']
|
1060
1268
|
end
|
1061
1269
|
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
@@ -1068,7 +1276,7 @@ describe PG::Connection do
|
|
1068
1276
|
@conn.transaction do |conn|
|
1069
1277
|
conn.internal_encoding = 'EUC-JP'
|
1070
1278
|
stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
|
1071
|
-
res = conn.
|
1279
|
+
res = conn.exec_params(stmt, [], 0)
|
1072
1280
|
conn.internal_encoding = 'utf-8'
|
1073
1281
|
out_string = res[0]['column1']
|
1074
1282
|
end
|
@@ -1081,56 +1289,215 @@ describe PG::Connection do
|
|
1081
1289
|
expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
|
1082
1290
|
end
|
1083
1291
|
|
1292
|
+
it "the connection should use JOHAB dummy encoding when it's set to JOHAB" do
|
1293
|
+
@conn.set_client_encoding "JOHAB"
|
1294
|
+
val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
|
1295
|
+
expect( val.encoding.name ).to eq( "JOHAB" )
|
1296
|
+
expect( val.unpack("H*")[0] ).to eq( "dc65" )
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
it "can retrieve server encoding as text" do
|
1300
|
+
enc = @conn.parameter_status "server_encoding"
|
1301
|
+
expect( enc ).to eq( "UTF8" )
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
it "can retrieve server encoding as ruby encoding" do
|
1305
|
+
expect( @conn.external_encoding ).to eq( Encoding::UTF_8 )
|
1306
|
+
end
|
1307
|
+
|
1084
1308
|
it "uses the client encoding for escaped string" do
|
1085
|
-
original = "
|
1309
|
+
original = "Möhre to 'scape".encode( "utf-16be" )
|
1086
1310
|
@conn.set_client_encoding( "euc_jp" )
|
1087
1311
|
escaped = @conn.escape( original )
|
1088
1312
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1089
|
-
expect( escaped ).to eq( "
|
1313
|
+
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
|
1090
1314
|
end
|
1091
1315
|
|
1092
|
-
it "uses the client encoding for escaped literal"
|
1093
|
-
original = "
|
1316
|
+
it "uses the client encoding for escaped literal" do
|
1317
|
+
original = "Möhre to 'scape".encode( "utf-16be" )
|
1094
1318
|
@conn.set_client_encoding( "euc_jp" )
|
1095
1319
|
escaped = @conn.escape_literal( original )
|
1096
1320
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1097
|
-
expect( escaped ).to eq( "'
|
1321
|
+
expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
|
1098
1322
|
end
|
1099
1323
|
|
1100
|
-
it "uses the client encoding for escaped identifier"
|
1101
|
-
original = "
|
1324
|
+
it "uses the client encoding for escaped identifier" do
|
1325
|
+
original = "Möhre to 'scape".encode( "utf-16le" )
|
1102
1326
|
@conn.set_client_encoding( "euc_jp" )
|
1103
1327
|
escaped = @conn.escape_identifier( original )
|
1104
1328
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1105
|
-
expect( escaped ).to eq( "\"
|
1329
|
+
expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
|
1106
1330
|
end
|
1107
1331
|
|
1108
1332
|
it "uses the client encoding for quote_ident" do
|
1109
|
-
original = "
|
1333
|
+
original = "Möhre to 'scape".encode( "utf-16le" )
|
1110
1334
|
@conn.set_client_encoding( "euc_jp" )
|
1111
1335
|
escaped = @conn.quote_ident( original )
|
1112
1336
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1113
|
-
expect( escaped ).to eq( "\"
|
1337
|
+
expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
|
1114
1338
|
end
|
1115
1339
|
|
1116
1340
|
it "uses the previous string encoding for escaped string" do
|
1117
|
-
original = "
|
1341
|
+
original = "Möhre to 'scape".encode( "iso-8859-1" )
|
1118
1342
|
@conn.set_client_encoding( "euc_jp" )
|
1119
1343
|
escaped = described_class.escape( original )
|
1120
1344
|
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1121
|
-
expect( escaped ).to eq( "
|
1345
|
+
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
|
1122
1346
|
end
|
1123
1347
|
|
1124
1348
|
it "uses the previous string encoding for quote_ident" do
|
1125
|
-
original = "
|
1349
|
+
original = "Möhre to 'scape".encode( "iso-8859-1" )
|
1126
1350
|
@conn.set_client_encoding( "euc_jp" )
|
1127
1351
|
escaped = described_class.quote_ident( original )
|
1128
1352
|
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1129
|
-
expect( escaped ).to eq( "\"
|
1353
|
+
expect( escaped.encode ).to eq( "\"Möhre to 'scape\"".encode(Encoding::ISO8859_1) )
|
1130
1354
|
end
|
1131
1355
|
|
1356
|
+
it "raises appropriate error if set_client_encoding is called with invalid arguments" do
|
1357
|
+
expect { @conn.set_client_encoding( "invalid" ) }.to raise_error(PG::Error, /invalid value/)
|
1358
|
+
expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
|
1359
|
+
expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
|
1360
|
+
end
|
1132
1361
|
end
|
1133
1362
|
|
1363
|
+
describe "respect and convert character encoding of input strings" do
|
1364
|
+
before :each do
|
1365
|
+
@conn.internal_encoding = __ENCODING__
|
1366
|
+
end
|
1367
|
+
|
1368
|
+
it "should convert query string and parameters to #exec_params" do
|
1369
|
+
r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
|
1370
|
+
['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
|
1371
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
it "should convert query string to #exec" do
|
1375
|
+
r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
|
1376
|
+
expect( r.values ).to eq( [['grün']] )
|
1377
|
+
end
|
1378
|
+
|
1379
|
+
it "should convert strings and parameters to #prepare and #exec_prepared" do
|
1380
|
+
@conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
|
1381
|
+
r = @conn.exec_prepared("weiß1".encode("utf-32le"),
|
1382
|
+
['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
|
1383
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1384
|
+
end
|
1385
|
+
|
1386
|
+
it "should convert strings to #describe_prepared" do
|
1387
|
+
@conn.prepare("weiß2", "VALUES(123)")
|
1388
|
+
r = @conn.describe_prepared("weiß2".encode("utf-16be"))
|
1389
|
+
expect( r.nfields ).to eq( 1 )
|
1390
|
+
end
|
1391
|
+
|
1392
|
+
it "should convert strings to #describe_portal" do
|
1393
|
+
@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
|
1394
|
+
r = @conn.describe_portal("cörsör".encode("utf-16le"))
|
1395
|
+
expect( r.nfields ).to eq( 3 )
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
it "should convert query string to #send_query" do
|
1399
|
+
@conn.send_query("VALUES('grün')".encode("utf-16be"))
|
1400
|
+
expect( @conn.get_last_result.values ).to eq( [['grün']] )
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
it "should convert query string and parameters to #send_query_params" do
|
1404
|
+
@conn.send_query_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
|
1405
|
+
['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
|
1406
|
+
expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
|
1410
|
+
@conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
|
1411
|
+
@conn.get_last_result
|
1412
|
+
@conn.send_query_prepared("weiß3".encode("utf-32le"),
|
1413
|
+
['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
|
1414
|
+
expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
it "should convert strings to #send_describe_prepared" do
|
1418
|
+
@conn.prepare("weiß4", "VALUES(123)")
|
1419
|
+
@conn.send_describe_prepared("weiß4".encode("utf-16be"))
|
1420
|
+
expect( @conn.get_last_result.nfields ).to eq( 1 )
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
it "should convert strings to #send_describe_portal" do
|
1424
|
+
@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
|
1425
|
+
@conn.send_describe_portal("cörsör".encode("utf-16le"))
|
1426
|
+
expect( @conn.get_last_result.nfields ).to eq( 3 )
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
it "should convert error string to #put_copy_end" do
|
1430
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1431
|
+
@conn.exec( "COPY copytable FROM STDIN" )
|
1432
|
+
@conn.put_copy_end("grün".encode("utf-16be"))
|
1433
|
+
expect( @conn.get_result.error_message ).to match(/grün/)
|
1434
|
+
@conn.get_result
|
1435
|
+
end
|
1436
|
+
end
|
1437
|
+
|
1438
|
+
it "rejects command strings with zero bytes" do
|
1439
|
+
expect{ @conn.exec( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1440
|
+
expect{ @conn.exec_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
|
1441
|
+
expect{ @conn.prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
|
1442
|
+
expect{ @conn.prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1443
|
+
expect{ @conn.exec_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
|
1444
|
+
expect{ @conn.describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1445
|
+
expect{ @conn.describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1446
|
+
expect{ @conn.send_query( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1447
|
+
expect{ @conn.send_query_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
|
1448
|
+
expect{ @conn.send_prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
|
1449
|
+
expect{ @conn.send_prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1450
|
+
expect{ @conn.send_query_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
|
1451
|
+
expect{ @conn.send_describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1452
|
+
expect{ @conn.send_describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
|
1453
|
+
end
|
1454
|
+
|
1455
|
+
it "rejects query params with zero bytes" do
|
1456
|
+
expect{ @conn.exec_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
|
1457
|
+
expect{ @conn.exec_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
|
1458
|
+
expect{ @conn.send_query_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
|
1459
|
+
expect{ @conn.send_query_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
|
1460
|
+
end
|
1461
|
+
|
1462
|
+
it "rejects string with zero bytes in escape" do
|
1463
|
+
expect{ @conn.escape( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
|
1464
|
+
end
|
1465
|
+
|
1466
|
+
it "rejects string with zero bytes in escape_literal" do
|
1467
|
+
expect{ @conn.escape_literal( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
it "rejects string with zero bytes in escape_identifier" do
|
1471
|
+
expect{ @conn.escape_identifier( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
|
1472
|
+
end
|
1473
|
+
|
1474
|
+
it "rejects string with zero bytes in quote_ident" do
|
1475
|
+
expect{ described_class.quote_ident( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
it "rejects Array with string with zero bytes" do
|
1479
|
+
original = ["xyz", "2\x00"]
|
1480
|
+
expect{ described_class.quote_ident( original ) }.to raise_error(ArgumentError, /null byte/)
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
it "can quote bigger strings with quote_ident" do
|
1484
|
+
original = "'01234567\"" * 100
|
1485
|
+
escaped = described_class.quote_ident( original )
|
1486
|
+
expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
|
1487
|
+
end
|
1488
|
+
|
1489
|
+
it "can quote Arrays with quote_ident" do
|
1490
|
+
original = "'01234567\""
|
1491
|
+
escaped = described_class.quote_ident( [original]*3 )
|
1492
|
+
expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
|
1493
|
+
expect( escaped ).to eq( expected.join(".") )
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
it "will raise a TypeError for invalid arguments to quote_ident" do
|
1497
|
+
expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
|
1498
|
+
expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
|
1499
|
+
expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
|
1500
|
+
end
|
1134
1501
|
|
1135
1502
|
describe "Ruby 1.9.x default_internal encoding" do
|
1136
1503
|
|
@@ -1143,12 +1510,12 @@ describe PG::Connection do
|
|
1143
1510
|
|
1144
1511
|
begin
|
1145
1512
|
prev_encoding = Encoding.default_internal
|
1146
|
-
Encoding.default_internal = Encoding::
|
1513
|
+
Encoding.default_internal = Encoding::ISO8859_2
|
1147
1514
|
|
1148
1515
|
conn = PG.connect( @conninfo )
|
1149
|
-
expect( conn.internal_encoding ).to eq( Encoding::
|
1516
|
+
expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
|
1150
1517
|
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
1151
|
-
expect( res[0]['foo'].encoding ).to eq( Encoding::
|
1518
|
+
expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
|
1152
1519
|
ensure
|
1153
1520
|
conn.exec( "DROP TABLE defaultinternaltest" )
|
1154
1521
|
conn.finish if conn
|
@@ -1193,7 +1560,7 @@ describe PG::Connection do
|
|
1193
1560
|
conn.finish if conn
|
1194
1561
|
end
|
1195
1562
|
|
1196
|
-
it "handles clearing result in or after set_notice_receiver"
|
1563
|
+
it "handles clearing result in or after set_notice_receiver" do
|
1197
1564
|
r = nil
|
1198
1565
|
@conn.set_notice_receiver do |result|
|
1199
1566
|
r = result
|
@@ -1208,7 +1575,7 @@ describe PG::Connection do
|
|
1208
1575
|
@conn.set_notice_receiver
|
1209
1576
|
end
|
1210
1577
|
|
1211
|
-
it "receives properly encoded messages in the notice callbacks"
|
1578
|
+
it "receives properly encoded messages in the notice callbacks" do
|
1212
1579
|
[:receiver, :processor].each do |kind|
|
1213
1580
|
notices = []
|
1214
1581
|
@conn.internal_encoding = 'utf-8'
|
@@ -1236,7 +1603,7 @@ describe PG::Connection do
|
|
1236
1603
|
end
|
1237
1604
|
end
|
1238
1605
|
|
1239
|
-
it "receives properly encoded text from wait_for_notify"
|
1606
|
+
it "receives properly encoded text from wait_for_notify" do
|
1240
1607
|
@conn.internal_encoding = 'utf-8'
|
1241
1608
|
@conn.exec( 'ROLLBACK' )
|
1242
1609
|
@conn.exec( 'LISTEN "Möhre"' )
|
@@ -1253,7 +1620,7 @@ describe PG::Connection do
|
|
1253
1620
|
expect( msg.encoding ).to eq( Encoding::UTF_8 )
|
1254
1621
|
end
|
1255
1622
|
|
1256
|
-
it "returns properly encoded text from notifies"
|
1623
|
+
it "returns properly encoded text from notifies" do
|
1257
1624
|
@conn.internal_encoding = 'utf-8'
|
1258
1625
|
@conn.exec( 'ROLLBACK' )
|
1259
1626
|
@conn.exec( 'LISTEN "Möhre"' )
|
@@ -1309,9 +1676,14 @@ describe PG::Connection do
|
|
1309
1676
|
end
|
1310
1677
|
|
1311
1678
|
it "shouldn't type map params unless requested" do
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1679
|
+
if @conn.server_version < 100000
|
1680
|
+
expect{
|
1681
|
+
@conn.exec_params( "SELECT $1", [5] )
|
1682
|
+
}.to raise_error(PG::IndeterminateDatatype)
|
1683
|
+
else
|
1684
|
+
# PostgreSQL-10 maps to TEXT type (OID 25)
|
1685
|
+
expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
|
1686
|
+
end
|
1315
1687
|
end
|
1316
1688
|
|
1317
1689
|
it "should raise an error on invalid encoder to put_copy_data" do
|
@@ -1378,15 +1750,15 @@ describe PG::Connection do
|
|
1378
1750
|
end
|
1379
1751
|
end
|
1380
1752
|
|
1381
|
-
it "can process #copy_data input queries with row encoder" do
|
1753
|
+
it "can process #copy_data input queries with row encoder and respects character encoding" do
|
1382
1754
|
@conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1383
1755
|
res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1384
1756
|
@conn2.put_copy_data [1]
|
1385
|
-
@conn2.put_copy_data ["
|
1757
|
+
@conn2.put_copy_data ["Möhre".encode("utf-16le")]
|
1386
1758
|
end
|
1387
1759
|
|
1388
1760
|
res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1389
|
-
expect( res.values ).to eq( [["1"], ["
|
1761
|
+
expect( res.values ).to eq( [["1"], ["Möhre"]] )
|
1390
1762
|
end
|
1391
1763
|
end
|
1392
1764
|
|
@@ -1428,14 +1800,16 @@ describe PG::Connection do
|
|
1428
1800
|
end
|
1429
1801
|
end
|
1430
1802
|
|
1431
|
-
it "can process #copy_data output with row decoder" do
|
1803
|
+
it "can process #copy_data output with row decoder and respects character encoding" do
|
1804
|
+
@conn2.internal_encoding = Encoding::ISO8859_1
|
1432
1805
|
rows = []
|
1433
|
-
res2 = @conn2.copy_data( "COPY (
|
1806
|
+
res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
|
1434
1807
|
while row=@conn2.get_copy_data
|
1435
1808
|
rows << row
|
1436
1809
|
end
|
1437
1810
|
end
|
1438
|
-
expect( rows ).to eq(
|
1811
|
+
expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
|
1812
|
+
expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
|
1439
1813
|
end
|
1440
1814
|
|
1441
1815
|
it "can type cast #copy_data output with explicit decoder" do
|
@@ -1456,4 +1830,43 @@ describe PG::Connection do
|
|
1456
1830
|
end
|
1457
1831
|
end
|
1458
1832
|
end
|
1833
|
+
|
1834
|
+
describe "deprecated forms of methods" do
|
1835
|
+
it "should forward exec to exec_params" do
|
1836
|
+
res = @conn.exec("VALUES($1::INT)", [7]).values
|
1837
|
+
expect(res).to eq( [["7"]] )
|
1838
|
+
res = @conn.exec("VALUES($1::INT)", [7], 1).values
|
1839
|
+
expect(res).to eq( [[[7].pack("N")]] )
|
1840
|
+
res = @conn.exec("VALUES(8)", [], 1).values
|
1841
|
+
expect(res).to eq( [[[8].pack("N")]] )
|
1842
|
+
end
|
1843
|
+
|
1844
|
+
it "should forward exec_params to exec" do
|
1845
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)").values
|
1846
|
+
expect(res).to eq( [["4"]] )
|
1847
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)", nil).values
|
1848
|
+
expect(res).to eq( [["4"]] )
|
1849
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil).values
|
1850
|
+
expect(res).to eq( [["4"]] )
|
1851
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, 1).values
|
1852
|
+
expect(res).to eq( [["4"]] )
|
1853
|
+
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil).values
|
1854
|
+
expect(res).to eq( [["4"]] )
|
1855
|
+
expect{
|
1856
|
+
@conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil, nil).values
|
1857
|
+
}.to raise_error(ArgumentError)
|
1858
|
+
end
|
1859
|
+
|
1860
|
+
it "should forward send_query to send_query_params" do
|
1861
|
+
@conn.send_query("VALUES($1)", [5])
|
1862
|
+
expect(@conn.get_last_result.values).to eq( [["5"]] )
|
1863
|
+
end
|
1864
|
+
|
1865
|
+
it "shouldn't forward send_query_params to send_query" do
|
1866
|
+
expect{ @conn.send_query_params("VALUES(4)").values }
|
1867
|
+
.to raise_error(ArgumentError)
|
1868
|
+
expect{ @conn.send_query_params("VALUES(4)", nil).values }
|
1869
|
+
.to raise_error(TypeError)
|
1870
|
+
end
|
1871
|
+
end
|
1459
1872
|
end
|