pg 0.17.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/BSDL +2 -2
- data/ChangeLog +0 -3506
- data/History.rdoc +308 -0
- data/Manifest.txt +35 -19
- data/README-Windows.rdoc +17 -28
- data/README.ja.rdoc +1 -2
- data/README.rdoc +113 -14
- data/Rakefile +67 -30
- data/Rakefile.cross +109 -83
- data/ext/errorcodes.def +101 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +33 -2
- data/ext/extconf.rb +55 -58
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +27 -39
- data/ext/pg.c +262 -130
- data/ext/pg.h +266 -54
- 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 +1689 -990
- data/ext/pg_copy_coder.c +599 -0
- data/ext/pg_errors.c +6 -0
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +897 -164
- 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/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 +153 -41
- 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/lib/pg.rb +29 -9
- data/spec/{lib/helpers.rb → helpers.rb} +151 -64
- data/spec/pg/basic_type_mapping_spec.rb +630 -0
- data/spec/pg/connection_spec.rb +1180 -477
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +456 -120
- 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 +26 -20
- data.tar.gz.sig +0 -0
- metadata +148 -91
- 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\\' \\\\'/ )
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
let(:uri) { 'postgresql://user:pass@pgsql.example.com:222/db01?sslmode=require' }
|
64
|
+
|
65
|
+
it "can connect using a URI" do
|
66
|
+
string = described_class.parse_connect_args( uri )
|
67
|
+
|
68
|
+
expect( string ).to be_a( String )
|
69
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
70
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
71
|
+
|
72
|
+
string = described_class.parse_connect_args( URI.parse(uri) )
|
73
|
+
|
74
|
+
expect( string ).to be_a( String )
|
75
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
76
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can create a connection URI from a URI and a hash" do
|
80
|
+
string = described_class.parse_connect_args( uri, :connect_timeout => 2 )
|
81
|
+
|
82
|
+
expect( string ).to be_a( String )
|
83
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
84
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
85
|
+
expect( string ).to match( %r{\?.*connect_timeout=2} )
|
86
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} )
|
87
115
|
end
|
88
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( '' )
|
91
119
|
end
|
92
120
|
|
93
121
|
it "connects successfully with connection string" do
|
94
|
-
|
95
|
-
|
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 )
|
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,74 +148,117 @@ 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
|
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
|
173
216
|
|
174
|
-
|
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"]])
|
175
222
|
end
|
176
223
|
|
177
|
-
conn.
|
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
|
178
262
|
end
|
179
263
|
|
180
264
|
it "raises proper error when sending fails" do
|
@@ -186,13 +270,38 @@ describe PG::Connection do
|
|
186
270
|
described_class.connect(@conninfo).finish
|
187
271
|
sleep 0.5
|
188
272
|
res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
|
189
|
-
WHERE usename IS NOT NULL])
|
273
|
+
WHERE usename IS NOT NULL AND application_name != ''])
|
190
274
|
# there's still the global @conn, but should be no more
|
191
|
-
res[0]['n'].
|
275
|
+
expect( res[0]['n'] ).to eq( '1' )
|
276
|
+
end
|
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" )
|
192
289
|
end
|
193
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
|
296
|
+
|
297
|
+
it "can set error context visibility", :postgresql_96 do
|
298
|
+
old = @conn.set_error_context_visibility( PG::PQSHOW_CONTEXT_NEVER )
|
299
|
+
new = @conn.set_error_context_visibility( old )
|
300
|
+
expect( new ).to eq( PG::PQSHOW_CONTEXT_NEVER )
|
301
|
+
end
|
194
302
|
|
195
|
-
|
303
|
+
let(:expected_trace_output) do
|
304
|
+
%{
|
196
305
|
To backend> Msg Q
|
197
306
|
To backend> "SELECT 1 AS one"
|
198
307
|
To backend> Msg complete, length 21
|
@@ -220,6 +329,7 @@ describe PG::Connection do
|
|
220
329
|
From backend (#4)> 5
|
221
330
|
From backend> T
|
222
331
|
}.gsub( /^\t{2}/, '' ).lstrip
|
332
|
+
end
|
223
333
|
|
224
334
|
it "trace and untrace client-server communication", :unix do
|
225
335
|
# be careful to explicitly close files so that the
|
@@ -230,25 +340,22 @@ describe PG::Connection do
|
|
230
340
|
@conn.trace( trace_io )
|
231
341
|
trace_io.close
|
232
342
|
|
233
|
-
|
343
|
+
@conn.exec("SELECT 1 AS one")
|
234
344
|
@conn.untrace
|
235
345
|
|
236
|
-
|
346
|
+
@conn.exec("SELECT 2 AS two")
|
237
347
|
|
238
348
|
trace_data = trace_file.read
|
239
349
|
|
240
|
-
|
241
|
-
#
|
242
|
-
#
|
243
|
-
#
|
244
|
-
# +From backend (#4)>
|
245
|
-
#
|
246
|
-
|
247
|
-
expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
|
248
|
-
expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
|
249
|
-
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' )
|
250
357
|
|
251
|
-
trace_data.
|
358
|
+
expect( trace_data ).to eq( expected_trace_output )
|
252
359
|
end
|
253
360
|
|
254
361
|
it "allows a query to be cancelled" do
|
@@ -259,7 +366,7 @@ describe PG::Connection do
|
|
259
366
|
if(tmpres.result_status != PG::PGRES_TUPLES_OK)
|
260
367
|
error = true
|
261
368
|
end
|
262
|
-
error.
|
369
|
+
expect( error ).to eq( true )
|
263
370
|
end
|
264
371
|
|
265
372
|
it "can stop a thread that runs a blocking query with async_exec" do
|
@@ -271,29 +378,21 @@ describe PG::Connection do
|
|
271
378
|
|
272
379
|
t.kill
|
273
380
|
t.join
|
274
|
-
(Time.now - start).
|
381
|
+
expect( (Time.now - start) ).to be < 10
|
275
382
|
end
|
276
383
|
|
277
|
-
it "should work together with signal handlers" do
|
384
|
+
it "should work together with signal handlers", :unix do
|
278
385
|
signal_received = false
|
279
|
-
trap '
|
386
|
+
trap 'USR2' do
|
280
387
|
signal_received = true
|
281
388
|
end
|
282
389
|
|
283
390
|
Thread.new do
|
284
391
|
sleep 0.1
|
285
|
-
Process.kill("
|
392
|
+
Process.kill("USR2", Process.pid)
|
286
393
|
end
|
287
394
|
@conn.exec("select pg_sleep(0.3)")
|
288
|
-
signal_received.
|
289
|
-
|
290
|
-
signal_received = false
|
291
|
-
Thread.new do
|
292
|
-
sleep 0.1
|
293
|
-
Process.kill("USR1", Process.pid)
|
294
|
-
end
|
295
|
-
@conn.async_exec("select pg_sleep(0.3)")
|
296
|
-
signal_received.should be_true
|
395
|
+
expect( signal_received ).to be_truthy
|
297
396
|
end
|
298
397
|
|
299
398
|
|
@@ -305,15 +404,19 @@ describe PG::Connection do
|
|
305
404
|
res = nil
|
306
405
|
@conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
|
307
406
|
|
308
|
-
|
309
|
-
|
310
|
-
@conn.
|
311
|
-
|
312
|
-
|
313
|
-
|
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 )
|
314
414
|
|
315
|
-
|
316
|
-
|
415
|
+
res = @conn.exec( "SELECT * FROM pie" )
|
416
|
+
expect( res.ntuples ).to eq( 0 )
|
417
|
+
ensure
|
418
|
+
@conn.exec( "DROP TABLE pie" )
|
419
|
+
end
|
317
420
|
end
|
318
421
|
|
319
422
|
it "returns the block result from Connection#transaction" do
|
@@ -323,7 +426,7 @@ describe PG::Connection do
|
|
323
426
|
res = @conn.transaction do
|
324
427
|
"transaction result"
|
325
428
|
end
|
326
|
-
res.
|
429
|
+
expect( res ).to eq( "transaction result" )
|
327
430
|
end
|
328
431
|
|
329
432
|
it "not read past the end of a large object" do
|
@@ -331,33 +434,45 @@ describe PG::Connection do
|
|
331
434
|
oid = @conn.lo_create( 0 )
|
332
435
|
fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
|
333
436
|
@conn.lo_write( fd, "foobar" )
|
334
|
-
@conn.lo_read( fd, 10 ).
|
437
|
+
expect( @conn.lo_read( fd, 10 ) ).to be_nil()
|
335
438
|
@conn.lo_lseek( fd, 0, PG::SEEK_SET )
|
336
|
-
@conn.lo_read( fd, 10 ).
|
439
|
+
expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
|
337
440
|
end
|
338
441
|
end
|
339
442
|
|
340
|
-
|
341
|
-
it "supports parameters passed to #exec (backward compatibility)" do
|
342
|
-
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
343
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
|
344
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
|
345
|
-
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
346
|
-
|
347
|
-
res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
|
348
|
-
res.values.should == [ ['Wally'], ['Sally'] ]
|
349
|
-
end
|
350
|
-
|
351
443
|
it "supports explicitly calling #exec_params" do
|
352
444
|
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
353
|
-
@conn.
|
354
|
-
@conn.
|
355
|
-
@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] )
|
356
448
|
|
357
449
|
res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
|
358
|
-
res.values.
|
450
|
+
expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
|
359
451
|
end
|
360
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'] )
|
460
|
+
end
|
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
|
361
476
|
|
362
477
|
it "can wait for NOTIFY events" do
|
363
478
|
@conn.exec( 'ROLLBACK' )
|
@@ -373,7 +488,7 @@ describe PG::Connection do
|
|
373
488
|
end
|
374
489
|
end
|
375
490
|
|
376
|
-
@conn.wait_for_notify( 10 ).
|
491
|
+
expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
|
377
492
|
@conn.exec( 'UNLISTEN woo' )
|
378
493
|
|
379
494
|
t.join
|
@@ -395,8 +510,8 @@ describe PG::Connection do
|
|
395
510
|
|
396
511
|
eventpid = event = nil
|
397
512
|
@conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
|
398
|
-
event.
|
399
|
-
eventpid.
|
513
|
+
expect( event ).to eq( 'woo' )
|
514
|
+
expect( eventpid ).to be_an( Integer )
|
400
515
|
|
401
516
|
@conn.exec( 'UNLISTEN woo' )
|
402
517
|
|
@@ -423,8 +538,8 @@ describe PG::Connection do
|
|
423
538
|
channels << @conn.wait_for_notify( 2 )
|
424
539
|
end
|
425
540
|
|
426
|
-
channels.
|
427
|
-
channels.
|
541
|
+
expect( channels.size ).to eq( 3 )
|
542
|
+
expect( channels ).to include( 'woo', 'war', 'woz' )
|
428
543
|
|
429
544
|
@conn.exec( 'UNLISTEN woz' )
|
430
545
|
@conn.exec( 'UNLISTEN war' )
|
@@ -446,52 +561,52 @@ describe PG::Connection do
|
|
446
561
|
# Cause the notification to buffer, but not be read yet
|
447
562
|
@conn.exec( 'SELECT 1' )
|
448
563
|
|
449
|
-
@conn.wait_for_notify( 10 ).
|
564
|
+
expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
|
450
565
|
@conn.exec( 'UNLISTEN woo' )
|
451
566
|
end
|
452
567
|
|
453
|
-
it "can receive notices while waiting for NOTIFY without exceeding the timeout"
|
568
|
+
it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
|
454
569
|
notices = []
|
455
570
|
@conn.set_notice_processor do |msg|
|
456
571
|
notices << [msg, Time.now]
|
457
572
|
end
|
458
573
|
st = Time.now
|
459
574
|
@conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
|
460
|
-
@conn.wait_for_notify( 1 ).
|
461
|
-
notices.first.
|
575
|
+
expect( @conn.wait_for_notify( 1 ) ).to be_nil
|
576
|
+
expect( notices.first ).to_not be_nil
|
462
577
|
et = Time.now
|
463
|
-
(et - notices.first[1]).
|
464
|
-
(et - st).
|
465
|
-
(et - st).
|
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
|
466
581
|
end
|
467
582
|
|
468
583
|
it "yields the result if block is given to exec" do
|
469
584
|
rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
|
470
585
|
values = []
|
471
|
-
result.
|
472
|
-
result.ntuples.
|
586
|
+
expect( result ).to be_kind_of( PG::Result )
|
587
|
+
expect( result.ntuples ).to eq( 2 )
|
473
588
|
result.each do |tuple|
|
474
589
|
values << tuple['a']
|
475
590
|
end
|
476
591
|
values
|
477
592
|
end
|
478
593
|
|
479
|
-
rval.
|
480
|
-
rval.
|
594
|
+
expect( rval.size ).to eq( 2 )
|
595
|
+
expect( rval ).to include( '5678', '1234' )
|
481
596
|
end
|
482
597
|
|
483
598
|
it "can process #copy_data output queries" do
|
484
599
|
rows = []
|
485
600
|
res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
|
486
|
-
res.result_status.
|
487
|
-
res.nfields.
|
601
|
+
expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
|
602
|
+
expect( res.nfields ).to eq( 1 )
|
488
603
|
while row=@conn.get_copy_data
|
489
604
|
rows << row
|
490
605
|
end
|
491
606
|
end
|
492
|
-
rows.
|
493
|
-
res2.result_status.
|
494
|
-
|
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
|
495
610
|
end
|
496
611
|
|
497
612
|
it "can handle incomplete #copy_data output queries" do
|
@@ -500,7 +615,7 @@ describe PG::Connection do
|
|
500
615
|
@conn.get_copy_data
|
501
616
|
end
|
502
617
|
}.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
|
503
|
-
|
618
|
+
expect( @conn ).to still_be_usable
|
504
619
|
end
|
505
620
|
|
506
621
|
it "can handle client errors in #copy_data for output" do
|
@@ -509,7 +624,7 @@ describe PG::Connection do
|
|
509
624
|
raise "boom"
|
510
625
|
end
|
511
626
|
}.to raise_error(RuntimeError, "boom")
|
512
|
-
|
627
|
+
expect( @conn ).to still_be_usable
|
513
628
|
end
|
514
629
|
|
515
630
|
it "can handle server errors in #copy_data for output" do
|
@@ -523,23 +638,23 @@ describe PG::Connection do
|
|
523
638
|
end
|
524
639
|
}.to raise_error(PG::Error, /test-error/)
|
525
640
|
end
|
526
|
-
|
641
|
+
expect( @conn ).to still_be_usable
|
527
642
|
end
|
528
643
|
|
529
644
|
it "can process #copy_data input queries" do
|
530
645
|
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
531
646
|
res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
532
|
-
res.result_status.
|
533
|
-
res.nfields.
|
647
|
+
expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
|
648
|
+
expect( res.nfields ).to eq( 1 )
|
534
649
|
@conn.put_copy_data "1\n"
|
535
650
|
@conn.put_copy_data "2\n"
|
536
651
|
end
|
537
|
-
res2.result_status.
|
652
|
+
expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
|
538
653
|
|
539
|
-
|
654
|
+
expect( @conn ).to still_be_usable
|
540
655
|
|
541
656
|
res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
|
542
|
-
res.values.
|
657
|
+
expect( res.values ).to eq( [["1"], ["2"]] )
|
543
658
|
end
|
544
659
|
|
545
660
|
it "can handle client errors in #copy_data for input" do
|
@@ -552,7 +667,8 @@ describe PG::Connection do
|
|
552
667
|
end
|
553
668
|
}.to raise_error(RuntimeError, "boom")
|
554
669
|
end
|
555
|
-
|
670
|
+
|
671
|
+
expect( @conn ).to still_be_usable
|
556
672
|
end
|
557
673
|
|
558
674
|
it "can handle server errors in #copy_data for input" do
|
@@ -563,9 +679,34 @@ describe PG::Connection do
|
|
563
679
|
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
564
680
|
@conn.put_copy_data "xyz\n"
|
565
681
|
end
|
566
|
-
}.to raise_error(PG::Error, /invalid input syntax for integer/)
|
682
|
+
}.to raise_error(PG::Error, /invalid input syntax for .*integer/)
|
683
|
+
end
|
684
|
+
expect( @conn ).to still_be_usable
|
685
|
+
end
|
686
|
+
|
687
|
+
it "gracefully handle SQL statements while in #copy_data for input" do
|
688
|
+
@conn.exec "ROLLBACK"
|
689
|
+
@conn.transaction do
|
690
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
|
691
|
+
expect {
|
692
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
693
|
+
@conn.exec "SELECT 1"
|
694
|
+
end
|
695
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
567
696
|
end
|
568
|
-
|
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
|
569
710
|
end
|
570
711
|
|
571
712
|
it "should raise an error for non copy statements in #copy_data" do
|
@@ -573,7 +714,7 @@ describe PG::Connection do
|
|
573
714
|
@conn.copy_data( "SELECT 1" ){}
|
574
715
|
}.to raise_error(ArgumentError, /no COPY/)
|
575
716
|
|
576
|
-
|
717
|
+
expect( @conn ).to still_be_usable
|
577
718
|
end
|
578
719
|
|
579
720
|
it "correctly finishes COPY queries passed to #async_exec" do
|
@@ -589,8 +730,8 @@ describe PG::Connection do
|
|
589
730
|
results << data if data
|
590
731
|
end until data.nil?
|
591
732
|
|
592
|
-
results.
|
593
|
-
results.
|
733
|
+
expect( results.size ).to eq( 2 )
|
734
|
+
expect( results ).to include( "1\n", "2\n" )
|
594
735
|
end
|
595
736
|
|
596
737
|
|
@@ -602,54 +743,138 @@ describe PG::Connection do
|
|
602
743
|
end
|
603
744
|
|
604
745
|
sleep 0.5
|
605
|
-
t.
|
746
|
+
expect( t ).to be_alive()
|
606
747
|
@conn.cancel
|
607
748
|
t.join
|
608
|
-
(Time.now - start).
|
749
|
+
expect( (Time.now - start) ).to be < 3
|
609
750
|
end
|
610
751
|
|
611
752
|
it "described_class#block should allow a timeout" do
|
612
|
-
@conn.send_query( "select pg_sleep(
|
753
|
+
@conn.send_query( "select pg_sleep(1)" )
|
613
754
|
|
614
755
|
start = Time.now
|
615
|
-
@conn.block( 0.
|
756
|
+
@conn.block( 0.3 )
|
616
757
|
finish = Time.now
|
617
758
|
|
618
|
-
(finish - start).
|
759
|
+
expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
|
619
760
|
end
|
620
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
|
621
768
|
|
622
|
-
it "can
|
623
|
-
described_class.
|
624
|
-
|
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 )
|
625
774
|
end
|
626
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
|
627
781
|
|
628
|
-
|
629
|
-
|
630
|
-
expect
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
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
|
639
864
|
end
|
640
865
|
|
641
866
|
|
642
867
|
it "allows fetching a column of values from a result by column number" do
|
643
868
|
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
644
|
-
res.column_values( 0 ).
|
645
|
-
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] )
|
646
871
|
end
|
647
872
|
|
648
873
|
|
649
874
|
it "allows fetching a column of values from a result by field name" do
|
650
875
|
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
651
|
-
res.field_values( 'column1' ).
|
652
|
-
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] )
|
653
878
|
end
|
654
879
|
|
655
880
|
|
@@ -677,26 +902,44 @@ describe PG::Connection do
|
|
677
902
|
end
|
678
903
|
|
679
904
|
|
680
|
-
it "
|
905
|
+
it "handles server close while asynchronous connect" do
|
681
906
|
serv = TCPServer.new( '127.0.0.1', 54320 )
|
682
907
|
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
683
|
-
[PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK].
|
908
|
+
expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
|
684
909
|
select( nil, [conn.socket_io], nil, 0.2 )
|
685
910
|
serv.close
|
686
911
|
if conn.connect_poll == PG::PGRES_POLLING_READING
|
687
912
|
select( [conn.socket_io], nil, nil, 0.2 )
|
688
913
|
end
|
689
|
-
conn.connect_poll.
|
914
|
+
expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
|
690
915
|
end
|
691
916
|
|
692
|
-
it "discards previous results
|
917
|
+
it "discards previous results at #discard_results" do
|
918
|
+
@conn.send_query( "select 1" )
|
919
|
+
@conn.discard_results
|
920
|
+
@conn.send_query( "select 41 as one" )
|
921
|
+
res = @conn.get_last_result
|
922
|
+
expect( res.to_a ).to eq( [{ 'one' => '41' }] )
|
923
|
+
end
|
924
|
+
|
925
|
+
it "discards previous results (if any) before waiting on #exec" do
|
926
|
+
@conn.send_query( "select 1" )
|
927
|
+
res = @conn.exec( "select 42 as one" )
|
928
|
+
expect( res.to_a ).to eq( [{ 'one' => '42' }] )
|
929
|
+
end
|
693
930
|
|
694
|
-
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
|
695
938
|
result = nil
|
696
|
-
@conn.
|
939
|
+
@conn.exec( "select 47 as one" ) do |pg_res|
|
697
940
|
result = pg_res[0]
|
698
941
|
end
|
699
|
-
result.
|
942
|
+
expect( result ).to eq( { 'one' => '47' } )
|
700
943
|
end
|
701
944
|
|
702
945
|
it "raises a rescue-able error if #finish is called twice", :without_transaction do
|
@@ -706,20 +949,34 @@ describe PG::Connection do
|
|
706
949
|
expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
707
950
|
end
|
708
951
|
|
709
|
-
it "
|
952
|
+
it "can use conn.reset to restart the connection" do
|
953
|
+
ios = IO.pipe
|
954
|
+
conn = PG.connect( @conninfo )
|
955
|
+
|
956
|
+
# Close the two pipe file descriptors, so that the file descriptor of
|
957
|
+
# newly established connection is probably distinct from the previous one.
|
958
|
+
ios.each(&:close)
|
959
|
+
conn.reset
|
960
|
+
|
961
|
+
# The new connection should work even when the file descriptor has changed.
|
962
|
+
expect( conn.exec("SELECT 1").values ).to eq([["1"]])
|
963
|
+
conn.close
|
964
|
+
end
|
965
|
+
|
966
|
+
it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction do
|
710
967
|
conn = PG.connect( @conninfo )
|
711
968
|
io = conn.socket_io
|
712
969
|
conn.finish
|
713
|
-
io.
|
970
|
+
expect( io ).to be_closed()
|
714
971
|
expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
715
972
|
end
|
716
973
|
|
717
|
-
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
|
718
975
|
conn = PG.connect( @conninfo )
|
719
976
|
io = conn.socket_io
|
720
977
|
conn.reset
|
721
|
-
io.
|
722
|
-
conn.socket_io.
|
978
|
+
expect( io ).to be_closed()
|
979
|
+
expect( conn.socket_io ).to_not equal( io )
|
723
980
|
conn.finish
|
724
981
|
end
|
725
982
|
|
@@ -734,157 +991,156 @@ describe PG::Connection do
|
|
734
991
|
expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
|
735
992
|
end
|
736
993
|
|
737
|
-
|
994
|
+
it "sets the fallback_application_name on new connections" do
|
995
|
+
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
738
996
|
|
739
|
-
|
740
|
-
|
741
|
-
|
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
|
742
1002
|
|
743
|
-
|
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"
|
744
1007
|
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
$0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
|
752
|
-
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
753
|
-
connection_string_should_contain_application_name(conn_string, $0)
|
754
|
-
ensure
|
755
|
-
$0 = old_0
|
756
|
-
end
|
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
|
757
1014
|
end
|
1015
|
+
end
|
758
1016
|
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
@conn.exec( 'ROLLBACK' )
|
763
|
-
@conn.exec( 'LISTEN knees' )
|
1017
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1018
|
+
"any number of arguments" do
|
764
1019
|
|
765
|
-
|
766
|
-
|
767
|
-
conn.finish
|
1020
|
+
@conn.exec( 'ROLLBACK' )
|
1021
|
+
@conn.exec( 'LISTEN knees' )
|
768
1022
|
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
end
|
773
|
-
@conn.exec( 'UNLISTEN knees' )
|
1023
|
+
conn = described_class.connect( @conninfo )
|
1024
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1025
|
+
conn.finish
|
774
1026
|
|
775
|
-
|
776
|
-
|
777
|
-
|
1027
|
+
event, pid, msg = nil
|
1028
|
+
@conn.wait_for_notify( 10 ) do |*args|
|
1029
|
+
event, pid, msg = *args
|
778
1030
|
end
|
1031
|
+
@conn.exec( 'UNLISTEN knees' )
|
779
1032
|
|
780
|
-
|
781
|
-
|
782
|
-
|
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
|
783
1037
|
|
784
|
-
|
785
|
-
|
786
|
-
|
1038
|
+
it "accepts nil as the timeout in #wait_for_notify " do
|
1039
|
+
@conn.exec( 'ROLLBACK' )
|
1040
|
+
@conn.exec( 'LISTEN knees' )
|
787
1041
|
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
end
|
792
|
-
@conn.exec( 'UNLISTEN knees' )
|
1042
|
+
conn = described_class.connect( @conninfo )
|
1043
|
+
conn.exec( %Q{NOTIFY knees} )
|
1044
|
+
conn.finish
|
793
1045
|
|
794
|
-
|
795
|
-
|
1046
|
+
event, pid = nil
|
1047
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1048
|
+
event, pid = *args
|
796
1049
|
end
|
1050
|
+
@conn.exec( 'UNLISTEN knees' )
|
797
1051
|
|
798
|
-
|
799
|
-
|
800
|
-
|
1052
|
+
expect( event ).to eq( 'knees' )
|
1053
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1054
|
+
end
|
801
1055
|
|
802
|
-
|
803
|
-
|
804
|
-
|
1056
|
+
it "sends nil as the payload if the notification wasn't given one" do
|
1057
|
+
@conn.exec( 'ROLLBACK' )
|
1058
|
+
@conn.exec( 'LISTEN knees' )
|
805
1059
|
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
end
|
810
|
-
@conn.exec( 'UNLISTEN knees' )
|
1060
|
+
conn = described_class.connect( @conninfo )
|
1061
|
+
conn.exec( %Q{NOTIFY knees} )
|
1062
|
+
conn.finish
|
811
1063
|
|
812
|
-
|
1064
|
+
payload = :notnil
|
1065
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1066
|
+
payload = args[ 2 ]
|
813
1067
|
end
|
1068
|
+
@conn.exec( 'UNLISTEN knees' )
|
814
1069
|
|
815
|
-
|
816
|
-
|
1070
|
+
expect( payload ).to be_nil()
|
1071
|
+
end
|
817
1072
|
|
818
|
-
|
819
|
-
|
1073
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1074
|
+
"two arguments" do
|
820
1075
|
|
821
|
-
|
822
|
-
|
823
|
-
conn.finish
|
1076
|
+
@conn.exec( 'ROLLBACK' )
|
1077
|
+
@conn.exec( 'LISTEN knees' )
|
824
1078
|
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
end
|
829
|
-
@conn.exec( 'UNLISTEN knees' )
|
1079
|
+
conn = described_class.connect( @conninfo )
|
1080
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1081
|
+
conn.finish
|
830
1082
|
|
831
|
-
|
832
|
-
|
833
|
-
msg
|
1083
|
+
event, pid, msg = nil
|
1084
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2|
|
1085
|
+
event, pid, msg = arg1, arg2
|
834
1086
|
end
|
1087
|
+
@conn.exec( 'UNLISTEN knees' )
|
835
1088
|
|
836
|
-
|
837
|
-
|
1089
|
+
expect( event ).to eq( 'knees' )
|
1090
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1091
|
+
expect( msg ).to be_nil()
|
1092
|
+
end
|
838
1093
|
|
839
|
-
|
840
|
-
|
1094
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it " +
|
1095
|
+
"doesn't accept arguments" do
|
841
1096
|
|
842
|
-
|
843
|
-
|
844
|
-
conn.finish
|
1097
|
+
@conn.exec( 'ROLLBACK' )
|
1098
|
+
@conn.exec( 'LISTEN knees' )
|
845
1099
|
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
end
|
850
|
-
@conn.exec( 'UNLISTEN knees' )
|
1100
|
+
conn = described_class.connect( @conninfo )
|
1101
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1102
|
+
conn.finish
|
851
1103
|
|
852
|
-
|
1104
|
+
notification_received = false
|
1105
|
+
@conn.wait_for_notify( 10 ) do
|
1106
|
+
notification_received = true
|
853
1107
|
end
|
1108
|
+
@conn.exec( 'UNLISTEN knees' )
|
854
1109
|
|
855
|
-
|
856
|
-
|
1110
|
+
expect( notification_received ).to be_truthy()
|
1111
|
+
end
|
857
1112
|
|
858
|
-
|
859
|
-
|
1113
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1114
|
+
"three arguments" do
|
860
1115
|
|
861
|
-
|
862
|
-
|
863
|
-
conn.finish
|
1116
|
+
@conn.exec( 'ROLLBACK' )
|
1117
|
+
@conn.exec( 'LISTEN knees' )
|
864
1118
|
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
end
|
869
|
-
@conn.exec( 'UNLISTEN knees' )
|
1119
|
+
conn = described_class.connect( @conninfo )
|
1120
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1121
|
+
conn.finish
|
870
1122
|
|
871
|
-
|
872
|
-
|
873
|
-
msg
|
1123
|
+
event, pid, msg = nil
|
1124
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
|
1125
|
+
event, pid, msg = arg1, arg2, arg3
|
874
1126
|
end
|
1127
|
+
@conn.exec( 'UNLISTEN knees' )
|
875
1128
|
|
1129
|
+
expect( event ).to eq( 'knees' )
|
1130
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1131
|
+
expect( msg ).to eq( 'skirt and boots' )
|
876
1132
|
end
|
877
1133
|
|
878
|
-
context "
|
1134
|
+
context "server ping", :without_transaction do
|
879
1135
|
|
880
1136
|
it "pings successfully with connection string" do
|
881
1137
|
ping = described_class.ping(@conninfo)
|
882
|
-
ping.
|
1138
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
883
1139
|
end
|
884
1140
|
|
885
1141
|
it "pings using 7 arguments converted to strings" do
|
886
1142
|
ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
|
887
|
-
ping.
|
1143
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
888
1144
|
end
|
889
1145
|
|
890
1146
|
it "pings using a hash of connection parameters" do
|
@@ -892,7 +1148,7 @@ describe PG::Connection do
|
|
892
1148
|
:host => 'localhost',
|
893
1149
|
:port => @port,
|
894
1150
|
:dbname => :test)
|
895
|
-
ping.
|
1151
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
896
1152
|
end
|
897
1153
|
|
898
1154
|
it "returns correct response when ping connection cannot be established" do
|
@@ -900,205 +1156,359 @@ describe PG::Connection do
|
|
900
1156
|
:host => 'localhost',
|
901
1157
|
:port => 9999,
|
902
1158
|
:dbname => :test)
|
903
|
-
ping.
|
1159
|
+
expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
|
904
1160
|
end
|
905
1161
|
|
906
|
-
it "returns
|
1162
|
+
it "returns error when ping connection arguments are wrong" do
|
907
1163
|
ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
|
908
|
-
ping.
|
1164
|
+
expect( ping ).to_not eq( PG::PQPING_OK )
|
909
1165
|
end
|
910
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
|
911
1174
|
|
912
1175
|
end
|
913
1176
|
|
914
|
-
|
915
|
-
describe "set_single_row_mode" do
|
1177
|
+
describe "set_single_row_mode" do
|
916
1178
|
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
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
|
921
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
|
922
1205
|
|
923
|
-
|
924
|
-
|
925
|
-
|
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
|
926
1209
|
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
results.length.should == 11
|
934
|
-
results[0..-2].each do |res|
|
935
|
-
res.result_status.should == PG::PGRES_SINGLE_TUPLE
|
936
|
-
values = res.field_values('generate_series')
|
937
|
-
values.length.should == 1
|
938
|
-
values.first.to_i.should > 0
|
939
|
-
end
|
940
|
-
results.last.result_status.should == PG::PGRES_TUPLES_OK
|
941
|
-
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
|
942
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
|
943
1220
|
|
944
|
-
|
945
|
-
|
946
|
-
|
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
|
947
1225
|
|
948
|
-
|
949
|
-
|
1226
|
+
first_result = nil
|
1227
|
+
expect do
|
950
1228
|
loop do
|
951
1229
|
res = @conn.get_result or break
|
952
1230
|
res.check
|
953
|
-
|
1231
|
+
first_result ||= res
|
954
1232
|
end
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
it "should receive rows before entire query fails" do
|
960
|
-
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
961
|
-
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
|
962
|
-
@conn.set_single_row_mode
|
963
|
-
|
964
|
-
first_result = nil
|
965
|
-
expect do
|
966
|
-
loop do
|
967
|
-
res = @conn.get_result or break
|
968
|
-
res.check
|
969
|
-
first_result ||= res
|
970
|
-
end
|
971
|
-
end.to raise_error(PG::Error)
|
972
|
-
first_result.kind_of?(PG::Result).should be_true
|
973
|
-
first_result.result_status.should == PG::PGRES_SINGLE_TUPLE
|
974
|
-
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 )
|
975
1236
|
end
|
1237
|
+
|
976
1238
|
end
|
977
1239
|
|
978
|
-
context "multinationalization support"
|
1240
|
+
context "multinationalization support" do
|
979
1241
|
|
980
1242
|
describe "rubyforge #22925: m17n support" do
|
981
1243
|
it "should return results in the same encoding as the client (iso-8859-1)" do
|
982
|
-
|
983
|
-
@conn.
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
end
|
988
|
-
out_string.should == 'fantasia'
|
989
|
-
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 )
|
990
1249
|
end
|
991
1250
|
|
992
1251
|
it "should return results in the same encoding as the client (utf-8)" do
|
993
|
-
|
994
|
-
@conn.
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
end
|
999
|
-
out_string.should == '世界線航跡蔵'
|
1000
|
-
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 )
|
1001
1257
|
end
|
1002
1258
|
|
1003
1259
|
it "should return results in the same encoding as the client (EUC-JP)" do
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
end
|
1011
|
-
out_string.should == '世界線航跡蔵'.encode('EUC-JP')
|
1012
|
-
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 )
|
1013
1266
|
end
|
1014
1267
|
|
1015
1268
|
it "returns the results in the correct encoding even if the client_encoding has " +
|
1016
1269
|
"changed since the results were fetched" do
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
end
|
1025
|
-
out_string.should == '世界線航跡蔵'.encode('EUC-JP')
|
1026
|
-
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 )
|
1027
1277
|
end
|
1028
1278
|
|
1029
1279
|
it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
|
1030
1280
|
@conn.exec "SET client_encoding TO SQL_ASCII"
|
1031
|
-
@conn.internal_encoding.
|
1281
|
+
expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
|
1032
1282
|
end
|
1033
1283
|
|
1034
|
-
it "
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
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 )
|
1046
1298
|
end
|
1047
1299
|
|
1048
1300
|
it "uses the client encoding for escaped string" do
|
1049
|
-
original = "
|
1301
|
+
original = "Möhre to 'scape".encode( "utf-16be" )
|
1050
1302
|
@conn.set_client_encoding( "euc_jp" )
|
1051
1303
|
escaped = @conn.escape( original )
|
1052
|
-
escaped.encoding.
|
1053
|
-
escaped.
|
1304
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1305
|
+
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
|
1054
1306
|
end
|
1055
1307
|
|
1056
|
-
it "uses the client encoding for escaped literal"
|
1057
|
-
original = "
|
1308
|
+
it "uses the client encoding for escaped literal" do
|
1309
|
+
original = "Möhre to 'scape".encode( "utf-16be" )
|
1058
1310
|
@conn.set_client_encoding( "euc_jp" )
|
1059
1311
|
escaped = @conn.escape_literal( original )
|
1060
|
-
escaped.encoding.
|
1061
|
-
escaped.
|
1312
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1313
|
+
expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
|
1062
1314
|
end
|
1063
1315
|
|
1064
|
-
it "uses the client encoding for escaped identifier"
|
1065
|
-
original = "
|
1316
|
+
it "uses the client encoding for escaped identifier" do
|
1317
|
+
original = "Möhre to 'scape".encode( "utf-16le" )
|
1066
1318
|
@conn.set_client_encoding( "euc_jp" )
|
1067
1319
|
escaped = @conn.escape_identifier( original )
|
1068
|
-
escaped.encoding.
|
1069
|
-
escaped.
|
1320
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1321
|
+
expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
|
1070
1322
|
end
|
1071
1323
|
|
1072
1324
|
it "uses the client encoding for quote_ident" do
|
1073
|
-
original = "
|
1325
|
+
original = "Möhre to 'scape".encode( "utf-16le" )
|
1074
1326
|
@conn.set_client_encoding( "euc_jp" )
|
1075
1327
|
escaped = @conn.quote_ident( original )
|
1076
|
-
escaped.encoding.
|
1077
|
-
escaped.
|
1328
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1329
|
+
expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
|
1078
1330
|
end
|
1079
1331
|
|
1080
1332
|
it "uses the previous string encoding for escaped string" do
|
1081
|
-
original = "
|
1333
|
+
original = "Möhre to 'scape".encode( "iso-8859-1" )
|
1082
1334
|
@conn.set_client_encoding( "euc_jp" )
|
1083
1335
|
escaped = described_class.escape( original )
|
1084
|
-
escaped.encoding.
|
1085
|
-
escaped.
|
1336
|
+
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1337
|
+
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
|
1086
1338
|
end
|
1087
1339
|
|
1088
1340
|
it "uses the previous string encoding for quote_ident" do
|
1089
|
-
original = "
|
1341
|
+
original = "Möhre to 'scape".encode( "iso-8859-1" )
|
1090
1342
|
@conn.set_client_encoding( "euc_jp" )
|
1091
1343
|
escaped = described_class.quote_ident( original )
|
1092
|
-
escaped.encoding.
|
1093
|
-
escaped.
|
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']] )
|
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 )
|
1094
1428
|
end
|
1095
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/)
|
1488
|
+
end
|
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("\"", "\"\"") + "\"" )
|
1096
1494
|
end
|
1097
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
|
1098
1508
|
|
1099
1509
|
describe "Ruby 1.9.x default_internal encoding" do
|
1100
1510
|
|
1101
|
-
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
|
1102
1512
|
@conn.transaction do |txn_conn|
|
1103
1513
|
txn_conn.internal_encoding = Encoding::ISO8859_1
|
1104
1514
|
txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
|
@@ -1107,13 +1517,14 @@ describe PG::Connection do
|
|
1107
1517
|
|
1108
1518
|
begin
|
1109
1519
|
prev_encoding = Encoding.default_internal
|
1110
|
-
Encoding.default_internal = Encoding::
|
1520
|
+
Encoding.default_internal = Encoding::ISO8859_2
|
1111
1521
|
|
1112
1522
|
conn = PG.connect( @conninfo )
|
1113
|
-
conn.internal_encoding.
|
1523
|
+
expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
|
1114
1524
|
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
1115
|
-
res[0]['foo'].encoding.
|
1525
|
+
expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
|
1116
1526
|
ensure
|
1527
|
+
conn.exec( "DROP TABLE defaultinternaltest" )
|
1117
1528
|
conn.finish if conn
|
1118
1529
|
Encoding.default_internal = prev_encoding
|
1119
1530
|
end
|
@@ -1126,7 +1537,7 @@ describe PG::Connection do
|
|
1126
1537
|
|
1127
1538
|
@conn.set_default_encoding
|
1128
1539
|
|
1129
|
-
@conn.internal_encoding.
|
1540
|
+
expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
|
1130
1541
|
ensure
|
1131
1542
|
Encoding.default_internal = prev_encoding
|
1132
1543
|
end
|
@@ -1147,7 +1558,7 @@ describe PG::Connection do
|
|
1147
1558
|
query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
|
1148
1559
|
conn.exec( query )
|
1149
1560
|
rescue => err
|
1150
|
-
err.message.encoding.
|
1561
|
+
expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
|
1151
1562
|
else
|
1152
1563
|
fail "No exception raised?!"
|
1153
1564
|
end
|
@@ -1156,7 +1567,22 @@ describe PG::Connection do
|
|
1156
1567
|
conn.finish if conn
|
1157
1568
|
end
|
1158
1569
|
|
1159
|
-
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
|
1160
1586
|
[:receiver, :processor].each do |kind|
|
1161
1587
|
notices = []
|
1162
1588
|
@conn.internal_encoding = 'utf-8'
|
@@ -1174,19 +1600,18 @@ describe PG::Connection do
|
|
1174
1600
|
@conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
|
1175
1601
|
end
|
1176
1602
|
|
1177
|
-
notices.length.
|
1603
|
+
expect( notices.length ).to eq( 3 )
|
1178
1604
|
notices.each do |notice|
|
1179
|
-
notice.
|
1180
|
-
notice.encoding.
|
1605
|
+
expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
|
1606
|
+
expect( notice.encoding ).to eq( Encoding::UTF_8 )
|
1181
1607
|
end
|
1182
1608
|
@conn.set_notice_receiver
|
1183
1609
|
@conn.set_notice_processor
|
1184
1610
|
end
|
1185
1611
|
end
|
1186
1612
|
|
1187
|
-
it "receives properly encoded text from wait_for_notify", :
|
1613
|
+
it "receives properly encoded text from wait_for_notify", :without_transaction do
|
1188
1614
|
@conn.internal_encoding = 'utf-8'
|
1189
|
-
@conn.exec( 'ROLLBACK' )
|
1190
1615
|
@conn.exec( 'LISTEN "Möhre"' )
|
1191
1616
|
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1192
1617
|
event, pid, msg = nil
|
@@ -1195,36 +1620,36 @@ describe PG::Connection do
|
|
1195
1620
|
end
|
1196
1621
|
@conn.exec( 'UNLISTEN "Möhre"' )
|
1197
1622
|
|
1198
|
-
event.
|
1199
|
-
event.encoding.
|
1200
|
-
|
1201
|
-
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 )
|
1202
1628
|
end
|
1203
1629
|
|
1204
|
-
it "returns properly encoded text from notifies", :
|
1630
|
+
it "returns properly encoded text from notifies", :without_transaction do
|
1205
1631
|
@conn.internal_encoding = 'utf-8'
|
1206
|
-
@conn.exec( 'ROLLBACK' )
|
1207
1632
|
@conn.exec( 'LISTEN "Möhre"' )
|
1208
1633
|
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1209
1634
|
@conn.exec( 'UNLISTEN "Möhre"' )
|
1210
1635
|
|
1211
1636
|
notification = @conn.notifies
|
1212
|
-
notification[:relname].
|
1213
|
-
notification[:relname].encoding.
|
1214
|
-
notification[:extra].
|
1215
|
-
notification[:extra].encoding.
|
1216
|
-
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
|
1217
1642
|
end
|
1218
1643
|
end
|
1219
1644
|
|
1220
|
-
context "OS thread support"
|
1645
|
+
context "OS thread support" do
|
1221
1646
|
it "Connection#exec shouldn't block a second thread" do
|
1222
1647
|
t = Thread.new do
|
1223
1648
|
@conn.exec( "select pg_sleep(1)" )
|
1224
1649
|
end
|
1225
1650
|
|
1226
1651
|
sleep 0.5
|
1227
|
-
t.
|
1652
|
+
expect( t ).to be_alive()
|
1228
1653
|
t.join
|
1229
1654
|
end
|
1230
1655
|
|
@@ -1238,9 +1663,287 @@ describe PG::Connection do
|
|
1238
1663
|
end
|
1239
1664
|
|
1240
1665
|
sleep 0.5
|
1241
|
-
t.
|
1666
|
+
expect( t ).to be_alive()
|
1242
1667
|
serv.close
|
1243
1668
|
t.join
|
1244
1669
|
end
|
1245
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
|
1948
|
+
end
|
1246
1949
|
end
|