pg 1.0.0 → 1.1.0.pre20180730144600

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +0 -6595
  5. data/History.rdoc +52 -0
  6. data/README.rdoc +11 -0
  7. data/Rakefile +1 -1
  8. data/Rakefile.cross +1 -1
  9. data/ext/errorcodes.rb +1 -1
  10. data/ext/extconf.rb +2 -0
  11. data/ext/pg.c +3 -2
  12. data/ext/pg.h +33 -5
  13. data/ext/pg_binary_decoder.c +69 -6
  14. data/ext/pg_binary_encoder.c +1 -1
  15. data/ext/pg_coder.c +52 -3
  16. data/ext/pg_connection.c +290 -103
  17. data/ext/pg_copy_coder.c +10 -5
  18. data/ext/pg_result.c +339 -113
  19. data/ext/pg_text_decoder.c +597 -37
  20. data/ext/pg_text_encoder.c +1 -1
  21. data/ext/pg_tuple.c +540 -0
  22. data/ext/pg_type_map.c +1 -1
  23. data/ext/pg_type_map_all_strings.c +1 -1
  24. data/ext/pg_type_map_by_class.c +1 -1
  25. data/ext/pg_type_map_by_column.c +1 -1
  26. data/ext/pg_type_map_by_mri_type.c +1 -1
  27. data/ext/pg_type_map_by_oid.c +1 -1
  28. data/ext/pg_type_map_in_ruby.c +1 -1
  29. data/ext/util.c +6 -6
  30. data/ext/util.h +2 -2
  31. data/lib/pg.rb +5 -3
  32. data/lib/pg/basic_type_mapping.rb +40 -7
  33. data/lib/pg/coder.rb +1 -1
  34. data/lib/pg/connection.rb +20 -1
  35. data/lib/pg/constants.rb +1 -1
  36. data/lib/pg/exceptions.rb +1 -1
  37. data/lib/pg/result.rb +1 -1
  38. data/lib/pg/text_decoder.rb +19 -23
  39. data/lib/pg/text_encoder.rb +35 -1
  40. data/lib/pg/type_map_by_column.rb +1 -1
  41. data/spec/helpers.rb +39 -7
  42. data/spec/pg/basic_type_mapping_spec.rb +230 -27
  43. data/spec/pg/connection_spec.rb +116 -77
  44. data/spec/pg/result_spec.rb +46 -11
  45. data/spec/pg/type_map_by_class_spec.rb +1 -1
  46. data/spec/pg/type_map_by_column_spec.rb +1 -1
  47. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  48. data/spec/pg/type_map_by_oid_spec.rb +1 -1
  49. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  50. data/spec/pg/type_map_spec.rb +1 -1
  51. data/spec/pg/type_spec.rb +177 -11
  52. data/spec/pg_spec.rb +1 -1
  53. metadata +24 -28
  54. metadata.gz.sig +0 -0
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -39,8 +39,8 @@ describe PG::Result do
39
39
  expect( e.to_a ).to eq [{'a'=>'1', 'b'=>'2'}]
40
40
  end
41
41
 
42
- context "result streaming" do
43
- it "can iterate over all tuples in single row mode" do
42
+ context "result streaming in single row mode" do
43
+ it "can iterate over all rows as Hash" do
44
44
  @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
45
45
  @conn.set_single_row_mode
46
46
  expect(
@@ -56,7 +56,7 @@ describe PG::Result do
56
56
  expect( @conn.get_result ).to be_nil
57
57
  end
58
58
 
59
- it "can iterate over all rows in single row mode" do
59
+ it "can iterate over all rows as Array" do
60
60
  @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
61
61
  @conn.set_single_row_mode
62
62
  expect(
@@ -72,6 +72,22 @@ describe PG::Result do
72
72
  expect( @conn.get_result ).to be_nil
73
73
  end
74
74
 
75
+ it "can iterate over all rows as PG::Tuple" do
76
+ @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
77
+ @conn.set_single_row_mode
78
+ tuples = @conn.get_result.stream_each_tuple.to_a
79
+ expect( tuples[0][0] ).to eq( "2" )
80
+ expect( tuples[1]["a"] ).to eq( "3" )
81
+ expect( tuples.size ).to eq( 3 )
82
+
83
+ tuples = @conn.get_result.enum_for(:stream_each_tuple).to_a
84
+ expect( tuples[-1][-1] ).to eq( "6" )
85
+ expect( tuples[-2]["b"] ).to eq( "1" )
86
+ expect( tuples.size ).to eq( 2 )
87
+
88
+ expect( @conn.get_result ).to be_nil
89
+ end
90
+
75
91
  it "complains when not in single row mode" do
76
92
  @conn.send_query( "SELECT generate_series(2,4)" )
77
93
  expect{
@@ -96,7 +112,7 @@ describe PG::Result do
96
112
  end
97
113
 
98
114
  it "inserts nil AS NULL and return NULL as nil" do
99
- res = @conn.exec("SELECT $1::int AS n", [nil])
115
+ res = @conn.exec_params("SELECT $1::int AS n", [nil])
100
116
  expect( res[0]['n'] ).to be_nil()
101
117
  end
102
118
 
@@ -161,7 +177,7 @@ describe PG::Result do
161
177
  it "returns the same bytes in binary format that are sent in binary format" do
162
178
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
163
179
  bytes = File.open(binary_file, 'rb').read
164
- res = @conn.exec('VALUES ($1::bytea)',
180
+ res = @conn.exec_params('VALUES ($1::bytea)',
165
181
  [ { :value => bytes, :format => 1 } ], 1)
166
182
  expect( res[0]['column1'] ).to eq( bytes )
167
183
  expect( res.getvalue(0,0) ).to eq( bytes )
@@ -173,7 +189,7 @@ describe PG::Result do
173
189
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
174
190
  bytes = File.open(binary_file, 'rb').read
175
191
  @conn.exec("SET standard_conforming_strings=on")
176
- res = @conn.exec("VALUES ('#{PG::Connection.escape_bytea(bytes)}'::bytea)", [], 1)
192
+ res = @conn.exec_params("VALUES ('#{PG::Connection.escape_bytea(bytes)}'::bytea)", [], 1)
177
193
  expect( res[0]['column1'] ).to eq( bytes )
178
194
  expect( res.getvalue(0,0) ).to eq( bytes )
179
195
  expect( res.values[0][0] ).to eq( bytes )
@@ -183,7 +199,7 @@ describe PG::Result do
183
199
  it "returns the same bytes in text format that are sent in binary format" do
184
200
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
185
201
  bytes = File.open(binary_file, 'rb').read
186
- res = @conn.exec('VALUES ($1::bytea)',
202
+ res = @conn.exec_params('VALUES ($1::bytea)',
187
203
  [ { :value => bytes, :format => 1 } ])
188
204
  expect( PG::Connection.unescape_bytea(res[0]['column1']) ).to eq( bytes )
189
205
  end
@@ -194,7 +210,7 @@ describe PG::Result do
194
210
 
195
211
  out_bytes = nil
196
212
  @conn.exec("SET standard_conforming_strings=on")
197
- res = @conn.exec("VALUES ('#{PG::Connection.escape_bytea(in_bytes)}'::bytea)", [], 0)
213
+ res = @conn.exec_params("VALUES ('#{PG::Connection.escape_bytea(in_bytes)}'::bytea)", [], 0)
198
214
  out_bytes = PG::Connection.unescape_bytea(res[0]['column1'])
199
215
  expect( out_bytes ).to eq( in_bytes )
200
216
  end
@@ -205,10 +221,10 @@ describe PG::Result do
205
221
  res = @conn.describe_prepared( 'queryfinder' )
206
222
 
207
223
  expect(
208
- @conn.exec( 'SELECT format_type($1, -1)', [res.paramtype(0)] ).getvalue( 0, 0 )
224
+ @conn.exec_params( 'SELECT format_type($1, -1)', [res.paramtype(0)] ).getvalue( 0, 0 )
209
225
  ).to eq( 'name' )
210
226
  expect(
211
- @conn.exec( 'SELECT format_type($1, -1)', [res.paramtype(1)] ).getvalue( 0, 0 )
227
+ @conn.exec_params( 'SELECT format_type($1, -1)', [res.paramtype(1)] ).getvalue( 0, 0 )
212
228
  ).to eq( 'text' )
213
229
  end
214
230
 
@@ -343,6 +359,24 @@ describe PG::Result do
343
359
  expect{ res.field_values(:x) }.to raise_error(TypeError)
344
360
  end
345
361
 
362
+ it "can return the values of a single tuple" do
363
+ res = @conn.exec( "SELECT 1 AS x, 'a' AS y UNION ALL SELECT 2, 'b'" )
364
+ expect( res.tuple_values(0) ).to eq( ['1', 'a'] )
365
+ expect( res.tuple_values(1) ).to eq( ['2', 'b'] )
366
+ expect{ res.tuple_values(2) }.to raise_error(IndexError)
367
+ expect{ res.tuple_values(-1) }.to raise_error(IndexError)
368
+ expect{ res.tuple_values("x") }.to raise_error(TypeError)
369
+ end
370
+
371
+ it "can return the values of a single vary lazy tuple" do
372
+ res = @conn.exec( "VALUES(1),(2)" )
373
+ expect( res.tuple(0) ).to be_kind_of( PG::Tuple )
374
+ expect( res.tuple(1) ).to be_kind_of( PG::Tuple )
375
+ expect{ res.tuple(2) }.to raise_error(IndexError)
376
+ expect{ res.tuple(-1) }.to raise_error(IndexError)
377
+ expect{ res.tuple("x") }.to raise_error(TypeError)
378
+ end
379
+
346
380
  it "raises a proper exception for a nonexistant table" do
347
381
  expect {
348
382
  @conn.exec( "SELECT * FROM nonexistant_table" )
@@ -436,6 +470,7 @@ describe PG::Result do
436
470
  expect( res.enum_for(:each).to_a ).to eq( [{'f' => 123}] )
437
471
  expect( res.column_values(0) ).to eq( [123] )
438
472
  expect( res.field_values('f') ).to eq( [123] )
473
+ expect( res.tuple_values(0) ).to eq( [123] )
439
474
  end
440
475
 
441
476
  it "should be usable for several querys" do
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,7 +1,8 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require 'pg'
5
+ require 'time'
5
6
 
6
7
 
7
8
  describe "PG::Type derivations" do
@@ -15,6 +16,9 @@ describe "PG::Type derivations" do
15
16
  let!(:textdec_string) { PG::TextDecoder::String.new }
16
17
  let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new }
17
18
  let!(:textdec_timestamp) { PG::TextDecoder::TimestampWithoutTimeZone.new }
19
+ let!(:textenc_timestamputc) { PG::TextEncoder::TimestampUtc.new }
20
+ let!(:textdec_timestamputc) { PG::TextDecoder::TimestampUtc.new }
21
+ let!(:textdec_timestampul) { PG::TextDecoder::TimestampUtcToLocal.new }
18
22
  let!(:textenc_timestamptz) { PG::TextEncoder::TimestampWithTimeZone.new }
19
23
  let!(:textdec_timestamptz) { PG::TextDecoder::TimestampWithTimeZone.new }
20
24
  let!(:textenc_bytea) { PG::TextEncoder::Bytea.new }
@@ -96,21 +100,35 @@ describe "PG::Type derivations" do
96
100
  end
97
101
 
98
102
  context 'timestamps' do
99
- it 'decodes timestamps without timezone' do
100
- expect( textdec_timestamp.decode('2016-01-02 23:23:59.123456') ).
101
- to be_within(0.000001).of( Time.new(2016,01,02, 23, 23, 59.123456) )
103
+ it 'decodes timestamps without timezone as local time' do
104
+ expect( textdec_timestamp.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
105
+ to eq( Time.new(2016,1,2, 23,23,59.123456).iso8601(5) )
106
+ expect( textdec_timestamp.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
107
+ to eq( Time.new(2016,8,2, 23,23,59.123456).iso8601(5) )
108
+ end
109
+ it 'decodes timestamps with UTC time and returns UTC timezone' do
110
+ expect( textdec_timestamputc.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
111
+ to eq( Time.utc(2016,1,2, 23,23,59.123456).iso8601(5) )
112
+ expect( textdec_timestamputc.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
113
+ to eq( Time.utc(2016,8,2, 23,23,59.123456).iso8601(5) )
114
+ end
115
+ it 'decodes timestamps with UTC time and returns local timezone' do
116
+ expect( textdec_timestampul.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
117
+ to eq( Time.utc(2016,1,2, 23,23,59.123456).getlocal.iso8601(5) )
118
+ expect( textdec_timestampul.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
119
+ to eq( Time.utc(2016,8,2, 23,23,59.123456).getlocal.iso8601(5) )
102
120
  end
103
121
  it 'decodes timestamps with hour timezone' do
104
- expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-04') ).
105
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:00") )
106
- expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10') ).
107
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:00") )
122
+ expect( textdec_timestamptz.decode('2016-01-02 23:23:59.123456-04').iso8601(5) ).
123
+ to eq( Time.new(2016,1,2, 23,23,59.123456, "-04:00").iso8601(5) )
124
+ expect( textdec_timestamptz.decode('2016-08-02 23:23:59.123456+10').iso8601(5) ).
125
+ to eq( Time.new(2016,8,2, 23,23,59.123456, "+10:00").iso8601(5) )
108
126
  end
109
127
  it 'decodes timestamps with hour:minute timezone' do
110
128
  expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-04:15') ).
111
129
  to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:15") )
112
- expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-0430') ).
113
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:30") )
130
+ expect( textdec_timestamptz.decode('2015-07-26 17:26:42.691511-04:30') ).
131
+ to be_within(0.000001).of( Time.new(2015,07,26, 17, 26, 42.691511, "-04:30") )
114
132
  expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10:45') ).
115
133
  to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:45") )
116
134
  end
@@ -121,6 +139,81 @@ describe "PG::Type derivations" do
121
139
  expect( textdec_timestamptz.decode('1916-01-01 00:00:00-00:25:21') ).
122
140
  to be_within(0.000001).of( Time.new(1916, 1, 1, 0, 0, 0, "-00:25:21") )
123
141
  end
142
+ it 'decodes timestamps with date before 1823' do
143
+ expect( textdec_timestamp.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
144
+ to eq( Time.new(1822,01,02, 23, 23, 59.123456).iso8601(5) )
145
+ expect( textdec_timestamputc.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
146
+ to eq( Time.utc(1822,01,02, 23, 23, 59.123456).iso8601(5) )
147
+ expect( textdec_timestampul.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
148
+ to eq( Time.utc(1822,01,02, 23, 23, 59.123456).getlocal.iso8601(5) )
149
+ expect( textdec_timestamptz.decode('1822-01-02 23:23:59.123456+04').iso8601(5) ).
150
+ to eq( Time.new(1822,01,02, 23, 23, 59.123456, "+04:00").iso8601(5) )
151
+ end
152
+ it 'decodes timestamps with date after 2116' do
153
+ expect( textdec_timestamp.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
154
+ to eq( Time.new(2117,01,02, 23, 23, 59.123456).iso8601(5) )
155
+ expect( textdec_timestamputc.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
156
+ to eq( Time.utc(2117,01,02, 23, 23, 59.123456).iso8601(5) )
157
+ expect( textdec_timestampul.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
158
+ to eq( Time.utc(2117,01,02, 23, 23, 59.123456).getlocal.iso8601(5) )
159
+ expect( textdec_timestamptz.decode('2117-01-02 23:23:59.123456+04').iso8601(5) ).
160
+ to eq( Time.new(2117,01,02, 23, 23, 59.123456, "+04:00").iso8601(5) )
161
+ end
162
+ it 'decodes timestamps with variable number of digits for the useconds part' do
163
+ sec = "59.12345678912345"
164
+ (4..sec.length).each do |i|
165
+ expect( textdec_timestamp.decode("2016-01-02 23:23:#{sec[0,i]}") ).
166
+ to be_within(0.000001).of( Time.new(2016,01,02, 23, 23, sec[0,i].to_f) )
167
+ end
168
+ end
169
+ it 'decodes timestamps with leap-second' do
170
+ expect( textdec_timestamp.decode('1998-12-31 23:59:60.1234') ).
171
+ to be_within(0.000001).of( Time.new(1998,12,31, 23, 59, 60.1234) )
172
+ end
173
+
174
+ def textdec_timestamptz_decode_should_fail(str)
175
+ expect(textdec_timestamptz.decode(str)).to eq(str)
176
+ end
177
+
178
+ it 'fails when the timestamp is an empty string' do
179
+ textdec_timestamptz_decode_should_fail('')
180
+ end
181
+ it 'fails when the timestamp contains values with less digits than expected' do
182
+ textdec_timestamptz_decode_should_fail('2016-0-02 23:23:59.123456+00:25:21')
183
+ textdec_timestamptz_decode_should_fail('2016-01-0 23:23:59.123456+00:25:21')
184
+ textdec_timestamptz_decode_should_fail('2016-01-02 2:23:59.123456+00:25:21')
185
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:2:59.123456+00:25:21')
186
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:5.123456+00:25:21')
187
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.+00:25:21')
188
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+0:25:21')
189
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:2:21')
190
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:25:2')
191
+ end
192
+ it 'fails when the timestamp contains values with more digits than expected' do
193
+ textdec_timestamptz_decode_should_fail('2016-011-02 23:23:59.123456+00:25:21')
194
+ textdec_timestamptz_decode_should_fail('2016-01-022 23:23:59.123456+00:25:21')
195
+ textdec_timestamptz_decode_should_fail('2016-01-02 233:23:59.123456+00:25:21')
196
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:233:59.123456+00:25:21')
197
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:599.123456+00:25:21')
198
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+000:25:21')
199
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:255:21')
200
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:25:211')
201
+ end
202
+ it 'fails when the timestamp contains values with invalid characters' do
203
+ str = '2013-01-02 23:23:59.123456+00:25:21'
204
+ str.length.times do |i|
205
+ textdec_timestamptz_decode_should_fail(str[0,i] + "x" + str[i+1..-1])
206
+ end
207
+ end
208
+ it 'fails when the timestamp contains leading characters' do
209
+ textdec_timestamptz_decode_should_fail(' 2016-01-02 23:23:59.123456')
210
+ end
211
+ it 'fails when the timestamp contains trailing characters' do
212
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456 ')
213
+ end
214
+ it 'fails when the timestamp contains non ASCII character' do
215
+ textdec_timestamptz_decode_should_fail('2016-01ª02 23:23:59.123456')
216
+ end
124
217
  end
125
218
 
126
219
  context 'identifier quotation' do
@@ -215,6 +308,27 @@ describe "PG::Type derivations" do
215
308
  expect( textenc_bytea.encode("\x00\x01\x02\x03\xef".b) ).to eq( "\\x00010203ef" )
216
309
  end
217
310
 
311
+ context 'timestamps' do
312
+ it 'encodes timestamps without timezone' do
313
+ expect( textenc_timestamp.encode(Time.new(2016,1,2, 23, 23, 59.123456, 3*60*60)) ).
314
+ to match( /^2016-01-02 23:23:59.12345\d+$/ )
315
+ expect( textenc_timestamp.encode(Time.new(2016,8,2, 23, 23, 59.123456, 3*60*60)) ).
316
+ to match( /^2016-08-02 23:23:59.12345\d+$/ )
317
+ end
318
+ it 'encodes timestamps with UTC timezone' do
319
+ expect( textenc_timestamputc.encode(Time.new(2016,1,2, 23, 23, 59.123456, 3*60*60)) ).
320
+ to match( /^2016-01-02 20:23:59.12345\d+$/ )
321
+ expect( textenc_timestamputc.encode(Time.new(2016,8,2, 23, 23, 59.123456, 3*60*60)) ).
322
+ to match( /^2016-08-02 20:23:59.12345\d+$/ )
323
+ end
324
+ it 'encodes timestamps with hour timezone' do
325
+ expect( textenc_timestamptz.encode(Time.new(2016,1,02, 23, 23, 59.123456, -4*60*60)) ).
326
+ to match( /^2016-01-02 23:23:59.12345\d+ \-04:00$/ )
327
+ expect( textenc_timestamptz.encode(Time.new(2016,8,02, 23, 23, 59.123456, 10*60*60)) ).
328
+ to match( /^2016-08-02 23:23:59.12345\d+ \+10:00$/ )
329
+ end
330
+ end
331
+
218
332
  context 'identifier quotation' do
219
333
  it 'should quote and escape identifier' do
220
334
  quoted_type = PG::TextEncoder::Identifier.new
@@ -303,6 +417,7 @@ describe "PG::Type derivations" do
303
417
  describe "Array types" do
304
418
  let!(:textenc_string_array) { PG::TextEncoder::Array.new elements_type: textenc_string }
305
419
  let!(:textdec_string_array) { PG::TextDecoder::Array.new elements_type: textdec_string }
420
+ let!(:textdec_string_array_raise) { PG::TextDecoder::Array.new elements_type: textdec_string, flags: PG::Coder:: FORMAT_ERROR_TO_RAISE }
306
421
  let!(:textenc_int_array) { PG::TextEncoder::Array.new elements_type: textenc_int, needs_quotation: false }
307
422
  let!(:textdec_int_array) { PG::TextDecoder::Array.new elements_type: textdec_int, needs_quotation: false }
308
423
  let!(:textenc_float_array) { PG::TextEncoder::Array.new elements_type: textenc_float, needs_quotation: false }
@@ -368,6 +483,57 @@ describe "PG::Type derivations" do
368
483
  it 'respects a different delimiter' do
369
484
  expect( textdec_string_array_with_delimiter.decode(%[{1;2;3}]) ).to eq( ['1','2','3'] )
370
485
  end
486
+
487
+ it 'ignores array dimensions' do
488
+ expect( textdec_string_array.decode(%[[2:4]={1,2,3}]) ).to eq( ['1','2','3'] )
489
+ expect( textdec_string_array.decode(%[[]={1,2,3}]) ).to eq( ['1','2','3'] )
490
+ expect( textdec_string_array.decode(%[ [-1:+2]= {4,3,2,1}]) ).to eq( ['4','3','2','1'] )
491
+ end
492
+
493
+ it 'ignores spaces after array' do
494
+ expect( textdec_string_array.decode(%[[2:4]={1,2,3} ]) ).to eq( ['1','2','3'] )
495
+ expect( textdec_string_array.decode(%[{1,2,3} ]) ).to eq( ['1','2','3'] )
496
+ end
497
+
498
+ describe "with malformed syntax are deprecated" do
499
+ it 'accepts broken array dimensions' do
500
+ expect( textdec_string_array.decode(%([2:4={1,2,3})) ).to eq([['1','2','3']])
501
+ expect( textdec_string_array.decode(%(2:4]={1,2,3})) ).to eq([['1','2','3']])
502
+ expect( textdec_string_array.decode(%(={1,2,3})) ).to eq([['1','2','3']])
503
+ expect( textdec_string_array.decode(%([x]={1,2,3})) ).to eq([['1','2','3']])
504
+ expect( textdec_string_array.decode(%([]{1,2,3})) ).to eq([['1','2','3']])
505
+ expect( textdec_string_array.decode(%(1,2,3)) ).to eq(['','2'])
506
+ end
507
+
508
+ it 'accepts malformed arrays' do
509
+ expect( textdec_string_array.decode(%({1,2,3)) ).to eq(['1','2'])
510
+ expect( textdec_string_array.decode(%({1,2,3}})) ).to eq(['1','2','3'])
511
+ expect( textdec_string_array.decode(%({1,2,3}x)) ).to eq(['1','2','3'])
512
+ expect( textdec_string_array.decode(%({{1,2},{2,3})) ).to eq([['1','2'],['2','3']])
513
+ expect( textdec_string_array.decode(%({{1,2},{2,3}}x)) ).to eq([['1','2'],['2','3']])
514
+ expect( textdec_string_array.decode(%({[1,2},{2,3}}})) ).to eq(['[1','2'])
515
+ end
516
+ end
517
+
518
+ describe "with malformed syntax are raised with pg-2.0+" do
519
+ it 'complains about broken array dimensions' do
520
+ expect{ textdec_string_array_raise.decode(%([2:4={1,2,3})) }.to raise_error(TypeError)
521
+ expect{ textdec_string_array_raise.decode(%(2:4]={1,2,3})) }.to raise_error(TypeError)
522
+ expect{ textdec_string_array_raise.decode(%(={1,2,3})) }.to raise_error(TypeError)
523
+ expect{ textdec_string_array_raise.decode(%([x]={1,2,3})) }.to raise_error(TypeError)
524
+ expect{ textdec_string_array_raise.decode(%([]{1,2,3})) }.to raise_error(TypeError)
525
+ expect{ textdec_string_array_raise.decode(%(1,2,3)) }.to raise_error(TypeError)
526
+ end
527
+
528
+ it 'complains about malformed array' do
529
+ expect{ textdec_string_array_raise.decode(%({1,2,3)) }.to raise_error(TypeError)
530
+ expect{ textdec_string_array_raise.decode(%({1,2,3}})) }.to raise_error(TypeError)
531
+ expect{ textdec_string_array_raise.decode(%({1,2,3}x)) }.to raise_error(TypeError)
532
+ expect{ textdec_string_array_raise.decode(%({{1,2},{2,3})) }.to raise_error(TypeError)
533
+ expect{ textdec_string_array_raise.decode(%({{1,2},{2,3}}x)) }.to raise_error(TypeError)
534
+ expect{ textdec_string_array_raise.decode(%({[1,2},{2,3}}})) }.to raise_error(TypeError)
535
+ end
536
+ end
371
537
  end
372
538
 
373
539
  context 'bytea' do
@@ -746,7 +912,7 @@ describe "PG::Type derivations" do
746
912
  end
747
913
 
748
914
  describe '#decode' do
749
- it "should decode different types of Ruby objects" do
915
+ it "should decode COPY text format to array of strings" do
750
916
  expect( decoder.decode("123\t \0#\t#\n#\r#\\ \t234\t#\x01#\002\n".gsub("#", "\\"))).to eq( ["123", " \0\t\n\r\\ ", "234", "\x01\x02"] )
751
917
  end
752
918
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative 'helpers'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0.pre20180730144600
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Granger
@@ -11,32 +11,26 @@ bindir: bin
11
11
  cert_chain:
12
12
  - |
13
13
  -----BEGIN CERTIFICATE-----
14
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- cZ+lPcg834G9DrqW2zhqUoLr1MTly4pqxYGb7XoDhoR7dd1kFE2a067+DzWC/ADt
24
- +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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
31
+ Lvw=
38
32
  -----END CERTIFICATE-----
39
- date: 2018-01-10 00:00:00.000000000 Z
33
+ date: 2018-07-30 00:00:00.000000000 Z
40
34
  dependencies:
41
35
  - !ruby/object:Gem::Dependency
42
36
  name: hoe-mercurial
@@ -218,6 +212,7 @@ extra_rdoc_files:
218
212
  - ext/pg_result.c
219
213
  - ext/pg_text_decoder.c
220
214
  - ext/pg_text_encoder.c
215
+ - ext/pg_tuple.c
221
216
  - ext/pg_type_map.c
222
217
  - ext/pg_type_map_all_strings.c
223
218
  - ext/pg_type_map_by_class.c
@@ -258,6 +253,7 @@ files:
258
253
  - ext/pg_result.c
259
254
  - ext/pg_text_decoder.c
260
255
  - ext/pg_text_encoder.c
256
+ - ext/pg_tuple.c
261
257
  - ext/pg_type_map.c
262
258
  - ext/pg_type_map_all_strings.c
263
259
  - ext/pg_type_map_by_class.c
@@ -311,9 +307,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
311
307
  version: 2.0.0
312
308
  required_rubygems_version: !ruby/object:Gem::Requirement
313
309
  requirements:
314
- - - ">="
310
+ - - ">"
315
311
  - !ruby/object:Gem::Version
316
- version: '0'
312
+ version: 1.3.1
317
313
  requirements: []
318
314
  rubyforge_project:
319
315
  rubygems_version: 2.7.3