pg 0.17.1 → 1.2.3

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