pg 0.17.1 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/BSDL +2 -2
  4. data/ChangeLog +0 -3506
  5. data/History.rdoc +308 -0
  6. data/Manifest.txt +35 -19
  7. data/README-Windows.rdoc +17 -28
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +113 -14
  10. data/Rakefile +67 -30
  11. data/Rakefile.cross +109 -83
  12. data/ext/errorcodes.def +101 -0
  13. data/ext/errorcodes.rb +1 -1
  14. data/ext/errorcodes.txt +33 -2
  15. data/ext/extconf.rb +55 -58
  16. data/ext/gvl_wrappers.c +4 -0
  17. data/ext/gvl_wrappers.h +27 -39
  18. data/ext/pg.c +262 -130
  19. data/ext/pg.h +266 -54
  20. data/ext/pg_binary_decoder.c +229 -0
  21. data/ext/pg_binary_encoder.c +163 -0
  22. data/ext/pg_coder.c +561 -0
  23. data/ext/pg_connection.c +1689 -990
  24. data/ext/pg_copy_coder.c +599 -0
  25. data/ext/pg_errors.c +6 -0
  26. data/ext/pg_record_coder.c +491 -0
  27. data/ext/pg_result.c +897 -164
  28. data/ext/pg_text_decoder.c +987 -0
  29. data/ext/pg_text_encoder.c +814 -0
  30. data/ext/pg_tuple.c +549 -0
  31. data/ext/pg_type_map.c +166 -0
  32. data/ext/pg_type_map_all_strings.c +116 -0
  33. data/ext/pg_type_map_by_class.c +244 -0
  34. data/ext/pg_type_map_by_column.c +313 -0
  35. data/ext/pg_type_map_by_mri_type.c +284 -0
  36. data/ext/pg_type_map_by_oid.c +356 -0
  37. data/ext/pg_type_map_in_ruby.c +299 -0
  38. data/ext/pg_util.c +149 -0
  39. data/ext/pg_util.h +65 -0
  40. data/lib/pg/basic_type_mapping.rb +522 -0
  41. data/lib/pg/binary_decoder.rb +23 -0
  42. data/lib/pg/coder.rb +104 -0
  43. data/lib/pg/connection.rb +153 -41
  44. data/lib/pg/constants.rb +2 -1
  45. data/lib/pg/exceptions.rb +2 -1
  46. data/lib/pg/result.rb +33 -6
  47. data/lib/pg/text_decoder.rb +46 -0
  48. data/lib/pg/text_encoder.rb +59 -0
  49. data/lib/pg/tuple.rb +30 -0
  50. data/lib/pg/type_map_by_column.rb +16 -0
  51. data/lib/pg.rb +29 -9
  52. data/spec/{lib/helpers.rb → helpers.rb} +151 -64
  53. data/spec/pg/basic_type_mapping_spec.rb +630 -0
  54. data/spec/pg/connection_spec.rb +1180 -477
  55. data/spec/pg/connection_sync_spec.rb +41 -0
  56. data/spec/pg/result_spec.rb +456 -120
  57. data/spec/pg/tuple_spec.rb +333 -0
  58. data/spec/pg/type_map_by_class_spec.rb +138 -0
  59. data/spec/pg/type_map_by_column_spec.rb +226 -0
  60. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  61. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  62. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  63. data/spec/pg/type_map_spec.rb +22 -0
  64. data/spec/pg/type_spec.rb +1123 -0
  65. data/spec/pg_spec.rb +26 -20
  66. data.tar.gz.sig +0 -0
  67. metadata +148 -91
  68. metadata.gz.sig +0 -0
  69. data/sample/array_insert.rb +0 -20
  70. data/sample/async_api.rb +0 -106
  71. data/sample/async_copyto.rb +0 -39
  72. data/sample/async_mixed.rb +0 -56
  73. data/sample/check_conn.rb +0 -21
  74. data/sample/copyfrom.rb +0 -81
  75. data/sample/copyto.rb +0 -19
  76. data/sample/cursor.rb +0 -21
  77. data/sample/disk_usage_report.rb +0 -186
  78. data/sample/issue-119.rb +0 -94
  79. data/sample/losample.rb +0 -69
  80. data/sample/minimal-testcase.rb +0 -17
  81. data/sample/notify_wait.rb +0 -72
  82. data/sample/pg_statistics.rb +0 -294
  83. data/sample/replication_monitor.rb +0 -231
  84. data/sample/test_binary_values.rb +0 -33
  85. data/sample/wal_shipper.rb +0 -434
  86. data/sample/warehouse_partitions.rb +0 -320
@@ -0,0 +1,41 @@
1
+ # -*- rspec -*-
2
+ #encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ context "running with sync_* methods" do
7
+ before :each do
8
+ PG::Connection.async_api = false
9
+ end
10
+
11
+ after :each do
12
+ PG::Connection.async_api = true
13
+ end
14
+
15
+ fname = File.expand_path("../connection_spec.rb", __FILE__)
16
+ eval File.read(fname, encoding: __ENCODING__), binding, fname
17
+
18
+
19
+ it "enables/disables async/sync methods by #async_api" do
20
+ [true, false].each do |async|
21
+ PG::Connection.async_api = async
22
+
23
+ start = Time.now
24
+ t = Thread.new do
25
+ @conn.exec( 'select pg_sleep(1)' )
26
+ end
27
+ sleep 0.1
28
+
29
+ t.kill
30
+ t.join
31
+ dt = Time.now - start
32
+
33
+ if async
34
+ expect( dt ).to be < 1.0
35
+ else
36
+ expect( dt ).to be >= 1.0
37
+ end
38
+ end
39
+ end
40
+
41
+ end
@@ -1,85 +1,283 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
- BEGIN {
5
- require 'pathname'
4
+ require_relative '../helpers'
6
5
 
7
- basedir = Pathname( __FILE__ ).dirname.parent.parent
8
- libdir = basedir + 'lib'
9
-
10
- $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
11
- $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
12
- }
13
-
14
- require 'rspec'
15
- require 'spec/lib/helpers'
16
6
  require 'pg'
7
+ require 'objspace'
8
+
17
9
 
18
10
  describe PG::Result do
19
11
 
20
- before( :all ) do
21
- @conn = setup_testing_db( "PG_Result" )
12
+ describe :field_name_type do
13
+ let!(:res) { @conn.exec('SELECT 1 AS a, 2 AS "B"') }
14
+
15
+ it "uses string field names per default" do
16
+ expect(res.field_name_type).to eq(:string)
17
+ end
18
+
19
+ it "can set string field names" do
20
+ res.field_name_type = :string
21
+ expect(res.field_name_type).to eq(:string)
22
+ end
23
+
24
+ it "can set symbol field names" do
25
+ res.field_name_type = :symbol
26
+ expect(res.field_name_type).to eq(:symbol)
27
+ end
28
+
29
+ it "can set static_symbol field names" do
30
+ res.field_name_type = :static_symbol
31
+ expect(res.field_name_type).to eq(:static_symbol)
32
+ end
33
+
34
+ it "can't set symbol field names after #fields" do
35
+ res.fields
36
+ expect{ res.field_name_type = :symbol }.to raise_error(ArgumentError, /already materialized/)
37
+ expect(res.field_name_type).to eq(:string)
38
+ end
39
+
40
+ it "can't set invalid values" do
41
+ expect{ res.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
42
+ expect{ res.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
43
+ end
22
44
  end
23
45
 
24
- before( :each ) do
25
- @conn.exec( 'BEGIN' )
46
+ it "acts as an array of hashes" do
47
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
48
+ expect( res[0]['a'] ).to eq( '1' )
49
+ expect( res[0]['b'] ).to eq( '2' )
26
50
  end
27
51
 
28
- after( :each ) do
29
- @conn.exec( 'ROLLBACK' )
52
+ it "acts as an array of hashes with symbols" do
53
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
54
+ res.field_name_type = :symbol
55
+ expect( res[0][:a] ).to eq( '1' )
56
+ expect( res[0][:b] ).to eq( '2' )
30
57
  end
31
58
 
32
- after( :all ) do
33
- teardown_testing_db( @conn )
59
+ it "acts as an array of hashes with static_symbols" do
60
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
61
+ res.field_name_type = :static_symbol
62
+ expect( res[0][:a] ).to eq( '1' )
63
+ expect( res[0][:b] ).to eq( '2' )
34
64
  end
35
65
 
66
+ it "yields a row as an array" do
67
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
68
+ list = []
69
+ res.each_row { |r| list << r }
70
+ expect( list ).to eq [['1', '2']]
71
+ end
36
72
 
37
- #
38
- # Examples
39
- #
73
+ it "yields a row as an Enumerator" do
74
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
75
+ e = res.each_row
76
+ expect( e ).to be_a_kind_of(Enumerator)
77
+ expect( e.size ).to eq( 1 )
78
+ expect( e.to_a ).to eq [['1', '2']]
79
+ end
40
80
 
41
- it "should act as an array of hashes" do
81
+ it "yields a row as an Enumerator of hashs" do
42
82
  res = @conn.exec("SELECT 1 AS a, 2 AS b")
43
- res[0]['a'].should== '1'
44
- res[0]['b'].should== '2'
83
+ e = res.each
84
+ expect( e ).to be_a_kind_of(Enumerator)
85
+ expect( e.size ).to eq( 1 )
86
+ expect( e.to_a ).to eq [{'a'=>'1', 'b'=>'2'}]
45
87
  end
46
88
 
47
- it "should yield a row as an array" do
89
+ it "yields a row as an Enumerator of hashs with symbols" do
48
90
  res = @conn.exec("SELECT 1 AS a, 2 AS b")
49
- list = []
50
- res.each_row { |r| list << r }
51
- list.should eq [['1', '2']]
91
+ res.field_name_type = :symbol
92
+ expect( res.each.to_a ).to eq [{:a=>'1', :b=>'2'}]
93
+ end
94
+
95
+ context "result streaming in single row mode" do
96
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
97
+
98
+ it "can iterate over all rows as Hash" do
99
+ @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
100
+ @conn.set_single_row_mode
101
+ expect(
102
+ @conn.get_result.stream_each.to_a
103
+ ).to eq(
104
+ [{'a'=>"2"}, {'a'=>"3"}, {'a'=>"4"}]
105
+ )
106
+ expect(
107
+ @conn.get_result.enum_for(:stream_each).to_a
108
+ ).to eq(
109
+ [{'b'=>"1", 'c'=>"5"}, {'b'=>"1", 'c'=>"6"}]
110
+ )
111
+ expect( @conn.get_result ).to be_nil
112
+ end
113
+
114
+ it "can iterate over all rows as Hash with symbols and typemap" do
115
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
116
+ @conn.set_single_row_mode
117
+ res = @conn.get_result.field_names_as(:symbol)
118
+ res.type_map = PG::TypeMapByColumn.new [textdec_int]
119
+ expect(
120
+ res.stream_each.to_a
121
+ ).to eq(
122
+ [{:a=>2}, {:a=>3}, {:a=>4}]
123
+ )
124
+ expect( @conn.get_result ).to be_nil
125
+ end
126
+
127
+ it "keeps last result on error while iterating stream_each" do
128
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
129
+ @conn.set_single_row_mode
130
+ res = @conn.get_result
131
+ expect do
132
+ res.stream_each_row do
133
+ raise ZeroDivisionError
134
+ end
135
+ end.to raise_error(ZeroDivisionError)
136
+ expect( res.values ).to eq([["2"]])
137
+ end
138
+
139
+ it "can iterate over all rows as Array" do
140
+ @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
141
+ @conn.set_single_row_mode
142
+ expect(
143
+ @conn.get_result.enum_for(:stream_each_row).to_a
144
+ ).to eq(
145
+ [["2"], ["3"], ["4"]]
146
+ )
147
+ expect(
148
+ @conn.get_result.stream_each_row.to_a
149
+ ).to eq(
150
+ [["1", "5"], ["1", "6"]]
151
+ )
152
+ expect( @conn.get_result ).to be_nil
153
+ end
154
+
155
+ it "keeps last result on error while iterating stream_each_row" do
156
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
157
+ @conn.set_single_row_mode
158
+ res = @conn.get_result
159
+ expect do
160
+ res.stream_each_row do
161
+ raise ZeroDivisionError
162
+ end
163
+ end.to raise_error(ZeroDivisionError)
164
+ expect( res.values ).to eq([["2"]])
165
+ end
166
+
167
+ it "can iterate over all rows as PG::Tuple" do
168
+ @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
169
+ @conn.set_single_row_mode
170
+ tuples = @conn.get_result.stream_each_tuple.to_a
171
+ expect( tuples[0][0] ).to eq( "2" )
172
+ expect( tuples[1]["a"] ).to eq( "3" )
173
+ expect( tuples.size ).to eq( 3 )
174
+
175
+ tuples = @conn.get_result.enum_for(:stream_each_tuple).to_a
176
+ expect( tuples[-1][-1] ).to eq( "6" )
177
+ expect( tuples[-2]["b"] ).to eq( "1" )
178
+ expect( tuples.size ).to eq( 2 )
179
+
180
+ expect( @conn.get_result ).to be_nil
181
+ end
182
+
183
+ it "clears result on error while iterating stream_each_tuple" do
184
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
185
+ @conn.set_single_row_mode
186
+ res = @conn.get_result
187
+ expect do
188
+ res.stream_each_tuple do
189
+ raise ZeroDivisionError
190
+ end
191
+ end.to raise_error(ZeroDivisionError)
192
+ expect( res.cleared? ).to eq(true)
193
+ end
194
+
195
+ it "should reuse field names in stream_each_tuple" do
196
+ @conn.send_query( "SELECT generate_series(2,3) AS a" )
197
+ @conn.set_single_row_mode
198
+ tuple1, tuple2 = *@conn.get_result.stream_each_tuple.to_a
199
+ expect( tuple1.keys[0].object_id ).to eq(tuple2.keys[0].object_id)
200
+ end
201
+
202
+ it "can iterate over all rows as PG::Tuple with symbols and typemap" do
203
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
204
+ @conn.set_single_row_mode
205
+ res = @conn.get_result.field_names_as(:symbol)
206
+ res.type_map = PG::TypeMapByColumn.new [textdec_int]
207
+ tuples = res.stream_each_tuple.to_a
208
+ expect( tuples[0][0] ).to eq( 2 )
209
+ expect( tuples[1][:a] ).to eq( 3 )
210
+ expect( @conn.get_result ).to be_nil
211
+ end
212
+
213
+ it "complains when not in single row mode" do
214
+ @conn.send_query( "SELECT generate_series(2,4)" )
215
+ expect{
216
+ @conn.get_result.stream_each_row.to_a
217
+ }.to raise_error(PG::InvalidResultStatus, /not in single row mode/)
218
+ end
219
+
220
+ it "complains when intersected with get_result" do
221
+ @conn.send_query( "SELECT 1" )
222
+ @conn.set_single_row_mode
223
+ expect{
224
+ @conn.get_result.stream_each_row.each{ @conn.get_result }
225
+ }.to raise_error(PG::NoResultError, /no result received/)
226
+ end
227
+
228
+ it "raises server errors" do
229
+ @conn.send_query( "SELECT 0/0" )
230
+ expect{
231
+ @conn.get_result.stream_each_row.to_a
232
+ }.to raise_error(PG::DivisionByZero)
233
+ end
52
234
  end
53
235
 
54
- it "should insert nil AS NULL and return NULL as nil" do
55
- res = @conn.exec("SELECT $1::int AS n", [nil])
56
- res[0]['n'].should be_nil()
236
+ it "inserts nil AS NULL and return NULL as nil" do
237
+ res = @conn.exec_params("SELECT $1::int AS n", [nil])
238
+ expect( res[0]['n'] ).to be_nil()
57
239
  end
58
240
 
59
- it "encapsulates errors in a PGError object" do
241
+ it "encapsulates errors in a PG::Error object" do
60
242
  exception = nil
61
243
  begin
62
244
  @conn.exec( "SELECT * FROM nonexistant_table" )
63
- rescue PGError => err
245
+ rescue PG::Error => err
64
246
  exception = err
65
247
  end
66
248
 
67
249
  result = exception.result
68
250
 
69
- result.should be_a( described_class() )
70
- result.error_field( PG::PG_DIAG_SEVERITY ).should == 'ERROR'
71
- result.error_field( PG::PG_DIAG_SQLSTATE ).should == '42P01'
72
- result.error_field( PG::PG_DIAG_MESSAGE_PRIMARY ).
73
- should == 'relation "nonexistant_table" does not exist'
74
- result.error_field( PG::PG_DIAG_MESSAGE_DETAIL ).should be_nil()
75
- result.error_field( PG::PG_DIAG_MESSAGE_HINT ).should be_nil()
76
- result.error_field( PG::PG_DIAG_STATEMENT_POSITION ).should == '15'
77
- result.error_field( PG::PG_DIAG_INTERNAL_POSITION ).should be_nil()
78
- result.error_field( PG::PG_DIAG_INTERNAL_QUERY ).should be_nil()
79
- result.error_field( PG::PG_DIAG_CONTEXT ).should be_nil()
80
- result.error_field( PG::PG_DIAG_SOURCE_FILE ).should =~ /parse_relation\.c$|namespace\.c$/
81
- result.error_field( PG::PG_DIAG_SOURCE_LINE ).should =~ /^\d+$/
82
- result.error_field( PG::PG_DIAG_SOURCE_FUNCTION ).should =~ /^parserOpenTable$|^RangeVarGetRelid$/
251
+ expect( result ).to be_a( described_class() )
252
+ expect( result.error_field(PG::PG_DIAG_SEVERITY) ).to eq( 'ERROR' )
253
+ expect( result.error_field(PG::PG_DIAG_SQLSTATE) ).to eq( '42P01' )
254
+ expect(
255
+ result.error_field(PG::PG_DIAG_MESSAGE_PRIMARY)
256
+ ).to eq( 'relation "nonexistant_table" does not exist' )
257
+ expect( result.error_field(PG::PG_DIAG_MESSAGE_DETAIL) ).to be_nil()
258
+ expect( result.error_field(PG::PG_DIAG_MESSAGE_HINT) ).to be_nil()
259
+ expect( result.error_field(PG::PG_DIAG_STATEMENT_POSITION) ).to eq( '15' )
260
+ expect( result.error_field(PG::PG_DIAG_INTERNAL_POSITION) ).to be_nil()
261
+ expect( result.error_field(PG::PG_DIAG_INTERNAL_QUERY) ).to be_nil()
262
+ expect( result.error_field(PG::PG_DIAG_CONTEXT) ).to be_nil()
263
+ expect(
264
+ result.error_field(PG::PG_DIAG_SOURCE_FILE)
265
+ ).to match( /parse_relation\.c$|namespace\.c$/ )
266
+ expect( result.error_field(PG::PG_DIAG_SOURCE_LINE) ).to match( /^\d+$/ )
267
+ expect(
268
+ result.error_field(PG::PG_DIAG_SOURCE_FUNCTION)
269
+ ).to match( /^parserOpenTable$|^RangeVarGetRelid$/ )
270
+ end
271
+
272
+ it "encapsulates PG_DIAG_SEVERITY_NONLOCALIZED error in a PG::Error object", :postgresql_96 do
273
+ result = nil
274
+ begin
275
+ @conn.exec( "SELECT * FROM nonexistant_table" )
276
+ rescue PG::Error => err
277
+ result = err.result
278
+ end
279
+
280
+ expect( result.error_field(PG::PG_DIAG_SEVERITY_NONLOCALIZED) ).to eq( 'ERROR' )
83
281
  end
84
282
 
85
283
  it "encapsulates database object names for integrity constraint violations", :postgresql_93 do
@@ -87,133 +285,184 @@ describe PG::Result do
87
285
  exception = nil
88
286
  begin
89
287
  @conn.exec( "INSERT INTO integrity VALUES (NULL)" )
90
- rescue PGError => err
288
+ rescue PG::Error => err
91
289
  exception = err
92
290
  end
93
291
  result = exception.result
94
292
 
95
- result.error_field( PG::PG_DIAG_SCHEMA_NAME ).should == 'public'
96
- result.error_field( PG::PG_DIAG_TABLE_NAME ).should == 'integrity'
97
- result.error_field( PG::PG_DIAG_COLUMN_NAME ).should == 'id'
98
- result.error_field( PG::PG_DIAG_DATATYPE_NAME ).should be_nil
99
- result.error_field( PG::PG_DIAG_CONSTRAINT_NAME ).should be_nil
293
+ expect( result.error_field(PG::PG_DIAG_SCHEMA_NAME) ).to eq( 'public' )
294
+ expect( result.error_field(PG::PG_DIAG_TABLE_NAME) ).to eq( 'integrity' )
295
+ expect( result.error_field(PG::PG_DIAG_COLUMN_NAME) ).to eq( 'id' )
296
+ expect( result.error_field(PG::PG_DIAG_DATATYPE_NAME) ).to be_nil
297
+ expect( result.error_field(PG::PG_DIAG_CONSTRAINT_NAME) ).to be_nil
100
298
  end
101
299
 
102
- it "should detect division by zero as SQLSTATE 22012" do
300
+ it "detects division by zero as SQLSTATE 22012" do
103
301
  sqlstate = nil
104
302
  begin
105
- res = @conn.exec("SELECT 1/0")
106
- rescue PGError => e
303
+ @conn.exec("SELECT 1/0")
304
+ rescue PG::Error => e
107
305
  sqlstate = e.result.result_error_field( PG::PG_DIAG_SQLSTATE ).to_i
108
306
  end
109
- sqlstate.should == 22012
307
+ expect( sqlstate ).to eq( 22012 )
308
+ end
309
+
310
+ it "provides the error message" do
311
+ @conn.send_query("SELECT xyz")
312
+ res = @conn.get_result; @conn.get_result
313
+ expect( res.error_message ).to match(/"xyz"/)
314
+ expect( res.result_error_message ).to match(/"xyz"/)
110
315
  end
111
316
 
112
- it "should return the same bytes in binary format that are sent in binary format" do
317
+ it "provides a verbose error message", :postgresql_96 do
318
+ @conn.send_query("SELECT xyz")
319
+ res = @conn.get_result; @conn.get_result
320
+ # PQERRORS_TERSE should give a single line result
321
+ expect( res.verbose_error_message(PG::PQERRORS_TERSE, PG::PQSHOW_CONTEXT_ALWAYS) ).to match(/\A.*\n\z/)
322
+ # PQERRORS_VERBOSE should give a multi line result
323
+ expect( res.result_verbose_error_message(PG::PQERRORS_VERBOSE, PG::PQSHOW_CONTEXT_NEVER) ).to match(/\n.*\n/)
324
+ end
325
+
326
+ it "provides a verbose error message with SQLSTATE", :postgresql_12 do
327
+ @conn.send_query("SELECT xyz")
328
+ res = @conn.get_result; @conn.get_result
329
+ expect( res.verbose_error_message(PG::PQERRORS_SQLSTATE, PG::PQSHOW_CONTEXT_NEVER) ).to match(/42703/)
330
+ end
331
+
332
+ it "returns the same bytes in binary format that are sent in binary format" do
113
333
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
114
334
  bytes = File.open(binary_file, 'rb').read
115
- res = @conn.exec('VALUES ($1::bytea)',
335
+ res = @conn.exec_params('VALUES ($1::bytea)',
116
336
  [ { :value => bytes, :format => 1 } ], 1)
117
- res[0]['column1'].should== bytes
118
- res.getvalue(0,0).should == bytes
119
- res.values[0][0].should == bytes
120
- res.column_values(0)[0].should == bytes
337
+ expect( res[0]['column1'] ).to eq( bytes )
338
+ expect( res.getvalue(0,0) ).to eq( bytes )
339
+ expect( res.values[0][0] ).to eq( bytes )
340
+ expect( res.column_values(0)[0] ).to eq( bytes )
121
341
  end
122
342
 
123
- it "should return the same bytes in binary format that are sent as inline text" do
343
+ it "returns the same bytes in binary format that are sent as inline text" do
124
344
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
125
345
  bytes = File.open(binary_file, 'rb').read
126
346
  @conn.exec("SET standard_conforming_strings=on")
127
- res = @conn.exec("VALUES ('#{PG::Connection.escape_bytea(bytes)}'::bytea)", [], 1)
128
- res[0]['column1'].should == bytes
129
- res.getvalue(0,0).should == bytes
130
- res.values[0][0].should == bytes
131
- res.column_values(0)[0].should == bytes
347
+ res = @conn.exec_params("VALUES ('#{PG::Connection.escape_bytea(bytes)}'::bytea)", [], 1)
348
+ expect( res[0]['column1'] ).to eq( bytes )
349
+ expect( res.getvalue(0,0) ).to eq( bytes )
350
+ expect( res.values[0][0] ).to eq( bytes )
351
+ expect( res.column_values(0)[0] ).to eq( bytes )
132
352
  end
133
353
 
134
- it "should return the same bytes in text format that are sent in binary format" do
354
+ it "returns the same bytes in text format that are sent in binary format" do
135
355
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
136
356
  bytes = File.open(binary_file, 'rb').read
137
- res = @conn.exec('VALUES ($1::bytea)',
357
+ res = @conn.exec_params('VALUES ($1::bytea)',
138
358
  [ { :value => bytes, :format => 1 } ])
139
- PG::Connection.unescape_bytea(res[0]['column1']).should== bytes
359
+ expect( PG::Connection.unescape_bytea(res[0]['column1']) ).to eq( bytes )
140
360
  end
141
361
 
142
- it "should return the same bytes in text format that are sent as inline text" do
362
+ it "returns the same bytes in text format that are sent as inline text" do
143
363
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
144
364
  in_bytes = File.open(binary_file, 'rb').read
145
365
 
146
366
  out_bytes = nil
147
367
  @conn.exec("SET standard_conforming_strings=on")
148
- res = @conn.exec("VALUES ('#{PG::Connection.escape_bytea(in_bytes)}'::bytea)", [], 0)
368
+ res = @conn.exec_params("VALUES ('#{PG::Connection.escape_bytea(in_bytes)}'::bytea)", [], 0)
149
369
  out_bytes = PG::Connection.unescape_bytea(res[0]['column1'])
150
- out_bytes.should == in_bytes
370
+ expect( out_bytes ).to eq( in_bytes )
151
371
  end
152
372
 
153
- it "should return the parameter type of the specified prepared statement parameter", :postgresql_92 do
373
+ it "returns the parameter type of the specified prepared statement parameter" do
154
374
  query = 'SELECT * FROM pg_stat_activity WHERE user = $1::name AND query = $2::text'
155
375
  @conn.prepare( 'queryfinder', query )
156
376
  res = @conn.describe_prepared( 'queryfinder' )
157
377
 
158
- @conn.exec( 'SELECT format_type($1, -1)', [res.paramtype(0)] ).getvalue( 0, 0 ).
159
- should == 'name'
160
- @conn.exec( 'SELECT format_type($1, -1)', [res.paramtype(1)] ).getvalue( 0, 0 ).
161
- should == 'text'
378
+ expect(
379
+ @conn.exec_params( 'SELECT format_type($1, -1)', [res.paramtype(0)] ).getvalue( 0, 0 )
380
+ ).to eq( 'name' )
381
+ expect(
382
+ @conn.exec_params( 'SELECT format_type($1, -1)', [res.paramtype(1)] ).getvalue( 0, 0 )
383
+ ).to eq( 'text' )
162
384
  end
163
385
 
164
- it "should raise an exception when a negative index is given to #fformat" do
386
+ it "raises an exception when a negative index is given to #fformat" do
165
387
  res = @conn.exec('SELECT * FROM pg_stat_activity')
166
388
  expect {
167
389
  res.fformat( -1 )
168
390
  }.to raise_error( ArgumentError, /column number/i )
169
391
  end
170
392
 
171
- it "should raise an exception when a negative index is given to #fmod" do
393
+ it "raises an exception when a negative index is given to #fmod" do
172
394
  res = @conn.exec('SELECT * FROM pg_stat_activity')
173
395
  expect {
174
396
  res.fmod( -1 )
175
397
  }.to raise_error( ArgumentError, /column number/i )
176
398
  end
177
399
 
178
- it "should raise an exception when a negative index is given to #[]" do
400
+ it "raises an exception when a negative index is given to #[]" do
179
401
  res = @conn.exec('SELECT * FROM pg_stat_activity')
180
402
  expect {
181
403
  res[ -1 ]
182
404
  }.to raise_error( IndexError, /-1 is out of range/i )
183
405
  end
184
406
 
185
- it "should raise allow for conversion to an array of arrays" do
407
+ it "raises allow for conversion to an array of arrays" do
186
408
  @conn.exec( 'CREATE TABLE valuestest ( foo varchar(33) )' )
187
409
  @conn.exec( 'INSERT INTO valuestest ("foo") values (\'bar\')' )
188
410
  @conn.exec( 'INSERT INTO valuestest ("foo") values (\'bar2\')' )
189
411
 
190
412
  res = @conn.exec( 'SELECT * FROM valuestest' )
191
- res.values.should == [ ["bar"], ["bar2"] ]
413
+ expect( res.values ).to eq( [ ["bar"], ["bar2"] ] )
414
+ end
415
+
416
+ it "can retrieve field names" do
417
+ res = @conn.exec('SELECT 1 AS a, 2 AS "B"')
418
+ expect(res.fields).to eq(["a", "B"])
419
+ end
420
+
421
+ it "can retrieve field names as symbols" do
422
+ res = @conn.exec('SELECT 1 AS a, 2 AS "B"')
423
+ res.field_name_type = :symbol
424
+ expect(res.fields).to eq([:a, :B])
425
+ end
426
+
427
+ it "can retrieve single field names" do
428
+ res = @conn.exec('SELECT 1 AS a, 2 AS "B"')
429
+ expect(res.fname(0)).to eq("a")
430
+ expect(res.fname(1)).to eq("B")
431
+ expect{res.fname(2)}.to raise_error(ArgumentError)
432
+ end
433
+
434
+ it "can retrieve single field names as symbol" do
435
+ res = @conn.exec('SELECT 1 AS a, 2 AS "B"')
436
+ res.field_name_type = :symbol
437
+ expect(res.fname(0)).to eq(:a)
438
+ expect(res.fname(1)).to eq(:B)
439
+ expect{res.fname(2)}.to raise_error(ArgumentError)
192
440
  end
193
441
 
194
442
  # PQfmod
195
443
  it "can return the type modifier for a result column" do
196
444
  @conn.exec( 'CREATE TABLE fmodtest ( foo varchar(33) )' )
197
445
  res = @conn.exec( 'SELECT * FROM fmodtest' )
198
- res.fmod( 0 ).should == 33 + 4 # Column length + varlena size (4)
446
+ expect( res.fmod(0) ).to eq( 33 + 4 ) # Column length + varlena size (4)
199
447
  end
200
448
 
201
- it "should raise an exception when an invalid index is passed to PG::Result#fmod" do
449
+ it "raises an exception when an invalid index is passed to PG::Result#fmod" do
202
450
  @conn.exec( 'CREATE TABLE fmodtest ( foo varchar(33) )' )
203
451
  res = @conn.exec( 'SELECT * FROM fmodtest' )
204
452
  expect { res.fmod(1) }.to raise_error( ArgumentError )
205
453
  end
206
454
 
207
- it "should raise an exception when an invalid (negative) index is passed to PG::Result#fmod" do
455
+ it "raises an exception when an invalid (negative) index is passed to PG::Result#fmod" do
208
456
  @conn.exec( 'CREATE TABLE fmodtest ( foo varchar(33) )' )
209
457
  res = @conn.exec( 'SELECT * FROM fmodtest' )
210
458
  expect { res.fmod(-11) }.to raise_error( ArgumentError )
211
459
  end
212
460
 
213
- it "shouldn't raise an exception when a valid index is passed to PG::Result#fmod for a column with no typemod" do
461
+ it "doesn't raise an exception when a valid index is passed to PG::Result#fmod for a" +
462
+ " column with no typemod" do
214
463
  @conn.exec( 'CREATE TABLE fmodtest ( foo text )' )
215
464
  res = @conn.exec( 'SELECT * FROM fmodtest' )
216
- res.fmod( 0 ).should == -1 # and it shouldn't raise an exception, either
465
+ expect( res.fmod(0) ).to eq( -1 )
217
466
  end
218
467
 
219
468
  # PQftable
@@ -221,28 +470,28 @@ describe PG::Result do
221
470
  @conn.exec( 'CREATE TABLE ftabletest ( foo text )' )
222
471
  res = @conn.exec( 'SELECT * FROM ftabletest' )
223
472
 
224
- res.ftable( 0 ).should == be_nonzero()
473
+ expect( res.ftable(0) ).to be_nonzero()
225
474
  end
226
475
 
227
- it "should raise an exception when an invalid index is passed to PG::Result#ftable" do
476
+ it "raises an exception when an invalid index is passed to PG::Result#ftable" do
228
477
  @conn.exec( 'CREATE TABLE ftabletest ( foo text )' )
229
478
  res = @conn.exec( 'SELECT * FROM ftabletest' )
230
479
 
231
480
  expect { res.ftable(18) }.to raise_error( ArgumentError )
232
481
  end
233
482
 
234
- it "should raise an exception when an invalid (negative) index is passed to PG::Result#ftable" do
483
+ it "raises an exception when an invalid (negative) index is passed to PG::Result#ftable" do
235
484
  @conn.exec( 'CREATE TABLE ftabletest ( foo text )' )
236
485
  res = @conn.exec( 'SELECT * FROM ftabletest' )
237
486
 
238
487
  expect { res.ftable(-2) }.to raise_error( ArgumentError )
239
488
  end
240
489
 
241
- it "shouldn't raise an exception when a valid index is passed to PG::Result#ftable for a " +
490
+ it "doesn't raise an exception when a valid index is passed to PG::Result#ftable for a " +
242
491
  "column with no corresponding table" do
243
492
  @conn.exec( 'CREATE TABLE ftabletest ( foo text )' )
244
493
  res = @conn.exec( 'SELECT foo, LENGTH(foo) as length FROM ftabletest' )
245
- res.ftable( 1 ).should == PG::INVALID_OID # and it shouldn't raise an exception, either
494
+ expect( res.ftable(1) ).to eq( PG::INVALID_OID )
246
495
  end
247
496
 
248
497
  # PQftablecol
@@ -250,29 +499,29 @@ describe PG::Result do
250
499
  @conn.exec( 'CREATE TABLE ftablecoltest ( foo text, bar numeric )' )
251
500
  res = @conn.exec( 'SELECT * FROM ftablecoltest' )
252
501
 
253
- res.ftablecol( 0 ).should == 1
254
- res.ftablecol( 1 ).should == 2
502
+ expect( res.ftablecol(0) ).to eq( 1 )
503
+ expect( res.ftablecol(1) ).to eq( 2 )
255
504
  end
256
505
 
257
- it "should raise an exception when an invalid index is passed to PG::Result#ftablecol" do
506
+ it "raises an exception when an invalid index is passed to PG::Result#ftablecol" do
258
507
  @conn.exec( 'CREATE TABLE ftablecoltest ( foo text, bar numeric )' )
259
508
  res = @conn.exec( 'SELECT * FROM ftablecoltest' )
260
509
 
261
510
  expect { res.ftablecol(32) }.to raise_error( ArgumentError )
262
511
  end
263
512
 
264
- it "should raise an exception when an invalid (negative) index is passed to PG::Result#ftablecol" do
513
+ it "raises an exception when an invalid (negative) index is passed to PG::Result#ftablecol" do
265
514
  @conn.exec( 'CREATE TABLE ftablecoltest ( foo text, bar numeric )' )
266
515
  res = @conn.exec( 'SELECT * FROM ftablecoltest' )
267
516
 
268
517
  expect { res.ftablecol(-1) }.to raise_error( ArgumentError )
269
518
  end
270
519
 
271
- it "shouldn't raise an exception when a valid index is passed to PG::Result#ftablecol for a " +
520
+ it "doesnn't raise an exception when a valid index is passed to PG::Result#ftablecol for a " +
272
521
  "column with no corresponding table" do
273
522
  @conn.exec( 'CREATE TABLE ftablecoltest ( foo text )' )
274
523
  res = @conn.exec( 'SELECT foo, LENGTH(foo) as length FROM ftablecoltest' )
275
- res.ftablecol(1).should == 0 # and it shouldn't raise an exception, either
524
+ expect( res.ftablecol(1) ).to eq( 0 )
276
525
  end
277
526
 
278
527
  it "can be manually checked for failed result status (async API)" do
@@ -285,41 +534,60 @@ describe PG::Result do
285
534
 
286
535
  it "can return the values of a single field" do
287
536
  res = @conn.exec( "SELECT 1 AS x, 'a' AS y UNION ALL SELECT 2, 'b'" )
288
- res.field_values( 'x' ).should == ['1', '2']
289
- res.field_values( 'y' ).should == ['a', 'b']
290
- expect{ res.field_values( '' ) }.to raise_error(IndexError)
291
- expect{ res.field_values( :x ) }.to raise_error(TypeError)
537
+ expect( res.field_values('x') ).to eq( ['1', '2'] )
538
+ expect( res.field_values('y') ).to eq( ['a', 'b'] )
539
+ expect( res.field_values(:x) ).to eq( ['1', '2'] )
540
+ expect{ res.field_values('') }.to raise_error(IndexError)
541
+ expect{ res.field_values(0) }.to raise_error(TypeError)
542
+ end
543
+
544
+ it "can return the values of a single tuple" do
545
+ res = @conn.exec( "SELECT 1 AS x, 'a' AS y UNION ALL SELECT 2, 'b'" )
546
+ expect( res.tuple_values(0) ).to eq( ['1', 'a'] )
547
+ expect( res.tuple_values(1) ).to eq( ['2', 'b'] )
548
+ expect{ res.tuple_values(2) }.to raise_error(IndexError)
549
+ expect{ res.tuple_values(-1) }.to raise_error(IndexError)
550
+ expect{ res.tuple_values("x") }.to raise_error(TypeError)
551
+ end
552
+
553
+ it "can return the values of a single vary lazy tuple" do
554
+ res = @conn.exec( "VALUES(1),(2)" )
555
+ expect( res.tuple(0) ).to be_kind_of( PG::Tuple )
556
+ expect( res.tuple(1) ).to be_kind_of( PG::Tuple )
557
+ expect{ res.tuple(2) }.to raise_error(IndexError)
558
+ expect{ res.tuple(-1) }.to raise_error(IndexError)
559
+ expect{ res.tuple("x") }.to raise_error(TypeError)
292
560
  end
293
561
 
294
- it "should raise a proper exception for a nonexistant table" do
562
+ it "raises a proper exception for a nonexistant table" do
295
563
  expect {
296
564
  @conn.exec( "SELECT * FROM nonexistant_table" )
297
565
  }.to raise_error( PG::UndefinedTable, /relation "nonexistant_table" does not exist/ )
298
566
  end
299
567
 
300
- it "should raise a more generic exception for an unknown SQLSTATE" do
568
+ it "raises a more generic exception for an unknown SQLSTATE" do
301
569
  old_error = PG::ERROR_CLASSES.delete('42P01')
302
570
  begin
303
571
  expect {
304
572
  @conn.exec( "SELECT * FROM nonexistant_table" )
305
573
  }.to raise_error{|error|
306
- error.should be_an_instance_of(PG::SyntaxErrorOrAccessRuleViolation)
307
- error.to_s.should match(/relation "nonexistant_table" does not exist/)
574
+ expect( error ).to be_an_instance_of(PG::SyntaxErrorOrAccessRuleViolation)
575
+ expect( error.to_s ).to match(/relation "nonexistant_table" does not exist/)
308
576
  }
309
577
  ensure
310
578
  PG::ERROR_CLASSES['42P01'] = old_error
311
579
  end
312
580
  end
313
581
 
314
- it "should raise a ServerError for an unknown SQLSTATE class" do
582
+ it "raises a ServerError for an unknown SQLSTATE class" do
315
583
  old_error1 = PG::ERROR_CLASSES.delete('42P01')
316
584
  old_error2 = PG::ERROR_CLASSES.delete('42')
317
585
  begin
318
586
  expect {
319
587
  @conn.exec( "SELECT * FROM nonexistant_table" )
320
588
  }.to raise_error{|error|
321
- error.should be_an_instance_of(PG::ServerError)
322
- error.to_s.should match(/relation "nonexistant_table" does not exist/)
589
+ expect( error ).to be_an_instance_of(PG::ServerError)
590
+ expect( error.to_s ).to match(/relation "nonexistant_table" does not exist/)
323
591
  }
324
592
  ensure
325
593
  PG::ERROR_CLASSES['42P01'] = old_error1
@@ -327,19 +595,87 @@ describe PG::Result do
327
595
  end
328
596
  end
329
597
 
330
- it "should raise a proper exception for a nonexistant schema" do
598
+ it "raises a proper exception for a nonexistant schema" do
331
599
  expect {
332
600
  @conn.exec( "DROP SCHEMA nonexistant_schema" )
333
601
  }.to raise_error( PG::InvalidSchemaName, /schema "nonexistant_schema" does not exist/ )
334
602
  end
335
603
 
336
- it "the raised result should be nil in case of a connection error" do
337
- c = PGconn.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
604
+ it "the raised result is nil in case of a connection error" do
605
+ c = PG::Connection.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
338
606
  expect {
339
607
  c.exec "select 1"
340
- }.to raise_error{|error|
341
- error.should be_an_instance_of(PG::UnableToSend)
342
- error.result.should == nil
608
+ }.to raise_error {|error|
609
+ expect( error ).to be_an_instance_of(PG::UnableToSend)
610
+ expect( error.result ).to eq( nil )
343
611
  }
344
612
  end
613
+
614
+ it "does not clear the result itself" do
615
+ r = @conn.exec "select 1"
616
+ expect( r.autoclear? ).to eq(false)
617
+ expect( r.cleared? ).to eq(false)
618
+ r.clear
619
+ expect( r.cleared? ).to eq(true)
620
+ end
621
+
622
+ it "can be inspected before and after clear" do
623
+ r = @conn.exec "select 1"
624
+ expect( r.inspect ).to match(/status=PGRES_TUPLES_OK/)
625
+ r.clear
626
+ expect( r.inspect ).to match(/cleared/)
627
+ end
628
+
629
+ it "should give account about memory usage" do
630
+ r = @conn.exec "select 1"
631
+ expect( ObjectSpace.memsize_of(r) ).to be > 1000
632
+ r.clear
633
+ expect( ObjectSpace.memsize_of(r) ).to be < 100
634
+ end
635
+
636
+ context 'result value conversions with TypeMapByColumn' do
637
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
638
+ let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT4', oid: 700 }
639
+
640
+ it "should allow reading, assigning and diabling type conversions" do
641
+ res = @conn.exec( "SELECT 123" )
642
+ expect( res.type_map ).to be_kind_of(PG::TypeMapAllStrings)
643
+ res.type_map = PG::TypeMapByColumn.new [textdec_int]
644
+ expect( res.type_map ).to be_an_instance_of(PG::TypeMapByColumn)
645
+ expect( res.type_map.coders ).to eq( [textdec_int] )
646
+ res.type_map = PG::TypeMapByColumn.new [textdec_float]
647
+ expect( res.type_map.coders ).to eq( [textdec_float] )
648
+ res.type_map = PG::TypeMapAllStrings.new
649
+ expect( res.type_map ).to be_kind_of(PG::TypeMapAllStrings)
650
+ end
651
+
652
+ it "should be applied to all value retrieving methods" do
653
+ res = @conn.exec( "SELECT 123 as f" )
654
+ res.type_map = PG::TypeMapByColumn.new [textdec_int]
655
+ expect( res.values ).to eq( [[123]] )
656
+ expect( res.getvalue(0,0) ).to eq( 123 )
657
+ expect( res[0] ).to eq( {'f' => 123 } )
658
+ expect( res.enum_for(:each_row).to_a ).to eq( [[123]] )
659
+ expect( res.enum_for(:each).to_a ).to eq( [{'f' => 123}] )
660
+ expect( res.column_values(0) ).to eq( [123] )
661
+ expect( res.field_values('f') ).to eq( [123] )
662
+ expect( res.field_values(:f) ).to eq( [123] )
663
+ expect( res.tuple_values(0) ).to eq( [123] )
664
+ end
665
+
666
+ it "should be usable for several querys" do
667
+ colmap = PG::TypeMapByColumn.new [textdec_int]
668
+ res = @conn.exec( "SELECT 123" )
669
+ res.type_map = colmap
670
+ expect( res.values ).to eq( [[123]] )
671
+ res = @conn.exec( "SELECT 456" )
672
+ res.type_map = colmap
673
+ expect( res.values ).to eq( [[456]] )
674
+ end
675
+
676
+ it "shouldn't allow invalid type maps" do
677
+ res = @conn.exec( "SELECT 1" )
678
+ expect{ res.type_map = 1 }.to raise_error(TypeError)
679
+ end
680
+ end
345
681
  end