pg 0.15.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.tar.gz.sig +0 -0
  4. data/BSDL +2 -2
  5. data/ChangeLog +0 -3022
  6. data/History.rdoc +370 -4
  7. data/Manifest.txt +39 -19
  8. data/README-Windows.rdoc +17 -28
  9. data/README.ja.rdoc +1 -2
  10. data/README.rdoc +113 -14
  11. data/Rakefile +97 -36
  12. data/Rakefile.cross +109 -83
  13. data/ext/errorcodes.def +1032 -0
  14. data/ext/errorcodes.rb +45 -0
  15. data/ext/errorcodes.txt +494 -0
  16. data/ext/extconf.rb +55 -52
  17. data/ext/gvl_wrappers.c +4 -0
  18. data/ext/gvl_wrappers.h +94 -38
  19. data/ext/pg.c +273 -121
  20. data/ext/pg.h +292 -50
  21. data/ext/pg_binary_decoder.c +229 -0
  22. data/ext/pg_binary_encoder.c +163 -0
  23. data/ext/pg_coder.c +561 -0
  24. data/ext/pg_connection.c +1811 -1051
  25. data/ext/pg_copy_coder.c +599 -0
  26. data/ext/pg_errors.c +95 -0
  27. data/ext/pg_record_coder.c +491 -0
  28. data/ext/pg_result.c +917 -203
  29. data/ext/pg_text_decoder.c +987 -0
  30. data/ext/pg_text_encoder.c +814 -0
  31. data/ext/pg_tuple.c +549 -0
  32. data/ext/pg_type_map.c +166 -0
  33. data/ext/pg_type_map_all_strings.c +116 -0
  34. data/ext/pg_type_map_by_class.c +244 -0
  35. data/ext/pg_type_map_by_column.c +313 -0
  36. data/ext/pg_type_map_by_mri_type.c +284 -0
  37. data/ext/pg_type_map_by_oid.c +356 -0
  38. data/ext/pg_type_map_in_ruby.c +299 -0
  39. data/ext/pg_util.c +149 -0
  40. data/ext/pg_util.h +65 -0
  41. data/lib/pg.rb +31 -9
  42. data/lib/pg/basic_type_mapping.rb +522 -0
  43. data/lib/pg/binary_decoder.rb +23 -0
  44. data/lib/pg/coder.rb +104 -0
  45. data/lib/pg/connection.rb +235 -30
  46. data/lib/pg/constants.rb +2 -1
  47. data/lib/pg/exceptions.rb +2 -1
  48. data/lib/pg/result.rb +33 -6
  49. data/lib/pg/text_decoder.rb +46 -0
  50. data/lib/pg/text_encoder.rb +59 -0
  51. data/lib/pg/tuple.rb +30 -0
  52. data/lib/pg/type_map_by_column.rb +16 -0
  53. data/spec/{lib/helpers.rb → helpers.rb} +154 -52
  54. data/spec/pg/basic_type_mapping_spec.rb +630 -0
  55. data/spec/pg/connection_spec.rb +1352 -426
  56. data/spec/pg/connection_sync_spec.rb +41 -0
  57. data/spec/pg/result_spec.rb +508 -105
  58. data/spec/pg/tuple_spec.rb +333 -0
  59. data/spec/pg/type_map_by_class_spec.rb +138 -0
  60. data/spec/pg/type_map_by_column_spec.rb +226 -0
  61. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  62. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  63. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  64. data/spec/pg/type_map_spec.rb +22 -0
  65. data/spec/pg/type_spec.rb +1123 -0
  66. data/spec/pg_spec.rb +35 -16
  67. metadata +163 -84
  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,204 +1,468 @@
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
- statement_pos = RSpec.configuration.exclusion_filter[:postgresql_90] ? nil : '15'
77
- result.error_field( PG::PG_DIAG_STATEMENT_POSITION ).should == statement_pos
78
- result.error_field( PG::PG_DIAG_INTERNAL_POSITION ).should be_nil()
79
- result.error_field( PG::PG_DIAG_INTERNAL_QUERY ).should be_nil()
80
- result.error_field( PG::PG_DIAG_CONTEXT ).should be_nil()
81
- result.error_field( PG::PG_DIAG_SOURCE_FILE ).should =~ /parse_relation\.c$|namespace\.c$/
82
- result.error_field( PG::PG_DIAG_SOURCE_LINE ).should =~ /^\d+$/
83
- result.error_field( PG::PG_DIAG_SOURCE_FUNCTION ).should =~ /^parserOpenTable$|^RangeVarGetRelid$/
84
-
85
- end
86
-
87
- it "should detect division by zero as SQLSTATE 22012" do
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' )
281
+ end
282
+
283
+ it "encapsulates database object names for integrity constraint violations", :postgresql_93 do
284
+ @conn.exec( "CREATE TABLE integrity (id SERIAL PRIMARY KEY)" )
285
+ exception = nil
286
+ begin
287
+ @conn.exec( "INSERT INTO integrity VALUES (NULL)" )
288
+ rescue PG::Error => err
289
+ exception = err
290
+ end
291
+ result = exception.result
292
+
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
298
+ end
299
+
300
+ it "detects division by zero as SQLSTATE 22012" do
88
301
  sqlstate = nil
89
302
  begin
90
- res = @conn.exec("SELECT 1/0")
91
- rescue PGError => e
303
+ @conn.exec("SELECT 1/0")
304
+ rescue PG::Error => e
92
305
  sqlstate = e.result.result_error_field( PG::PG_DIAG_SQLSTATE ).to_i
93
306
  end
94
- 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"/)
315
+ end
316
+
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/)
95
324
  end
96
325
 
97
- it "should return the same bytes in binary format that are sent in binary format" do
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
98
333
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
99
334
  bytes = File.open(binary_file, 'rb').read
100
- res = @conn.exec('VALUES ($1::bytea)',
335
+ res = @conn.exec_params('VALUES ($1::bytea)',
101
336
  [ { :value => bytes, :format => 1 } ], 1)
102
- res[0]['column1'].should== bytes
103
- res.getvalue(0,0).should == bytes
104
- res.values[0][0].should == bytes
105
- 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 )
106
341
  end
107
342
 
108
- 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
109
344
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
110
345
  bytes = File.open(binary_file, 'rb').read
111
346
  @conn.exec("SET standard_conforming_strings=on")
112
- res = @conn.exec("VALUES ('#{PG::Connection.escape_bytea(bytes)}'::bytea)", [], 1)
113
- res[0]['column1'].should == bytes
114
- res.getvalue(0,0).should == bytes
115
- res.values[0][0].should == bytes
116
- 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 )
117
352
  end
118
353
 
119
- 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
120
355
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
121
356
  bytes = File.open(binary_file, 'rb').read
122
- res = @conn.exec('VALUES ($1::bytea)',
357
+ res = @conn.exec_params('VALUES ($1::bytea)',
123
358
  [ { :value => bytes, :format => 1 } ])
124
- PG::Connection.unescape_bytea(res[0]['column1']).should== bytes
359
+ expect( PG::Connection.unescape_bytea(res[0]['column1']) ).to eq( bytes )
125
360
  end
126
361
 
127
- 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
128
363
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
129
364
  in_bytes = File.open(binary_file, 'rb').read
130
365
 
131
366
  out_bytes = nil
132
367
  @conn.exec("SET standard_conforming_strings=on")
133
- 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)
134
369
  out_bytes = PG::Connection.unescape_bytea(res[0]['column1'])
135
- out_bytes.should == in_bytes
370
+ expect( out_bytes ).to eq( in_bytes )
136
371
  end
137
372
 
138
- 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
139
374
  query = 'SELECT * FROM pg_stat_activity WHERE user = $1::name AND query = $2::text'
140
375
  @conn.prepare( 'queryfinder', query )
141
376
  res = @conn.describe_prepared( 'queryfinder' )
142
377
 
143
- @conn.exec( 'SELECT format_type($1, -1)', [res.paramtype(0)] ).getvalue( 0, 0 ).
144
- should == 'name'
145
- @conn.exec( 'SELECT format_type($1, -1)', [res.paramtype(1)] ).getvalue( 0, 0 ).
146
- 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' )
147
384
  end
148
385
 
149
- 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
150
387
  res = @conn.exec('SELECT * FROM pg_stat_activity')
151
388
  expect {
152
389
  res.fformat( -1 )
153
390
  }.to raise_error( ArgumentError, /column number/i )
154
391
  end
155
392
 
156
- 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
157
394
  res = @conn.exec('SELECT * FROM pg_stat_activity')
158
395
  expect {
159
396
  res.fmod( -1 )
160
397
  }.to raise_error( ArgumentError, /column number/i )
161
398
  end
162
399
 
163
- 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
164
401
  res = @conn.exec('SELECT * FROM pg_stat_activity')
165
402
  expect {
166
403
  res[ -1 ]
167
404
  }.to raise_error( IndexError, /-1 is out of range/i )
168
405
  end
169
406
 
170
- it "should raise allow for conversion to an array of arrays" do
407
+ it "raises allow for conversion to an array of arrays" do
171
408
  @conn.exec( 'CREATE TABLE valuestest ( foo varchar(33) )' )
172
409
  @conn.exec( 'INSERT INTO valuestest ("foo") values (\'bar\')' )
173
410
  @conn.exec( 'INSERT INTO valuestest ("foo") values (\'bar2\')' )
174
411
 
175
412
  res = @conn.exec( 'SELECT * FROM valuestest' )
176
- 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)
177
440
  end
178
441
 
179
442
  # PQfmod
180
443
  it "can return the type modifier for a result column" do
181
444
  @conn.exec( 'CREATE TABLE fmodtest ( foo varchar(33) )' )
182
445
  res = @conn.exec( 'SELECT * FROM fmodtest' )
183
- 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)
184
447
  end
185
448
 
186
- 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
187
450
  @conn.exec( 'CREATE TABLE fmodtest ( foo varchar(33) )' )
188
451
  res = @conn.exec( 'SELECT * FROM fmodtest' )
189
452
  expect { res.fmod(1) }.to raise_error( ArgumentError )
190
453
  end
191
454
 
192
- 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
193
456
  @conn.exec( 'CREATE TABLE fmodtest ( foo varchar(33) )' )
194
457
  res = @conn.exec( 'SELECT * FROM fmodtest' )
195
458
  expect { res.fmod(-11) }.to raise_error( ArgumentError )
196
459
  end
197
460
 
198
- 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
199
463
  @conn.exec( 'CREATE TABLE fmodtest ( foo text )' )
200
464
  res = @conn.exec( 'SELECT * FROM fmodtest' )
201
- res.fmod( 0 ).should == -1 # and it shouldn't raise an exception, either
465
+ expect( res.fmod(0) ).to eq( -1 )
202
466
  end
203
467
 
204
468
  # PQftable
@@ -206,28 +470,28 @@ describe PG::Result do
206
470
  @conn.exec( 'CREATE TABLE ftabletest ( foo text )' )
207
471
  res = @conn.exec( 'SELECT * FROM ftabletest' )
208
472
 
209
- res.ftable( 0 ).should == be_nonzero()
473
+ expect( res.ftable(0) ).to be_nonzero()
210
474
  end
211
475
 
212
- 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
213
477
  @conn.exec( 'CREATE TABLE ftabletest ( foo text )' )
214
478
  res = @conn.exec( 'SELECT * FROM ftabletest' )
215
479
 
216
480
  expect { res.ftable(18) }.to raise_error( ArgumentError )
217
481
  end
218
482
 
219
- 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
220
484
  @conn.exec( 'CREATE TABLE ftabletest ( foo text )' )
221
485
  res = @conn.exec( 'SELECT * FROM ftabletest' )
222
486
 
223
487
  expect { res.ftable(-2) }.to raise_error( ArgumentError )
224
488
  end
225
489
 
226
- 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 " +
227
491
  "column with no corresponding table" do
228
492
  @conn.exec( 'CREATE TABLE ftabletest ( foo text )' )
229
493
  res = @conn.exec( 'SELECT foo, LENGTH(foo) as length FROM ftabletest' )
230
- 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 )
231
495
  end
232
496
 
233
497
  # PQftablecol
@@ -235,29 +499,29 @@ describe PG::Result do
235
499
  @conn.exec( 'CREATE TABLE ftablecoltest ( foo text, bar numeric )' )
236
500
  res = @conn.exec( 'SELECT * FROM ftablecoltest' )
237
501
 
238
- res.ftablecol( 0 ).should == 1
239
- res.ftablecol( 1 ).should == 2
502
+ expect( res.ftablecol(0) ).to eq( 1 )
503
+ expect( res.ftablecol(1) ).to eq( 2 )
240
504
  end
241
505
 
242
- 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
243
507
  @conn.exec( 'CREATE TABLE ftablecoltest ( foo text, bar numeric )' )
244
508
  res = @conn.exec( 'SELECT * FROM ftablecoltest' )
245
509
 
246
510
  expect { res.ftablecol(32) }.to raise_error( ArgumentError )
247
511
  end
248
512
 
249
- 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
250
514
  @conn.exec( 'CREATE TABLE ftablecoltest ( foo text, bar numeric )' )
251
515
  res = @conn.exec( 'SELECT * FROM ftablecoltest' )
252
516
 
253
517
  expect { res.ftablecol(-1) }.to raise_error( ArgumentError )
254
518
  end
255
519
 
256
- 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 " +
257
521
  "column with no corresponding table" do
258
522
  @conn.exec( 'CREATE TABLE ftablecoltest ( foo text )' )
259
523
  res = @conn.exec( 'SELECT foo, LENGTH(foo) as length FROM ftablecoltest' )
260
- res.ftablecol(1).should == 0 # and it shouldn't raise an exception, either
524
+ expect( res.ftablecol(1) ).to eq( 0 )
261
525
  end
262
526
 
263
527
  it "can be manually checked for failed result status (async API)" do
@@ -270,9 +534,148 @@ describe PG::Result do
270
534
 
271
535
  it "can return the values of a single field" do
272
536
  res = @conn.exec( "SELECT 1 AS x, 'a' AS y UNION ALL SELECT 2, 'b'" )
273
- res.field_values( 'x' ).should == ['1', '2']
274
- res.field_values( 'y' ).should == ['a', 'b']
275
- expect{ res.field_values( '' ) }.to raise_error(IndexError)
276
- 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)
560
+ end
561
+
562
+ it "raises a proper exception for a nonexistant table" do
563
+ expect {
564
+ @conn.exec( "SELECT * FROM nonexistant_table" )
565
+ }.to raise_error( PG::UndefinedTable, /relation "nonexistant_table" does not exist/ )
566
+ end
567
+
568
+ it "raises a more generic exception for an unknown SQLSTATE" do
569
+ old_error = PG::ERROR_CLASSES.delete('42P01')
570
+ begin
571
+ expect {
572
+ @conn.exec( "SELECT * FROM nonexistant_table" )
573
+ }.to raise_error{|error|
574
+ expect( error ).to be_an_instance_of(PG::SyntaxErrorOrAccessRuleViolation)
575
+ expect( error.to_s ).to match(/relation "nonexistant_table" does not exist/)
576
+ }
577
+ ensure
578
+ PG::ERROR_CLASSES['42P01'] = old_error
579
+ end
580
+ end
581
+
582
+ it "raises a ServerError for an unknown SQLSTATE class" do
583
+ old_error1 = PG::ERROR_CLASSES.delete('42P01')
584
+ old_error2 = PG::ERROR_CLASSES.delete('42')
585
+ begin
586
+ expect {
587
+ @conn.exec( "SELECT * FROM nonexistant_table" )
588
+ }.to raise_error{|error|
589
+ expect( error ).to be_an_instance_of(PG::ServerError)
590
+ expect( error.to_s ).to match(/relation "nonexistant_table" does not exist/)
591
+ }
592
+ ensure
593
+ PG::ERROR_CLASSES['42P01'] = old_error1
594
+ PG::ERROR_CLASSES['42'] = old_error2
595
+ end
596
+ end
597
+
598
+ it "raises a proper exception for a nonexistant schema" do
599
+ expect {
600
+ @conn.exec( "DROP SCHEMA nonexistant_schema" )
601
+ }.to raise_error( PG::InvalidSchemaName, /schema "nonexistant_schema" does not exist/ )
602
+ end
603
+
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" )
606
+ expect {
607
+ c.exec "select 1"
608
+ }.to raise_error {|error|
609
+ expect( error ).to be_an_instance_of(PG::UnableToSend)
610
+ expect( error.result ).to eq( nil )
611
+ }
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
277
680
  end
278
681
  end