pg 1.0.0 → 1.5.9

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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Gemfile +20 -0
  4. data/History.md +932 -0
  5. data/Manifest.txt +8 -3
  6. data/README-Windows.rdoc +4 -4
  7. data/README.ja.md +300 -0
  8. data/README.md +286 -0
  9. data/Rakefile +41 -138
  10. data/Rakefile.cross +71 -66
  11. data/certs/ged.pem +24 -0
  12. data/certs/kanis@comcard.de.pem +20 -0
  13. data/certs/larskanis-2022.pem +26 -0
  14. data/certs/larskanis-2023.pem +24 -0
  15. data/certs/larskanis-2024.pem +24 -0
  16. data/ext/errorcodes.def +84 -5
  17. data/ext/errorcodes.rb +1 -1
  18. data/ext/errorcodes.txt +23 -6
  19. data/ext/extconf.rb +109 -25
  20. data/ext/gvl_wrappers.c +4 -0
  21. data/ext/gvl_wrappers.h +23 -0
  22. data/ext/pg.c +213 -155
  23. data/ext/pg.h +89 -23
  24. data/ext/pg_binary_decoder.c +164 -16
  25. data/ext/pg_binary_encoder.c +238 -13
  26. data/ext/pg_coder.c +159 -35
  27. data/ext/pg_connection.c +1584 -967
  28. data/ext/pg_copy_coder.c +373 -43
  29. data/ext/pg_errors.c +1 -1
  30. data/ext/pg_record_coder.c +522 -0
  31. data/ext/pg_result.c +710 -217
  32. data/ext/pg_text_decoder.c +630 -43
  33. data/ext/pg_text_encoder.c +222 -72
  34. data/ext/pg_tuple.c +572 -0
  35. data/ext/pg_type_map.c +45 -11
  36. data/ext/pg_type_map_all_strings.c +21 -7
  37. data/ext/pg_type_map_by_class.c +59 -27
  38. data/ext/pg_type_map_by_column.c +80 -37
  39. data/ext/pg_type_map_by_mri_type.c +49 -20
  40. data/ext/pg_type_map_by_oid.c +62 -29
  41. data/ext/pg_type_map_in_ruby.c +56 -22
  42. data/ext/{util.c → pg_util.c} +12 -12
  43. data/ext/{util.h → pg_util.h} +2 -2
  44. data/lib/pg/basic_type_map_based_on_result.rb +67 -0
  45. data/lib/pg/basic_type_map_for_queries.rb +202 -0
  46. data/lib/pg/basic_type_map_for_results.rb +104 -0
  47. data/lib/pg/basic_type_registry.rb +311 -0
  48. data/lib/pg/binary_decoder/date.rb +9 -0
  49. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  50. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  51. data/lib/pg/coder.rb +36 -13
  52. data/lib/pg/connection.rb +769 -70
  53. data/lib/pg/exceptions.rb +22 -2
  54. data/lib/pg/result.rb +14 -2
  55. data/lib/pg/text_decoder/date.rb +21 -0
  56. data/lib/pg/text_decoder/inet.rb +9 -0
  57. data/lib/pg/text_decoder/json.rb +17 -0
  58. data/lib/pg/text_decoder/numeric.rb +9 -0
  59. data/lib/pg/text_decoder/timestamp.rb +30 -0
  60. data/lib/pg/text_encoder/date.rb +13 -0
  61. data/lib/pg/text_encoder/inet.rb +31 -0
  62. data/lib/pg/text_encoder/json.rb +17 -0
  63. data/lib/pg/text_encoder/numeric.rb +9 -0
  64. data/lib/pg/text_encoder/timestamp.rb +24 -0
  65. data/lib/pg/tuple.rb +30 -0
  66. data/lib/pg/type_map_by_column.rb +3 -2
  67. data/lib/pg/version.rb +4 -0
  68. data/lib/pg.rb +106 -39
  69. data/misc/openssl-pg-segfault.rb +31 -0
  70. data/misc/postgres/History.txt +9 -0
  71. data/misc/postgres/Manifest.txt +5 -0
  72. data/misc/postgres/README.txt +21 -0
  73. data/misc/postgres/Rakefile +21 -0
  74. data/misc/postgres/lib/postgres.rb +16 -0
  75. data/misc/ruby-pg/History.txt +9 -0
  76. data/misc/ruby-pg/Manifest.txt +5 -0
  77. data/misc/ruby-pg/README.txt +21 -0
  78. data/misc/ruby-pg/Rakefile +21 -0
  79. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  80. data/pg.gemspec +36 -0
  81. data/rakelib/task_extension.rb +46 -0
  82. data/sample/array_insert.rb +20 -0
  83. data/sample/async_api.rb +102 -0
  84. data/sample/async_copyto.rb +39 -0
  85. data/sample/async_mixed.rb +56 -0
  86. data/sample/check_conn.rb +21 -0
  87. data/sample/copydata.rb +71 -0
  88. data/sample/copyfrom.rb +81 -0
  89. data/sample/copyto.rb +19 -0
  90. data/sample/cursor.rb +21 -0
  91. data/sample/disk_usage_report.rb +177 -0
  92. data/sample/issue-119.rb +94 -0
  93. data/sample/losample.rb +69 -0
  94. data/sample/minimal-testcase.rb +17 -0
  95. data/sample/notify_wait.rb +72 -0
  96. data/sample/pg_statistics.rb +285 -0
  97. data/sample/replication_monitor.rb +222 -0
  98. data/sample/test_binary_values.rb +33 -0
  99. data/sample/wal_shipper.rb +434 -0
  100. data/sample/warehouse_partitions.rb +311 -0
  101. data.tar.gz.sig +0 -0
  102. metadata +138 -223
  103. metadata.gz.sig +0 -0
  104. data/.gemtest +0 -0
  105. data/ChangeLog +0 -6595
  106. data/History.rdoc +0 -422
  107. data/README.ja.rdoc +0 -14
  108. data/README.rdoc +0 -167
  109. data/lib/pg/basic_type_mapping.rb +0 -426
  110. data/lib/pg/constants.rb +0 -11
  111. data/lib/pg/text_decoder.rb +0 -51
  112. data/lib/pg/text_encoder.rb +0 -35
  113. data/spec/data/expected_trace.out +0 -26
  114. data/spec/data/random_binary_data +0 -0
  115. data/spec/helpers.rb +0 -348
  116. data/spec/pg/basic_type_mapping_spec.rb +0 -305
  117. data/spec/pg/connection_spec.rb +0 -1719
  118. data/spec/pg/result_spec.rb +0 -456
  119. data/spec/pg/type_map_by_class_spec.rb +0 -138
  120. data/spec/pg/type_map_by_column_spec.rb +0 -222
  121. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  122. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  123. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  124. data/spec/pg/type_map_spec.rb +0 -22
  125. data/spec/pg/type_spec.rb +0 -777
  126. data/spec/pg_spec.rb +0 -50
@@ -1,1719 +0,0 @@
1
- #!/usr/bin/env 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
- socket = tmpconn.socket_io
180
- status = tmpconn.connect_poll
181
-
182
- while status != PG::PGRES_POLLING_OK
183
- if status == PG::PGRES_POLLING_READING
184
- select( [socket], [], [], 5.0 ) or
185
- raise "Asynchronous connection timed out!"
186
-
187
- elsif status == PG::PGRES_POLLING_WRITING
188
- select( [], [socket], [], 5.0 ) or
189
- raise "Asynchronous connection timed out!"
190
- end
191
- status = tmpconn.connect_poll
192
- end
193
-
194
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
195
- tmpconn.finish
196
- end
197
-
198
- it "can connect asynchronously for the duration of a block", :socket_io do
199
- conn = nil
200
-
201
- described_class.connect_start(@conninfo) do |tmpconn|
202
- expect( tmpconn ).to be_a( described_class )
203
- conn = tmpconn
204
- socket = tmpconn.socket_io
205
- status = tmpconn.connect_poll
206
-
207
- while status != PG::PGRES_POLLING_OK
208
- if status == PG::PGRES_POLLING_READING
209
- if(not select([socket],[],[],5.0))
210
- raise "Asynchronous connection timed out!"
211
- end
212
- elsif(status == PG::PGRES_POLLING_WRITING)
213
- if(not select([],[socket],[],5.0))
214
- raise "Asynchronous connection timed out!"
215
- end
216
- end
217
- status = tmpconn.connect_poll
218
- end
219
-
220
- expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
221
- end
222
-
223
- expect( conn ).to be_finished()
224
- end
225
-
226
- it "raises proper error when sending fails" do
227
- conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
228
- expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
229
- end
230
-
231
- it "doesn't leave stale server connections after finish" do
232
- described_class.connect(@conninfo).finish
233
- sleep 0.5
234
- res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
235
- WHERE usename IS NOT NULL AND application_name != ''])
236
- # there's still the global @conn, but should be no more
237
- expect( res[0]['n'] ).to eq( '1' )
238
- end
239
-
240
- it "can retrieve it's connection parameters for the established connection" do
241
- expect( @conn.db ).to eq( "test" )
242
- expect( @conn.user ).to be_a_kind_of( String )
243
- expect( @conn.pass ).to eq( "" )
244
- expect( @conn.port ).to eq( 54321 )
245
- expect( @conn.tty ).to eq( "" )
246
- expect( @conn.options ).to eq( "" )
247
- end
248
- it "can retrieve it's connection parameters for the established connection",
249
- skip: RUBY_PLATFORM=~/x64-mingw/ ? "host segfaults on Windows-x64" : false do
250
- expect( @conn.host ).to eq( "localhost" )
251
- end
252
-
253
- EXPECTED_TRACE_OUTPUT = %{
254
- To backend> Msg Q
255
- To backend> "SELECT 1 AS one"
256
- To backend> Msg complete, length 21
257
- From backend> T
258
- From backend (#4)> 28
259
- From backend (#2)> 1
260
- From backend> "one"
261
- From backend (#4)> 0
262
- From backend (#2)> 0
263
- From backend (#4)> 23
264
- From backend (#2)> 4
265
- From backend (#4)> -1
266
- From backend (#2)> 0
267
- From backend> D
268
- From backend (#4)> 11
269
- From backend (#2)> 1
270
- From backend (#4)> 1
271
- From backend (1)> 1
272
- From backend> C
273
- From backend (#4)> 13
274
- From backend> "SELECT 1"
275
- From backend> Z
276
- From backend (#4)> 5
277
- From backend> Z
278
- From backend (#4)> 5
279
- From backend> T
280
- }.gsub( /^\t{2}/, '' ).lstrip
281
-
282
- it "trace and untrace client-server communication", :unix do
283
- # be careful to explicitly close files so that the
284
- # directory can be removed and we don't have to wait for
285
- # the GC to run.
286
- trace_file = TEST_DIRECTORY + "test_trace.out"
287
- trace_io = trace_file.open( 'w', 0600 )
288
- @conn.trace( trace_io )
289
- trace_io.close
290
-
291
- res = @conn.exec("SELECT 1 AS one")
292
- @conn.untrace
293
-
294
- res = @conn.exec("SELECT 2 AS two")
295
-
296
- trace_data = trace_file.read
297
-
298
- expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
299
- # For PostgreSQL < 9.0, the output will be different:
300
- # -From backend (#4)> 13
301
- # -From backend> "SELECT 1"
302
- # +From backend (#4)> 11
303
- # +From backend> "SELECT"
304
- if @conn.server_version < 90000
305
- expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
306
- expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
307
- end
308
-
309
- expect( trace_data ).to eq( expected_trace_output )
310
- end
311
-
312
- it "allows a query to be cancelled" do
313
- error = false
314
- @conn.send_query("SELECT pg_sleep(1000)")
315
- @conn.cancel
316
- tmpres = @conn.get_result
317
- if(tmpres.result_status != PG::PGRES_TUPLES_OK)
318
- error = true
319
- end
320
- expect( error ).to eq( true )
321
- end
322
-
323
- it "can stop a thread that runs a blocking query with async_exec" do
324
- pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
325
-
326
- start = Time.now
327
- t = Thread.new do
328
- @conn.async_exec( 'select pg_sleep(10)' )
329
- end
330
- sleep 0.1
331
-
332
- t.kill
333
- t.join
334
- expect( (Time.now - start) ).to be < 10
335
- end
336
-
337
- it "should work together with signal handlers", :unix do
338
- signal_received = false
339
- trap 'USR1' do
340
- signal_received = true
341
- end
342
-
343
- Thread.new do
344
- sleep 0.1
345
- Process.kill("USR1", Process.pid)
346
- end
347
- @conn.exec("select pg_sleep(0.3)")
348
- expect( signal_received ).to be_truthy
349
-
350
- signal_received = false
351
- Thread.new do
352
- sleep 0.1
353
- Process.kill("USR1", Process.pid)
354
- end
355
- @conn.async_exec("select pg_sleep(0.3)")
356
- expect( signal_received ).to be_truthy
357
- end
358
-
359
-
360
- it "automatically rolls back a transaction started with Connection#transaction if an exception " +
361
- "is raised" do
362
- # abort the per-example transaction so we can test our own
363
- @conn.exec( 'ROLLBACK' )
364
-
365
- res = nil
366
- @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
367
-
368
- begin
369
- expect {
370
- res = @conn.transaction do
371
- @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
372
- raise "Oh noes! All pie is gone!"
373
- end
374
- }.to raise_exception( RuntimeError, /all pie is gone/i )
375
-
376
- res = @conn.exec( "SELECT * FROM pie" )
377
- expect( res.ntuples ).to eq( 0 )
378
- ensure
379
- @conn.exec( "DROP TABLE pie" )
380
- end
381
- end
382
-
383
- it "returns the block result from Connection#transaction" do
384
- # abort the per-example transaction so we can test our own
385
- @conn.exec( 'ROLLBACK' )
386
-
387
- res = @conn.transaction do
388
- "transaction result"
389
- end
390
- expect( res ).to eq( "transaction result" )
391
- end
392
-
393
- it "not read past the end of a large object" do
394
- @conn.transaction do
395
- oid = @conn.lo_create( 0 )
396
- fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
397
- @conn.lo_write( fd, "foobar" )
398
- expect( @conn.lo_read( fd, 10 ) ).to be_nil()
399
- @conn.lo_lseek( fd, 0, PG::SEEK_SET )
400
- expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
401
- end
402
- end
403
-
404
-
405
- it "supports parameters passed to #exec (backward compatibility)" do
406
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
407
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
408
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
409
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
410
-
411
- res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
412
- expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
413
- end
414
-
415
- it "supports explicitly calling #exec_params" do
416
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
417
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
418
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
419
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
420
-
421
- res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
422
- expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
423
- end
424
-
425
- it "supports hash form parameters for #exec_params" do
426
- hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
427
- hash_param_nil = { value: nil, type: 17, format: 1 }
428
- res = @conn.exec_params( "SELECT $1, $2",
429
- [ hash_param_bin, hash_param_nil ] )
430
- expect( res.values ).to eq( [["\\x00ff", nil]] )
431
- expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
432
- end
433
-
434
- it "should work with arbitrary number of params" do
435
- begin
436
- 3.step( 12, 0.2 ) do |exp|
437
- num_params = (2 ** exp).to_i
438
- sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
439
- params = num_params.times.to_a
440
- res = @conn.exec_params( "SELECT #{sql}", params )
441
- expect( res.nfields ).to eq( num_params )
442
- expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
443
- end
444
- rescue PG::ProgramLimitExceeded
445
- # Stop silently if the server complains about too many params
446
- end
447
- end
448
-
449
- it "can wait for NOTIFY events" do
450
- @conn.exec( 'ROLLBACK' )
451
- @conn.exec( 'LISTEN woo' )
452
-
453
- t = Thread.new do
454
- begin
455
- conn = described_class.connect( @conninfo )
456
- sleep 1
457
- conn.async_exec( 'NOTIFY woo' )
458
- ensure
459
- conn.finish
460
- end
461
- end
462
-
463
- expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
464
- @conn.exec( 'UNLISTEN woo' )
465
-
466
- t.join
467
- end
468
-
469
- it "calls a block for NOTIFY events if one is given" do
470
- @conn.exec( 'ROLLBACK' )
471
- @conn.exec( 'LISTEN woo' )
472
-
473
- t = Thread.new do
474
- begin
475
- conn = described_class.connect( @conninfo )
476
- sleep 1
477
- conn.async_exec( 'NOTIFY woo' )
478
- ensure
479
- conn.finish
480
- end
481
- end
482
-
483
- eventpid = event = nil
484
- @conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
485
- expect( event ).to eq( 'woo' )
486
- expect( eventpid ).to be_an( Integer )
487
-
488
- @conn.exec( 'UNLISTEN woo' )
489
-
490
- t.join
491
- end
492
-
493
- it "doesn't collapse sequential notifications" do
494
- @conn.exec( 'ROLLBACK' )
495
- @conn.exec( 'LISTEN woo' )
496
- @conn.exec( 'LISTEN war' )
497
- @conn.exec( 'LISTEN woz' )
498
-
499
- begin
500
- conn = described_class.connect( @conninfo )
501
- conn.exec( 'NOTIFY woo' )
502
- conn.exec( 'NOTIFY war' )
503
- conn.exec( 'NOTIFY woz' )
504
- ensure
505
- conn.finish
506
- end
507
-
508
- channels = []
509
- 3.times do
510
- channels << @conn.wait_for_notify( 2 )
511
- end
512
-
513
- expect( channels.size ).to eq( 3 )
514
- expect( channels ).to include( 'woo', 'war', 'woz' )
515
-
516
- @conn.exec( 'UNLISTEN woz' )
517
- @conn.exec( 'UNLISTEN war' )
518
- @conn.exec( 'UNLISTEN woo' )
519
- end
520
-
521
- it "returns notifications which are already in the queue before wait_for_notify is called " +
522
- "without waiting for the socket to become readable" do
523
- @conn.exec( 'ROLLBACK' )
524
- @conn.exec( 'LISTEN woo' )
525
-
526
- begin
527
- conn = described_class.connect( @conninfo )
528
- conn.exec( 'NOTIFY woo' )
529
- ensure
530
- conn.finish
531
- end
532
-
533
- # Cause the notification to buffer, but not be read yet
534
- @conn.exec( 'SELECT 1' )
535
-
536
- expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
537
- @conn.exec( 'UNLISTEN woo' )
538
- end
539
-
540
- it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
541
- notices = []
542
- @conn.set_notice_processor do |msg|
543
- notices << [msg, Time.now]
544
- end
545
- st = Time.now
546
- @conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
547
- expect( @conn.wait_for_notify( 1 ) ).to be_nil
548
- expect( notices.first ).to_not be_nil
549
- et = Time.now
550
- expect( (et - notices.first[1]) ).to be >= 0.4
551
- expect( (et - st) ).to be >= 0.9
552
- expect( (et - st) ).to be < 1.4
553
- end
554
-
555
- it "yields the result if block is given to exec" do
556
- rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
557
- values = []
558
- expect( result ).to be_kind_of( PG::Result )
559
- expect( result.ntuples ).to eq( 2 )
560
- result.each do |tuple|
561
- values << tuple['a']
562
- end
563
- values
564
- end
565
-
566
- expect( rval.size ).to eq( 2 )
567
- expect( rval ).to include( '5678', '1234' )
568
- end
569
-
570
- it "can process #copy_data output queries" do
571
- rows = []
572
- res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
573
- expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
574
- expect( res.nfields ).to eq( 1 )
575
- while row=@conn.get_copy_data
576
- rows << row
577
- end
578
- end
579
- expect( rows ).to eq( ["1\n", "2\n"] )
580
- expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
581
- expect( @conn ).to still_be_usable
582
- end
583
-
584
- it "can handle incomplete #copy_data output queries" do
585
- expect {
586
- @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
587
- @conn.get_copy_data
588
- end
589
- }.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
590
- expect( @conn ).to still_be_usable
591
- end
592
-
593
- it "can handle client errors in #copy_data for output" do
594
- expect {
595
- @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do
596
- raise "boom"
597
- end
598
- }.to raise_error(RuntimeError, "boom")
599
- expect( @conn ).to still_be_usable
600
- end
601
-
602
- it "can handle server errors in #copy_data for output" do
603
- @conn.exec "ROLLBACK"
604
- @conn.transaction do
605
- @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
606
- expect {
607
- @conn.copy_data( "COPY (SELECT errfunc()) TO STDOUT" ) do |res|
608
- while @conn.get_copy_data
609
- end
610
- end
611
- }.to raise_error(PG::Error, /test-error/)
612
- end
613
- expect( @conn ).to still_be_usable
614
- end
615
-
616
- it "can process #copy_data input queries" do
617
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
618
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
619
- expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
620
- expect( res.nfields ).to eq( 1 )
621
- @conn.put_copy_data "1\n"
622
- @conn.put_copy_data "2\n"
623
- end
624
- expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
625
-
626
- expect( @conn ).to still_be_usable
627
-
628
- res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
629
- expect( res.values ).to eq( [["1"], ["2"]] )
630
- end
631
-
632
- it "can handle client errors in #copy_data for input" do
633
- @conn.exec "ROLLBACK"
634
- @conn.transaction do
635
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
636
- expect {
637
- @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
638
- raise "boom"
639
- end
640
- }.to raise_error(RuntimeError, "boom")
641
- end
642
-
643
- expect( @conn ).to still_be_usable
644
- end
645
-
646
- it "can handle server errors in #copy_data for input" do
647
- @conn.exec "ROLLBACK"
648
- @conn.transaction do
649
- @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
650
- expect {
651
- @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
652
- @conn.put_copy_data "xyz\n"
653
- end
654
- }.to raise_error(PG::Error, /invalid input syntax for integer/)
655
- end
656
- expect( @conn ).to still_be_usable
657
- end
658
-
659
- it "gracefully handle SQL statements while in #copy_data for input" do
660
- @conn.exec "ROLLBACK"
661
- @conn.transaction do
662
- @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
663
- expect {
664
- @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
665
- @conn.exec "SELECT 1"
666
- end
667
- }.to raise_error(PG::Error, /no COPY in progress/)
668
- end
669
- expect( @conn ).to still_be_usable
670
- end
671
-
672
- it "gracefully handle SQL statements while in #copy_data for output" do
673
- @conn.exec "ROLLBACK"
674
- @conn.transaction do
675
- expect {
676
- @conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
677
- @conn.exec "SELECT 3"
678
- end
679
- }.to raise_error(PG::Error, /no COPY in progress/)
680
- end
681
- expect( @conn ).to still_be_usable
682
- end
683
-
684
- it "should raise an error for non copy statements in #copy_data" do
685
- expect {
686
- @conn.copy_data( "SELECT 1" ){}
687
- }.to raise_error(ArgumentError, /no COPY/)
688
-
689
- expect( @conn ).to still_be_usable
690
- end
691
-
692
- it "correctly finishes COPY queries passed to #async_exec" do
693
- @conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
694
-
695
- results = []
696
- begin
697
- data = @conn.get_copy_data( true )
698
- if false == data
699
- @conn.block( 2.0 )
700
- data = @conn.get_copy_data( true )
701
- end
702
- results << data if data
703
- end until data.nil?
704
-
705
- expect( results.size ).to eq( 2 )
706
- expect( results ).to include( "1\n", "2\n" )
707
- end
708
-
709
-
710
- it "described_class#block shouldn't block a second thread" do
711
- start = Time.now
712
- t = Thread.new do
713
- @conn.send_query( "select pg_sleep(3)" )
714
- @conn.block
715
- end
716
-
717
- sleep 0.5
718
- expect( t ).to be_alive()
719
- @conn.cancel
720
- t.join
721
- expect( (Time.now - start) ).to be < 3
722
- end
723
-
724
- it "described_class#block should allow a timeout" do
725
- @conn.send_query( "select pg_sleep(1)" )
726
-
727
- start = Time.now
728
- @conn.block( 0.3 )
729
- finish = Time.now
730
-
731
- expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
732
- end
733
-
734
- it "can return the default connection options" do
735
- expect( described_class.conndefaults ).to be_a( Array )
736
- expect( described_class.conndefaults ).to all( be_a(Hash) )
737
- expect( described_class.conndefaults[0] ).to include( :keyword, :label, :dispchar, :dispsize )
738
- expect( @conn.conndefaults ).to eq( described_class.conndefaults )
739
- end
740
-
741
- it "can return the default connection options as a Hash" do
742
- expect( described_class.conndefaults_hash ).to be_a( Hash )
743
- expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
744
- expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
745
- expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
746
- end
747
-
748
- it "can return the connection's connection options", :postgresql_93 do
749
- expect( @conn.conninfo ).to be_a( Array )
750
- expect( @conn.conninfo ).to all( be_a(Hash) )
751
- expect( @conn.conninfo[0] ).to include( :keyword, :label, :dispchar, :dispsize )
752
- end
753
-
754
-
755
- it "can return the connection's connection options as a Hash", :postgresql_93 do
756
- expect( @conn.conninfo_hash ).to be_a( Hash )
757
- expect( @conn.conninfo_hash ).to include( :user, :password, :connect_timeout, :dbname, :host )
758
- expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
759
- end
760
-
761
- describe "connection information related to SSL" do
762
-
763
- it "can retrieve connection's ssl state", :postgresql_95 do
764
- expect( @conn.ssl_in_use? ).to be false
765
- end
766
-
767
- it "can retrieve connection's ssl attribute_names", :postgresql_95 do
768
- expect( @conn.ssl_attribute_names ).to be_a(Array)
769
- end
770
-
771
- it "can retrieve a single ssl connection attribute", :postgresql_95 do
772
- expect( @conn.ssl_attribute('dbname') ).to eq( nil )
773
- end
774
-
775
- it "can retrieve all connection's ssl attributes", :postgresql_95 do
776
- expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
777
- end
778
- end
779
-
780
-
781
- it "honors the connect_timeout connection parameter", :postgresql_93 do
782
- conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
783
- begin
784
- expect( conn.conninfo_hash[:connect_timeout] ).to eq( "11" )
785
- ensure
786
- conn.finish
787
- end
788
- end
789
-
790
- describe "deprecated password encryption method" do
791
- it "can encrypt password for a given user" do
792
- expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
793
- end
794
-
795
- it "raises an appropriate error if either of the required arguments is not valid" do
796
- expect {
797
- described_class.encrypt_password( nil, nil )
798
- }.to raise_error( TypeError )
799
- expect {
800
- described_class.encrypt_password( "postgres", nil )
801
- }.to raise_error( TypeError )
802
- expect {
803
- described_class.encrypt_password( nil, "postgres" )
804
- }.to raise_error( TypeError )
805
- end
806
- end
807
-
808
- describe "password encryption method", :postgresql_10 do
809
- it "can encrypt without algorithm" do
810
- expect( @conn.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
811
- expect( @conn.encrypt_password("postgres", "postgres", nil) ).to match( /\S+/ )
812
- end
813
-
814
- it "can encrypt with algorithm" do
815
- expect( @conn.encrypt_password("postgres", "postgres", "md5") ).to match( /md5\S+/i )
816
- expect( @conn.encrypt_password("postgres", "postgres", "scram-sha-256") ).to match( /SCRAM-SHA-256\S+/i )
817
- end
818
-
819
- it "raises an appropriate error if either of the required arguments is not valid" do
820
- expect {
821
- @conn.encrypt_password( nil, nil )
822
- }.to raise_error( TypeError )
823
- expect {
824
- @conn.encrypt_password( "postgres", nil )
825
- }.to raise_error( TypeError )
826
- expect {
827
- @conn.encrypt_password( nil, "postgres" )
828
- }.to raise_error( TypeError )
829
- expect {
830
- @conn.encrypt_password( "postgres", "postgres", :invalid )
831
- }.to raise_error( TypeError )
832
- expect {
833
- @conn.encrypt_password( "postgres", "postgres", "invalid" )
834
- }.to raise_error( PG::Error, /unrecognized/ )
835
- end
836
- end
837
-
838
-
839
- it "allows fetching a column of values from a result by column number" do
840
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
841
- expect( res.column_values( 0 ) ).to eq( %w[1 2 3] )
842
- expect( res.column_values( 1 ) ).to eq( %w[2 3 4] )
843
- end
844
-
845
-
846
- it "allows fetching a column of values from a result by field name" do
847
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
848
- expect( res.field_values( 'column1' ) ).to eq( %w[1 2 3] )
849
- expect( res.field_values( 'column2' ) ).to eq( %w[2 3 4] )
850
- end
851
-
852
-
853
- it "raises an error if selecting an invalid column index" do
854
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
855
- expect {
856
- res.column_values( 20 )
857
- }.to raise_error( IndexError )
858
- end
859
-
860
-
861
- it "raises an error if selecting an invalid field name" do
862
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
863
- expect {
864
- res.field_values( 'hUUuurrg' )
865
- }.to raise_error( IndexError )
866
- end
867
-
868
-
869
- it "raises an error if column index is not a number" do
870
- res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
871
- expect {
872
- res.column_values( 'hUUuurrg' )
873
- }.to raise_error( TypeError )
874
- end
875
-
876
-
877
- it "can connect asynchronously", :socket_io do
878
- serv = TCPServer.new( '127.0.0.1', 54320 )
879
- conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
880
- expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
881
- select( nil, [conn.socket_io], nil, 0.2 )
882
- serv.close
883
- if conn.connect_poll == PG::PGRES_POLLING_READING
884
- select( [conn.socket_io], nil, nil, 0.2 )
885
- end
886
- expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
887
- end
888
-
889
- it "discards previous results (if any) before waiting on an #async_exec"
890
-
891
- it "calls the block if one is provided to #async_exec" do
892
- result = nil
893
- @conn.async_exec( "select 47 as one" ) do |pg_res|
894
- result = pg_res[0]
895
- end
896
- expect( result ).to eq( { 'one' => '47' } )
897
- end
898
-
899
- it "raises a rescue-able error if #finish is called twice", :without_transaction do
900
- conn = PG.connect( @conninfo )
901
-
902
- conn.finish
903
- expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
904
- end
905
-
906
- it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
907
- conn = PG.connect( @conninfo )
908
- io = conn.socket_io
909
- conn.finish
910
- expect( io ).to be_closed()
911
- expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
912
- end
913
-
914
- it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
915
- conn = PG.connect( @conninfo )
916
- io = conn.socket_io
917
- conn.reset
918
- expect( io ).to be_closed()
919
- expect( conn.socket_io ).to_not equal( io )
920
- conn.finish
921
- end
922
-
923
- it "block should raise ConnectionBad for a closed connection" do
924
- serv = TCPServer.new( '127.0.0.1', 54320 )
925
- conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
926
- while [PG::CONNECTION_STARTED, PG::CONNECTION_MADE].include?(conn.connect_poll)
927
- sleep 0.1
928
- end
929
- serv.close
930
- expect{ conn.block }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
931
- expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
932
- end
933
-
934
- it "sets the fallback_application_name on new connections" do
935
- conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
936
-
937
- conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
938
- expect( conn_name ).to include( $0[0..10] )
939
- expect( conn_name ).to include( $0[-10..-1] )
940
- expect( conn_name.length ).to be <= 64
941
- end
942
-
943
- it "sets a shortened fallback_application_name on new connections" do
944
- old_0 = $0
945
- begin
946
- $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
947
- conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
948
- conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
949
- expect( conn_name ).to include( $0[0..10] )
950
- expect( conn_name ).to include( $0[-10..-1] )
951
- expect( conn_name.length ).to be <= 64
952
- ensure
953
- $0 = old_0
954
- end
955
- end
956
-
957
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
958
- "any number of arguments" do
959
-
960
- @conn.exec( 'ROLLBACK' )
961
- @conn.exec( 'LISTEN knees' )
962
-
963
- conn = described_class.connect( @conninfo )
964
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
965
- conn.finish
966
-
967
- event, pid, msg = nil
968
- @conn.wait_for_notify( 10 ) do |*args|
969
- event, pid, msg = *args
970
- end
971
- @conn.exec( 'UNLISTEN knees' )
972
-
973
- expect( event ).to eq( 'knees' )
974
- expect( pid ).to be_a_kind_of( Integer )
975
- expect( msg ).to eq( 'skirt and boots' )
976
- end
977
-
978
- it "accepts nil as the timeout in #wait_for_notify " do
979
- @conn.exec( 'ROLLBACK' )
980
- @conn.exec( 'LISTEN knees' )
981
-
982
- conn = described_class.connect( @conninfo )
983
- conn.exec( %Q{NOTIFY knees} )
984
- conn.finish
985
-
986
- event, pid = nil
987
- @conn.wait_for_notify( nil ) do |*args|
988
- event, pid = *args
989
- end
990
- @conn.exec( 'UNLISTEN knees' )
991
-
992
- expect( event ).to eq( 'knees' )
993
- expect( pid ).to be_a_kind_of( Integer )
994
- end
995
-
996
- it "sends nil as the payload if the notification wasn't given one" do
997
- @conn.exec( 'ROLLBACK' )
998
- @conn.exec( 'LISTEN knees' )
999
-
1000
- conn = described_class.connect( @conninfo )
1001
- conn.exec( %Q{NOTIFY knees} )
1002
- conn.finish
1003
-
1004
- payload = :notnil
1005
- @conn.wait_for_notify( nil ) do |*args|
1006
- payload = args[ 2 ]
1007
- end
1008
- @conn.exec( 'UNLISTEN knees' )
1009
-
1010
- expect( payload ).to be_nil()
1011
- end
1012
-
1013
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1014
- "two arguments" do
1015
-
1016
- @conn.exec( 'ROLLBACK' )
1017
- @conn.exec( 'LISTEN knees' )
1018
-
1019
- conn = described_class.connect( @conninfo )
1020
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1021
- conn.finish
1022
-
1023
- event, pid, msg = nil
1024
- @conn.wait_for_notify( 10 ) do |arg1, arg2|
1025
- event, pid, msg = arg1, arg2
1026
- end
1027
- @conn.exec( 'UNLISTEN knees' )
1028
-
1029
- expect( event ).to eq( 'knees' )
1030
- expect( pid ).to be_a_kind_of( Integer )
1031
- expect( msg ).to be_nil()
1032
- end
1033
-
1034
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
1035
- "doesn't accept arguments" do
1036
-
1037
- @conn.exec( 'ROLLBACK' )
1038
- @conn.exec( 'LISTEN knees' )
1039
-
1040
- conn = described_class.connect( @conninfo )
1041
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1042
- conn.finish
1043
-
1044
- notification_received = false
1045
- @conn.wait_for_notify( 10 ) do
1046
- notification_received = true
1047
- end
1048
- @conn.exec( 'UNLISTEN knees' )
1049
-
1050
- expect( notification_received ).to be_truthy()
1051
- end
1052
-
1053
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1054
- "three arguments" do
1055
-
1056
- @conn.exec( 'ROLLBACK' )
1057
- @conn.exec( 'LISTEN knees' )
1058
-
1059
- conn = described_class.connect( @conninfo )
1060
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1061
- conn.finish
1062
-
1063
- event, pid, msg = nil
1064
- @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
1065
- event, pid, msg = arg1, arg2, arg3
1066
- end
1067
- @conn.exec( 'UNLISTEN knees' )
1068
-
1069
- expect( event ).to eq( 'knees' )
1070
- expect( pid ).to be_a_kind_of( Integer )
1071
- expect( msg ).to eq( 'skirt and boots' )
1072
- end
1073
-
1074
- context "server ping", :without_transaction do
1075
-
1076
- it "pings successfully with connection string" do
1077
- ping = described_class.ping(@conninfo)
1078
- expect( ping ).to eq( PG::PQPING_OK )
1079
- end
1080
-
1081
- it "pings using 7 arguments converted to strings" do
1082
- ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
1083
- expect( ping ).to eq( PG::PQPING_OK )
1084
- end
1085
-
1086
- it "pings using a hash of connection parameters" do
1087
- ping = described_class.ping(
1088
- :host => 'localhost',
1089
- :port => @port,
1090
- :dbname => :test)
1091
- expect( ping ).to eq( PG::PQPING_OK )
1092
- end
1093
-
1094
- it "returns correct response when ping connection cannot be established" do
1095
- ping = described_class.ping(
1096
- :host => 'localhost',
1097
- :port => 9999,
1098
- :dbname => :test)
1099
- expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
1100
- end
1101
-
1102
- it "returns correct response when ping connection arguments are wrong" do
1103
- ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
1104
- expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1105
- end
1106
-
1107
- end
1108
-
1109
- describe "set_single_row_mode" do
1110
-
1111
- it "raises an error when called at the wrong time" do
1112
- expect {
1113
- @conn.set_single_row_mode
1114
- }.to raise_error(PG::Error)
1115
- end
1116
-
1117
- it "should work in single row mode" do
1118
- @conn.send_query( "SELECT generate_series(1,10)" )
1119
- @conn.set_single_row_mode
1120
-
1121
- results = []
1122
- loop do
1123
- @conn.block
1124
- res = @conn.get_result or break
1125
- results << res
1126
- end
1127
- expect( results.length ).to eq( 11 )
1128
- results[0..-2].each do |res|
1129
- expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1130
- values = res.field_values('generate_series')
1131
- expect( values.length ).to eq( 1 )
1132
- expect( values.first.to_i ).to be > 0
1133
- end
1134
- expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
1135
- expect( results.last.ntuples ).to eq( 0 )
1136
- end
1137
-
1138
- it "should receive rows before entire query is finished" do
1139
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
1140
- @conn.set_single_row_mode
1141
-
1142
- start_time = Time.now
1143
- first_row_time = nil
1144
- loop do
1145
- res = @conn.get_result or break
1146
- res.check
1147
- first_row_time = Time.now unless first_row_time
1148
- end
1149
- expect( (Time.now - start_time) ).to be >= 0.9
1150
- expect( (first_row_time - start_time) ).to be < 0.9
1151
- end
1152
-
1153
- it "should receive rows before entire query fails" do
1154
- @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
1155
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
1156
- @conn.set_single_row_mode
1157
-
1158
- first_result = nil
1159
- expect do
1160
- loop do
1161
- res = @conn.get_result or break
1162
- res.check
1163
- first_result ||= res
1164
- end
1165
- end.to raise_error(PG::Error)
1166
- expect( first_result.kind_of?(PG::Result) ).to be_truthy
1167
- expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1168
- end
1169
-
1170
- end
1171
-
1172
- context "multinationalization support", :ruby_19 do
1173
-
1174
- describe "rubyforge #22925: m17n support" do
1175
- it "should return results in the same encoding as the client (iso-8859-1)" do
1176
- out_string = nil
1177
- @conn.transaction do |conn|
1178
- conn.internal_encoding = 'iso8859-1'
1179
- res = conn.exec("VALUES ('fantasia')", [], 0)
1180
- out_string = res[0]['column1']
1181
- end
1182
- expect( out_string ).to eq( 'fantasia' )
1183
- expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
1184
- end
1185
-
1186
- it "should return results in the same encoding as the client (utf-8)" do
1187
- out_string = nil
1188
- @conn.transaction do |conn|
1189
- conn.internal_encoding = 'utf-8'
1190
- res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
1191
- out_string = res[0]['column1']
1192
- end
1193
- expect( out_string ).to eq( '世界線航跡蔵' )
1194
- expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1195
- end
1196
-
1197
- it "should return results in the same encoding as the client (EUC-JP)" do
1198
- out_string = nil
1199
- @conn.transaction do |conn|
1200
- conn.internal_encoding = 'EUC-JP'
1201
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1202
- res = conn.exec(stmt, [], 0)
1203
- out_string = res[0]['column1']
1204
- end
1205
- expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1206
- expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1207
- end
1208
-
1209
- it "returns the results in the correct encoding even if the client_encoding has " +
1210
- "changed since the results were fetched" do
1211
- out_string = nil
1212
- @conn.transaction do |conn|
1213
- conn.internal_encoding = 'EUC-JP'
1214
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1215
- res = conn.exec(stmt, [], 0)
1216
- conn.internal_encoding = 'utf-8'
1217
- out_string = res[0]['column1']
1218
- end
1219
- expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1220
- expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1221
- end
1222
-
1223
- it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
1224
- @conn.exec "SET client_encoding TO SQL_ASCII"
1225
- expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1226
- end
1227
-
1228
- it "uses the client encoding for escaped string" do
1229
- original = "Möhre to\0 escape".encode( "utf-16be" )
1230
- @conn.set_client_encoding( "euc_jp" )
1231
- escaped = @conn.escape( original )
1232
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1233
- expect( escaped ).to eq( "Möhre to".encode(Encoding::EUC_JP) )
1234
- end
1235
-
1236
- it "uses the client encoding for escaped literal" do
1237
- original = "Möhre to\0 escape".encode( "utf-16be" )
1238
- @conn.set_client_encoding( "euc_jp" )
1239
- escaped = @conn.escape_literal( original )
1240
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1241
- expect( escaped ).to eq( "'Möhre to'".encode(Encoding::EUC_JP) )
1242
- end
1243
-
1244
- it "uses the client encoding for escaped identifier" do
1245
- original = "Möhre to\0 escape".encode( "utf-16le" )
1246
- @conn.set_client_encoding( "euc_jp" )
1247
- escaped = @conn.escape_identifier( original )
1248
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1249
- expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
1250
- end
1251
-
1252
- it "uses the client encoding for quote_ident" do
1253
- original = "Möhre to\0 escape".encode( "utf-16le" )
1254
- @conn.set_client_encoding( "euc_jp" )
1255
- escaped = @conn.quote_ident( original )
1256
- expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1257
- expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
1258
- end
1259
-
1260
- it "uses the previous string encoding for escaped string" do
1261
- original = "Möhre to\0 escape".encode( "iso-8859-1" )
1262
- @conn.set_client_encoding( "euc_jp" )
1263
- escaped = described_class.escape( original )
1264
- expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1265
- expect( escaped ).to eq( "Möhre to".encode(Encoding::ISO8859_1) )
1266
- end
1267
-
1268
- it "uses the previous string encoding for quote_ident" do
1269
- original = "Möhre to\0 escape".encode( "iso-8859-1" )
1270
- @conn.set_client_encoding( "euc_jp" )
1271
- escaped = described_class.quote_ident( original )
1272
- expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1273
- expect( escaped.encode ).to eq( "\"Möhre to\"".encode(Encoding::ISO8859_1) )
1274
- end
1275
-
1276
- it "raises appropriate error if set_client_encoding is called with invalid arguments" do
1277
- expect { @conn.set_client_encoding( "invalid" ) }.to raise_error(PG::Error, /invalid value/)
1278
- expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
1279
- expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
1280
- end
1281
- end
1282
-
1283
- describe "respect and convert character encoding of input strings" do
1284
- before :each do
1285
- @conn.internal_encoding = __ENCODING__
1286
- end
1287
-
1288
- it "should convert query string and parameters to #exec_params" do
1289
- r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
1290
- ['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
1291
- expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1292
- end
1293
-
1294
- it "should convert query string and parameters to #async_exec" do
1295
- r = @conn.async_exec("VALUES( $1, $2, $1=$2, 'grün')".encode("cp936"),
1296
- ['grün'.encode('cp850'), 'grün'.encode('utf-16le')])
1297
- expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1298
- end
1299
-
1300
- it "should convert query string to #exec" do
1301
- r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
1302
- expect( r.values ).to eq( [['grün']] )
1303
- end
1304
-
1305
- it "should convert query string to #async_exec" do
1306
- r = @conn.async_exec("SELECT 'grün'".encode("utf-16le"))
1307
- expect( r.values ).to eq( [['grün']] )
1308
- end
1309
-
1310
- it "should convert strings and parameters to #prepare and #exec_prepared" do
1311
- @conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
1312
- r = @conn.exec_prepared("weiß1".encode("utf-32le"),
1313
- ['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
1314
- expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1315
- end
1316
-
1317
- it "should convert strings to #describe_prepared" do
1318
- @conn.prepare("weiß2", "VALUES(123)")
1319
- r = @conn.describe_prepared("weiß2".encode("utf-16be"))
1320
- expect( r.nfields ).to eq( 1 )
1321
- end
1322
-
1323
- it "should convert strings to #describe_portal" do
1324
- @conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
1325
- r = @conn.describe_portal("cörsör".encode("utf-16le"))
1326
- expect( r.nfields ).to eq( 3 )
1327
- end
1328
-
1329
- it "should convert query string to #send_query" do
1330
- @conn.send_query("VALUES('grün')".encode("utf-16be"))
1331
- expect( @conn.get_last_result.values ).to eq( [['grün']] )
1332
- end
1333
-
1334
- it "should convert query string and parameters to #send_query" do
1335
- @conn.send_query("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
1336
- ['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
1337
- expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1338
- end
1339
-
1340
- it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
1341
- @conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
1342
- @conn.get_last_result
1343
- @conn.send_query_prepared("weiß3".encode("utf-32le"),
1344
- ['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
1345
- expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1346
- end
1347
-
1348
- it "should convert strings to #send_describe_prepared" do
1349
- @conn.prepare("weiß4", "VALUES(123)")
1350
- @conn.send_describe_prepared("weiß4".encode("utf-16be"))
1351
- expect( @conn.get_last_result.nfields ).to eq( 1 )
1352
- end
1353
-
1354
- it "should convert strings to #send_describe_portal" do
1355
- @conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
1356
- @conn.send_describe_portal("cörsör".encode("utf-16le"))
1357
- expect( @conn.get_last_result.nfields ).to eq( 3 )
1358
- end
1359
-
1360
- it "should convert error string to #put_copy_end" do
1361
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1362
- @conn.exec( "COPY copytable FROM STDIN" )
1363
- @conn.put_copy_end("grün".encode("utf-16be"))
1364
- expect( @conn.get_result.error_message ).to match(/grün/)
1365
- @conn.get_result
1366
- end
1367
- end
1368
-
1369
- it "can quote bigger strings with quote_ident" do
1370
- original = "'01234567\"" * 100
1371
- escaped = described_class.quote_ident( original + "\0afterzero" )
1372
- expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
1373
- end
1374
-
1375
- it "can quote Arrays with quote_ident" do
1376
- original = "'01234567\""
1377
- escaped = described_class.quote_ident( [original]*3 )
1378
- expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
1379
- expect( escaped ).to eq( expected.join(".") )
1380
- end
1381
-
1382
- it "will raise a TypeError for invalid arguments to quote_ident" do
1383
- expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
1384
- expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
1385
- expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
1386
- end
1387
-
1388
- describe "Ruby 1.9.x default_internal encoding" do
1389
-
1390
- it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
1391
- @conn.transaction do |txn_conn|
1392
- txn_conn.internal_encoding = Encoding::ISO8859_1
1393
- txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
1394
- txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
1395
- end
1396
-
1397
- begin
1398
- prev_encoding = Encoding.default_internal
1399
- Encoding.default_internal = Encoding::ISO8859_2
1400
-
1401
- conn = PG.connect( @conninfo )
1402
- expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
1403
- res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1404
- expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
1405
- ensure
1406
- conn.exec( "DROP TABLE defaultinternaltest" )
1407
- conn.finish if conn
1408
- Encoding.default_internal = prev_encoding
1409
- end
1410
- end
1411
-
1412
- it "allows users of the async interface to set the client_encoding to the default_internal" do
1413
- begin
1414
- prev_encoding = Encoding.default_internal
1415
- Encoding.default_internal = Encoding::KOI8_R
1416
-
1417
- @conn.set_default_encoding
1418
-
1419
- expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
1420
- ensure
1421
- Encoding.default_internal = prev_encoding
1422
- end
1423
- end
1424
-
1425
- end
1426
-
1427
-
1428
- it "encodes exception messages with the connection's encoding (#96)", :without_transaction do
1429
- # Use a new connection so the client_encoding isn't set outside of this example
1430
- conn = PG.connect( @conninfo )
1431
- conn.client_encoding = 'iso-8859-15'
1432
-
1433
- conn.transaction do
1434
- conn.exec "CREATE TABLE foo (bar TEXT)"
1435
-
1436
- begin
1437
- query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
1438
- conn.exec( query )
1439
- rescue => err
1440
- expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
1441
- else
1442
- fail "No exception raised?!"
1443
- end
1444
- end
1445
-
1446
- conn.finish if conn
1447
- end
1448
-
1449
- it "handles clearing result in or after set_notice_receiver" do
1450
- r = nil
1451
- @conn.set_notice_receiver do |result|
1452
- r = result
1453
- expect( r.cleared? ).to eq(false)
1454
- end
1455
- @conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
1456
- sleep 0.2
1457
- expect( r ).to be_a( PG::Result )
1458
- expect( r.cleared? ).to eq(true)
1459
- expect( r.autoclear? ).to eq(true)
1460
- r.clear
1461
- @conn.set_notice_receiver
1462
- end
1463
-
1464
- it "receives properly encoded messages in the notice callbacks" do
1465
- [:receiver, :processor].each do |kind|
1466
- notices = []
1467
- @conn.internal_encoding = 'utf-8'
1468
- if kind == :processor
1469
- @conn.set_notice_processor do |msg|
1470
- notices << msg
1471
- end
1472
- else
1473
- @conn.set_notice_receiver do |result|
1474
- notices << result.error_message
1475
- end
1476
- end
1477
-
1478
- 3.times do
1479
- @conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
1480
- end
1481
-
1482
- expect( notices.length ).to eq( 3 )
1483
- notices.each do |notice|
1484
- expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
1485
- expect( notice.encoding ).to eq( Encoding::UTF_8 )
1486
- end
1487
- @conn.set_notice_receiver
1488
- @conn.set_notice_processor
1489
- end
1490
- end
1491
-
1492
- it "receives properly encoded text from wait_for_notify" do
1493
- @conn.internal_encoding = 'utf-8'
1494
- @conn.exec( 'ROLLBACK' )
1495
- @conn.exec( 'LISTEN "Möhre"' )
1496
- @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1497
- event, pid, msg = nil
1498
- @conn.wait_for_notify( 10 ) do |*args|
1499
- event, pid, msg = *args
1500
- end
1501
- @conn.exec( 'UNLISTEN "Möhre"' )
1502
-
1503
- expect( event ).to eq( "Möhre" )
1504
- expect( event.encoding ).to eq( Encoding::UTF_8 )
1505
- expect( msg ).to eq( '世界線航跡蔵' )
1506
- expect( msg.encoding ).to eq( Encoding::UTF_8 )
1507
- end
1508
-
1509
- it "returns properly encoded text from notifies" do
1510
- @conn.internal_encoding = 'utf-8'
1511
- @conn.exec( 'ROLLBACK' )
1512
- @conn.exec( 'LISTEN "Möhre"' )
1513
- @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1514
- @conn.exec( 'UNLISTEN "Möhre"' )
1515
-
1516
- notification = @conn.notifies
1517
- expect( notification[:relname] ).to eq( "Möhre" )
1518
- expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
1519
- expect( notification[:extra] ).to eq( '世界線航跡蔵' )
1520
- expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
1521
- expect( notification[:be_pid] ).to be > 0
1522
- end
1523
- end
1524
-
1525
- context "OS thread support", :ruby_19 do
1526
- it "Connection#exec shouldn't block a second thread" do
1527
- t = Thread.new do
1528
- @conn.exec( "select pg_sleep(1)" )
1529
- end
1530
-
1531
- sleep 0.5
1532
- expect( t ).to be_alive()
1533
- t.join
1534
- end
1535
-
1536
- it "Connection.new shouldn't block a second thread" do
1537
- serv = nil
1538
- t = Thread.new do
1539
- serv = TCPServer.new( '127.0.0.1', 54320 )
1540
- expect {
1541
- described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
1542
- }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
1543
- end
1544
-
1545
- sleep 0.5
1546
- expect( t ).to be_alive()
1547
- serv.close
1548
- t.join
1549
- end
1550
- end
1551
-
1552
- describe "type casting" do
1553
- it "should raise an error on invalid param mapping" do
1554
- expect{
1555
- @conn.exec_params( "SELECT 1", [], nil, :invalid )
1556
- }.to raise_error(TypeError)
1557
- end
1558
-
1559
- it "should return nil if no type mapping is set" do
1560
- expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
1561
- expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
1562
- end
1563
-
1564
- it "shouldn't type map params unless requested" do
1565
- if @conn.server_version < 100000
1566
- expect{
1567
- @conn.exec_params( "SELECT $1", [5] )
1568
- }.to raise_error(PG::IndeterminateDatatype)
1569
- else
1570
- # PostgreSQL-10 maps to TEXT type (OID 25)
1571
- expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
1572
- end
1573
- end
1574
-
1575
- it "should raise an error on invalid encoder to put_copy_data" do
1576
- expect{
1577
- @conn.put_copy_data [1], :invalid
1578
- }.to raise_error(TypeError)
1579
- end
1580
-
1581
- it "can type cast parameters to put_copy_data with explicit encoder" do
1582
- tm = PG::TypeMapByColumn.new [nil]
1583
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1584
-
1585
- @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1586
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1587
- @conn.put_copy_data [1], row_encoder
1588
- @conn.put_copy_data ["2"], row_encoder
1589
- end
1590
-
1591
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1592
- @conn.put_copy_data [3]
1593
- @conn.put_copy_data ["4"]
1594
- end
1595
-
1596
- res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
1597
- expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
1598
- end
1599
-
1600
- context "with default query type map" do
1601
- before :each do
1602
- @conn2 = described_class.new(@conninfo)
1603
- tm = PG::TypeMapByClass.new
1604
- tm[Integer] = PG::TextEncoder::Integer.new oid: 20
1605
- @conn2.type_map_for_queries = tm
1606
-
1607
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1608
- @conn2.encoder_for_put_copy_data = row_encoder
1609
- end
1610
- after :each do
1611
- @conn2.close
1612
- end
1613
-
1614
- it "should respect a type mapping for params and it's OID and format code" do
1615
- res = @conn2.exec_params( "SELECT $1", [5] )
1616
- expect( res.values ).to eq( [["5"]] )
1617
- expect( res.ftype(0) ).to eq( 20 )
1618
- end
1619
-
1620
- it "should return the current type mapping" do
1621
- expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
1622
- end
1623
-
1624
- it "should work with arbitrary number of params in conjunction with type casting" do
1625
- begin
1626
- 3.step( 12, 0.2 ) do |exp|
1627
- num_params = (2 ** exp).to_i
1628
- sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
1629
- params = num_params.times.to_a
1630
- res = @conn2.exec_params( "SELECT #{sql}", params )
1631
- expect( res.nfields ).to eq( num_params )
1632
- expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
1633
- end
1634
- rescue PG::ProgramLimitExceeded
1635
- # Stop silently as soon the server complains about too many params
1636
- end
1637
- end
1638
-
1639
- it "can process #copy_data input queries with row encoder and respects character encoding" do
1640
- @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1641
- res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1642
- @conn2.put_copy_data [1]
1643
- @conn2.put_copy_data ["Möhre".encode("utf-16le")]
1644
- end
1645
-
1646
- res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1647
- expect( res.values ).to eq( [["1"], ["Möhre"]] )
1648
- end
1649
- end
1650
-
1651
- context "with default result type map" do
1652
- before :each do
1653
- @conn2 = described_class.new(@conninfo)
1654
- tm = PG::TypeMapByOid.new
1655
- tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
1656
- @conn2.type_map_for_results = tm
1657
-
1658
- row_decoder = PG::TextDecoder::CopyRow.new
1659
- @conn2.decoder_for_get_copy_data = row_decoder
1660
- end
1661
- after :each do
1662
- @conn2.close
1663
- end
1664
-
1665
- it "should respect a type mapping for result" do
1666
- res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
1667
- expect( res.values ).to eq( [[5]] )
1668
- end
1669
-
1670
- it "should return the current type mapping" do
1671
- expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
1672
- end
1673
-
1674
- it "should work with arbitrary number of params in conjunction with type casting" do
1675
- begin
1676
- 3.step( 12, 0.2 ) do |exp|
1677
- num_params = (2 ** exp).to_i
1678
- sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
1679
- params = num_params.times.to_a
1680
- res = @conn2.exec_params( "SELECT #{sql}", params )
1681
- expect( res.nfields ).to eq( num_params )
1682
- expect( res.values ).to eq( [num_params.times.to_a] )
1683
- end
1684
- rescue PG::ProgramLimitExceeded
1685
- # Stop silently as soon the server complains about too many params
1686
- end
1687
- end
1688
-
1689
- it "can process #copy_data output with row decoder and respects character encoding" do
1690
- @conn2.internal_encoding = Encoding::ISO8859_1
1691
- rows = []
1692
- res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
1693
- while row=@conn2.get_copy_data
1694
- rows << row
1695
- end
1696
- end
1697
- expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
1698
- expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
1699
- end
1700
-
1701
- it "can type cast #copy_data output with explicit decoder" do
1702
- tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
1703
- row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
1704
- rows = []
1705
- @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
1706
- while row=@conn.get_copy_data
1707
- rows << row
1708
- end
1709
- end
1710
- @conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
1711
- while row=@conn.get_copy_data( false, row_decoder )
1712
- rows << row
1713
- end
1714
- end
1715
- expect( rows ).to eq( [[1], [2], [3], [4]] )
1716
- end
1717
- end
1718
- end
1719
- end