pg 1.2.1-x64-mingw32 → 1.3.0.rc2-x64-mingw32
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.appveyor.yml +36 -0
- data/.gems +6 -0
- data/.github/workflows/binary-gems.yml +85 -0
- data/.github/workflows/source-gem.yml +130 -0
- data/.gitignore +13 -0
- data/.hgsigs +34 -0
- data/.hgtags +41 -0
- data/.irbrc +23 -0
- data/.pryrc +23 -0
- data/.tm_properties +21 -0
- data/.travis.yml +49 -0
- data/Gemfile +14 -0
- data/History.rdoc +93 -7
- data/Manifest.txt +0 -1
- data/README.rdoc +8 -7
- data/Rakefile +31 -140
- data/Rakefile.cross +55 -56
- data/certs/ged.pem +24 -0
- data/ext/errorcodes.def +8 -0
- data/ext/errorcodes.txt +3 -1
- data/ext/extconf.rb +90 -19
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -0
- data/ext/pg.c +59 -4
- data/ext/pg.h +18 -0
- data/ext/pg_coder.c +90 -24
- data/ext/pg_connection.c +606 -533
- data/ext/pg_copy_coder.c +45 -15
- data/ext/pg_record_coder.c +38 -9
- data/ext/pg_result.c +61 -31
- data/ext/pg_text_decoder.c +1 -1
- data/ext/pg_text_encoder.c +6 -6
- data/ext/pg_tuple.c +47 -21
- data/ext/pg_type_map.c +41 -8
- data/ext/pg_type_map_all_strings.c +14 -1
- data/ext/pg_type_map_by_class.c +50 -21
- data/ext/pg_type_map_by_column.c +64 -28
- data/ext/pg_type_map_by_mri_type.c +47 -18
- data/ext/pg_type_map_by_oid.c +52 -23
- data/ext/pg_type_map_in_ruby.c +50 -19
- data/ext/pg_util.c +2 -2
- data/lib/2.5/pg_ext.so +0 -0
- data/lib/2.6/pg_ext.so +0 -0
- data/lib/2.7/pg_ext.so +0 -0
- data/lib/3.0/pg_ext.so +0 -0
- data/lib/pg/basic_type_map_based_on_result.rb +47 -0
- data/lib/pg/basic_type_map_for_queries.rb +193 -0
- data/lib/pg/basic_type_map_for_results.rb +81 -0
- data/lib/pg/basic_type_registry.rb +296 -0
- data/lib/pg/coder.rb +1 -1
- data/lib/pg/connection.rb +579 -57
- data/lib/pg/version.rb +4 -0
- data/lib/pg.rb +38 -24
- data/lib/x64-mingw32/libpq.dll +0 -0
- data/misc/openssl-pg-segfault.rb +31 -0
- data/misc/postgres/History.txt +9 -0
- data/misc/postgres/Manifest.txt +5 -0
- data/misc/postgres/README.txt +21 -0
- data/misc/postgres/Rakefile +21 -0
- data/misc/postgres/lib/postgres.rb +16 -0
- data/misc/ruby-pg/History.txt +9 -0
- data/misc/ruby-pg/Manifest.txt +5 -0
- data/misc/ruby-pg/README.txt +21 -0
- data/misc/ruby-pg/Rakefile +21 -0
- data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
- data/pg.gemspec +32 -0
- data/sample/array_insert.rb +20 -0
- data/sample/async_api.rb +106 -0
- data/sample/async_copyto.rb +39 -0
- data/sample/async_mixed.rb +56 -0
- data/sample/check_conn.rb +21 -0
- data/sample/copydata.rb +71 -0
- data/sample/copyfrom.rb +81 -0
- data/sample/copyto.rb +19 -0
- data/sample/cursor.rb +21 -0
- data/sample/disk_usage_report.rb +177 -0
- data/sample/issue-119.rb +94 -0
- data/sample/losample.rb +69 -0
- data/sample/minimal-testcase.rb +17 -0
- data/sample/notify_wait.rb +72 -0
- data/sample/pg_statistics.rb +285 -0
- data/sample/replication_monitor.rb +222 -0
- data/sample/test_binary_values.rb +33 -0
- data/sample/wal_shipper.rb +434 -0
- data/sample/warehouse_partitions.rb +311 -0
- data.tar.gz.sig +0 -0
- metadata +94 -235
- metadata.gz.sig +0 -0
- data/ChangeLog +0 -0
- data/lib/2.2/pg_ext.so +0 -0
- data/lib/2.3/pg_ext.so +0 -0
- data/lib/2.4/pg_ext.so +0 -0
- data/lib/libpq.dll +0 -0
- data/lib/pg/basic_type_mapping.rb +0 -522
- data/spec/data/expected_trace.out +0 -26
- data/spec/data/random_binary_data +0 -0
- data/spec/helpers.rb +0 -382
- data/spec/pg/basic_type_mapping_spec.rb +0 -645
- data/spec/pg/connection_spec.rb +0 -1911
- data/spec/pg/connection_sync_spec.rb +0 -41
- data/spec/pg/result_spec.rb +0 -681
- data/spec/pg/tuple_spec.rb +0 -333
- data/spec/pg/type_map_by_class_spec.rb +0 -138
- data/spec/pg/type_map_by_column_spec.rb +0 -226
- data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
- data/spec/pg/type_map_by_oid_spec.rb +0 -149
- data/spec/pg/type_map_in_ruby_spec.rb +0 -164
- data/spec/pg/type_map_spec.rb +0 -22
- data/spec/pg/type_spec.rb +0 -1123
- data/spec/pg_spec.rb +0 -50
data/spec/pg/connection_spec.rb
DELETED
@@ -1,1911 +0,0 @@
|
|
1
|
-
# -*- rspec -*-
|
2
|
-
#encoding: utf-8
|
3
|
-
|
4
|
-
require_relative '../helpers'
|
5
|
-
|
6
|
-
require 'timeout'
|
7
|
-
require 'socket'
|
8
|
-
require 'pg'
|
9
|
-
|
10
|
-
describe PG::Connection do
|
11
|
-
|
12
|
-
it "can create a connection option string from a Hash of options" do
|
13
|
-
optstring = described_class.parse_connect_args(
|
14
|
-
:host => 'pgsql.example.com',
|
15
|
-
:dbname => 'db01',
|
16
|
-
'sslmode' => 'require'
|
17
|
-
)
|
18
|
-
|
19
|
-
expect( optstring ).to be_a( String )
|
20
|
-
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
21
|
-
expect( optstring ).to match( /(^|\s)dbname='db01'/ )
|
22
|
-
expect( optstring ).to match( /(^|\s)sslmode='require'/ )
|
23
|
-
end
|
24
|
-
|
25
|
-
it "can create a connection option string from positional parameters" do
|
26
|
-
optstring = described_class.parse_connect_args( 'pgsql.example.com', nil, '-c geqo=off', nil,
|
27
|
-
'sales' )
|
28
|
-
|
29
|
-
expect( optstring ).to be_a( String )
|
30
|
-
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
31
|
-
expect( optstring ).to match( /(^|\s)dbname='sales'/ )
|
32
|
-
expect( optstring ).to match( /(^|\s)options='-c geqo=off'/ )
|
33
|
-
|
34
|
-
expect( optstring ).to_not match( /port=/ )
|
35
|
-
expect( optstring ).to_not match( /tty=/ )
|
36
|
-
end
|
37
|
-
|
38
|
-
it "can create a connection option string from a mix of positional and hash parameters" do
|
39
|
-
optstring = described_class.parse_connect_args( 'pgsql.example.com',
|
40
|
-
:dbname => 'licensing', :user => 'jrandom' )
|
41
|
-
|
42
|
-
expect( optstring ).to be_a( String )
|
43
|
-
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
44
|
-
expect( optstring ).to match( /(^|\s)dbname='licensing'/ )
|
45
|
-
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
46
|
-
end
|
47
|
-
|
48
|
-
it "can create a connection option string from an option string and a hash" do
|
49
|
-
optstring = described_class.parse_connect_args( 'dbname=original', :user => 'jrandom' )
|
50
|
-
|
51
|
-
expect( optstring ).to be_a( String )
|
52
|
-
expect( optstring ).to match( /(^|\s)dbname=original/ )
|
53
|
-
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
54
|
-
end
|
55
|
-
|
56
|
-
it "escapes single quotes and backslashes in connection parameters" do
|
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
|
-
|
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
|
-
|
117
|
-
it "connects with defaults if no connection parameters are given" do
|
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} )
|
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 )
|
137
|
-
tmpconn.finish
|
138
|
-
end
|
139
|
-
|
140
|
-
it "connects using 7 arguments converted to strings" do
|
141
|
-
tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
|
142
|
-
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
143
|
-
tmpconn.finish
|
144
|
-
end
|
145
|
-
|
146
|
-
it "connects using a hash of connection parameters" do
|
147
|
-
tmpconn = described_class.connect(
|
148
|
-
:host => 'localhost',
|
149
|
-
:port => @port,
|
150
|
-
:dbname => :test)
|
151
|
-
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
152
|
-
tmpconn.finish
|
153
|
-
end
|
154
|
-
|
155
|
-
it "connects using a hash of optional connection parameters" do
|
156
|
-
tmpconn = described_class.connect(
|
157
|
-
:host => 'localhost',
|
158
|
-
:port => @port,
|
159
|
-
:dbname => :test,
|
160
|
-
:keepalives => 1)
|
161
|
-
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
162
|
-
tmpconn.finish
|
163
|
-
end
|
164
|
-
|
165
|
-
it "raises an exception when connecting with an invalid number of arguments" do
|
166
|
-
expect {
|
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
|
174
|
-
end
|
175
|
-
|
176
|
-
it "can connect asynchronously", :socket_io do
|
177
|
-
tmpconn = described_class.connect_start( @conninfo )
|
178
|
-
expect( tmpconn ).to be_a( described_class )
|
179
|
-
|
180
|
-
wait_for_polling_ok(tmpconn)
|
181
|
-
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
182
|
-
tmpconn.finish
|
183
|
-
end
|
184
|
-
|
185
|
-
it "can connect asynchronously for the duration of a block", :socket_io do
|
186
|
-
conn = nil
|
187
|
-
|
188
|
-
described_class.connect_start(@conninfo) do |tmpconn|
|
189
|
-
expect( tmpconn ).to be_a( described_class )
|
190
|
-
conn = tmpconn
|
191
|
-
|
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", :socket_io do
|
200
|
-
before :each do
|
201
|
-
@conn2 = described_class.connect_start( @conninfo )
|
202
|
-
wait_for_polling_ok(@conn2)
|
203
|
-
expect( @conn2 ).to still_be_usable
|
204
|
-
end
|
205
|
-
|
206
|
-
after :each do
|
207
|
-
expect( @conn2 ).to still_be_usable
|
208
|
-
@conn2.close
|
209
|
-
end
|
210
|
-
|
211
|
-
it "conn.send_query and IO.select work" do
|
212
|
-
@conn2.send_query("SELECT 1")
|
213
|
-
res = wait_for_query_result(@conn2)
|
214
|
-
expect( res.values ).to eq([["1"]])
|
215
|
-
end
|
216
|
-
|
217
|
-
it "conn.send_query and conn.block work" do
|
218
|
-
@conn2.send_query("SELECT 2")
|
219
|
-
@conn2.block
|
220
|
-
res = @conn2.get_last_result
|
221
|
-
expect( res.values ).to eq([["2"]])
|
222
|
-
end
|
223
|
-
|
224
|
-
it "conn.async_query works" do
|
225
|
-
res = @conn2.async_query("SELECT 3")
|
226
|
-
expect( res.values ).to eq([["3"]])
|
227
|
-
expect( @conn2 ).to still_be_usable
|
228
|
-
|
229
|
-
res = @conn2.query("SELECT 4")
|
230
|
-
end
|
231
|
-
|
232
|
-
it "can use conn.reset_start to restart the connection" do
|
233
|
-
ios = IO.pipe
|
234
|
-
conn = described_class.connect_start( @conninfo )
|
235
|
-
wait_for_polling_ok(conn)
|
236
|
-
|
237
|
-
# Close the two pipe file descriptors, so that the file descriptor of
|
238
|
-
# newly established connection is probably distinct from the previous one.
|
239
|
-
ios.each(&:close)
|
240
|
-
conn.reset_start
|
241
|
-
wait_for_polling_ok(conn, :reset_poll)
|
242
|
-
|
243
|
-
# The new connection should work even when the file descriptor has changed.
|
244
|
-
conn.send_query("SELECT 1")
|
245
|
-
res = wait_for_query_result(conn)
|
246
|
-
expect( res.values ).to eq([["1"]])
|
247
|
-
|
248
|
-
conn.close
|
249
|
-
end
|
250
|
-
|
251
|
-
it "should properly close a socket IO when GC'ed" do
|
252
|
-
# This results in
|
253
|
-
# Errno::ENOTSOCK: An operation was attempted on something that is not a socket.
|
254
|
-
# on Windows when rb_w32_unwrap_io_handle() isn't called in pgconn_gc_free().
|
255
|
-
5.times do
|
256
|
-
conn = described_class.connect( @conninfo )
|
257
|
-
conn.socket_io.close
|
258
|
-
end
|
259
|
-
GC.start
|
260
|
-
IO.pipe.each(&:close)
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
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/)
|
267
|
-
end
|
268
|
-
|
269
|
-
it "doesn't leave stale server connections after finish" do
|
270
|
-
described_class.connect(@conninfo).finish
|
271
|
-
sleep 0.5
|
272
|
-
res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
|
273
|
-
WHERE usename IS NOT NULL AND application_name != ''])
|
274
|
-
# there's still the global @conn, but should be no more
|
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" )
|
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
|
296
|
-
|
297
|
-
it "can set error context visibility", :postgresql_96 do
|
298
|
-
old = @conn.set_error_context_visibility( PG::PQSHOW_CONTEXT_NEVER )
|
299
|
-
new = @conn.set_error_context_visibility( old )
|
300
|
-
expect( new ).to eq( PG::PQSHOW_CONTEXT_NEVER )
|
301
|
-
end
|
302
|
-
|
303
|
-
let(:expected_trace_output) do
|
304
|
-
%{
|
305
|
-
To backend> Msg Q
|
306
|
-
To backend> "SELECT 1 AS one"
|
307
|
-
To backend> Msg complete, length 21
|
308
|
-
From backend> T
|
309
|
-
From backend (#4)> 28
|
310
|
-
From backend (#2)> 1
|
311
|
-
From backend> "one"
|
312
|
-
From backend (#4)> 0
|
313
|
-
From backend (#2)> 0
|
314
|
-
From backend (#4)> 23
|
315
|
-
From backend (#2)> 4
|
316
|
-
From backend (#4)> -1
|
317
|
-
From backend (#2)> 0
|
318
|
-
From backend> D
|
319
|
-
From backend (#4)> 11
|
320
|
-
From backend (#2)> 1
|
321
|
-
From backend (#4)> 1
|
322
|
-
From backend (1)> 1
|
323
|
-
From backend> C
|
324
|
-
From backend (#4)> 13
|
325
|
-
From backend> "SELECT 1"
|
326
|
-
From backend> Z
|
327
|
-
From backend (#4)> 5
|
328
|
-
From backend> Z
|
329
|
-
From backend (#4)> 5
|
330
|
-
From backend> T
|
331
|
-
}.gsub( /^\t{2}/, '' ).lstrip
|
332
|
-
end
|
333
|
-
|
334
|
-
it "trace and untrace client-server communication", :unix do
|
335
|
-
# be careful to explicitly close files so that the
|
336
|
-
# directory can be removed and we don't have to wait for
|
337
|
-
# the GC to run.
|
338
|
-
trace_file = TEST_DIRECTORY + "test_trace.out"
|
339
|
-
trace_io = trace_file.open( 'w', 0600 )
|
340
|
-
@conn.trace( trace_io )
|
341
|
-
trace_io.close
|
342
|
-
|
343
|
-
res = @conn.exec("SELECT 1 AS one")
|
344
|
-
@conn.untrace
|
345
|
-
|
346
|
-
res = @conn.exec("SELECT 2 AS two")
|
347
|
-
|
348
|
-
trace_data = trace_file.read
|
349
|
-
|
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' )
|
357
|
-
|
358
|
-
expect( trace_data ).to eq( expected_trace_output )
|
359
|
-
end
|
360
|
-
|
361
|
-
it "allows a query to be cancelled" do
|
362
|
-
error = false
|
363
|
-
@conn.send_query("SELECT pg_sleep(1000)")
|
364
|
-
@conn.cancel
|
365
|
-
tmpres = @conn.get_result
|
366
|
-
if(tmpres.result_status != PG::PGRES_TUPLES_OK)
|
367
|
-
error = true
|
368
|
-
end
|
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
|
382
|
-
end
|
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
|
-
|
399
|
-
it "automatically rolls back a transaction started with Connection#transaction if an exception " +
|
400
|
-
"is raised" do
|
401
|
-
# abort the per-example transaction so we can test our own
|
402
|
-
@conn.exec( 'ROLLBACK' )
|
403
|
-
|
404
|
-
res = nil
|
405
|
-
@conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
|
406
|
-
|
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 )
|
414
|
-
|
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" )
|
430
|
-
end
|
431
|
-
|
432
|
-
it "not read past the end of a large object" do
|
433
|
-
@conn.transaction do
|
434
|
-
oid = @conn.lo_create( 0 )
|
435
|
-
fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
|
436
|
-
@conn.lo_write( fd, "foobar" )
|
437
|
-
expect( @conn.lo_read( fd, 10 ) ).to be_nil()
|
438
|
-
@conn.lo_lseek( fd, 0, PG::SEEK_SET )
|
439
|
-
expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
it "supports explicitly calling #exec_params" do
|
444
|
-
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
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] )
|
448
|
-
|
449
|
-
res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
|
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'] )
|
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
|
476
|
-
|
477
|
-
it "can wait for NOTIFY events" do
|
478
|
-
@conn.exec( 'ROLLBACK' )
|
479
|
-
@conn.exec( 'LISTEN woo' )
|
480
|
-
|
481
|
-
t = Thread.new do
|
482
|
-
begin
|
483
|
-
conn = described_class.connect( @conninfo )
|
484
|
-
sleep 1
|
485
|
-
conn.async_exec( 'NOTIFY woo' )
|
486
|
-
ensure
|
487
|
-
conn.finish
|
488
|
-
end
|
489
|
-
end
|
490
|
-
|
491
|
-
expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
|
492
|
-
@conn.exec( 'UNLISTEN woo' )
|
493
|
-
|
494
|
-
t.join
|
495
|
-
end
|
496
|
-
|
497
|
-
it "calls a block for NOTIFY events if one is given" do
|
498
|
-
@conn.exec( 'ROLLBACK' )
|
499
|
-
@conn.exec( 'LISTEN woo' )
|
500
|
-
|
501
|
-
t = Thread.new do
|
502
|
-
begin
|
503
|
-
conn = described_class.connect( @conninfo )
|
504
|
-
sleep 1
|
505
|
-
conn.async_exec( 'NOTIFY woo' )
|
506
|
-
ensure
|
507
|
-
conn.finish
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
eventpid = event = nil
|
512
|
-
@conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
|
513
|
-
expect( event ).to eq( 'woo' )
|
514
|
-
expect( eventpid ).to be_an( Integer )
|
515
|
-
|
516
|
-
@conn.exec( 'UNLISTEN woo' )
|
517
|
-
|
518
|
-
t.join
|
519
|
-
end
|
520
|
-
|
521
|
-
it "doesn't collapse sequential notifications" do
|
522
|
-
@conn.exec( 'ROLLBACK' )
|
523
|
-
@conn.exec( 'LISTEN woo' )
|
524
|
-
@conn.exec( 'LISTEN war' )
|
525
|
-
@conn.exec( 'LISTEN woz' )
|
526
|
-
|
527
|
-
begin
|
528
|
-
conn = described_class.connect( @conninfo )
|
529
|
-
conn.exec( 'NOTIFY woo' )
|
530
|
-
conn.exec( 'NOTIFY war' )
|
531
|
-
conn.exec( 'NOTIFY woz' )
|
532
|
-
ensure
|
533
|
-
conn.finish
|
534
|
-
end
|
535
|
-
|
536
|
-
channels = []
|
537
|
-
3.times do
|
538
|
-
channels << @conn.wait_for_notify( 2 )
|
539
|
-
end
|
540
|
-
|
541
|
-
expect( channels.size ).to eq( 3 )
|
542
|
-
expect( channels ).to include( 'woo', 'war', 'woz' )
|
543
|
-
|
544
|
-
@conn.exec( 'UNLISTEN woz' )
|
545
|
-
@conn.exec( 'UNLISTEN war' )
|
546
|
-
@conn.exec( 'UNLISTEN woo' )
|
547
|
-
end
|
548
|
-
|
549
|
-
it "returns notifications which are already in the queue before wait_for_notify is called " +
|
550
|
-
"without waiting for the socket to become readable" do
|
551
|
-
@conn.exec( 'ROLLBACK' )
|
552
|
-
@conn.exec( 'LISTEN woo' )
|
553
|
-
|
554
|
-
begin
|
555
|
-
conn = described_class.connect( @conninfo )
|
556
|
-
conn.exec( 'NOTIFY woo' )
|
557
|
-
ensure
|
558
|
-
conn.finish
|
559
|
-
end
|
560
|
-
|
561
|
-
# Cause the notification to buffer, but not be read yet
|
562
|
-
@conn.exec( 'SELECT 1' )
|
563
|
-
|
564
|
-
expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
|
565
|
-
@conn.exec( 'UNLISTEN woo' )
|
566
|
-
end
|
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
|
-
|
583
|
-
it "yields the result if block is given to exec" do
|
584
|
-
rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
|
585
|
-
values = []
|
586
|
-
expect( result ).to be_kind_of( PG::Result )
|
587
|
-
expect( result.ntuples ).to eq( 2 )
|
588
|
-
result.each do |tuple|
|
589
|
-
values << tuple['a']
|
590
|
-
end
|
591
|
-
values
|
592
|
-
end
|
593
|
-
|
594
|
-
expect( rval.size ).to eq( 2 )
|
595
|
-
expect( rval ).to include( '5678', '1234' )
|
596
|
-
end
|
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
|
719
|
-
|
720
|
-
it "correctly finishes COPY queries passed to #async_exec" do
|
721
|
-
@conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
|
722
|
-
|
723
|
-
results = []
|
724
|
-
begin
|
725
|
-
data = @conn.get_copy_data( true )
|
726
|
-
if false == data
|
727
|
-
@conn.block( 2.0 )
|
728
|
-
data = @conn.get_copy_data( true )
|
729
|
-
end
|
730
|
-
results << data if data
|
731
|
-
end until data.nil?
|
732
|
-
|
733
|
-
expect( results.size ).to eq( 2 )
|
734
|
-
expect( results ).to include( "1\n", "2\n" )
|
735
|
-
end
|
736
|
-
|
737
|
-
|
738
|
-
it "described_class#block shouldn't block a second thread" do
|
739
|
-
start = Time.now
|
740
|
-
t = Thread.new do
|
741
|
-
@conn.send_query( "select pg_sleep(3)" )
|
742
|
-
@conn.block
|
743
|
-
end
|
744
|
-
|
745
|
-
sleep 0.5
|
746
|
-
expect( t ).to be_alive()
|
747
|
-
@conn.cancel
|
748
|
-
t.join
|
749
|
-
expect( (Time.now - start) ).to be < 3
|
750
|
-
end
|
751
|
-
|
752
|
-
it "described_class#block should allow a timeout" do
|
753
|
-
@conn.send_query( "select pg_sleep(1)" )
|
754
|
-
|
755
|
-
start = Time.now
|
756
|
-
@conn.block( 0.3 )
|
757
|
-
finish = Time.now
|
758
|
-
|
759
|
-
expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
|
760
|
-
end
|
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
|
768
|
-
|
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 )
|
774
|
-
end
|
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
|
781
|
-
|
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
|
864
|
-
end
|
865
|
-
|
866
|
-
|
867
|
-
it "allows fetching a column of values from a result by column number" do
|
868
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
869
|
-
expect( res.column_values( 0 ) ).to eq( %w[1 2 3] )
|
870
|
-
expect( res.column_values( 1 ) ).to eq( %w[2 3 4] )
|
871
|
-
end
|
872
|
-
|
873
|
-
|
874
|
-
it "allows fetching a column of values from a result by field name" do
|
875
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
876
|
-
expect( res.field_values( 'column1' ) ).to eq( %w[1 2 3] )
|
877
|
-
expect( res.field_values( 'column2' ) ).to eq( %w[2 3 4] )
|
878
|
-
end
|
879
|
-
|
880
|
-
|
881
|
-
it "raises an error if selecting an invalid column index" do
|
882
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
883
|
-
expect {
|
884
|
-
res.column_values( 20 )
|
885
|
-
}.to raise_error( IndexError )
|
886
|
-
end
|
887
|
-
|
888
|
-
|
889
|
-
it "raises an error if selecting an invalid field name" do
|
890
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
891
|
-
expect {
|
892
|
-
res.field_values( 'hUUuurrg' )
|
893
|
-
}.to raise_error( IndexError )
|
894
|
-
end
|
895
|
-
|
896
|
-
|
897
|
-
it "raises an error if column index is not a number" do
|
898
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
899
|
-
expect {
|
900
|
-
res.column_values( 'hUUuurrg' )
|
901
|
-
}.to raise_error( TypeError )
|
902
|
-
end
|
903
|
-
|
904
|
-
|
905
|
-
it "handles server close while asynchronous connect", :socket_io do
|
906
|
-
serv = TCPServer.new( '127.0.0.1', 54320 )
|
907
|
-
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
908
|
-
expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
|
909
|
-
select( nil, [conn.socket_io], nil, 0.2 )
|
910
|
-
serv.close
|
911
|
-
if conn.connect_poll == PG::PGRES_POLLING_READING
|
912
|
-
select( [conn.socket_io], nil, nil, 0.2 )
|
913
|
-
end
|
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' }] )
|
923
|
-
end
|
924
|
-
|
925
|
-
it "discards previous results (if any) before waiting on #exec" do
|
926
|
-
@conn.send_query( "select 1" )
|
927
|
-
res = @conn.exec( "select 42 as one" )
|
928
|
-
expect( res.to_a ).to eq( [{ 'one' => '42' }] )
|
929
|
-
end
|
930
|
-
|
931
|
-
it "discards previous errors before waiting on #exec", :without_transaction do
|
932
|
-
@conn.send_query( "ERROR" )
|
933
|
-
res = @conn.exec( "select 43 as one" )
|
934
|
-
expect( res.to_a ).to eq( [{ 'one' => '43' }] )
|
935
|
-
end
|
936
|
-
|
937
|
-
it "calls the block if one is provided to #exec" do
|
938
|
-
result = nil
|
939
|
-
@conn.exec( "select 47 as one" ) do |pg_res|
|
940
|
-
result = pg_res[0]
|
941
|
-
end
|
942
|
-
expect( result ).to eq( { 'one' => '47' } )
|
943
|
-
end
|
944
|
-
|
945
|
-
it "raises a rescue-able error if #finish is called twice", :without_transaction do
|
946
|
-
conn = PG.connect( @conninfo )
|
947
|
-
|
948
|
-
conn.finish
|
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
|
964
|
-
end
|
965
|
-
|
966
|
-
it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
|
967
|
-
conn = PG.connect( @conninfo )
|
968
|
-
io = conn.socket_io
|
969
|
-
conn.finish
|
970
|
-
expect( io ).to be_closed()
|
971
|
-
expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
972
|
-
end
|
973
|
-
|
974
|
-
it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
|
975
|
-
conn = PG.connect( @conninfo )
|
976
|
-
io = conn.socket_io
|
977
|
-
conn.reset
|
978
|
-
expect( io ).to be_closed()
|
979
|
-
expect( conn.socket_io ).to_not equal( io )
|
980
|
-
conn.finish
|
981
|
-
end
|
982
|
-
|
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
|
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
|
993
|
-
|
994
|
-
it "sets the fallback_application_name on new connections" do
|
995
|
-
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
996
|
-
|
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
|
1002
|
-
|
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
|
1016
|
-
|
1017
|
-
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1018
|
-
"any number of arguments" do
|
1019
|
-
|
1020
|
-
@conn.exec( 'ROLLBACK' )
|
1021
|
-
@conn.exec( 'LISTEN knees' )
|
1022
|
-
|
1023
|
-
conn = described_class.connect( @conninfo )
|
1024
|
-
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1025
|
-
conn.finish
|
1026
|
-
|
1027
|
-
event, pid, msg = nil
|
1028
|
-
@conn.wait_for_notify( 10 ) do |*args|
|
1029
|
-
event, pid, msg = *args
|
1030
|
-
end
|
1031
|
-
@conn.exec( 'UNLISTEN knees' )
|
1032
|
-
|
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
|
1037
|
-
|
1038
|
-
it "accepts nil as the timeout in #wait_for_notify " do
|
1039
|
-
@conn.exec( 'ROLLBACK' )
|
1040
|
-
@conn.exec( 'LISTEN knees' )
|
1041
|
-
|
1042
|
-
conn = described_class.connect( @conninfo )
|
1043
|
-
conn.exec( %Q{NOTIFY knees} )
|
1044
|
-
conn.finish
|
1045
|
-
|
1046
|
-
event, pid = nil
|
1047
|
-
@conn.wait_for_notify( nil ) do |*args|
|
1048
|
-
event, pid = *args
|
1049
|
-
end
|
1050
|
-
@conn.exec( 'UNLISTEN knees' )
|
1051
|
-
|
1052
|
-
expect( event ).to eq( 'knees' )
|
1053
|
-
expect( pid ).to be_a_kind_of( Integer )
|
1054
|
-
end
|
1055
|
-
|
1056
|
-
it "sends nil as the payload if the notification wasn't given one" do
|
1057
|
-
@conn.exec( 'ROLLBACK' )
|
1058
|
-
@conn.exec( 'LISTEN knees' )
|
1059
|
-
|
1060
|
-
conn = described_class.connect( @conninfo )
|
1061
|
-
conn.exec( %Q{NOTIFY knees} )
|
1062
|
-
conn.finish
|
1063
|
-
|
1064
|
-
payload = :notnil
|
1065
|
-
@conn.wait_for_notify( nil ) do |*args|
|
1066
|
-
payload = args[ 2 ]
|
1067
|
-
end
|
1068
|
-
@conn.exec( 'UNLISTEN knees' )
|
1069
|
-
|
1070
|
-
expect( payload ).to be_nil()
|
1071
|
-
end
|
1072
|
-
|
1073
|
-
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1074
|
-
"two arguments" do
|
1075
|
-
|
1076
|
-
@conn.exec( 'ROLLBACK' )
|
1077
|
-
@conn.exec( 'LISTEN knees' )
|
1078
|
-
|
1079
|
-
conn = described_class.connect( @conninfo )
|
1080
|
-
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1081
|
-
conn.finish
|
1082
|
-
|
1083
|
-
event, pid, msg = nil
|
1084
|
-
@conn.wait_for_notify( 10 ) do |arg1, arg2|
|
1085
|
-
event, pid, msg = arg1, arg2
|
1086
|
-
end
|
1087
|
-
@conn.exec( 'UNLISTEN knees' )
|
1088
|
-
|
1089
|
-
expect( event ).to eq( 'knees' )
|
1090
|
-
expect( pid ).to be_a_kind_of( Integer )
|
1091
|
-
expect( msg ).to be_nil()
|
1092
|
-
end
|
1093
|
-
|
1094
|
-
it "calls the block supplied to wait_for_notify with the notify payload if it " +
|
1095
|
-
"doesn't accept arguments" do
|
1096
|
-
|
1097
|
-
@conn.exec( 'ROLLBACK' )
|
1098
|
-
@conn.exec( 'LISTEN knees' )
|
1099
|
-
|
1100
|
-
conn = described_class.connect( @conninfo )
|
1101
|
-
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1102
|
-
conn.finish
|
1103
|
-
|
1104
|
-
notification_received = false
|
1105
|
-
@conn.wait_for_notify( 10 ) do
|
1106
|
-
notification_received = true
|
1107
|
-
end
|
1108
|
-
@conn.exec( 'UNLISTEN knees' )
|
1109
|
-
|
1110
|
-
expect( notification_received ).to be_truthy()
|
1111
|
-
end
|
1112
|
-
|
1113
|
-
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1114
|
-
"three arguments" do
|
1115
|
-
|
1116
|
-
@conn.exec( 'ROLLBACK' )
|
1117
|
-
@conn.exec( 'LISTEN knees' )
|
1118
|
-
|
1119
|
-
conn = described_class.connect( @conninfo )
|
1120
|
-
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1121
|
-
conn.finish
|
1122
|
-
|
1123
|
-
event, pid, msg = nil
|
1124
|
-
@conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
|
1125
|
-
event, pid, msg = arg1, arg2, arg3
|
1126
|
-
end
|
1127
|
-
@conn.exec( 'UNLISTEN knees' )
|
1128
|
-
|
1129
|
-
expect( event ).to eq( 'knees' )
|
1130
|
-
expect( pid ).to be_a_kind_of( Integer )
|
1131
|
-
expect( msg ).to eq( 'skirt and boots' )
|
1132
|
-
end
|
1133
|
-
|
1134
|
-
context "server ping", :without_transaction do
|
1135
|
-
|
1136
|
-
it "pings successfully with connection string" do
|
1137
|
-
ping = described_class.ping(@conninfo)
|
1138
|
-
expect( ping ).to eq( PG::PQPING_OK )
|
1139
|
-
end
|
1140
|
-
|
1141
|
-
it "pings using 7 arguments converted to strings" do
|
1142
|
-
ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
|
1143
|
-
expect( ping ).to eq( PG::PQPING_OK )
|
1144
|
-
end
|
1145
|
-
|
1146
|
-
it "pings using a hash of connection parameters" do
|
1147
|
-
ping = described_class.ping(
|
1148
|
-
:host => 'localhost',
|
1149
|
-
:port => @port,
|
1150
|
-
:dbname => :test)
|
1151
|
-
expect( ping ).to eq( PG::PQPING_OK )
|
1152
|
-
end
|
1153
|
-
|
1154
|
-
it "returns correct response when ping connection cannot be established" do
|
1155
|
-
ping = described_class.ping(
|
1156
|
-
:host => 'localhost',
|
1157
|
-
:port => 9999,
|
1158
|
-
:dbname => :test)
|
1159
|
-
expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
|
1160
|
-
end
|
1161
|
-
|
1162
|
-
it "returns error when ping connection arguments are wrong" do
|
1163
|
-
ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
|
1164
|
-
expect( ping ).to_not eq( PG::PQPING_OK )
|
1165
|
-
end
|
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
|
1174
|
-
|
1175
|
-
end
|
1176
|
-
|
1177
|
-
describe "set_single_row_mode" do
|
1178
|
-
|
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
|
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
|
1205
|
-
|
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
|
1209
|
-
|
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
|
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
|
1220
|
-
|
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
|
1225
|
-
|
1226
|
-
first_result = nil
|
1227
|
-
expect do
|
1228
|
-
loop do
|
1229
|
-
res = @conn.get_result or break
|
1230
|
-
res.check
|
1231
|
-
first_result ||= res
|
1232
|
-
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 )
|
1236
|
-
end
|
1237
|
-
|
1238
|
-
end
|
1239
|
-
|
1240
|
-
context "multinationalization support" do
|
1241
|
-
|
1242
|
-
describe "rubyforge #22925: m17n support" do
|
1243
|
-
it "should return results in the same encoding as the client (iso-8859-1)" do
|
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 )
|
1249
|
-
end
|
1250
|
-
|
1251
|
-
it "should return results in the same encoding as the client (utf-8)" do
|
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 )
|
1257
|
-
end
|
1258
|
-
|
1259
|
-
it "should return results in the same encoding as the client (EUC-JP)" do
|
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 )
|
1266
|
-
end
|
1267
|
-
|
1268
|
-
it "returns the results in the correct encoding even if the client_encoding has " +
|
1269
|
-
"changed since the results were fetched" do
|
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 )
|
1277
|
-
end
|
1278
|
-
|
1279
|
-
it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
|
1280
|
-
@conn.exec "SET client_encoding TO SQL_ASCII"
|
1281
|
-
expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
|
1282
|
-
end
|
1283
|
-
|
1284
|
-
it "the connection should use JOHAB dummy encoding when it's set to JOHAB" do
|
1285
|
-
@conn.set_client_encoding "JOHAB"
|
1286
|
-
val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
|
1287
|
-
expect( val.encoding.name ).to eq( "JOHAB" )
|
1288
|
-
expect( val.unpack("H*")[0] ).to eq( "dc65" )
|
1289
|
-
end
|
1290
|
-
|
1291
|
-
it "can retrieve server encoding as text" do
|
1292
|
-
enc = @conn.parameter_status "server_encoding"
|
1293
|
-
expect( enc ).to eq( "UTF8" )
|
1294
|
-
end
|
1295
|
-
|
1296
|
-
it "can retrieve server encoding as ruby encoding" do
|
1297
|
-
expect( @conn.external_encoding ).to eq( Encoding::UTF_8 )
|
1298
|
-
end
|
1299
|
-
|
1300
|
-
it "uses the client encoding for escaped string" do
|
1301
|
-
original = "Möhre to 'scape".encode( "utf-16be" )
|
1302
|
-
@conn.set_client_encoding( "euc_jp" )
|
1303
|
-
escaped = @conn.escape( original )
|
1304
|
-
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1305
|
-
expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
|
1306
|
-
end
|
1307
|
-
|
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" )
|
1311
|
-
escaped = @conn.escape_literal( original )
|
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']] )
|
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/)
|
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("\"", "\"\"") + "\"" )
|
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
|
1508
|
-
|
1509
|
-
describe "Ruby 1.9.x default_internal encoding" do
|
1510
|
-
|
1511
|
-
it "honors the Encoding.default_internal if it's set and the synchronous interface is used", :without_transaction do
|
1512
|
-
@conn.transaction do |txn_conn|
|
1513
|
-
txn_conn.internal_encoding = Encoding::ISO8859_1
|
1514
|
-
txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
|
1515
|
-
txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
|
1516
|
-
end
|
1517
|
-
|
1518
|
-
begin
|
1519
|
-
prev_encoding = Encoding.default_internal
|
1520
|
-
Encoding.default_internal = Encoding::ISO8859_2
|
1521
|
-
|
1522
|
-
conn = PG.connect( @conninfo )
|
1523
|
-
expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
|
1524
|
-
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
1525
|
-
expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
|
1526
|
-
ensure
|
1527
|
-
conn.exec( "DROP TABLE defaultinternaltest" )
|
1528
|
-
conn.finish if conn
|
1529
|
-
Encoding.default_internal = prev_encoding
|
1530
|
-
end
|
1531
|
-
end
|
1532
|
-
|
1533
|
-
it "allows users of the async interface to set the client_encoding to the default_internal" do
|
1534
|
-
begin
|
1535
|
-
prev_encoding = Encoding.default_internal
|
1536
|
-
Encoding.default_internal = Encoding::KOI8_R
|
1537
|
-
|
1538
|
-
@conn.set_default_encoding
|
1539
|
-
|
1540
|
-
expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
|
1541
|
-
ensure
|
1542
|
-
Encoding.default_internal = prev_encoding
|
1543
|
-
end
|
1544
|
-
end
|
1545
|
-
|
1546
|
-
end
|
1547
|
-
|
1548
|
-
|
1549
|
-
it "encodes exception messages with the connection's encoding (#96)", :without_transaction do
|
1550
|
-
# Use a new connection so the client_encoding isn't set outside of this example
|
1551
|
-
conn = PG.connect( @conninfo )
|
1552
|
-
conn.client_encoding = 'iso-8859-15'
|
1553
|
-
|
1554
|
-
conn.transaction do
|
1555
|
-
conn.exec "CREATE TABLE foo (bar TEXT)"
|
1556
|
-
|
1557
|
-
begin
|
1558
|
-
query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
|
1559
|
-
conn.exec( query )
|
1560
|
-
rescue => err
|
1561
|
-
expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
|
1562
|
-
else
|
1563
|
-
fail "No exception raised?!"
|
1564
|
-
end
|
1565
|
-
end
|
1566
|
-
|
1567
|
-
conn.finish if conn
|
1568
|
-
end
|
1569
|
-
|
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
|
1586
|
-
[:receiver, :processor].each do |kind|
|
1587
|
-
notices = []
|
1588
|
-
@conn.internal_encoding = 'utf-8'
|
1589
|
-
if kind == :processor
|
1590
|
-
@conn.set_notice_processor do |msg|
|
1591
|
-
notices << msg
|
1592
|
-
end
|
1593
|
-
else
|
1594
|
-
@conn.set_notice_receiver do |result|
|
1595
|
-
notices << result.error_message
|
1596
|
-
end
|
1597
|
-
end
|
1598
|
-
|
1599
|
-
3.times do
|
1600
|
-
@conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
|
1601
|
-
end
|
1602
|
-
|
1603
|
-
expect( notices.length ).to eq( 3 )
|
1604
|
-
notices.each do |notice|
|
1605
|
-
expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
|
1606
|
-
expect( notice.encoding ).to eq( Encoding::UTF_8 )
|
1607
|
-
end
|
1608
|
-
@conn.set_notice_receiver
|
1609
|
-
@conn.set_notice_processor
|
1610
|
-
end
|
1611
|
-
end
|
1612
|
-
|
1613
|
-
it "receives properly encoded text from wait_for_notify", :without_transaction do
|
1614
|
-
@conn.internal_encoding = 'utf-8'
|
1615
|
-
@conn.exec( 'LISTEN "Möhre"' )
|
1616
|
-
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1617
|
-
event, pid, msg = nil
|
1618
|
-
@conn.wait_for_notify( 10 ) do |*args|
|
1619
|
-
event, pid, msg = *args
|
1620
|
-
end
|
1621
|
-
@conn.exec( 'UNLISTEN "Möhre"' )
|
1622
|
-
|
1623
|
-
expect( event ).to eq( "Möhre" )
|
1624
|
-
expect( event.encoding ).to eq( Encoding::UTF_8 )
|
1625
|
-
expect( msg ).to eq( '世界線航跡蔵' )
|
1626
|
-
expect( msg.encoding ).to eq( Encoding::UTF_8 )
|
1627
|
-
end
|
1628
|
-
|
1629
|
-
it "returns properly encoded text from notifies", :without_transaction do
|
1630
|
-
@conn.internal_encoding = 'utf-8'
|
1631
|
-
@conn.exec( 'LISTEN "Möhre"' )
|
1632
|
-
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1633
|
-
@conn.exec( 'UNLISTEN "Möhre"' )
|
1634
|
-
|
1635
|
-
notification = @conn.notifies
|
1636
|
-
expect( notification[:relname] ).to eq( "Möhre" )
|
1637
|
-
expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
|
1638
|
-
expect( notification[:extra] ).to eq( '世界線航跡蔵' )
|
1639
|
-
expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
|
1640
|
-
expect( notification[:be_pid] ).to be > 0
|
1641
|
-
end
|
1642
|
-
end
|
1643
|
-
|
1644
|
-
context "OS thread support" do
|
1645
|
-
it "Connection#exec shouldn't block a second thread" do
|
1646
|
-
t = Thread.new do
|
1647
|
-
@conn.exec( "select pg_sleep(1)" )
|
1648
|
-
end
|
1649
|
-
|
1650
|
-
sleep 0.5
|
1651
|
-
expect( t ).to be_alive()
|
1652
|
-
t.join
|
1653
|
-
end
|
1654
|
-
|
1655
|
-
it "Connection.new shouldn't block a second thread" do
|
1656
|
-
serv = nil
|
1657
|
-
t = Thread.new do
|
1658
|
-
serv = TCPServer.new( '127.0.0.1', 54320 )
|
1659
|
-
expect {
|
1660
|
-
described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
1661
|
-
}.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
|
1662
|
-
end
|
1663
|
-
|
1664
|
-
sleep 0.5
|
1665
|
-
expect( t ).to be_alive()
|
1666
|
-
serv.close
|
1667
|
-
t.join
|
1668
|
-
end
|
1669
|
-
end
|
1670
|
-
|
1671
|
-
describe "type casting" do
|
1672
|
-
it "should raise an error on invalid param mapping" do
|
1673
|
-
expect{
|
1674
|
-
@conn.exec_params( "SELECT 1", [], nil, :invalid )
|
1675
|
-
}.to raise_error(TypeError)
|
1676
|
-
end
|
1677
|
-
|
1678
|
-
it "should return nil if no type mapping is set" do
|
1679
|
-
expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
|
1680
|
-
expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
|
1681
|
-
end
|
1682
|
-
|
1683
|
-
it "shouldn't type map params unless requested" do
|
1684
|
-
if @conn.server_version < 100000
|
1685
|
-
expect{
|
1686
|
-
@conn.exec_params( "SELECT $1", [5] )
|
1687
|
-
}.to raise_error(PG::IndeterminateDatatype)
|
1688
|
-
else
|
1689
|
-
# PostgreSQL-10 maps to TEXT type (OID 25)
|
1690
|
-
expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
|
1691
|
-
end
|
1692
|
-
end
|
1693
|
-
|
1694
|
-
it "should raise an error on invalid encoder to put_copy_data" do
|
1695
|
-
expect{
|
1696
|
-
@conn.put_copy_data [1], :invalid
|
1697
|
-
}.to raise_error(TypeError)
|
1698
|
-
end
|
1699
|
-
|
1700
|
-
it "can type cast parameters to put_copy_data with explicit encoder" do
|
1701
|
-
tm = PG::TypeMapByColumn.new [nil]
|
1702
|
-
row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
1703
|
-
|
1704
|
-
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1705
|
-
res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1706
|
-
@conn.put_copy_data [1], row_encoder
|
1707
|
-
@conn.put_copy_data ["2"], row_encoder
|
1708
|
-
end
|
1709
|
-
|
1710
|
-
res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
|
1711
|
-
@conn.put_copy_data [3]
|
1712
|
-
@conn.put_copy_data ["4"]
|
1713
|
-
end
|
1714
|
-
|
1715
|
-
res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1716
|
-
expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
|
1717
|
-
end
|
1718
|
-
|
1719
|
-
context "with default query type map" do
|
1720
|
-
before :each do
|
1721
|
-
@conn2 = described_class.new(@conninfo)
|
1722
|
-
tm = PG::TypeMapByClass.new
|
1723
|
-
tm[Integer] = PG::TextEncoder::Integer.new oid: 20
|
1724
|
-
@conn2.type_map_for_queries = tm
|
1725
|
-
|
1726
|
-
row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
1727
|
-
@conn2.encoder_for_put_copy_data = row_encoder
|
1728
|
-
end
|
1729
|
-
after :each do
|
1730
|
-
@conn2.close
|
1731
|
-
end
|
1732
|
-
|
1733
|
-
it "should respect a type mapping for params and it's OID and format code" do
|
1734
|
-
res = @conn2.exec_params( "SELECT $1", [5] )
|
1735
|
-
expect( res.values ).to eq( [["5"]] )
|
1736
|
-
expect( res.ftype(0) ).to eq( 20 )
|
1737
|
-
end
|
1738
|
-
|
1739
|
-
it "should return the current type mapping" do
|
1740
|
-
expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
|
1741
|
-
end
|
1742
|
-
|
1743
|
-
it "should work with arbitrary number of params in conjunction with type casting" do
|
1744
|
-
begin
|
1745
|
-
3.step( 12, 0.2 ) do |exp|
|
1746
|
-
num_params = (2 ** exp).to_i
|
1747
|
-
sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
|
1748
|
-
params = num_params.times.to_a
|
1749
|
-
res = @conn2.exec_params( "SELECT #{sql}", params )
|
1750
|
-
expect( res.nfields ).to eq( num_params )
|
1751
|
-
expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
|
1752
|
-
end
|
1753
|
-
rescue PG::ProgramLimitExceeded
|
1754
|
-
# Stop silently as soon the server complains about too many params
|
1755
|
-
end
|
1756
|
-
end
|
1757
|
-
|
1758
|
-
it "can process #copy_data input queries with row encoder and respects character encoding" do
|
1759
|
-
@conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1760
|
-
res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1761
|
-
@conn2.put_copy_data [1]
|
1762
|
-
@conn2.put_copy_data ["Möhre".encode("utf-16le")]
|
1763
|
-
end
|
1764
|
-
|
1765
|
-
res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1766
|
-
expect( res.values ).to eq( [["1"], ["Möhre"]] )
|
1767
|
-
end
|
1768
|
-
end
|
1769
|
-
|
1770
|
-
context "with default result type map" do
|
1771
|
-
before :each do
|
1772
|
-
@conn2 = described_class.new(@conninfo)
|
1773
|
-
tm = PG::TypeMapByOid.new
|
1774
|
-
tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
|
1775
|
-
@conn2.type_map_for_results = tm
|
1776
|
-
|
1777
|
-
row_decoder = PG::TextDecoder::CopyRow.new
|
1778
|
-
@conn2.decoder_for_get_copy_data = row_decoder
|
1779
|
-
end
|
1780
|
-
after :each do
|
1781
|
-
@conn2.close
|
1782
|
-
end
|
1783
|
-
|
1784
|
-
it "should respect a type mapping for result" do
|
1785
|
-
res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
|
1786
|
-
expect( res.values ).to eq( [[5]] )
|
1787
|
-
end
|
1788
|
-
|
1789
|
-
it "should return the current type mapping" do
|
1790
|
-
expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
|
1791
|
-
end
|
1792
|
-
|
1793
|
-
it "should work with arbitrary number of params in conjunction with type casting" do
|
1794
|
-
begin
|
1795
|
-
3.step( 12, 0.2 ) do |exp|
|
1796
|
-
num_params = (2 ** exp).to_i
|
1797
|
-
sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
|
1798
|
-
params = num_params.times.to_a
|
1799
|
-
res = @conn2.exec_params( "SELECT #{sql}", params )
|
1800
|
-
expect( res.nfields ).to eq( num_params )
|
1801
|
-
expect( res.values ).to eq( [num_params.times.to_a] )
|
1802
|
-
end
|
1803
|
-
rescue PG::ProgramLimitExceeded
|
1804
|
-
# Stop silently as soon the server complains about too many params
|
1805
|
-
end
|
1806
|
-
end
|
1807
|
-
|
1808
|
-
it "can process #copy_data output with row decoder and respects character encoding" do
|
1809
|
-
@conn2.internal_encoding = Encoding::ISO8859_1
|
1810
|
-
rows = []
|
1811
|
-
res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
|
1812
|
-
while row=@conn2.get_copy_data
|
1813
|
-
rows << row
|
1814
|
-
end
|
1815
|
-
end
|
1816
|
-
expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
|
1817
|
-
expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
|
1818
|
-
end
|
1819
|
-
|
1820
|
-
it "can type cast #copy_data output with explicit decoder" do
|
1821
|
-
tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
|
1822
|
-
row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
|
1823
|
-
rows = []
|
1824
|
-
@conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
|
1825
|
-
while row=@conn.get_copy_data
|
1826
|
-
rows << row
|
1827
|
-
end
|
1828
|
-
end
|
1829
|
-
@conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
|
1830
|
-
while row=@conn.get_copy_data( false, row_decoder )
|
1831
|
-
rows << row
|
1832
|
-
end
|
1833
|
-
end
|
1834
|
-
expect( rows ).to eq( [[1], [2], [3], [4]] )
|
1835
|
-
end
|
1836
|
-
end
|
1837
|
-
end
|
1838
|
-
|
1839
|
-
describe :field_name_type do
|
1840
|
-
before :each do
|
1841
|
-
@conn2 = PG.connect(@conninfo)
|
1842
|
-
end
|
1843
|
-
after :each do
|
1844
|
-
@conn2.close
|
1845
|
-
end
|
1846
|
-
|
1847
|
-
it "uses string field names per default" do
|
1848
|
-
expect(@conn2.field_name_type).to eq(:string)
|
1849
|
-
end
|
1850
|
-
|
1851
|
-
it "can set string field names" do
|
1852
|
-
@conn2.field_name_type = :string
|
1853
|
-
expect(@conn2.field_name_type).to eq(:string)
|
1854
|
-
res = @conn2.exec("SELECT 1 as az")
|
1855
|
-
expect(res.field_name_type).to eq(:string)
|
1856
|
-
expect(res.fields).to eq(["az"])
|
1857
|
-
end
|
1858
|
-
|
1859
|
-
it "can set symbol field names" do
|
1860
|
-
@conn2.field_name_type = :symbol
|
1861
|
-
expect(@conn2.field_name_type).to eq(:symbol)
|
1862
|
-
res = @conn2.exec("SELECT 1 as az")
|
1863
|
-
expect(res.field_name_type).to eq(:symbol)
|
1864
|
-
expect(res.fields).to eq([:az])
|
1865
|
-
end
|
1866
|
-
|
1867
|
-
it "can't set invalid values" do
|
1868
|
-
expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
|
1869
|
-
expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
|
1870
|
-
end
|
1871
|
-
end
|
1872
|
-
|
1873
|
-
describe "deprecated forms of methods" do
|
1874
|
-
it "should forward exec to exec_params" do
|
1875
|
-
res = @conn.exec("VALUES($1::INT)", [7]).values
|
1876
|
-
expect(res).to eq( [["7"]] )
|
1877
|
-
res = @conn.exec("VALUES($1::INT)", [7], 1).values
|
1878
|
-
expect(res).to eq( [[[7].pack("N")]] )
|
1879
|
-
res = @conn.exec("VALUES(8)", [], 1).values
|
1880
|
-
expect(res).to eq( [[[8].pack("N")]] )
|
1881
|
-
end
|
1882
|
-
|
1883
|
-
it "should forward exec_params to exec" do
|
1884
|
-
res = @conn.exec_params("VALUES(3); VALUES(4)").values
|
1885
|
-
expect(res).to eq( [["4"]] )
|
1886
|
-
res = @conn.exec_params("VALUES(3); VALUES(4)", nil).values
|
1887
|
-
expect(res).to eq( [["4"]] )
|
1888
|
-
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil).values
|
1889
|
-
expect(res).to eq( [["4"]] )
|
1890
|
-
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, 1).values
|
1891
|
-
expect(res).to eq( [["4"]] )
|
1892
|
-
res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil).values
|
1893
|
-
expect(res).to eq( [["4"]] )
|
1894
|
-
expect{
|
1895
|
-
@conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil, nil).values
|
1896
|
-
}.to raise_error(ArgumentError)
|
1897
|
-
end
|
1898
|
-
|
1899
|
-
it "should forward send_query to send_query_params" do
|
1900
|
-
@conn.send_query("VALUES($1)", [5])
|
1901
|
-
expect(@conn.get_last_result.values).to eq( [["5"]] )
|
1902
|
-
end
|
1903
|
-
|
1904
|
-
it "shouldn't forward send_query_params to send_query" do
|
1905
|
-
expect{ @conn.send_query_params("VALUES(4)").values }
|
1906
|
-
.to raise_error(ArgumentError)
|
1907
|
-
expect{ @conn.send_query_params("VALUES(4)", nil).values }
|
1908
|
-
.to raise_error(TypeError)
|
1909
|
-
end
|
1910
|
-
end
|
1911
|
-
end
|