pg 0.15.1 → 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 -3022
- data/History.rdoc +370 -4
- data/Manifest.txt +39 -19
- data/README-Windows.rdoc +17 -28
- data/README.ja.rdoc +1 -2
- data/README.rdoc +113 -14
- data/Rakefile +97 -36
- data/Rakefile.cross +109 -83
- data/ext/errorcodes.def +1032 -0
- data/ext/errorcodes.rb +45 -0
- data/ext/errorcodes.txt +494 -0
- data/ext/extconf.rb +55 -52
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +94 -38
- data/ext/pg.c +273 -121
- data/ext/pg.h +292 -50
- data/ext/pg_binary_decoder.c +229 -0
- data/ext/pg_binary_encoder.c +163 -0
- data/ext/pg_coder.c +561 -0
- data/ext/pg_connection.c +1811 -1051
- data/ext/pg_copy_coder.c +599 -0
- data/ext/pg_errors.c +95 -0
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +917 -203
- data/ext/pg_text_decoder.c +987 -0
- data/ext/pg_text_encoder.c +814 -0
- data/ext/pg_tuple.c +549 -0
- data/ext/pg_type_map.c +166 -0
- data/ext/pg_type_map_all_strings.c +116 -0
- data/ext/pg_type_map_by_class.c +244 -0
- data/ext/pg_type_map_by_column.c +313 -0
- data/ext/pg_type_map_by_mri_type.c +284 -0
- data/ext/pg_type_map_by_oid.c +356 -0
- data/ext/pg_type_map_in_ruby.c +299 -0
- data/ext/pg_util.c +149 -0
- data/ext/pg_util.h +65 -0
- data/lib/pg.rb +31 -9
- data/lib/pg/basic_type_mapping.rb +522 -0
- data/lib/pg/binary_decoder.rb +23 -0
- data/lib/pg/coder.rb +104 -0
- data/lib/pg/connection.rb +235 -30
- data/lib/pg/constants.rb +2 -1
- data/lib/pg/exceptions.rb +2 -1
- data/lib/pg/result.rb +33 -6
- data/lib/pg/text_decoder.rb +46 -0
- data/lib/pg/text_encoder.rb +59 -0
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +16 -0
- data/spec/{lib/helpers.rb → helpers.rb} +154 -52
- data/spec/pg/basic_type_mapping_spec.rb +630 -0
- data/spec/pg/connection_spec.rb +1352 -426
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +508 -105
- data/spec/pg/tuple_spec.rb +333 -0
- data/spec/pg/type_map_by_class_spec.rb +138 -0
- data/spec/pg/type_map_by_column_spec.rb +226 -0
- data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
- data/spec/pg/type_map_by_oid_spec.rb +149 -0
- data/spec/pg/type_map_in_ruby_spec.rb +164 -0
- data/spec/pg/type_map_spec.rb +22 -0
- data/spec/pg/type_spec.rb +1123 -0
- data/spec/pg_spec.rb +35 -16
- metadata +163 -84
- 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,49 +1,14 @@
|
|
1
|
-
|
1
|
+
# -*- rspec -*-
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
|
-
|
5
|
-
require 'pathname'
|
4
|
+
require_relative '../helpers'
|
6
5
|
|
7
|
-
basedir = Pathname( __FILE__ ).dirname.parent.parent
|
8
|
-
libdir = basedir + 'lib'
|
9
|
-
|
10
|
-
$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
|
11
|
-
$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
|
12
|
-
}
|
13
|
-
|
14
|
-
require 'rspec'
|
15
|
-
require 'spec/lib/helpers'
|
16
6
|
require 'timeout'
|
17
7
|
require 'socket'
|
18
8
|
require 'pg'
|
19
9
|
|
20
10
|
describe PG::Connection do
|
21
11
|
|
22
|
-
before( :all ) do
|
23
|
-
@conn = setup_testing_db( described_class.name )
|
24
|
-
end
|
25
|
-
|
26
|
-
before( :each ) do
|
27
|
-
@conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
|
28
|
-
if PG.respond_to?( :library_version )
|
29
|
-
@conn.exec_params %Q{SET application_name TO '%s'} %
|
30
|
-
[@conn.escape_string(example.description[0,60])]
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
after( :each ) do
|
35
|
-
@conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
|
36
|
-
end
|
37
|
-
|
38
|
-
after( :all ) do
|
39
|
-
teardown_testing_db( @conn )
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
# Examples
|
45
|
-
#
|
46
|
-
|
47
12
|
it "can create a connection option string from a Hash of options" do
|
48
13
|
optstring = described_class.parse_connect_args(
|
49
14
|
:host => 'pgsql.example.com',
|
@@ -51,54 +16,130 @@ describe PG::Connection do
|
|
51
16
|
'sslmode' => 'require'
|
52
17
|
)
|
53
18
|
|
54
|
-
optstring.
|
55
|
-
optstring.
|
56
|
-
optstring.
|
57
|
-
optstring.
|
19
|
+
expect( optstring ).to be_a( String )
|
20
|
+
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
21
|
+
expect( optstring ).to match( /(^|\s)dbname='db01'/ )
|
22
|
+
expect( optstring ).to match( /(^|\s)sslmode='require'/ )
|
58
23
|
end
|
59
24
|
|
60
25
|
it "can create a connection option string from positional parameters" do
|
61
26
|
optstring = described_class.parse_connect_args( 'pgsql.example.com', nil, '-c geqo=off', nil,
|
62
27
|
'sales' )
|
63
28
|
|
64
|
-
optstring.
|
65
|
-
optstring.
|
66
|
-
optstring.
|
67
|
-
optstring.
|
29
|
+
expect( optstring ).to be_a( String )
|
30
|
+
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
31
|
+
expect( optstring ).to match( /(^|\s)dbname='sales'/ )
|
32
|
+
expect( optstring ).to match( /(^|\s)options='-c geqo=off'/ )
|
68
33
|
|
69
|
-
optstring.
|
70
|
-
optstring.
|
34
|
+
expect( optstring ).to_not match( /port=/ )
|
35
|
+
expect( optstring ).to_not match( /tty=/ )
|
71
36
|
end
|
72
37
|
|
73
38
|
it "can create a connection option string from a mix of positional and hash parameters" do
|
74
39
|
optstring = described_class.parse_connect_args( 'pgsql.example.com',
|
75
40
|
:dbname => 'licensing', :user => 'jrandom' )
|
76
41
|
|
77
|
-
optstring.
|
78
|
-
optstring.
|
79
|
-
optstring.
|
80
|
-
optstring.
|
42
|
+
expect( optstring ).to be_a( String )
|
43
|
+
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
44
|
+
expect( optstring ).to match( /(^|\s)dbname='licensing'/ )
|
45
|
+
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
46
|
+
end
|
47
|
+
|
48
|
+
it "can create a connection option string from an option string and a hash" do
|
49
|
+
optstring = described_class.parse_connect_args( 'dbname=original', :user => 'jrandom' )
|
50
|
+
|
51
|
+
expect( optstring ).to be_a( String )
|
52
|
+
expect( optstring ).to match( /(^|\s)dbname=original/ )
|
53
|
+
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
81
54
|
end
|
82
55
|
|
83
56
|
it "escapes single quotes and backslashes in connection parameters" do
|
84
|
-
|
85
|
-
|
57
|
+
expect(
|
58
|
+
described_class.parse_connect_args( "DB 'browser' \\" )
|
59
|
+
).to match( /host='DB \\'browser\\' \\\\'/ )
|
86
60
|
|
87
61
|
end
|
88
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
|
+
|
89
117
|
it "connects with defaults if no connection parameters are given" do
|
90
|
-
described_class.parse_connect_args.
|
118
|
+
expect( described_class.parse_connect_args ).to eq( '' )
|
119
|
+
end
|
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} )
|
91
132
|
end
|
92
133
|
|
93
134
|
it "connects successfully with connection string" do
|
94
|
-
tmpconn = described_class.connect(@conninfo)
|
95
|
-
tmpconn.status.
|
135
|
+
tmpconn = described_class.connect( @conninfo )
|
136
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
96
137
|
tmpconn.finish
|
97
138
|
end
|
98
139
|
|
99
140
|
it "connects using 7 arguments converted to strings" do
|
100
|
-
tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
|
101
|
-
tmpconn.status.
|
141
|
+
tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
|
142
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
102
143
|
tmpconn.finish
|
103
144
|
end
|
104
145
|
|
@@ -107,87 +148,160 @@ describe PG::Connection do
|
|
107
148
|
:host => 'localhost',
|
108
149
|
:port => @port,
|
109
150
|
:dbname => :test)
|
110
|
-
tmpconn.status.
|
151
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
111
152
|
tmpconn.finish
|
112
153
|
end
|
113
154
|
|
114
|
-
it "connects using a hash of optional connection parameters"
|
155
|
+
it "connects using a hash of optional connection parameters" do
|
115
156
|
tmpconn = described_class.connect(
|
116
157
|
:host => 'localhost',
|
117
158
|
:port => @port,
|
118
159
|
:dbname => :test,
|
119
160
|
:keepalives => 1)
|
120
|
-
tmpconn.status.
|
161
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
121
162
|
tmpconn.finish
|
122
163
|
end
|
123
164
|
|
124
165
|
it "raises an exception when connecting with an invalid number of arguments" do
|
125
166
|
expect {
|
126
|
-
described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
|
127
|
-
}.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
|
128
174
|
end
|
129
175
|
|
130
|
-
it "can connect asynchronously"
|
176
|
+
it "can connect asynchronously" do
|
131
177
|
tmpconn = described_class.connect_start( @conninfo )
|
132
|
-
tmpconn.
|
133
|
-
socket = tmpconn.socket_io
|
134
|
-
status = tmpconn.connect_poll
|
135
|
-
|
136
|
-
while status != PG::PGRES_POLLING_OK
|
137
|
-
if status == PG::PGRES_POLLING_READING
|
138
|
-
select( [socket], [], [], 5.0 ) or
|
139
|
-
raise "Asynchronous connection timed out!"
|
178
|
+
expect( tmpconn ).to be_a( described_class )
|
140
179
|
|
141
|
-
|
142
|
-
|
143
|
-
raise "Asynchronous connection timed out!"
|
144
|
-
end
|
145
|
-
status = tmpconn.connect_poll
|
146
|
-
end
|
147
|
-
|
148
|
-
tmpconn.status.should == PG::CONNECTION_OK
|
180
|
+
wait_for_polling_ok(tmpconn)
|
181
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
149
182
|
tmpconn.finish
|
150
183
|
end
|
151
184
|
|
152
|
-
it "can connect asynchronously for the duration of a block"
|
185
|
+
it "can connect asynchronously for the duration of a block" do
|
153
186
|
conn = nil
|
154
187
|
|
155
188
|
described_class.connect_start(@conninfo) do |tmpconn|
|
156
|
-
tmpconn.
|
189
|
+
expect( tmpconn ).to be_a( described_class )
|
157
190
|
conn = tmpconn
|
158
|
-
socket = tmpconn.socket_io
|
159
|
-
status = tmpconn.connect_poll
|
160
191
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
192
|
+
wait_for_polling_ok(tmpconn)
|
193
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
194
|
+
end
|
195
|
+
|
196
|
+
expect( conn ).to be_finished()
|
197
|
+
end
|
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
|
173
210
|
|
174
|
-
|
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"]])
|
175
215
|
end
|
176
216
|
|
177
|
-
conn.
|
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
|
+
|
264
|
+
it "raises proper error when sending fails" do
|
265
|
+
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
266
|
+
expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
|
178
267
|
end
|
179
268
|
|
180
269
|
it "doesn't leave stale server connections after finish" do
|
181
270
|
described_class.connect(@conninfo).finish
|
182
271
|
sleep 0.5
|
183
272
|
res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
|
184
|
-
WHERE usename IS NOT NULL])
|
273
|
+
WHERE usename IS NOT NULL AND application_name != ''])
|
185
274
|
# there's still the global @conn, but should be no more
|
186
|
-
res[0]['n'].
|
275
|
+
expect( res[0]['n'] ).to eq( '1' )
|
187
276
|
end
|
188
277
|
|
278
|
+
it "can retrieve it's connection parameters for the established connection" do
|
279
|
+
expect( @conn.db ).to eq( "test" )
|
280
|
+
expect( @conn.user ).to be_a_kind_of( String )
|
281
|
+
expect( @conn.pass ).to eq( "" )
|
282
|
+
expect( @conn.port ).to eq( @port )
|
283
|
+
expect( @conn.tty ).to eq( "" )
|
284
|
+
expect( @conn.options ).to eq( "" )
|
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
|
290
|
+
|
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
|
189
296
|
|
190
|
-
|
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
|
+
%{
|
191
305
|
To backend> Msg Q
|
192
306
|
To backend> "SELECT 1 AS one"
|
193
307
|
To backend> Msg complete, length 21
|
@@ -215,6 +329,7 @@ describe PG::Connection do
|
|
215
329
|
From backend (#4)> 5
|
216
330
|
From backend> T
|
217
331
|
}.gsub( /^\t{2}/, '' ).lstrip
|
332
|
+
end
|
218
333
|
|
219
334
|
it "trace and untrace client-server communication", :unix do
|
220
335
|
# be careful to explicitly close files so that the
|
@@ -225,25 +340,22 @@ describe PG::Connection do
|
|
225
340
|
@conn.trace( trace_io )
|
226
341
|
trace_io.close
|
227
342
|
|
228
|
-
|
343
|
+
@conn.exec("SELECT 1 AS one")
|
229
344
|
@conn.untrace
|
230
345
|
|
231
|
-
|
346
|
+
@conn.exec("SELECT 2 AS two")
|
232
347
|
|
233
348
|
trace_data = trace_file.read
|
234
349
|
|
235
|
-
|
236
|
-
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
# +From backend (#4)>
|
240
|
-
#
|
241
|
-
|
242
|
-
expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
|
243
|
-
expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
|
244
|
-
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' )
|
245
357
|
|
246
|
-
trace_data.
|
358
|
+
expect( trace_data ).to eq( expected_trace_output )
|
247
359
|
end
|
248
360
|
|
249
361
|
it "allows a query to be cancelled" do
|
@@ -254,9 +366,36 @@ describe PG::Connection do
|
|
254
366
|
if(tmpres.result_status != PG::PGRES_TUPLES_OK)
|
255
367
|
error = true
|
256
368
|
end
|
257
|
-
error.
|
369
|
+
expect( error ).to eq( true )
|
370
|
+
end
|
371
|
+
|
372
|
+
it "can stop a thread that runs a blocking query with async_exec" do
|
373
|
+
start = Time.now
|
374
|
+
t = Thread.new do
|
375
|
+
@conn.async_exec( 'select pg_sleep(10)' )
|
376
|
+
end
|
377
|
+
sleep 0.1
|
378
|
+
|
379
|
+
t.kill
|
380
|
+
t.join
|
381
|
+
expect( (Time.now - start) ).to be < 10
|
258
382
|
end
|
259
383
|
|
384
|
+
it "should work together with signal handlers", :unix do
|
385
|
+
signal_received = false
|
386
|
+
trap 'USR2' do
|
387
|
+
signal_received = true
|
388
|
+
end
|
389
|
+
|
390
|
+
Thread.new do
|
391
|
+
sleep 0.1
|
392
|
+
Process.kill("USR2", Process.pid)
|
393
|
+
end
|
394
|
+
@conn.exec("select pg_sleep(0.3)")
|
395
|
+
expect( signal_received ).to be_truthy
|
396
|
+
end
|
397
|
+
|
398
|
+
|
260
399
|
it "automatically rolls back a transaction started with Connection#transaction if an exception " +
|
261
400
|
"is raised" do
|
262
401
|
# abort the per-example transaction so we can test our own
|
@@ -265,15 +404,29 @@ describe PG::Connection do
|
|
265
404
|
res = nil
|
266
405
|
@conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
|
267
406
|
|
268
|
-
|
269
|
-
|
270
|
-
@conn.
|
271
|
-
|
272
|
-
|
273
|
-
|
407
|
+
begin
|
408
|
+
expect {
|
409
|
+
res = @conn.transaction do
|
410
|
+
@conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
|
411
|
+
raise "Oh noes! All pie is gone!"
|
412
|
+
end
|
413
|
+
}.to raise_exception( RuntimeError, /all pie is gone/i )
|
274
414
|
|
275
|
-
|
276
|
-
|
415
|
+
res = @conn.exec( "SELECT * FROM pie" )
|
416
|
+
expect( res.ntuples ).to eq( 0 )
|
417
|
+
ensure
|
418
|
+
@conn.exec( "DROP TABLE pie" )
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
it "returns the block result from Connection#transaction" do
|
423
|
+
# abort the per-example transaction so we can test our own
|
424
|
+
@conn.exec( 'ROLLBACK' )
|
425
|
+
|
426
|
+
res = @conn.transaction do
|
427
|
+
"transaction result"
|
428
|
+
end
|
429
|
+
expect( res ).to eq( "transaction result" )
|
277
430
|
end
|
278
431
|
|
279
432
|
it "not read past the end of a large object" do
|
@@ -281,33 +434,45 @@ describe PG::Connection do
|
|
281
434
|
oid = @conn.lo_create( 0 )
|
282
435
|
fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
|
283
436
|
@conn.lo_write( fd, "foobar" )
|
284
|
-
@conn.lo_read( fd, 10 ).
|
437
|
+
expect( @conn.lo_read( fd, 10 ) ).to be_nil()
|
285
438
|
@conn.lo_lseek( fd, 0, PG::SEEK_SET )
|
286
|
-
@conn.lo_read( fd, 10 ).
|
439
|
+
expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
|
287
440
|
end
|
288
441
|
end
|
289
442
|
|
290
|
-
|
291
|
-
it "supports parameters passed to #exec (backward compatibility)" do
|
292
|
-
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
293
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
|
294
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
|
295
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
296
|
-
|
297
|
-
res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
|
298
|
-
res.values.should == [ ['Wally'], ['Sally'] ]
|
299
|
-
end
|
300
|
-
|
301
443
|
it "supports explicitly calling #exec_params" do
|
302
444
|
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
303
|
-
@conn.
|
304
|
-
@conn.
|
305
|
-
@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] )
|
306
448
|
|
307
449
|
res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
|
308
|
-
res.values.
|
450
|
+
expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
|
451
|
+
end
|
452
|
+
|
453
|
+
it "supports hash form parameters for #exec_params" do
|
454
|
+
hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
|
455
|
+
hash_param_nil = { value: nil, type: 17, format: 1 }
|
456
|
+
res = @conn.exec_params( "SELECT $1, $2",
|
457
|
+
[ hash_param_bin, hash_param_nil ] )
|
458
|
+
expect( res.values ).to eq( [["\\x00ff", nil]] )
|
459
|
+
expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
|
309
460
|
end
|
310
461
|
|
462
|
+
it "should work with arbitrary number of params" do
|
463
|
+
begin
|
464
|
+
3.step( 12, 0.2 ) do |exp|
|
465
|
+
num_params = (2 ** exp).to_i
|
466
|
+
sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
|
467
|
+
params = num_params.times.to_a
|
468
|
+
res = @conn.exec_params( "SELECT #{sql}", params )
|
469
|
+
expect( res.nfields ).to eq( num_params )
|
470
|
+
expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
|
471
|
+
end
|
472
|
+
rescue PG::ProgramLimitExceeded
|
473
|
+
# Stop silently if the server complains about too many params
|
474
|
+
end
|
475
|
+
end
|
311
476
|
|
312
477
|
it "can wait for NOTIFY events" do
|
313
478
|
@conn.exec( 'ROLLBACK' )
|
@@ -323,7 +488,7 @@ describe PG::Connection do
|
|
323
488
|
end
|
324
489
|
end
|
325
490
|
|
326
|
-
@conn.wait_for_notify( 10 ).
|
491
|
+
expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
|
327
492
|
@conn.exec( 'UNLISTEN woo' )
|
328
493
|
|
329
494
|
t.join
|
@@ -345,8 +510,8 @@ describe PG::Connection do
|
|
345
510
|
|
346
511
|
eventpid = event = nil
|
347
512
|
@conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
|
348
|
-
event.
|
349
|
-
eventpid.
|
513
|
+
expect( event ).to eq( 'woo' )
|
514
|
+
expect( eventpid ).to be_an( Integer )
|
350
515
|
|
351
516
|
@conn.exec( 'UNLISTEN woo' )
|
352
517
|
|
@@ -373,8 +538,8 @@ describe PG::Connection do
|
|
373
538
|
channels << @conn.wait_for_notify( 2 )
|
374
539
|
end
|
375
540
|
|
376
|
-
channels.
|
377
|
-
channels.
|
541
|
+
expect( channels.size ).to eq( 3 )
|
542
|
+
expect( channels ).to include( 'woo', 'war', 'woz' )
|
378
543
|
|
379
544
|
@conn.exec( 'UNLISTEN woz' )
|
380
545
|
@conn.exec( 'UNLISTEN war' )
|
@@ -396,25 +561,161 @@ describe PG::Connection do
|
|
396
561
|
# Cause the notification to buffer, but not be read yet
|
397
562
|
@conn.exec( 'SELECT 1' )
|
398
563
|
|
399
|
-
@conn.wait_for_notify( 10 ).
|
564
|
+
expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
|
400
565
|
@conn.exec( 'UNLISTEN woo' )
|
401
566
|
end
|
402
567
|
|
568
|
+
it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
|
569
|
+
notices = []
|
570
|
+
@conn.set_notice_processor do |msg|
|
571
|
+
notices << [msg, Time.now]
|
572
|
+
end
|
573
|
+
st = Time.now
|
574
|
+
@conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
|
575
|
+
expect( @conn.wait_for_notify( 1 ) ).to be_nil
|
576
|
+
expect( notices.first ).to_not be_nil
|
577
|
+
et = Time.now
|
578
|
+
expect( (et - notices.first[1]) ).to be >= 0.3
|
579
|
+
expect( (et - st) ).to be >= 0.9
|
580
|
+
expect( (et - st) ).to be < 1.4
|
581
|
+
end
|
582
|
+
|
403
583
|
it "yields the result if block is given to exec" do
|
404
584
|
rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
|
405
585
|
values = []
|
406
|
-
result.
|
407
|
-
result.ntuples.
|
586
|
+
expect( result ).to be_kind_of( PG::Result )
|
587
|
+
expect( result.ntuples ).to eq( 2 )
|
408
588
|
result.each do |tuple|
|
409
589
|
values << tuple['a']
|
410
590
|
end
|
411
591
|
values
|
412
592
|
end
|
413
593
|
|
414
|
-
rval.
|
415
|
-
rval.
|
594
|
+
expect( rval.size ).to eq( 2 )
|
595
|
+
expect( rval ).to include( '5678', '1234' )
|
416
596
|
end
|
417
597
|
|
598
|
+
it "can process #copy_data output queries" do
|
599
|
+
rows = []
|
600
|
+
res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
|
601
|
+
expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
|
602
|
+
expect( res.nfields ).to eq( 1 )
|
603
|
+
while row=@conn.get_copy_data
|
604
|
+
rows << row
|
605
|
+
end
|
606
|
+
end
|
607
|
+
expect( rows ).to eq( ["1\n", "2\n"] )
|
608
|
+
expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
|
609
|
+
expect( @conn ).to still_be_usable
|
610
|
+
end
|
611
|
+
|
612
|
+
it "can handle incomplete #copy_data output queries" do
|
613
|
+
expect {
|
614
|
+
@conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
|
615
|
+
@conn.get_copy_data
|
616
|
+
end
|
617
|
+
}.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
|
618
|
+
expect( @conn ).to still_be_usable
|
619
|
+
end
|
620
|
+
|
621
|
+
it "can handle client errors in #copy_data for output" do
|
622
|
+
expect {
|
623
|
+
@conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do
|
624
|
+
raise "boom"
|
625
|
+
end
|
626
|
+
}.to raise_error(RuntimeError, "boom")
|
627
|
+
expect( @conn ).to still_be_usable
|
628
|
+
end
|
629
|
+
|
630
|
+
it "can handle server errors in #copy_data for output" do
|
631
|
+
@conn.exec "ROLLBACK"
|
632
|
+
@conn.transaction do
|
633
|
+
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
634
|
+
expect {
|
635
|
+
@conn.copy_data( "COPY (SELECT errfunc()) TO STDOUT" ) do |res|
|
636
|
+
while @conn.get_copy_data
|
637
|
+
end
|
638
|
+
end
|
639
|
+
}.to raise_error(PG::Error, /test-error/)
|
640
|
+
end
|
641
|
+
expect( @conn ).to still_be_usable
|
642
|
+
end
|
643
|
+
|
644
|
+
it "can process #copy_data input queries" do
|
645
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
646
|
+
res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
647
|
+
expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
|
648
|
+
expect( res.nfields ).to eq( 1 )
|
649
|
+
@conn.put_copy_data "1\n"
|
650
|
+
@conn.put_copy_data "2\n"
|
651
|
+
end
|
652
|
+
expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
|
653
|
+
|
654
|
+
expect( @conn ).to still_be_usable
|
655
|
+
|
656
|
+
res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
|
657
|
+
expect( res.values ).to eq( [["1"], ["2"]] )
|
658
|
+
end
|
659
|
+
|
660
|
+
it "can handle client errors in #copy_data for input" do
|
661
|
+
@conn.exec "ROLLBACK"
|
662
|
+
@conn.transaction do
|
663
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
664
|
+
expect {
|
665
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
666
|
+
raise "boom"
|
667
|
+
end
|
668
|
+
}.to raise_error(RuntimeError, "boom")
|
669
|
+
end
|
670
|
+
|
671
|
+
expect( @conn ).to still_be_usable
|
672
|
+
end
|
673
|
+
|
674
|
+
it "can handle server errors in #copy_data for input" do
|
675
|
+
@conn.exec "ROLLBACK"
|
676
|
+
@conn.transaction do
|
677
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
|
678
|
+
expect {
|
679
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
680
|
+
@conn.put_copy_data "xyz\n"
|
681
|
+
end
|
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/)
|
708
|
+
end
|
709
|
+
expect( @conn ).to still_be_usable
|
710
|
+
end
|
711
|
+
|
712
|
+
it "should raise an error for non copy statements in #copy_data" do
|
713
|
+
expect {
|
714
|
+
@conn.copy_data( "SELECT 1" ){}
|
715
|
+
}.to raise_error(ArgumentError, /no COPY/)
|
716
|
+
|
717
|
+
expect( @conn ).to still_be_usable
|
718
|
+
end
|
418
719
|
|
419
720
|
it "correctly finishes COPY queries passed to #async_exec" do
|
420
721
|
@conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
|
@@ -429,8 +730,8 @@ describe PG::Connection do
|
|
429
730
|
results << data if data
|
430
731
|
end until data.nil?
|
431
732
|
|
432
|
-
results.
|
433
|
-
results.
|
733
|
+
expect( results.size ).to eq( 2 )
|
734
|
+
expect( results ).to include( "1\n", "2\n" )
|
434
735
|
end
|
435
736
|
|
436
737
|
|
@@ -442,54 +743,138 @@ describe PG::Connection do
|
|
442
743
|
end
|
443
744
|
|
444
745
|
sleep 0.5
|
445
|
-
t.
|
746
|
+
expect( t ).to be_alive()
|
446
747
|
@conn.cancel
|
447
748
|
t.join
|
448
|
-
(Time.now - start).
|
749
|
+
expect( (Time.now - start) ).to be < 3
|
449
750
|
end
|
450
751
|
|
451
752
|
it "described_class#block should allow a timeout" do
|
452
|
-
@conn.send_query( "select pg_sleep(
|
753
|
+
@conn.send_query( "select pg_sleep(1)" )
|
453
754
|
|
454
755
|
start = Time.now
|
455
|
-
@conn.block( 0.
|
756
|
+
@conn.block( 0.3 )
|
456
757
|
finish = Time.now
|
457
758
|
|
458
|
-
(finish - start).
|
759
|
+
expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
|
459
760
|
end
|
460
761
|
|
762
|
+
it "can return the default connection options" do
|
763
|
+
expect( described_class.conndefaults ).to be_a( Array )
|
764
|
+
expect( described_class.conndefaults ).to all( be_a(Hash) )
|
765
|
+
expect( described_class.conndefaults[0] ).to include( :keyword, :label, :dispchar, :dispsize )
|
766
|
+
expect( @conn.conndefaults ).to eq( described_class.conndefaults )
|
767
|
+
end
|
461
768
|
|
462
|
-
it "can
|
463
|
-
described_class.
|
464
|
-
|
769
|
+
it "can return the default connection options as a Hash" do
|
770
|
+
expect( described_class.conndefaults_hash ).to be_a( Hash )
|
771
|
+
expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
|
772
|
+
expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
|
773
|
+
expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
|
465
774
|
end
|
466
775
|
|
776
|
+
it "can return the connection's connection options", :postgresql_93 do
|
777
|
+
expect( @conn.conninfo ).to be_a( Array )
|
778
|
+
expect( @conn.conninfo ).to all( be_a(Hash) )
|
779
|
+
expect( @conn.conninfo[0] ).to include( :keyword, :label, :dispchar, :dispsize )
|
780
|
+
end
|
467
781
|
|
468
|
-
|
469
|
-
|
470
|
-
expect
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
782
|
+
|
783
|
+
it "can return the connection's connection options as a Hash", :postgresql_93 do
|
784
|
+
expect( @conn.conninfo_hash ).to be_a( Hash )
|
785
|
+
expect( @conn.conninfo_hash ).to include( :user, :password, :connect_timeout, :dbname, :host )
|
786
|
+
expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
|
787
|
+
end
|
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
|
+
|
808
|
+
|
809
|
+
it "honors the connect_timeout connection parameter", :postgresql_93 do
|
810
|
+
conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
|
811
|
+
begin
|
812
|
+
expect( conn.conninfo_hash[:connect_timeout] ).to eq( "11" )
|
813
|
+
ensure
|
814
|
+
conn.finish
|
815
|
+
end
|
816
|
+
end
|
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
|
822
|
+
|
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
|
479
864
|
end
|
480
865
|
|
481
866
|
|
482
867
|
it "allows fetching a column of values from a result by column number" do
|
483
868
|
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
484
|
-
res.column_values( 0 ).
|
485
|
-
res.column_values( 1 ).
|
869
|
+
expect( res.column_values( 0 ) ).to eq( %w[1 2 3] )
|
870
|
+
expect( res.column_values( 1 ) ).to eq( %w[2 3 4] )
|
486
871
|
end
|
487
872
|
|
488
873
|
|
489
874
|
it "allows fetching a column of values from a result by field name" do
|
490
875
|
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
491
|
-
res.field_values( 'column1' ).
|
492
|
-
res.field_values( 'column2' ).
|
876
|
+
expect( res.field_values( 'column1' ) ).to eq( %w[1 2 3] )
|
877
|
+
expect( res.field_values( 'column2' ) ).to eq( %w[2 3 4] )
|
493
878
|
end
|
494
879
|
|
495
880
|
|
@@ -517,202 +902,245 @@ describe PG::Connection do
|
|
517
902
|
end
|
518
903
|
|
519
904
|
|
520
|
-
it "
|
905
|
+
it "handles server close while asynchronous connect" do
|
521
906
|
serv = TCPServer.new( '127.0.0.1', 54320 )
|
522
907
|
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
523
|
-
[PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK].
|
908
|
+
expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
|
524
909
|
select( nil, [conn.socket_io], nil, 0.2 )
|
525
910
|
serv.close
|
526
911
|
if conn.connect_poll == PG::PGRES_POLLING_READING
|
527
912
|
select( [conn.socket_io], nil, nil, 0.2 )
|
528
913
|
end
|
529
|
-
conn.connect_poll.
|
914
|
+
expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
|
915
|
+
end
|
916
|
+
|
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' }] )
|
530
923
|
end
|
531
924
|
|
532
|
-
it "discards previous results (if any) before waiting on
|
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
|
533
930
|
|
534
|
-
it "
|
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
|
936
|
+
|
937
|
+
it "calls the block if one is provided to #exec" do
|
535
938
|
result = nil
|
536
|
-
@conn.
|
939
|
+
@conn.exec( "select 47 as one" ) do |pg_res|
|
537
940
|
result = pg_res[0]
|
538
941
|
end
|
539
|
-
result.
|
942
|
+
expect( result ).to eq( { 'one' => '47' } )
|
540
943
|
end
|
541
944
|
|
542
945
|
it "raises a rescue-able error if #finish is called twice", :without_transaction do
|
543
946
|
conn = PG.connect( @conninfo )
|
544
947
|
|
545
948
|
conn.finish
|
546
|
-
expect { conn.finish }.to raise_error( PG::
|
949
|
+
expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
950
|
+
end
|
951
|
+
|
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
|
547
964
|
end
|
548
965
|
|
549
|
-
it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction
|
966
|
+
it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction do
|
550
967
|
conn = PG.connect( @conninfo )
|
551
968
|
io = conn.socket_io
|
552
969
|
conn.finish
|
553
|
-
io.
|
554
|
-
expect { conn.socket_io }.to raise_error( PG::
|
970
|
+
expect( io ).to be_closed()
|
971
|
+
expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
555
972
|
end
|
556
973
|
|
557
|
-
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
|
558
975
|
conn = PG.connect( @conninfo )
|
559
976
|
io = conn.socket_io
|
560
977
|
conn.reset
|
561
|
-
io.
|
562
|
-
conn.socket_io.
|
978
|
+
expect( io ).to be_closed()
|
979
|
+
expect( conn.socket_io ).to_not equal( io )
|
563
980
|
conn.finish
|
564
981
|
end
|
565
982
|
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
983
|
+
it "block should raise ConnectionBad for a closed connection" do
|
984
|
+
serv = TCPServer.new( '127.0.0.1', 54320 )
|
985
|
+
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
986
|
+
while [PG::CONNECTION_STARTED, PG::CONNECTION_MADE].include?(conn.connect_poll)
|
987
|
+
sleep 0.1
|
571
988
|
end
|
989
|
+
serv.close
|
990
|
+
expect{ conn.block }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
|
991
|
+
expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
|
992
|
+
end
|
572
993
|
|
573
|
-
|
574
|
-
|
575
|
-
end
|
994
|
+
it "sets the fallback_application_name on new connections" do
|
995
|
+
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
576
996
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
ensure
|
583
|
-
$0 = old_0
|
584
|
-
end
|
585
|
-
end
|
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
|
586
1002
|
|
587
|
-
|
588
|
-
|
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"
|
1007
|
+
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
1008
|
+
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
1009
|
+
expect( conn_name ).to include( $0[0..10] )
|
1010
|
+
expect( conn_name ).to include( $0[-10..-1] )
|
1011
|
+
expect( conn_name.length ).to be <= 64
|
1012
|
+
ensure
|
1013
|
+
$0 = old_0
|
1014
|
+
end
|
1015
|
+
end
|
589
1016
|
|
590
|
-
|
591
|
-
|
1017
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1018
|
+
"any number of arguments" do
|
592
1019
|
|
593
|
-
|
594
|
-
|
595
|
-
conn.finish
|
1020
|
+
@conn.exec( 'ROLLBACK' )
|
1021
|
+
@conn.exec( 'LISTEN knees' )
|
596
1022
|
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
end
|
601
|
-
@conn.exec( 'UNLISTEN knees' )
|
1023
|
+
conn = described_class.connect( @conninfo )
|
1024
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1025
|
+
conn.finish
|
602
1026
|
|
603
|
-
|
604
|
-
|
605
|
-
|
1027
|
+
event, pid, msg = nil
|
1028
|
+
@conn.wait_for_notify( 10 ) do |*args|
|
1029
|
+
event, pid, msg = *args
|
606
1030
|
end
|
1031
|
+
@conn.exec( 'UNLISTEN knees' )
|
607
1032
|
|
608
|
-
|
609
|
-
|
610
|
-
|
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
|
611
1037
|
|
612
|
-
|
613
|
-
|
614
|
-
|
1038
|
+
it "accepts nil as the timeout in #wait_for_notify " do
|
1039
|
+
@conn.exec( 'ROLLBACK' )
|
1040
|
+
@conn.exec( 'LISTEN knees' )
|
615
1041
|
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
end
|
620
|
-
@conn.exec( 'UNLISTEN knees' )
|
1042
|
+
conn = described_class.connect( @conninfo )
|
1043
|
+
conn.exec( %Q{NOTIFY knees} )
|
1044
|
+
conn.finish
|
621
1045
|
|
622
|
-
|
623
|
-
|
1046
|
+
event, pid = nil
|
1047
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1048
|
+
event, pid = *args
|
624
1049
|
end
|
1050
|
+
@conn.exec( 'UNLISTEN knees' )
|
625
1051
|
|
626
|
-
|
627
|
-
|
628
|
-
|
1052
|
+
expect( event ).to eq( 'knees' )
|
1053
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1054
|
+
end
|
629
1055
|
|
630
|
-
|
631
|
-
|
632
|
-
|
1056
|
+
it "sends nil as the payload if the notification wasn't given one" do
|
1057
|
+
@conn.exec( 'ROLLBACK' )
|
1058
|
+
@conn.exec( 'LISTEN knees' )
|
633
1059
|
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
end
|
638
|
-
@conn.exec( 'UNLISTEN knees' )
|
1060
|
+
conn = described_class.connect( @conninfo )
|
1061
|
+
conn.exec( %Q{NOTIFY knees} )
|
1062
|
+
conn.finish
|
639
1063
|
|
640
|
-
|
1064
|
+
payload = :notnil
|
1065
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1066
|
+
payload = args[ 2 ]
|
641
1067
|
end
|
1068
|
+
@conn.exec( 'UNLISTEN knees' )
|
642
1069
|
|
643
|
-
|
644
|
-
|
1070
|
+
expect( payload ).to be_nil()
|
1071
|
+
end
|
645
1072
|
|
646
|
-
|
647
|
-
|
1073
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1074
|
+
"two arguments" do
|
648
1075
|
|
649
|
-
|
650
|
-
|
651
|
-
conn.finish
|
1076
|
+
@conn.exec( 'ROLLBACK' )
|
1077
|
+
@conn.exec( 'LISTEN knees' )
|
652
1078
|
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
end
|
657
|
-
@conn.exec( 'UNLISTEN knees' )
|
1079
|
+
conn = described_class.connect( @conninfo )
|
1080
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1081
|
+
conn.finish
|
658
1082
|
|
659
|
-
|
660
|
-
|
661
|
-
msg
|
1083
|
+
event, pid, msg = nil
|
1084
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2|
|
1085
|
+
event, pid, msg = arg1, arg2
|
662
1086
|
end
|
1087
|
+
@conn.exec( 'UNLISTEN knees' )
|
663
1088
|
|
664
|
-
|
665
|
-
|
1089
|
+
expect( event ).to eq( 'knees' )
|
1090
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1091
|
+
expect( msg ).to be_nil()
|
1092
|
+
end
|
666
1093
|
|
667
|
-
|
668
|
-
|
1094
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it " +
|
1095
|
+
"doesn't accept arguments" do
|
669
1096
|
|
670
|
-
|
671
|
-
|
672
|
-
conn.finish
|
1097
|
+
@conn.exec( 'ROLLBACK' )
|
1098
|
+
@conn.exec( 'LISTEN knees' )
|
673
1099
|
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
end
|
678
|
-
@conn.exec( 'UNLISTEN knees' )
|
1100
|
+
conn = described_class.connect( @conninfo )
|
1101
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1102
|
+
conn.finish
|
679
1103
|
|
680
|
-
|
1104
|
+
notification_received = false
|
1105
|
+
@conn.wait_for_notify( 10 ) do
|
1106
|
+
notification_received = true
|
681
1107
|
end
|
1108
|
+
@conn.exec( 'UNLISTEN knees' )
|
682
1109
|
|
683
|
-
|
684
|
-
|
1110
|
+
expect( notification_received ).to be_truthy()
|
1111
|
+
end
|
685
1112
|
|
686
|
-
|
687
|
-
|
1113
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1114
|
+
"three arguments" do
|
688
1115
|
|
689
|
-
|
690
|
-
|
691
|
-
conn.finish
|
1116
|
+
@conn.exec( 'ROLLBACK' )
|
1117
|
+
@conn.exec( 'LISTEN knees' )
|
692
1118
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
end
|
697
|
-
@conn.exec( 'UNLISTEN knees' )
|
1119
|
+
conn = described_class.connect( @conninfo )
|
1120
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1121
|
+
conn.finish
|
698
1122
|
|
699
|
-
|
700
|
-
|
701
|
-
msg
|
1123
|
+
event, pid, msg = nil
|
1124
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
|
1125
|
+
event, pid, msg = arg1, arg2, arg3
|
702
1126
|
end
|
1127
|
+
@conn.exec( 'UNLISTEN knees' )
|
703
1128
|
|
1129
|
+
expect( event ).to eq( 'knees' )
|
1130
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1131
|
+
expect( msg ).to eq( 'skirt and boots' )
|
704
1132
|
end
|
705
1133
|
|
706
|
-
context "
|
1134
|
+
context "server ping", :without_transaction do
|
707
1135
|
|
708
1136
|
it "pings successfully with connection string" do
|
709
1137
|
ping = described_class.ping(@conninfo)
|
710
|
-
ping.
|
1138
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
711
1139
|
end
|
712
1140
|
|
713
1141
|
it "pings using 7 arguments converted to strings" do
|
714
1142
|
ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
|
715
|
-
ping.
|
1143
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
716
1144
|
end
|
717
1145
|
|
718
1146
|
it "pings using a hash of connection parameters" do
|
@@ -720,7 +1148,7 @@ describe PG::Connection do
|
|
720
1148
|
:host => 'localhost',
|
721
1149
|
:port => @port,
|
722
1150
|
:dbname => :test)
|
723
|
-
ping.
|
1151
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
724
1152
|
end
|
725
1153
|
|
726
1154
|
it "returns correct response when ping connection cannot be established" do
|
@@ -728,169 +1156,359 @@ describe PG::Connection do
|
|
728
1156
|
:host => 'localhost',
|
729
1157
|
:port => 9999,
|
730
1158
|
:dbname => :test)
|
731
|
-
ping.
|
1159
|
+
expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
|
732
1160
|
end
|
733
1161
|
|
734
|
-
it "returns
|
1162
|
+
it "returns error when ping connection arguments are wrong" do
|
735
1163
|
ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
|
736
|
-
ping.
|
1164
|
+
expect( ping ).to_not eq( PG::PQPING_OK )
|
737
1165
|
end
|
738
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
|
739
1174
|
|
740
1175
|
end
|
741
1176
|
|
742
|
-
|
743
|
-
describe "set_single_row_mode" do
|
1177
|
+
describe "set_single_row_mode" do
|
744
1178
|
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
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
|
749
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
|
750
1205
|
|
751
|
-
|
752
|
-
|
753
|
-
|
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
|
754
1209
|
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
results.length.should == 11
|
762
|
-
results[0..-2].each do |res|
|
763
|
-
res.result_status.should == PG::PGRES_SINGLE_TUPLE
|
764
|
-
values = res.field_values('generate_series')
|
765
|
-
values.length.should == 1
|
766
|
-
values.first.to_i.should > 0
|
767
|
-
end
|
768
|
-
results.last.result_status.should == PG::PGRES_TUPLES_OK
|
769
|
-
results.last.ntuples.should == 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
|
770
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
|
771
1220
|
|
772
|
-
|
773
|
-
|
774
|
-
|
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
|
775
1225
|
|
776
|
-
|
777
|
-
|
1226
|
+
first_result = nil
|
1227
|
+
expect do
|
778
1228
|
loop do
|
779
1229
|
res = @conn.get_result or break
|
780
1230
|
res.check
|
781
|
-
|
1231
|
+
first_result ||= res
|
782
1232
|
end
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
it "should receive rows before entire query fails" do
|
788
|
-
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
789
|
-
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
|
790
|
-
@conn.set_single_row_mode
|
791
|
-
|
792
|
-
first_result = nil
|
793
|
-
expect do
|
794
|
-
loop do
|
795
|
-
res = @conn.get_result or break
|
796
|
-
res.check
|
797
|
-
first_result ||= res
|
798
|
-
end
|
799
|
-
end.to raise_error(PG::Error)
|
800
|
-
first_result.kind_of?(PG::Result).should be_true
|
801
|
-
first_result.result_status.should == PG::PGRES_SINGLE_TUPLE
|
802
|
-
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 )
|
803
1236
|
end
|
1237
|
+
|
804
1238
|
end
|
805
1239
|
|
806
|
-
context "multinationalization support"
|
1240
|
+
context "multinationalization support" do
|
807
1241
|
|
808
1242
|
describe "rubyforge #22925: m17n support" do
|
809
1243
|
it "should return results in the same encoding as the client (iso-8859-1)" do
|
810
|
-
|
811
|
-
@conn.
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
end
|
816
|
-
out_string.should == 'fantasia'
|
817
|
-
out_string.encoding.should == Encoding::ISO8859_1
|
1244
|
+
@conn.internal_encoding = 'iso8859-1'
|
1245
|
+
res = @conn.exec_params("VALUES ('fantasia')", [], 0)
|
1246
|
+
out_string = res[0]['column1']
|
1247
|
+
expect( out_string ).to eq( 'fantasia' )
|
1248
|
+
expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
|
818
1249
|
end
|
819
1250
|
|
820
1251
|
it "should return results in the same encoding as the client (utf-8)" do
|
821
|
-
|
822
|
-
@conn.
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
end
|
827
|
-
out_string.should == '世界線航跡蔵'
|
828
|
-
out_string.encoding.should == Encoding::UTF_8
|
1252
|
+
@conn.internal_encoding = 'utf-8'
|
1253
|
+
res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
|
1254
|
+
out_string = res[0]['column1']
|
1255
|
+
expect( out_string ).to eq( '世界線航跡蔵' )
|
1256
|
+
expect( out_string.encoding ).to eq( Encoding::UTF_8 )
|
829
1257
|
end
|
830
1258
|
|
831
1259
|
it "should return results in the same encoding as the client (EUC-JP)" do
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
end
|
839
|
-
out_string.should == '世界線航跡蔵'.encode('EUC-JP')
|
840
|
-
out_string.encoding.should == Encoding::EUC_JP
|
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']
|
1264
|
+
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
1265
|
+
expect( out_string.encoding ).to eq( Encoding::EUC_JP )
|
841
1266
|
end
|
842
1267
|
|
843
1268
|
it "returns the results in the correct encoding even if the client_encoding has " +
|
844
1269
|
"changed since the results were fetched" do
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
end
|
853
|
-
out_string.should == '世界線航跡蔵'.encode('EUC-JP')
|
854
|
-
out_string.encoding.should == Encoding::EUC_JP
|
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']
|
1275
|
+
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
1276
|
+
expect( out_string.encoding ).to eq( Encoding::EUC_JP )
|
855
1277
|
end
|
856
1278
|
|
857
1279
|
it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
|
858
1280
|
@conn.exec "SET client_encoding TO SQL_ASCII"
|
859
|
-
@conn.internal_encoding.
|
1281
|
+
expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
|
860
1282
|
end
|
861
1283
|
|
862
|
-
it "
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
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 )
|
874
1298
|
end
|
875
1299
|
|
876
1300
|
it "uses the client encoding for escaped string" do
|
877
|
-
original = "
|
1301
|
+
original = "Möhre to 'scape".encode( "utf-16be" )
|
878
1302
|
@conn.set_client_encoding( "euc_jp" )
|
879
1303
|
escaped = @conn.escape( original )
|
880
|
-
escaped.encoding.
|
1304
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1305
|
+
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
|
881
1306
|
end
|
882
1307
|
|
883
|
-
it "
|
884
|
-
original = "
|
1308
|
+
it "uses the client encoding for escaped literal" do
|
1309
|
+
original = "Möhre to 'scape".encode( "utf-16be" )
|
1310
|
+
@conn.set_client_encoding( "euc_jp" )
|
885
1311
|
escaped = @conn.escape_literal( original )
|
886
|
-
escaped.
|
1312
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1313
|
+
expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
|
1314
|
+
end
|
1315
|
+
|
1316
|
+
it "uses the client encoding for escaped identifier" do
|
1317
|
+
original = "Möhre to 'scape".encode( "utf-16le" )
|
1318
|
+
@conn.set_client_encoding( "euc_jp" )
|
1319
|
+
escaped = @conn.escape_identifier( original )
|
1320
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1321
|
+
expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
it "uses the client encoding for quote_ident" do
|
1325
|
+
original = "Möhre to 'scape".encode( "utf-16le" )
|
1326
|
+
@conn.set_client_encoding( "euc_jp" )
|
1327
|
+
escaped = @conn.quote_ident( original )
|
1328
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1329
|
+
expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
it "uses the previous string encoding for escaped string" do
|
1333
|
+
original = "Möhre to 'scape".encode( "iso-8859-1" )
|
1334
|
+
@conn.set_client_encoding( "euc_jp" )
|
1335
|
+
escaped = described_class.escape( original )
|
1336
|
+
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1337
|
+
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
it "uses the previous string encoding for quote_ident" do
|
1341
|
+
original = "Möhre to 'scape".encode( "iso-8859-1" )
|
1342
|
+
@conn.set_client_encoding( "euc_jp" )
|
1343
|
+
escaped = described_class.quote_ident( original )
|
1344
|
+
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
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__
|
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']] )
|
887
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/)
|
888
1488
|
end
|
889
1489
|
|
1490
|
+
it "can quote bigger strings with quote_ident" do
|
1491
|
+
original = "'01234567\"" * 100
|
1492
|
+
escaped = described_class.quote_ident( original )
|
1493
|
+
expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
it "can quote Arrays with quote_ident" do
|
1497
|
+
original = "'01234567\""
|
1498
|
+
escaped = described_class.quote_ident( [original]*3 )
|
1499
|
+
expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
|
1500
|
+
expect( escaped ).to eq( expected.join(".") )
|
1501
|
+
end
|
1502
|
+
|
1503
|
+
it "will raise a TypeError for invalid arguments to quote_ident" do
|
1504
|
+
expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
|
1505
|
+
expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
|
1506
|
+
expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
|
1507
|
+
end
|
890
1508
|
|
891
1509
|
describe "Ruby 1.9.x default_internal encoding" do
|
892
1510
|
|
893
|
-
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
|
894
1512
|
@conn.transaction do |txn_conn|
|
895
1513
|
txn_conn.internal_encoding = Encoding::ISO8859_1
|
896
1514
|
txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
|
@@ -899,13 +1517,14 @@ describe PG::Connection do
|
|
899
1517
|
|
900
1518
|
begin
|
901
1519
|
prev_encoding = Encoding.default_internal
|
902
|
-
Encoding.default_internal = Encoding::
|
1520
|
+
Encoding.default_internal = Encoding::ISO8859_2
|
903
1521
|
|
904
1522
|
conn = PG.connect( @conninfo )
|
905
|
-
conn.internal_encoding.
|
1523
|
+
expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
|
906
1524
|
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
907
|
-
res[0]['foo'].encoding.
|
1525
|
+
expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
|
908
1526
|
ensure
|
1527
|
+
conn.exec( "DROP TABLE defaultinternaltest" )
|
909
1528
|
conn.finish if conn
|
910
1529
|
Encoding.default_internal = prev_encoding
|
911
1530
|
end
|
@@ -918,7 +1537,7 @@ describe PG::Connection do
|
|
918
1537
|
|
919
1538
|
@conn.set_default_encoding
|
920
1539
|
|
921
|
-
@conn.internal_encoding.
|
1540
|
+
expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
|
922
1541
|
ensure
|
923
1542
|
Encoding.default_internal = prev_encoding
|
924
1543
|
end
|
@@ -939,7 +1558,7 @@ describe PG::Connection do
|
|
939
1558
|
query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
|
940
1559
|
conn.exec( query )
|
941
1560
|
rescue => err
|
942
|
-
err.message.encoding.
|
1561
|
+
expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
|
943
1562
|
else
|
944
1563
|
fail "No exception raised?!"
|
945
1564
|
end
|
@@ -948,7 +1567,22 @@ describe PG::Connection do
|
|
948
1567
|
conn.finish if conn
|
949
1568
|
end
|
950
1569
|
|
951
|
-
it "
|
1570
|
+
it "handles clearing result in or after set_notice_receiver" do
|
1571
|
+
r = nil
|
1572
|
+
@conn.set_notice_receiver do |result|
|
1573
|
+
r = result
|
1574
|
+
expect( r.cleared? ).to eq(false)
|
1575
|
+
end
|
1576
|
+
@conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
|
1577
|
+
sleep 0.2
|
1578
|
+
expect( r ).to be_a( PG::Result )
|
1579
|
+
expect( r.cleared? ).to eq(true)
|
1580
|
+
expect( r.autoclear? ).to eq(true)
|
1581
|
+
r.clear
|
1582
|
+
@conn.set_notice_receiver
|
1583
|
+
end
|
1584
|
+
|
1585
|
+
it "receives properly encoded messages in the notice callbacks" do
|
952
1586
|
[:receiver, :processor].each do |kind|
|
953
1587
|
notices = []
|
954
1588
|
@conn.internal_encoding = 'utf-8'
|
@@ -966,19 +1600,18 @@ describe PG::Connection do
|
|
966
1600
|
@conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
|
967
1601
|
end
|
968
1602
|
|
969
|
-
notices.length.
|
1603
|
+
expect( notices.length ).to eq( 3 )
|
970
1604
|
notices.each do |notice|
|
971
|
-
notice.
|
972
|
-
notice.encoding.
|
1605
|
+
expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
|
1606
|
+
expect( notice.encoding ).to eq( Encoding::UTF_8 )
|
973
1607
|
end
|
974
1608
|
@conn.set_notice_receiver
|
975
1609
|
@conn.set_notice_processor
|
976
1610
|
end
|
977
1611
|
end
|
978
1612
|
|
979
|
-
it "receives properly encoded text from wait_for_notify", :
|
1613
|
+
it "receives properly encoded text from wait_for_notify", :without_transaction do
|
980
1614
|
@conn.internal_encoding = 'utf-8'
|
981
|
-
@conn.exec( 'ROLLBACK' )
|
982
1615
|
@conn.exec( 'LISTEN "Möhre"' )
|
983
1616
|
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
984
1617
|
event, pid, msg = nil
|
@@ -987,37 +1620,330 @@ describe PG::Connection do
|
|
987
1620
|
end
|
988
1621
|
@conn.exec( 'UNLISTEN "Möhre"' )
|
989
1622
|
|
990
|
-
event.
|
991
|
-
event.encoding.
|
992
|
-
|
993
|
-
msg.
|
1623
|
+
expect( event ).to eq( "Möhre" )
|
1624
|
+
expect( event.encoding ).to eq( Encoding::UTF_8 )
|
1625
|
+
expect( pid ).to be_a_kind_of(Integer)
|
1626
|
+
expect( msg ).to eq( '世界線航跡蔵' )
|
1627
|
+
expect( msg.encoding ).to eq( Encoding::UTF_8 )
|
994
1628
|
end
|
995
1629
|
|
996
|
-
it "returns properly encoded text from notifies", :
|
1630
|
+
it "returns properly encoded text from notifies", :without_transaction do
|
997
1631
|
@conn.internal_encoding = 'utf-8'
|
998
|
-
@conn.exec( 'ROLLBACK' )
|
999
1632
|
@conn.exec( 'LISTEN "Möhre"' )
|
1000
1633
|
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1001
1634
|
@conn.exec( 'UNLISTEN "Möhre"' )
|
1002
1635
|
|
1003
1636
|
notification = @conn.notifies
|
1004
|
-
notification[:relname].
|
1005
|
-
notification[:relname].encoding.
|
1006
|
-
notification[:extra].
|
1007
|
-
notification[:extra].encoding.
|
1008
|
-
notification[:be_pid].
|
1637
|
+
expect( notification[:relname] ).to eq( "Möhre" )
|
1638
|
+
expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
|
1639
|
+
expect( notification[:extra] ).to eq( '世界線航跡蔵' )
|
1640
|
+
expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
|
1641
|
+
expect( notification[:be_pid] ).to be > 0
|
1009
1642
|
end
|
1010
1643
|
end
|
1011
1644
|
|
1012
|
-
context "OS thread support"
|
1013
|
-
it "
|
1645
|
+
context "OS thread support" do
|
1646
|
+
it "Connection#exec shouldn't block a second thread" do
|
1014
1647
|
t = Thread.new do
|
1015
1648
|
@conn.exec( "select pg_sleep(1)" )
|
1016
1649
|
end
|
1017
1650
|
|
1018
1651
|
sleep 0.5
|
1019
|
-
t.
|
1652
|
+
expect( t ).to be_alive()
|
1020
1653
|
t.join
|
1021
1654
|
end
|
1655
|
+
|
1656
|
+
it "Connection.new shouldn't block a second thread" do
|
1657
|
+
serv = nil
|
1658
|
+
t = Thread.new do
|
1659
|
+
serv = TCPServer.new( '127.0.0.1', 54320 )
|
1660
|
+
expect {
|
1661
|
+
described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
1662
|
+
}.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
|
1663
|
+
end
|
1664
|
+
|
1665
|
+
sleep 0.5
|
1666
|
+
expect( t ).to be_alive()
|
1667
|
+
serv.close
|
1668
|
+
t.join
|
1669
|
+
end
|
1670
|
+
end
|
1671
|
+
|
1672
|
+
describe "type casting" do
|
1673
|
+
it "should raise an error on invalid param mapping" do
|
1674
|
+
expect{
|
1675
|
+
@conn.exec_params( "SELECT 1", [], nil, :invalid )
|
1676
|
+
}.to raise_error(TypeError)
|
1677
|
+
end
|
1678
|
+
|
1679
|
+
it "should return nil if no type mapping is set" do
|
1680
|
+
expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
|
1681
|
+
expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
it "shouldn't type map params unless requested" do
|
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
|
1693
|
+
end
|
1694
|
+
|
1695
|
+
it "should raise an error on invalid encoder to put_copy_data" do
|
1696
|
+
expect{
|
1697
|
+
@conn.put_copy_data [1], :invalid
|
1698
|
+
}.to raise_error(TypeError)
|
1699
|
+
end
|
1700
|
+
|
1701
|
+
it "can type cast parameters to put_copy_data with explicit encoder" do
|
1702
|
+
tm = PG::TypeMapByColumn.new [nil]
|
1703
|
+
row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
1704
|
+
|
1705
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1706
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1707
|
+
@conn.put_copy_data [1], row_encoder
|
1708
|
+
@conn.put_copy_data ["2"], row_encoder
|
1709
|
+
end
|
1710
|
+
|
1711
|
+
@conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
|
1712
|
+
@conn.put_copy_data [3]
|
1713
|
+
@conn.put_copy_data ["4"]
|
1714
|
+
end
|
1715
|
+
|
1716
|
+
res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1717
|
+
expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
context "with default query type map" do
|
1721
|
+
before :each do
|
1722
|
+
@conn2 = described_class.new(@conninfo)
|
1723
|
+
tm = PG::TypeMapByClass.new
|
1724
|
+
tm[Integer] = PG::TextEncoder::Integer.new oid: 20
|
1725
|
+
@conn2.type_map_for_queries = tm
|
1726
|
+
|
1727
|
+
row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
1728
|
+
@conn2.encoder_for_put_copy_data = row_encoder
|
1729
|
+
end
|
1730
|
+
after :each do
|
1731
|
+
@conn2.close
|
1732
|
+
end
|
1733
|
+
|
1734
|
+
it "should respect a type mapping for params and it's OID and format code" do
|
1735
|
+
res = @conn2.exec_params( "SELECT $1", [5] )
|
1736
|
+
expect( res.values ).to eq( [["5"]] )
|
1737
|
+
expect( res.ftype(0) ).to eq( 20 )
|
1738
|
+
end
|
1739
|
+
|
1740
|
+
it "should return the current type mapping" do
|
1741
|
+
expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
|
1742
|
+
end
|
1743
|
+
|
1744
|
+
it "should work with arbitrary number of params in conjunction with type casting" do
|
1745
|
+
begin
|
1746
|
+
3.step( 12, 0.2 ) do |exp|
|
1747
|
+
num_params = (2 ** exp).to_i
|
1748
|
+
sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
|
1749
|
+
params = num_params.times.to_a
|
1750
|
+
res = @conn2.exec_params( "SELECT #{sql}", params )
|
1751
|
+
expect( res.nfields ).to eq( num_params )
|
1752
|
+
expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
|
1753
|
+
end
|
1754
|
+
rescue PG::ProgramLimitExceeded
|
1755
|
+
# Stop silently as soon the server complains about too many params
|
1756
|
+
end
|
1757
|
+
end
|
1758
|
+
|
1759
|
+
it "can process #copy_data input queries with row encoder and respects character encoding" do
|
1760
|
+
@conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1761
|
+
@conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1762
|
+
@conn2.put_copy_data [1]
|
1763
|
+
@conn2.put_copy_data ["Möhre".encode("utf-16le")]
|
1764
|
+
end
|
1765
|
+
|
1766
|
+
res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1767
|
+
expect( res.values ).to eq( [["1"], ["Möhre"]] )
|
1768
|
+
end
|
1769
|
+
end
|
1770
|
+
|
1771
|
+
context "with default result type map" do
|
1772
|
+
before :each do
|
1773
|
+
@conn2 = described_class.new(@conninfo)
|
1774
|
+
tm = PG::TypeMapByOid.new
|
1775
|
+
tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
|
1776
|
+
@conn2.type_map_for_results = tm
|
1777
|
+
|
1778
|
+
row_decoder = PG::TextDecoder::CopyRow.new
|
1779
|
+
@conn2.decoder_for_get_copy_data = row_decoder
|
1780
|
+
end
|
1781
|
+
after :each do
|
1782
|
+
@conn2.close
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
it "should respect a type mapping for result" do
|
1786
|
+
res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
|
1787
|
+
expect( res.values ).to eq( [[5]] )
|
1788
|
+
end
|
1789
|
+
|
1790
|
+
it "should return the current type mapping" do
|
1791
|
+
expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
|
1792
|
+
end
|
1793
|
+
|
1794
|
+
it "should work with arbitrary number of params in conjunction with type casting" do
|
1795
|
+
begin
|
1796
|
+
3.step( 12, 0.2 ) do |exp|
|
1797
|
+
num_params = (2 ** exp).to_i
|
1798
|
+
sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
|
1799
|
+
params = num_params.times.to_a
|
1800
|
+
res = @conn2.exec_params( "SELECT #{sql}", params )
|
1801
|
+
expect( res.nfields ).to eq( num_params )
|
1802
|
+
expect( res.values ).to eq( [num_params.times.to_a] )
|
1803
|
+
end
|
1804
|
+
rescue PG::ProgramLimitExceeded
|
1805
|
+
# Stop silently as soon the server complains about too many params
|
1806
|
+
end
|
1807
|
+
end
|
1808
|
+
|
1809
|
+
it "can process #copy_data output with row decoder and respects character encoding" do
|
1810
|
+
@conn2.internal_encoding = Encoding::ISO8859_1
|
1811
|
+
rows = []
|
1812
|
+
@conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
|
1813
|
+
while row=@conn2.get_copy_data
|
1814
|
+
rows << row
|
1815
|
+
end
|
1816
|
+
end
|
1817
|
+
expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
|
1818
|
+
expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
|
1819
|
+
end
|
1820
|
+
|
1821
|
+
it "can type cast #copy_data output with explicit decoder" do
|
1822
|
+
tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
|
1823
|
+
row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
|
1824
|
+
rows = []
|
1825
|
+
@conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
|
1826
|
+
while row=@conn.get_copy_data
|
1827
|
+
rows << row
|
1828
|
+
end
|
1829
|
+
end
|
1830
|
+
@conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
|
1831
|
+
while row=@conn.get_copy_data( false, row_decoder )
|
1832
|
+
rows << row
|
1833
|
+
end
|
1834
|
+
end
|
1835
|
+
expect( rows ).to eq( [[1], [2], [3], [4]] )
|
1836
|
+
end
|
1837
|
+
end
|
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
|
1022
1948
|
end
|
1023
1949
|
end
|