pg-jdguyot 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/spec/m17n_spec.rb ADDED
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env spec
2
+ # encoding: utf-8
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ require 'rbconfig'
7
+
8
+ basedir = Pathname( __FILE__ ).dirname.parent
9
+ libdir = basedir + 'lib'
10
+ archlib = libdir + Config::CONFIG['sitearch']
11
+
12
+ $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
13
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
14
+ $LOAD_PATH.unshift( archlib.to_s ) unless $LOAD_PATH.include?( archlib.to_s )
15
+ }
16
+
17
+ require 'rspec'
18
+ require 'spec/lib/helpers'
19
+ require 'pg'
20
+
21
+ describe "multinationalization support", :ruby_19 => true do
22
+ include PgTestingHelpers
23
+
24
+ before( :all ) do
25
+ @conn = setup_testing_db( "m17n" )
26
+ @conn.exec( 'BEGIN' )
27
+ end
28
+
29
+
30
+ it "should return the same bytes in text format that are sent as inline text" do
31
+ binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
32
+ in_bytes = File.open(binary_file, 'r:ASCII-8BIT').read
33
+
34
+ out_bytes = nil
35
+ @conn.transaction do |conn|
36
+ conn.exec("SET standard_conforming_strings=on")
37
+ res = conn.exec("VALUES ('#{PGconn.escape_bytea(in_bytes)}'::bytea)", [], 0)
38
+ out_bytes = PGconn.unescape_bytea(res[0]['column1'])
39
+ end
40
+ out_bytes.should == in_bytes
41
+ end
42
+
43
+ describe "rubyforge #22925: m17n support" do
44
+ it "should return results in the same encoding as the client (iso-8859-1)" do
45
+ out_string = nil
46
+ @conn.transaction do |conn|
47
+ conn.internal_encoding = 'iso8859-1'
48
+ res = conn.exec("VALUES ('fantasia')", [], 0)
49
+ out_string = res[0]['column1']
50
+ end
51
+ out_string.should == 'fantasia'
52
+ out_string.encoding.should == Encoding::ISO8859_1
53
+ end
54
+
55
+ it "should return results in the same encoding as the client (utf-8)" do
56
+ out_string = nil
57
+ @conn.transaction do |conn|
58
+ conn.internal_encoding = 'utf-8'
59
+ res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
60
+ out_string = res[0]['column1']
61
+ end
62
+ out_string.should == '世界線航跡蔵'
63
+ out_string.encoding.should == Encoding::UTF_8
64
+ end
65
+
66
+ it "should return results in the same encoding as the client (EUC-JP)" do
67
+ out_string = nil
68
+ @conn.transaction do |conn|
69
+ conn.internal_encoding = 'EUC-JP'
70
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
71
+ res = conn.exec(stmt, [], 0)
72
+ out_string = res[0]['column1']
73
+ end
74
+ out_string.should == '世界線航跡蔵'.encode('EUC-JP')
75
+ out_string.encoding.should == Encoding::EUC_JP
76
+ end
77
+
78
+ it "returns the results in the correct encoding even if the client_encoding has " +
79
+ "changed since the results were fetched" do
80
+ out_string = nil
81
+ @conn.transaction do |conn|
82
+ conn.internal_encoding = 'EUC-JP'
83
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
84
+ res = conn.exec(stmt, [], 0)
85
+ conn.internal_encoding = 'utf-8'
86
+ out_string = res[0]['column1']
87
+ end
88
+ out_string.should == '世界線航跡蔵'.encode('EUC-JP')
89
+ out_string.encoding.should == Encoding::EUC_JP
90
+ end
91
+
92
+ it "the connection should return ASCII-8BIT when the server encoding is SQL_ASCII" do
93
+ @conn.external_encoding.should == Encoding::ASCII_8BIT
94
+ end
95
+
96
+ it "works around the unsupported JOHAB encoding by returning stuff in 'ASCII_8BIT'" do
97
+ pending "figuring out how to create a string in the JOHAB encoding" do
98
+ out_string = nil
99
+ @conn.transaction do |conn|
100
+ conn.exec( "set client_encoding = 'JOHAB';" )
101
+ stmt = "VALUES ('foo')".encode('JOHAB')
102
+ res = conn.exec( stmt, [], 0 )
103
+ out_string = res[0]['column1']
104
+ end
105
+ out_string.should == 'foo'.encode( Encoding::ASCII_8BIT )
106
+ out_string.encoding.should == Encoding::ASCII_8BIT
107
+ end
108
+ end
109
+
110
+ it "uses the client encoding for escaped string" do
111
+ original = "string to escape".force_encoding( "euc-jp" )
112
+ @conn.set_client_encoding( "euc_jp" )
113
+ escaped = @conn.escape( original )
114
+ escaped.encoding.should == Encoding::EUC_JP
115
+ end
116
+ end
117
+
118
+ describe "Ruby 1.9.x default_internal encoding" do
119
+
120
+ it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
121
+ @conn.transaction do |txn_conn|
122
+ txn_conn.internal_encoding = Encoding::ISO8859_1
123
+ txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
124
+ txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
125
+ end
126
+
127
+ begin
128
+ prev_encoding = Encoding.default_internal
129
+ Encoding.default_internal = Encoding::UTF_8
130
+
131
+ conn = PGconn.connect( @conninfo )
132
+ conn.internal_encoding.should == Encoding::UTF_8
133
+ res = conn.exec( "SELECT foo FROM defaultinternaltest" )
134
+ res[0]['foo'].encoding.should == Encoding::UTF_8
135
+ ensure
136
+ conn.finish if conn
137
+ Encoding.default_internal = prev_encoding
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+
144
+ after( :each ) do
145
+ @conn.exec( 'ROLLBACK' ) if @conn
146
+ end
147
+
148
+ after( :all ) do
149
+ teardown_testing_db( @conn ) if @conn
150
+ end
151
+ end
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env spec
2
+ # encoding: utf-8
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ require 'rbconfig'
7
+
8
+ basedir = Pathname( __FILE__ ).dirname.parent
9
+ libdir = basedir + 'lib'
10
+ archlib = libdir + Config::CONFIG['sitearch']
11
+
12
+ $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
13
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
14
+ $LOAD_PATH.unshift( archlib.to_s ) unless $LOAD_PATH.include?( archlib.to_s )
15
+ }
16
+
17
+ require 'rspec'
18
+ require 'spec/lib/helpers'
19
+ require 'pg'
20
+ require 'timeout'
21
+
22
+ describe PGconn do
23
+ include PgTestingHelpers
24
+
25
+ before( :all ) do
26
+ @conn = setup_testing_db( "PGconn" )
27
+ end
28
+
29
+ before( :each ) do
30
+ @conn.exec( 'BEGIN' )
31
+ end
32
+
33
+
34
+ it "should connect successfully with connection string" do
35
+ tmpconn = PGconn.connect(@conninfo)
36
+ tmpconn.status.should== PGconn::CONNECTION_OK
37
+ tmpconn.finish
38
+ end
39
+
40
+ it "should connect using 7 arguments converted to strings" do
41
+ tmpconn = PGconn.connect('localhost', @port, nil, nil, :test, nil, nil)
42
+ tmpconn.status.should== PGconn::CONNECTION_OK
43
+ tmpconn.finish
44
+ end
45
+
46
+ it "should connect using hash" do
47
+ tmpconn = PGconn.connect(
48
+ :host => 'localhost',
49
+ :port => @port,
50
+ :dbname => :test)
51
+ tmpconn.status.should== PGconn::CONNECTION_OK
52
+ tmpconn.finish
53
+ end
54
+
55
+ it "should connect asynchronously" do
56
+ tmpconn = PGconn.connect_start(@conninfo)
57
+ socket = IO.for_fd(tmpconn.socket)
58
+ status = tmpconn.connect_poll
59
+ while(status != PGconn::PGRES_POLLING_OK) do
60
+ if(status == PGconn::PGRES_POLLING_READING)
61
+ if(not select([socket],[],[],5.0))
62
+ raise "Asynchronous connection timed out!"
63
+ end
64
+ elsif(status == PGconn::PGRES_POLLING_WRITING)
65
+ if(not select([],[socket],[],5.0))
66
+ raise "Asynchronous connection timed out!"
67
+ end
68
+ end
69
+ status = tmpconn.connect_poll
70
+ end
71
+ tmpconn.status.should== PGconn::CONNECTION_OK
72
+ tmpconn.finish
73
+ end
74
+
75
+ it "should not leave stale server connections after finish" do
76
+ PGconn.connect(@conninfo).finish
77
+ sleep 0.5
78
+ res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
79
+ WHERE usename IS NOT NULL])
80
+ # there's still the global @conn, but should be no more
81
+ res[0]['n'].should == '1'
82
+ end
83
+
84
+
85
+ EXPECTED_TRACE_OUTPUT = %{
86
+ To backend> Msg Q
87
+ To backend> "SELECT 1 AS one"
88
+ To backend> Msg complete, length 21
89
+ From backend> T
90
+ From backend (#4)> 28
91
+ From backend (#2)> 1
92
+ From backend> "one"
93
+ From backend (#4)> 0
94
+ From backend (#2)> 0
95
+ From backend (#4)> 23
96
+ From backend (#2)> 4
97
+ From backend (#4)> -1
98
+ From backend (#2)> 0
99
+ From backend> D
100
+ From backend (#4)> 11
101
+ From backend (#2)> 1
102
+ From backend (#4)> 1
103
+ From backend (1)> 1
104
+ From backend> C
105
+ From backend (#4)> 13
106
+ From backend> "SELECT 1"
107
+ From backend> Z
108
+ From backend (#4)> 5
109
+ From backend> Z
110
+ From backend (#4)> 5
111
+ From backend> T
112
+ }.gsub( /^\t{2}/, '' ).lstrip
113
+
114
+ unless RUBY_PLATFORM =~ /mswin|mingw/
115
+ it "should trace and untrace client-server communication" do
116
+ # be careful to explicitly close files so that the
117
+ # directory can be removed and we don't have to wait for
118
+ # the GC to run.
119
+ trace_file = @test_directory + "test_trace.out"
120
+ trace_io = trace_file.open( 'w', 0600 )
121
+ @conn.trace( trace_io )
122
+ trace_io.close
123
+
124
+ res = @conn.exec("SELECT 1 AS one")
125
+ @conn.untrace
126
+
127
+ res = @conn.exec("SELECT 2 AS two")
128
+
129
+ trace_data = trace_file.read
130
+
131
+ expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
132
+ # For PostgreSQL < 9.0, the output will be different:
133
+ # -From backend (#4)> 13
134
+ # -From backend> "SELECT 1"
135
+ # +From backend (#4)> 11
136
+ # +From backend> "SELECT"
137
+ if @conn.server_version < 90000
138
+ expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
139
+ expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
140
+ end
141
+
142
+ trace_data.should == expected_trace_output
143
+ end
144
+ end
145
+
146
+ it "allows a query to be cancelled" do
147
+ error = false
148
+ @conn.send_query("SELECT pg_sleep(1000)")
149
+ @conn.cancel
150
+ tmpres = @conn.get_result
151
+ if(tmpres.result_status != PGresult::PGRES_TUPLES_OK)
152
+ error = true
153
+ end
154
+ error.should == true
155
+ end
156
+
157
+ it "automatically rolls back a transaction started with PGconn#transaction if an exception " +
158
+ "is raised" do
159
+ # abort the per-example transaction so we can test our own
160
+ @conn.exec( 'ROLLBACK' )
161
+
162
+ res = nil
163
+ @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
164
+
165
+ expect {
166
+ res = @conn.transaction do
167
+ @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
168
+ raise "Oh noes! All pie is gone!"
169
+ end
170
+ }.to raise_exception( RuntimeError, /all pie is gone/i )
171
+
172
+ res = @conn.exec( "SELECT * FROM pie" )
173
+ res.ntuples.should == 0
174
+ end
175
+
176
+ it "should not read past the end of a large object" do
177
+ @conn.transaction do
178
+ oid = @conn.lo_create( 0 )
179
+ fd = @conn.lo_open( oid, PGconn::INV_READ|PGconn::INV_WRITE )
180
+ @conn.lo_write( fd, "foobar" )
181
+ @conn.lo_read( fd, 10 ).should be_nil()
182
+ @conn.lo_lseek( fd, 0, PGconn::SEEK_SET )
183
+ @conn.lo_read( fd, 10 ).should == 'foobar'
184
+ end
185
+ end
186
+
187
+
188
+ it "can wait for NOTIFY events" do
189
+ @conn.exec( 'ROLLBACK' )
190
+ @conn.exec( 'LISTEN woo' )
191
+
192
+ pid = fork do
193
+ begin
194
+ conn = PGconn.connect( @conninfo )
195
+ sleep 1
196
+ conn.exec( 'NOTIFY woo' )
197
+ ensure
198
+ conn.finish
199
+ exit!
200
+ end
201
+ end
202
+
203
+ @conn.wait_for_notify( 10 ).should == 'woo'
204
+ @conn.exec( 'UNLISTEN woo' )
205
+
206
+ Process.wait( pid )
207
+ end
208
+
209
+ it "calls a block for NOTIFY events if one is given" do
210
+ @conn.exec( 'ROLLBACK' )
211
+ @conn.exec( 'LISTEN woo' )
212
+
213
+ pid = fork do
214
+ begin
215
+ conn = PGconn.connect( @conninfo )
216
+ sleep 1
217
+ conn.exec( 'NOTIFY woo' )
218
+ ensure
219
+ conn.finish
220
+ exit!
221
+ end
222
+ end
223
+
224
+ eventpid = event = nil
225
+ @conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
226
+ event.should == 'woo'
227
+ eventpid.should be_an( Integer )
228
+
229
+ @conn.exec( 'UNLISTEN woo' )
230
+
231
+ Process.wait( pid )
232
+ end
233
+
234
+ it "doesn't collapse sequential notifications" do
235
+ @conn.exec( 'ROLLBACK' )
236
+ @conn.exec( 'LISTEN woo' )
237
+ @conn.exec( 'LISTEN war' )
238
+ @conn.exec( 'LISTEN woz' )
239
+
240
+ pid = fork do
241
+ begin
242
+ conn = PGconn.connect( @conninfo )
243
+ conn.exec( 'NOTIFY woo' )
244
+ conn.exec( 'NOTIFY war' )
245
+ conn.exec( 'NOTIFY woz' )
246
+ ensure
247
+ conn.finish
248
+ exit!
249
+ end
250
+ end
251
+
252
+ Process.wait( pid )
253
+
254
+ channels = []
255
+ 3.times do
256
+ channels << @conn.wait_for_notify( 2 )
257
+ end
258
+
259
+ channels.should have( 3 ).members
260
+ channels.should include( 'woo', 'war', 'woz' )
261
+
262
+ @conn.exec( 'UNLISTEN woz' )
263
+ @conn.exec( 'UNLISTEN war' )
264
+ @conn.exec( 'UNLISTEN woo' )
265
+ end
266
+
267
+ it "returns notifications which are already in the queue before wait_for_notify is called " +
268
+ "without waiting for the socket to become readable" do
269
+ @conn.exec( 'ROLLBACK' )
270
+ @conn.exec( 'LISTEN woo' )
271
+
272
+ pid = fork do
273
+ begin
274
+ conn = PGconn.connect( @conninfo )
275
+ conn.exec( 'NOTIFY woo' )
276
+ ensure
277
+ conn.finish
278
+ exit!
279
+ end
280
+ end
281
+
282
+ # Wait for the forked child to send the notification
283
+ Process.wait( pid )
284
+
285
+ # Cause the notification to buffer, but not be read yet
286
+ @conn.exec( 'SELECT 1' )
287
+
288
+ @conn.wait_for_notify( 10 ).should == 'woo'
289
+ @conn.exec( 'UNLISTEN woo' )
290
+ end
291
+
292
+ it "yields the result if block is given to exec" do
293
+ rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
294
+ values = []
295
+ result.should be_kind_of( PGresult )
296
+ result.ntuples.should == 2
297
+ result.each do |tuple|
298
+ values << tuple['a']
299
+ end
300
+ values
301
+ end
302
+
303
+ rval.should have( 2 ).members
304
+ rval.should include( '5678', '1234' )
305
+ end
306
+
307
+
308
+ it "correctly finishes COPY queries passed to #async_exec" do
309
+ @conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
310
+
311
+ results = []
312
+ begin
313
+ data = @conn.get_copy_data( true )
314
+ if false == data
315
+ @conn.block( 2.0 )
316
+ data = @conn.get_copy_data( true )
317
+ end
318
+ results << data if data
319
+ end until data.nil?
320
+
321
+ results.should have( 2 ).members
322
+ results.should include( "1\n", "2\n" )
323
+ end
324
+
325
+
326
+ it "PGconn#block shouldn't block a second thread" do
327
+ t = Thread.new do
328
+ @conn.send_query( "select pg_sleep(3)" )
329
+ @conn.block
330
+ end
331
+
332
+ # :FIXME: There's a race here, but hopefully it's pretty small.
333
+ t.should be_alive()
334
+
335
+ @conn.cancel
336
+ t.join
337
+ end
338
+
339
+ it "PGconn#block should allow a timeout" do
340
+ @conn.send_query( "select pg_sleep(3)" )
341
+
342
+ start = Time.now
343
+ @conn.block( 0.1 )
344
+ finish = Time.now
345
+
346
+ (finish - start).should be_close( 0.1, 0.05 )
347
+ end
348
+
349
+
350
+ it "can encrypt a string given a password and username" do
351
+ PGconn.encrypt_password("postgres", "postgres").
352
+ should =~ /\S+/
353
+ end
354
+
355
+
356
+ it "raises an appropriate error if either of the required arguments for encrypt_password " +
357
+ "is not valid" do
358
+ expect {
359
+ PGconn.encrypt_password( nil, nil )
360
+ }.to raise_error( TypeError )
361
+ expect {
362
+ PGconn.encrypt_password( "postgres", nil )
363
+ }.to raise_error( TypeError )
364
+ expect {
365
+ PGconn.encrypt_password( nil, "postgres" )
366
+ }.to raise_error( TypeError )
367
+ end
368
+
369
+
370
+ it "allows fetching a column of values from a result by column number" do
371
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
372
+ res.column_values( 0 ).should == %w[1 2 3]
373
+ res.column_values( 1 ).should == %w[2 3 4]
374
+ end
375
+
376
+
377
+ it "allows fetching a column of values from a result by field name" do
378
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
379
+ res.field_values( 'column1' ).should == %w[1 2 3]
380
+ res.field_values( 'column2' ).should == %w[2 3 4]
381
+ end
382
+
383
+
384
+ it "raises an error if selecting an invalid column index" do
385
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
386
+ expect {
387
+ res.column_values( 20 )
388
+ }.to raise_error( IndexError )
389
+ end
390
+
391
+
392
+ it "raises an error if selecting an invalid field name" do
393
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
394
+ expect {
395
+ res.field_values( 'hUUuurrg' )
396
+ }.to raise_error( IndexError )
397
+ end
398
+
399
+
400
+ it "raises an error if column index is not a number" do
401
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
402
+ expect {
403
+ res.column_values( 'hUUuurrg' )
404
+ }.to raise_error( TypeError )
405
+ end
406
+
407
+
408
+ it "can connect asynchronously" do
409
+ serv = TCPServer.new( '127.0.0.1', 54320 )
410
+ conn = PGconn.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
411
+ conn.connect_poll.should == PGconn::PGRES_POLLING_WRITING
412
+ select( nil, [IO.for_fd(conn.socket)], nil, 0.2 )
413
+ serv.close
414
+ if conn.connect_poll == PGconn::PGRES_POLLING_READING
415
+ select( [IO.for_fd(conn.socket)], nil, nil, 0.2 )
416
+ end
417
+ conn.connect_poll.should == PGconn::PGRES_POLLING_FAILED
418
+ end
419
+
420
+ it "discards previous results (if any) before waiting on an #async_exec"
421
+
422
+ it "calls the block if one is provided to #async_exec" do
423
+ result = nil
424
+ @conn.async_exec( "select 47 as one" ) do |pg_res|
425
+ result = pg_res[0]
426
+ end
427
+ result.should == { 'one' => '47' }
428
+ end
429
+
430
+ after( :each ) do
431
+ @conn.exec( 'ROLLBACK' )
432
+ end
433
+
434
+ after( :all ) do
435
+ teardown_testing_db( @conn )
436
+ end
437
+ end