pg-ct 0.10.0

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.
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