pg 0.17.1-x64-mingw32 → 0.18.0.pre20141017160319-x64-mingw32

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 (50) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +1885 -169
  5. data/History.rdoc +6 -0
  6. data/Manifest.txt +25 -1
  7. data/README.rdoc +47 -0
  8. data/Rakefile +21 -12
  9. data/Rakefile.cross +39 -33
  10. data/ext/extconf.rb +27 -26
  11. data/ext/pg.c +73 -19
  12. data/ext/pg.h +194 -6
  13. data/ext/pg_binary_decoder.c +160 -0
  14. data/ext/pg_binary_encoder.c +160 -0
  15. data/ext/pg_coder.c +473 -0
  16. data/ext/pg_connection.c +872 -534
  17. data/ext/pg_copy_coder.c +557 -0
  18. data/ext/pg_result.c +266 -111
  19. data/ext/pg_text_decoder.c +424 -0
  20. data/ext/pg_text_encoder.c +631 -0
  21. data/ext/pg_type_map.c +113 -0
  22. data/ext/pg_type_map_all_strings.c +113 -0
  23. data/ext/pg_type_map_by_column.c +254 -0
  24. data/ext/pg_type_map_by_mri_type.c +266 -0
  25. data/ext/pg_type_map_by_oid.c +341 -0
  26. data/ext/util.c +149 -0
  27. data/ext/util.h +65 -0
  28. data/lib/2.0/pg_ext.so +0 -0
  29. data/lib/2.1/pg_ext.so +0 -0
  30. data/lib/pg.rb +11 -1
  31. data/lib/pg/basic_type_mapping.rb +377 -0
  32. data/lib/pg/coder.rb +74 -0
  33. data/lib/pg/connection.rb +43 -1
  34. data/lib/pg/result.rb +13 -3
  35. data/lib/pg/text_decoder.rb +42 -0
  36. data/lib/pg/text_encoder.rb +27 -0
  37. data/lib/pg/type_map_by_column.rb +15 -0
  38. data/lib/x64-mingw32/libpq.dll +0 -0
  39. data/spec/{lib/helpers.rb → helpers.rb} +95 -35
  40. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  41. data/spec/pg/connection_spec.rb +416 -214
  42. data/spec/pg/result_spec.rb +146 -116
  43. data/spec/pg/type_map_by_column_spec.rb +135 -0
  44. data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
  45. data/spec/pg/type_map_by_oid_spec.rb +133 -0
  46. data/spec/pg/type_map_spec.rb +39 -0
  47. data/spec/pg/type_spec.rb +649 -0
  48. data/spec/pg_spec.rb +10 -18
  49. metadata +129 -50
  50. metadata.gz.sig +0 -0
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByColumn do
10
+
11
+ let!(:textenc_int){ PG::TextEncoder::Integer.new name: 'INT4', oid: 23 }
12
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
13
+ let!(:textenc_float){ PG::TextEncoder::Float.new name: 'FLOAT4', oid: 700 }
14
+ let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT4', oid: 700 }
15
+ let!(:textenc_string){ PG::TextEncoder::String.new name: 'TEXT', oid: 25 }
16
+ let!(:textdec_string){ PG::TextDecoder::String.new name: 'TEXT', oid: 25 }
17
+ let!(:textdec_bytea){ PG::TextDecoder::Bytea.new name: 'BYTEA', oid: 17 }
18
+ let!(:binaryenc_bytea){ PG::BinaryEncoder::Bytea.new name: 'BYTEA', oid: 17, format: 1 }
19
+ let!(:binarydec_bytea){ PG::BinaryDecoder::Bytea.new name: 'BYTEA', oid: 17, format: 1 }
20
+ let!(:pass_through_type) do
21
+ type = Class.new(PG::SimpleDecoder) do
22
+ def decode(*v)
23
+ v
24
+ end
25
+ end.new
26
+ type.oid = 123456
27
+ type.format = 1
28
+ type.name = 'pass_through'
29
+ type
30
+ end
31
+
32
+ it "should retrieve it's conversions" do
33
+ cm = PG::TypeMapByColumn.new( [textdec_int, textenc_string, textdec_float, pass_through_type, nil] )
34
+ expect( cm.coders ).to eq( [
35
+ textdec_int,
36
+ textenc_string,
37
+ textdec_float,
38
+ pass_through_type,
39
+ nil
40
+ ] )
41
+ expect( cm.inspect ).to eq( "#<PG::TypeMapByColumn INT4:0 TEXT:0 FLOAT4:0 pass_through:1 nil>" )
42
+ end
43
+
44
+ it "should retrieve it's oids" do
45
+ cm = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
46
+ expect( cm.oids ).to eq( [23, 25, 700, 123456, nil] )
47
+ end
48
+
49
+
50
+ #
51
+ # Encoding Examples
52
+ #
53
+
54
+ it "should encode integer params" do
55
+ col_map = PG::TypeMapByColumn.new( [textenc_int]*3 )
56
+ res = @conn.exec_params( "SELECT $1, $2, $3", [ 0, nil, "-999" ], 0, col_map )
57
+ expect( res.values ).to eq( [
58
+ [ "0", nil, "-999" ],
59
+ ] )
60
+ end
61
+
62
+ it "should encode bytea params" do
63
+ data = "'\u001F\\"
64
+ col_map = PG::TypeMapByColumn.new( [binaryenc_bytea]*2 )
65
+ res = @conn.exec_params( "SELECT $1, $2", [ data, nil ], 0, col_map )
66
+ res.type_map = PG::TypeMapByColumn.new( [textdec_bytea]*2 )
67
+ expect( res.values ).to eq( [
68
+ [ data, nil ],
69
+ ] )
70
+ end
71
+
72
+
73
+ it "should allow hash form parameters for default encoder" do
74
+ col_map = PG::TypeMapByColumn.new( [nil, nil] )
75
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
76
+ hash_param_nil = { value: nil, type: 17, format: 1 }
77
+ res = @conn.exec_params( "SELECT $1, $2",
78
+ [ hash_param_bin, hash_param_nil ], 0, col_map )
79
+ expect( res.values ).to eq( [["\\x00ff", nil]] )
80
+ expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
81
+ end
82
+
83
+ it "should convert hash form parameters to string when using string encoders" do
84
+ col_map = PG::TypeMapByColumn.new( [textenc_string, textenc_string] )
85
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
86
+ hash_param_nil = { value: nil, type: 17, format: 1 }
87
+ res = @conn.exec_params( "SELECT $1::text, $2::text",
88
+ [ hash_param_bin, hash_param_nil ], 0, col_map )
89
+ expect( res.values ).to eq( [["{:value=>\"\\x00\\xFF\", :type=>17, :format=>1}", "{:value=>nil, :type=>17, :format=>1}"]] )
90
+ end
91
+
92
+ it "shouldn't allow param mappings with different number of fields" do
93
+ expect{
94
+ @conn.exec_params( "SELECT $1", [ 123 ], 0, PG::TypeMapByColumn.new([]) )
95
+ }.to raise_error(ArgumentError, /mapped columns/)
96
+ end
97
+
98
+ #
99
+ # Decoding Examples
100
+ #
101
+
102
+ class Exception_in_decode < PG::SimpleDecoder
103
+ def decode(res, tuple, field)
104
+ raise "no type decoder defined for tuple #{tuple} field #{field}"
105
+ end
106
+ end
107
+
108
+ it "should raise an error from decode method of type converter" do
109
+ res = @conn.exec( "SELECT now()" )
110
+ types = Array.new( res.nfields, Exception_in_decode.new )
111
+ res.type_map = PG::TypeMapByColumn.new( types )
112
+ expect{ res.values }.to raise_error(/no type decoder defined/)
113
+ end
114
+
115
+ it "should raise an error for invalid params" do
116
+ expect{ PG::TypeMapByColumn.new( :WrongType ) }.to raise_error(TypeError, /wrong argument type/)
117
+ expect{ PG::TypeMapByColumn.new( [123] ) }.to raise_error(ArgumentError, /invalid/)
118
+ end
119
+
120
+ it "shouldn't allow result mappings with different number of fields" do
121
+ res = @conn.exec( "SELECT 1" )
122
+ expect{ res.type_map = PG::TypeMapByColumn.new([]) }.to raise_error(ArgumentError, /mapped columns/)
123
+ end
124
+
125
+ #
126
+ # Decoding Examples text format
127
+ #
128
+
129
+ it "should allow mixed type conversions" do
130
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE, 3" )
131
+ res.type_map = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
132
+ expect( res.values ).to eq( [[1, 'a', 2.0, ['2013-06-30', 0, 3], '3' ]] )
133
+ end
134
+
135
+ end
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByMriType do
10
+
11
+ let!(:textenc_int){ PG::TextEncoder::Integer.new name: 'INT4', oid: 23 }
12
+ let!(:textenc_float){ PG::TextEncoder::Float.new name: 'FLOAT8', oid: 701 }
13
+ let!(:textenc_string){ PG::TextEncoder::String.new name: 'TEXT', oid: 25 }
14
+ let!(:binaryenc_int){ PG::BinaryEncoder::Int8.new name: 'INT8', oid: 20, format: 1 }
15
+ let!(:pass_through_type) do
16
+ type = Class.new(PG::SimpleEncoder) do
17
+ def encode(*v)
18
+ v.inspect
19
+ end
20
+ end.new
21
+ type.oid = 25
22
+ type.format = 0
23
+ type.name = 'pass_through'
24
+ type
25
+ end
26
+
27
+ let!(:tm) do
28
+ tm = PG::TypeMapByMriType.new
29
+ tm['T_FIXNUM'] = binaryenc_int
30
+ tm['T_FLOAT'] = textenc_float
31
+ tm['T_SYMBOL'] = pass_through_type
32
+ tm
33
+ end
34
+
35
+ let!(:derived_tm) do
36
+ tm = Class.new(PG::TypeMapByMriType) do
37
+ def array_type_map_for(value)
38
+ PG::TextEncoder::Array.new name: '_INT4', oid: 1007, elements_type: PG::TextEncoder::Integer.new
39
+ end
40
+ end.new
41
+ tm['T_FIXNUM'] = proc{|value| textenc_int }
42
+ tm['T_REGEXP'] = proc{|value| :invalid }
43
+ tm['T_ARRAY'] = :array_type_map_for
44
+ tm
45
+ end
46
+
47
+ it "should retrieve all conversions" do
48
+ expect( tm.coders ).to eq( {
49
+ "T_FIXNUM" => binaryenc_int,
50
+ "T_FLOAT" => textenc_float,
51
+ "T_SYMBOL" => pass_through_type,
52
+ "T_HASH" => nil,
53
+ "T_ARRAY" => nil,
54
+ "T_BIGNUM" => nil,
55
+ "T_CLASS" => nil,
56
+ "T_COMPLEX" => nil,
57
+ "T_DATA" => nil,
58
+ "T_FALSE" => nil,
59
+ "T_FILE" => nil,
60
+ "T_MODULE" => nil,
61
+ "T_OBJECT" => nil,
62
+ "T_RATIONAL" => nil,
63
+ "T_REGEXP" => nil,
64
+ "T_STRING" => nil,
65
+ "T_STRUCT" => nil,
66
+ "T_TRUE" => nil,
67
+ } )
68
+ end
69
+
70
+ it "should retrieve particular conversions" do
71
+ expect( tm['T_FIXNUM'] ).to eq(binaryenc_int)
72
+ expect( tm['T_FLOAT'] ).to eq(textenc_float)
73
+ expect( tm['T_BIGNUM'] ).to be_nil
74
+ expect( derived_tm['T_REGEXP'] ).to be_kind_of(Proc)
75
+ expect( derived_tm['T_ARRAY'] ).to eq(:array_type_map_for)
76
+ end
77
+
78
+ it "should allow deletion of coders" do
79
+ tm['T_FIXNUM'] = nil
80
+ expect( tm['T_FIXNUM'] ).to be_nil
81
+ end
82
+
83
+ it "should check MRI type key" do
84
+ expect{ tm['NO_TYPE'] }.to raise_error(ArgumentError)
85
+ expect{ tm[123] }.to raise_error(TypeError)
86
+ expect{ tm['NO_TYPE'] = textenc_float }.to raise_error(ArgumentError)
87
+ expect{ tm[123] = textenc_float }.to raise_error(TypeError)
88
+ end
89
+
90
+ #
91
+ # Decoding Examples
92
+ #
93
+
94
+ it "should raise an error when used for results" do
95
+ res = @conn.exec_params( "SELECT 1", [], 1 )
96
+ expect{ res.type_map = tm }.to raise_error(NotImplementedError, /not suitable to map result values/)
97
+ end
98
+
99
+ #
100
+ # Encoding Examples
101
+ #
102
+
103
+ it "should allow mixed type conversions" do
104
+ res = @conn.exec_params( "SELECT $1, $2, $3", [5, 1.23, :TestSymbol], 0, tm )
105
+ expect( res.values ).to eq([['5', '1.23', '[:TestSymbol]']])
106
+ expect( res.ftype(0) ).to eq(20)
107
+ end
108
+
109
+ it "should allow mixed type conversions with derived type map" do
110
+ res = @conn.exec_params( "SELECT $1, $2", [6, [7]], 0, derived_tm )
111
+ expect( res.values ).to eq([['6', '{7}']])
112
+ expect( res.ftype(0) ).to eq(23)
113
+ expect( res.ftype(1) ).to eq(1007)
114
+ end
115
+
116
+ it "should raise TypeError with derived type map" do
117
+ expect{
118
+ @conn.exec_params( "SELECT $1", [//], 0, derived_tm )
119
+ }.to raise_error(TypeError, /argument 1/)
120
+ end
121
+
122
+ end
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByOid do
10
+
11
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
12
+ let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT8', oid: 701 }
13
+ let!(:textdec_string){ PG::TextDecoder::String.new name: 'TEXT', oid: 25 }
14
+ let!(:textdec_bytea){ PG::TextDecoder::Bytea.new name: 'BYTEA', oid: 17 }
15
+ let!(:binarydec_float){ PG::BinaryDecoder::Float.new name: 'FLOAT8', oid: 701, format: 1 }
16
+ let!(:pass_through_type) do
17
+ type = Class.new(PG::SimpleDecoder) do
18
+ def decode(*v)
19
+ v
20
+ end
21
+ end.new
22
+ type.oid = 1082
23
+ type.format = 0
24
+ type.name = 'pass_through'
25
+ type
26
+ end
27
+
28
+ let!(:tm) do
29
+ tm = PG::TypeMapByOid.new
30
+ tm.add_coder textdec_int
31
+ tm.add_coder textdec_float
32
+ tm.add_coder binarydec_float
33
+ tm.add_coder pass_through_type
34
+ tm
35
+ end
36
+
37
+ it "should retrieve it's conversions" do
38
+ expect( tm.coders ).to eq( [
39
+ textdec_int,
40
+ textdec_float,
41
+ pass_through_type,
42
+ binarydec_float,
43
+ ] )
44
+ end
45
+
46
+ it "should allow deletion of coders" do
47
+ expect( tm.rm_coder 0, 701 ).to eq(textdec_float)
48
+ expect( tm.rm_coder 0, 701 ).to eq(nil)
49
+ expect( tm.rm_coder 1, 701 ).to eq(binarydec_float)
50
+ expect( tm.coders ).to eq( [
51
+ textdec_int,
52
+ pass_through_type,
53
+ ] )
54
+ end
55
+
56
+ it "should check format when deleting coders" do
57
+ expect{ tm.rm_coder 2, 123 }.to raise_error(ArgumentError)
58
+ expect{ tm.rm_coder -1, 123 }.to raise_error(ArgumentError)
59
+ end
60
+
61
+ it "should check format when adding coders" do
62
+ textdec_int.format = 2
63
+ expect{ tm.add_coder textdec_int }.to raise_error(ArgumentError)
64
+ textdec_int.format = -1
65
+ expect{ tm.add_coder textdec_int }.to raise_error(ArgumentError)
66
+ end
67
+
68
+ it "should check coder type when adding coders" do
69
+ expect{ tm.add_coder :dummy }.to raise_error(ArgumentError)
70
+ end
71
+
72
+ it "should allow reading and writing max_rows_for_online_lookup" do
73
+ expect( tm.max_rows_for_online_lookup ).to eq(10)
74
+ tm.max_rows_for_online_lookup = 5
75
+ expect( tm.max_rows_for_online_lookup ).to eq(5)
76
+ end
77
+
78
+ it "should allow building new TypeMapByColumn for a given result" do
79
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
80
+ tm2 = tm.fit_to_result(res, true)
81
+ expect( tm2 ).to eq( tm )
82
+
83
+ tm2 = tm.fit_to_result(res, false)
84
+ expect( tm2 ).to be_a_kind_of(PG::TypeMapByColumn)
85
+ expect( tm2.coders ).to eq( [textdec_int, nil, textdec_float, pass_through_type] )
86
+ end
87
+
88
+ #
89
+ # Decoding Examples text format
90
+ #
91
+
92
+ it "should allow mixed type conversions in text format" do
93
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
94
+ res.type_map = tm
95
+ expect( res.values ).to eq( [[1, 'a', 2.0, ['2013-06-30', 0, 3] ]] )
96
+ end
97
+
98
+ it "should build a TypeMapByColumn when assigned and the number of rows is high enough" do
99
+ res = @conn.exec( "SELECT generate_series(1,20), 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
100
+ expect( tm.fit_to_result(res) ).to be_a_kind_of(PG::TypeMapByColumn)
101
+ res.type_map = tm
102
+ expect( res.type_map ).to be_kind_of( PG::TypeMapByColumn )
103
+ expect( res.type_map.coders ).to eq( [textdec_int, nil, textdec_float, pass_through_type] )
104
+ end
105
+
106
+ it "should use TypeMapByOid for online lookup and the number of rows is low enough" do
107
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
108
+ expect( tm.fit_to_result(res) ).to eq( tm )
109
+ res.type_map = tm
110
+ expect( res.type_map ).to be_kind_of( PG::TypeMapByOid )
111
+ end
112
+
113
+ #
114
+ # Decoding Examples binary format
115
+ #
116
+
117
+ it "should allow mixed type conversions in binary format" do
118
+ res = @conn.exec_params( "SELECT 1, 2.0::FLOAT", [], 1 )
119
+ res.type_map = tm
120
+ expect( res.values ).to eq( [["\x00\x00\x00\x01", 2.0 ]] )
121
+ end
122
+
123
+ #
124
+ # Encoding Examples
125
+ #
126
+
127
+ it "should raise an error used for query params" do
128
+ expect{
129
+ @conn.exec_params( "SELECT $1", [5], 0, tm )
130
+ }.to raise_error(NotImplementedError, /not suitable to map query params/)
131
+ end
132
+
133
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMap do
10
+ let!(:tm){ PG::TypeMap.new }
11
+
12
+ it "should respond to fit_to_query" do
13
+ expect{ tm.fit_to_query( [123] ) }.to raise_error(NotImplementedError, /not suitable to map query params/)
14
+ end
15
+
16
+ it "should respond to fit_to_result" do
17
+ res = @conn.exec( "SELECT 1" )
18
+ expect{ tm.fit_to_result( res ) }.to raise_error(NotImplementedError, /not suitable to map result values/)
19
+ end
20
+
21
+ it "should check params type" do
22
+ expect{ tm.fit_to_query( :invalid ) }.to raise_error(TypeError, /expected Array/)
23
+ end
24
+
25
+ it "should check result class" do
26
+ expect{ tm.fit_to_result( :invalid ) }.to raise_error(TypeError, /expected kind of PG::Result/)
27
+ end
28
+
29
+ it "should raise an error when used for param type casts" do
30
+ expect{
31
+ @conn.exec_params( "SELECT $1", [5], 0, tm )
32
+ }.to raise_error(NotImplementedError, /not suitable to map query params/)
33
+ end
34
+
35
+ it "should raise an error when used for result type casts" do
36
+ res = @conn.exec( "SELECT 1" )
37
+ expect{ res.map_types!(tm) }.to raise_error(NotImplementedError, /not suitable to map result values/)
38
+ end
39
+ end
@@ -0,0 +1,649 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require 'pg'
5
+
6
+
7
+ describe "PG::Type derivations" do
8
+ let!(:textenc_int) { PG::TextEncoder::Integer.new name: 'Integer', oid: 23 }
9
+ let!(:textdec_int) { PG::TextDecoder::Integer.new name: 'Integer', oid: 23 }
10
+ let!(:textenc_boolean) { PG::TextEncoder::Boolean.new }
11
+ let!(:textdec_boolean) { PG::TextDecoder::Boolean.new }
12
+ let!(:textenc_float) { PG::TextEncoder::Float.new }
13
+ let!(:textdec_float) { PG::TextDecoder::Float.new }
14
+ let!(:textenc_string) { PG::TextEncoder::String.new }
15
+ let!(:textdec_string) { PG::TextDecoder::String.new }
16
+ let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new }
17
+ let!(:textdec_timestamp) { PG::TextDecoder::TimestampWithoutTimeZone.new }
18
+ let!(:textdec_bytea) { PG::TextDecoder::Bytea.new }
19
+ let!(:binaryenc_int2) { PG::BinaryEncoder::Int2.new }
20
+ let!(:binaryenc_int4) { PG::BinaryEncoder::Int4.new }
21
+ let!(:binaryenc_int8) { PG::BinaryEncoder::Int8.new }
22
+ let!(:binarydec_integer) { PG::BinaryDecoder::Integer.new }
23
+
24
+ let!(:intenc_incrementer) do
25
+ Class.new(PG::SimpleEncoder) do
26
+ def encode(value)
27
+ (value.to_i + 1).to_s + " "
28
+ end
29
+ end.new
30
+ end
31
+ let!(:intdec_incrementer) do
32
+ Class.new(PG::SimpleDecoder) do
33
+ def decode(string, tuple=nil, field=nil)
34
+ string.to_i+1
35
+ end
36
+ end.new
37
+ end
38
+
39
+ let!(:intenc_incrementer_with_int_result) do
40
+ Class.new(PG::SimpleEncoder) do
41
+ def encode(value)
42
+ value.to_i+1
43
+ end
44
+ end.new
45
+ end
46
+
47
+ it "shouldn't be possible to build a PG::Type directly" do
48
+ expect{ PG::Coder.new }.to raise_error(TypeError, /cannot/)
49
+ end
50
+
51
+ describe PG::SimpleCoder do
52
+ describe '#decode' do
53
+ it "should offer decode method with tuple/field" do
54
+ res = textdec_int.decode("123", 1, 1)
55
+ expect( res ).to eq( 123 )
56
+ end
57
+
58
+ it "should offer decode method without tuple/field" do
59
+ res = textdec_int.decode("234")
60
+ expect( res ).to eq( 234 )
61
+ end
62
+
63
+ it "should decode with ruby decoder" do
64
+ expect( intdec_incrementer.decode("3") ).to eq( 4 )
65
+ end
66
+
67
+ it "should decode integers of different lengths form text format" do
68
+ 30.times do |zeros|
69
+ expect( textdec_int.decode("1" + "0"*zeros) ).to eq( 10 ** zeros )
70
+ expect( textdec_int.decode(zeros==0 ? "0" : "9"*zeros) ).to eq( 10 ** zeros - 1 )
71
+ expect( textdec_int.decode("-1" + "0"*zeros) ).to eq( -10 ** zeros )
72
+ expect( textdec_int.decode(zeros==0 ? "0" : "-" + "9"*zeros) ).to eq( -10 ** zeros + 1 )
73
+ end
74
+ 66.times do |bits|
75
+ expect( textdec_int.decode((2 ** bits).to_s) ).to eq( 2 ** bits )
76
+ expect( textdec_int.decode((2 ** bits - 1).to_s) ).to eq( 2 ** bits - 1 )
77
+ expect( textdec_int.decode((-2 ** bits).to_s) ).to eq( -2 ** bits )
78
+ expect( textdec_int.decode((-2 ** bits + 1).to_s) ).to eq( -2 ** bits + 1 )
79
+ end
80
+ end
81
+
82
+ it 'decodes bytea to a binary string' do
83
+ expect( textdec_bytea.decode("\\x00010203EF") ).to eq( "\x00\x01\x02\x03\xef".b )
84
+ expect( textdec_bytea.decode("\\377\\000") ).to eq( "\xff\0".b )
85
+ end
86
+
87
+ it "should raise when decode method is called with wrong args" do
88
+ expect{ textdec_int.decode() }.to raise_error(ArgumentError)
89
+ expect{ textdec_int.decode("123", 2, 3, 4) }.to raise_error(ArgumentError)
90
+ expect{ textdec_int.decode(2, 3, 4) }.to raise_error(TypeError)
91
+ expect( intdec_incrementer.decode(2, 3, 4) ).to eq( 3 )
92
+ end
93
+
94
+ it "should pass through nil values" do
95
+ expect( textdec_string.encode( nil )).to be_nil
96
+ expect( textdec_int.encode( nil )).to be_nil
97
+ end
98
+ end
99
+
100
+ describe '#encode' do
101
+ it "should offer encode method for text type" do
102
+ res = textenc_int.encode(123)
103
+ expect( res ).to eq( "123" )
104
+ end
105
+
106
+ it "should offer encode method for binary type" do
107
+ res = binaryenc_int8.encode(123)
108
+ expect( res ).to eq( [123].pack("q>") )
109
+ end
110
+
111
+ it "should encode integers from string to binary format" do
112
+ expect( binaryenc_int2.encode(" -123 ") ).to eq( [-123].pack("s>") )
113
+ expect( binaryenc_int4.encode(" -123 ") ).to eq( [-123].pack("l>") )
114
+ expect( binaryenc_int8.encode(" -123 ") ).to eq( [-123].pack("q>") )
115
+ expect( binaryenc_int2.encode(" 123-xyz ") ).to eq( [123].pack("s>") )
116
+ expect( binaryenc_int4.encode(" 123-xyz ") ).to eq( [123].pack("l>") )
117
+ expect( binaryenc_int8.encode(" 123-xyz ") ).to eq( [123].pack("q>") )
118
+ end
119
+
120
+ it "should encode integers of different lengths to text format" do
121
+ 30.times do |zeros|
122
+ expect( textenc_int.encode(10 ** zeros) ).to eq( "1" + "0"*zeros )
123
+ expect( textenc_int.encode(10 ** zeros - 1) ).to eq( zeros==0 ? "0" : "9"*zeros )
124
+ expect( textenc_int.encode(-10 ** zeros) ).to eq( "-1" + "0"*zeros )
125
+ expect( textenc_int.encode(-10 ** zeros + 1) ).to eq( zeros==0 ? "0" : "-" + "9"*zeros )
126
+ end
127
+ 66.times do |bits|
128
+ expect( textenc_int.encode(2 ** bits) ).to eq( (2 ** bits).to_s )
129
+ expect( textenc_int.encode(2 ** bits - 1) ).to eq( (2 ** bits - 1).to_s )
130
+ expect( textenc_int.encode(-2 ** bits) ).to eq( (-2 ** bits).to_s )
131
+ expect( textenc_int.encode(-2 ** bits + 1) ).to eq( (-2 ** bits + 1).to_s )
132
+ end
133
+ end
134
+
135
+ it "should encode integers from string to text format" do
136
+ expect( textenc_int.encode(" -123 ") ).to eq( "-123" )
137
+ expect( textenc_int.encode(" 123-xyz ") ).to eq( "123" )
138
+ end
139
+
140
+ it "should encode false and 0 to SQL FALSE value" do
141
+ [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].each do |value|
142
+ expect( textenc_boolean.encode(value) ).to eq( "f" )
143
+ end
144
+ end
145
+
146
+ it "should encode true, 1 to SQL TRUE value" do
147
+ [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].each do |value|
148
+ expect( textenc_boolean.encode(value) ).to eq( "t" )
149
+ end
150
+ end
151
+
152
+ it "should encode special floats equally to Float#to_s" do
153
+ expect( textenc_float.encode(Float::INFINITY) ).to eq( Float::INFINITY.to_s )
154
+ expect( textenc_float.encode(-Float::INFINITY) ).to eq( (-Float::INFINITY).to_s )
155
+ expect( textenc_float.encode(-Float::NAN) ).to eq( Float::NAN.to_s )
156
+ end
157
+
158
+ it "should encode with ruby encoder" do
159
+ expect( intenc_incrementer.encode(3) ).to eq( "4 " )
160
+ end
161
+
162
+ it "should return when ruby encoder returns non string values" do
163
+ expect( intenc_incrementer_with_int_result.encode(3) ).to eq( 4 )
164
+ end
165
+
166
+ it "should pass through nil values" do
167
+ expect( textenc_string.encode( nil )).to be_nil
168
+ expect( textenc_int.encode( nil )).to be_nil
169
+ end
170
+ end
171
+
172
+ it "should be possible to marshal encoders" do
173
+ mt = Marshal.dump(textenc_int)
174
+ lt = Marshal.load(mt)
175
+ expect( lt.to_h ).to eq( textenc_int.to_h )
176
+ end
177
+
178
+ it "should be possible to marshal decoders" do
179
+ mt = Marshal.dump(textdec_int)
180
+ lt = Marshal.load(mt)
181
+ expect( lt.to_h ).to eq( textdec_int.to_h )
182
+ end
183
+
184
+ it "should respond to to_h" do
185
+ expect( textenc_int.to_h ).to eq( {
186
+ name: 'Integer', oid: 23, format: 0
187
+ } )
188
+ end
189
+
190
+ it "should have reasonable default values" do
191
+ t = PG::TextEncoder::String.new
192
+ expect( t.format ).to eq( 0 )
193
+ expect( t.oid ).to eq( 0 )
194
+ expect( t.name ).to be_nil
195
+ end
196
+ end
197
+
198
+ describe PG::CompositeCoder do
199
+ describe "Array types" do
200
+ let!(:textenc_string_array) { PG::TextEncoder::Array.new elements_type: textenc_string }
201
+ let!(:textdec_string_array) { PG::TextDecoder::Array.new elements_type: textdec_string }
202
+ let!(:textenc_int_array) { PG::TextEncoder::Array.new elements_type: textenc_int, needs_quotation: false }
203
+ let!(:textdec_int_array) { PG::TextDecoder::Array.new elements_type: textdec_int, needs_quotation: false }
204
+ let!(:textenc_float_array) { PG::TextEncoder::Array.new elements_type: textenc_float, needs_quotation: false }
205
+ let!(:textdec_float_array) { PG::TextDecoder::Array.new elements_type: textdec_float, needs_quotation: false }
206
+ let!(:textenc_timestamp_array) { PG::TextEncoder::Array.new elements_type: textenc_timestamp, needs_quotation: false }
207
+ let!(:textdec_timestamp_array) { PG::TextDecoder::Array.new elements_type: textdec_timestamp, needs_quotation: false }
208
+ let!(:textenc_string_array_with_delimiter) { PG::TextEncoder::Array.new elements_type: textenc_string, delimiter: ';' }
209
+ let!(:textdec_string_array_with_delimiter) { PG::TextDecoder::Array.new elements_type: textdec_string, delimiter: ';' }
210
+ let!(:textdec_bytea_array) { PG::TextDecoder::Array.new elements_type: textdec_bytea }
211
+
212
+ #
213
+ # Array parser specs are thankfully borrowed from here:
214
+ # https://github.com/dockyard/pg_array_parser
215
+ #
216
+ describe '#decode' do
217
+ context 'one dimensional arrays' do
218
+ context 'empty' do
219
+ it 'returns an empty array' do
220
+ expect( textdec_string_array.decode(%[{}]) ).to eq( [] )
221
+ end
222
+ end
223
+
224
+ context 'no strings' do
225
+ it 'returns an array of strings' do
226
+ expect( textdec_string_array.decode(%[{1,2,3}]) ).to eq( ['1','2','3'] )
227
+ end
228
+ end
229
+
230
+ context 'NULL values' do
231
+ it 'returns an array of strings, with nils replacing NULL characters' do
232
+ expect( textdec_string_array.decode(%[{1,NULL,NULL}]) ).to eq( ['1',nil,nil] )
233
+ end
234
+ end
235
+
236
+ context 'quoted NULL' do
237
+ it 'returns an array with the word NULL' do
238
+ expect( textdec_string_array.decode(%[{1,"NULL",3}]) ).to eq( ['1','NULL','3'] )
239
+ end
240
+ end
241
+
242
+ context 'strings' do
243
+ it 'returns an array of strings when containing commas in a quoted string' do
244
+ expect( textdec_string_array.decode(%[{1,"2,3",4}]) ).to eq( ['1','2,3','4'] )
245
+ end
246
+
247
+ it 'returns an array of strings when containing an escaped quote' do
248
+ expect( textdec_string_array.decode(%[{1,"2\\",3",4}]) ).to eq( ['1','2",3','4'] )
249
+ end
250
+
251
+ it 'returns an array of strings when containing an escaped backslash' do
252
+ expect( textdec_string_array.decode(%[{1,"2\\\\",3,4}]) ).to eq( ['1','2\\','3','4'] )
253
+ expect( textdec_string_array.decode(%[{1,"2\\\\\\",3",4}]) ).to eq( ['1','2\\",3','4'] )
254
+ end
255
+
256
+ it 'returns an array containing empty strings' do
257
+ expect( textdec_string_array.decode(%[{1,"",3,""}]) ).to eq( ['1', '', '3', ''] )
258
+ end
259
+
260
+ it 'returns an array containing unicode strings' do
261
+ expect( textdec_string_array.decode(%[{"Paragraph 399(b)(i) – “valid leave” – meaning"}]) ).to eq(['Paragraph 399(b)(i) – “valid leave” – meaning'])
262
+ end
263
+
264
+ it 'respects a different delimiter' do
265
+ expect( textdec_string_array_with_delimiter.decode(%[{1;2;3}]) ).to eq( ['1','2','3'] )
266
+ end
267
+ end
268
+
269
+ context 'bytea' do
270
+ it 'returns an array of binary strings' do
271
+ expect( textdec_bytea_array.decode(%[{"\\\\x00010203EF","2,3",\\377}]) ).to eq( ["\x00\x01\x02\x03\xef".b,"2,3".b,"\xff".b] )
272
+ end
273
+ end
274
+
275
+ end
276
+
277
+ context 'two dimensional arrays' do
278
+ context 'empty' do
279
+ it 'returns an empty array' do
280
+ expect( textdec_string_array.decode(%[{{}}]) ).to eq( [[]] )
281
+ expect( textdec_string_array.decode(%[{{},{}}]) ).to eq( [[],[]] )
282
+ end
283
+ end
284
+ context 'no strings' do
285
+ it 'returns an array of strings with a sub array' do
286
+ expect( textdec_string_array.decode(%[{1,{2,3},4}]) ).to eq( ['1',['2','3'],'4'] )
287
+ end
288
+ end
289
+ context 'strings' do
290
+ it 'returns an array of strings with a sub array' do
291
+ expect( textdec_string_array.decode(%[{1,{"2,3"},4}]) ).to eq( ['1',['2,3'],'4'] )
292
+ end
293
+ it 'returns an array of strings with a sub array and a quoted }' do
294
+ expect( textdec_string_array.decode(%[{1,{"2,}3",NULL},4}]) ).to eq( ['1',['2,}3',nil],'4'] )
295
+ end
296
+ it 'returns an array of strings with a sub array and a quoted {' do
297
+ expect( textdec_string_array.decode(%[{1,{"2,{3"},4}]) ).to eq( ['1',['2,{3'],'4'] )
298
+ end
299
+ it 'returns an array of strings with a sub array and a quoted { and escaped quote' do
300
+ expect( textdec_string_array.decode(%[{1,{"2\\",{3"},4}]) ).to eq( ['1',['2",{3'],'4'] )
301
+ end
302
+ it 'returns an array of strings with a sub array with empty strings' do
303
+ expect( textdec_string_array.decode(%[{1,{""},4,{""}}]) ).to eq( ['1',[''],'4',['']] )
304
+ end
305
+ end
306
+ context 'timestamps' do
307
+ it 'decodes an array of timestamps with sub arrays' do
308
+ expect( textdec_timestamp_array.decode('{2014-12-31 00:00:00,{NULL,2016-01-02 23:23:59.0000000}}') ).
309
+ to eq( [Time.new(2014,12,31),[nil, Time.new(2016,01,02, 23, 23, 59)]] )
310
+ end
311
+ end
312
+ end
313
+ context 'three dimensional arrays' do
314
+ context 'empty' do
315
+ it 'returns an empty array' do
316
+ expect( textdec_string_array.decode(%[{{{}}}]) ).to eq( [[[]]] )
317
+ expect( textdec_string_array.decode(%[{{{},{}},{{},{}}}]) ).to eq( [[[],[]],[[],[]]] )
318
+ end
319
+ end
320
+ it 'returns an array of strings with sub arrays' do
321
+ expect( textdec_string_array.decode(%[{1,{2,{3,4}},{NULL,6},7}]) ).to eq( ['1',['2',['3','4']],[nil,'6'],'7'] )
322
+ end
323
+ end
324
+
325
+ it 'should decode array of types with decoder in ruby space' do
326
+ array_type = PG::TextDecoder::Array.new elements_type: intdec_incrementer
327
+ expect( array_type.decode(%[{3,4}]) ).to eq( [4,5] )
328
+ end
329
+
330
+ it 'should decode array of nil types' do
331
+ array_type = PG::TextDecoder::Array.new elements_type: nil
332
+ expect( array_type.decode(%[{3,4}]) ).to eq( ['3','4'] )
333
+ end
334
+
335
+ context 'identifier quotation' do
336
+ it 'should build an array out of an quoted identifier string' do
337
+ quoted_type = PG::TextDecoder::Identifier.new elements_type: textdec_string
338
+ expect( quoted_type.decode(%["A.".".B"]) ).to eq( ["A.", ".B"] )
339
+ expect( quoted_type.decode(%["'A"".""B'"]) ).to eq( ['\'A"."B\''] )
340
+ end
341
+
342
+ it 'should split unquoted identifier string' do
343
+ quoted_type = PG::TextDecoder::Identifier.new elements_type: textdec_string
344
+ expect( quoted_type.decode(%[a.b]) ).to eq( ['a','b'] )
345
+ expect( quoted_type.decode(%[a]) ).to eq( ['a'] )
346
+ end
347
+ end
348
+ end
349
+
350
+ describe '#encode' do
351
+ context 'three dimensional arrays' do
352
+ it 'encodes an array of strings and numbers with sub arrays' do
353
+ expect( textenc_string_array.encode(['1',['2',['3','4']],[nil,6],7.8]) ).to eq( %[{1,{2,{3,4}},{NULL,6},7.8}] )
354
+ end
355
+ it 'encodes an array of strings with quotes' do
356
+ expect( textenc_string_array.encode(['',[' ',['{','}','\\',',','"','\t']]]) ).to eq( %[{"",{" ",{"{","}","\\\\",",","\\"","\\\\t"}}}] )
357
+ end
358
+ it 'encodes an array of int8 with sub arrays' do
359
+ expect( textenc_int_array.encode([1,[2,[3,4]],[nil,6],7]) ).to eq( %[{1,{2,{3,4}},{NULL,6},7}] )
360
+ end
361
+ it 'encodes an array of int8 with strings' do
362
+ expect( textenc_int_array.encode(['1',['2'],'3']) ).to eq( %[{1,{2},3}] )
363
+ end
364
+ it 'encodes an array of float8 with sub arrays' do
365
+ expect( textenc_float_array.encode([1000.11,[-0.00221,[3.31,-441]],[nil,6.61],-7.71]) ).to match(Regexp.new(%[^{1.0001*E+*03,{-2.2*E-*03,{3.3*E+*00,-4.4*E+*02}},{NULL,6.6*E+*00},-7.7*E+*00}$].gsub(/([\.\+\{\}\,])/, "\\\\\\1").gsub(/\*/, "\\d*")))
366
+ end
367
+ end
368
+ context 'two dimensional arrays' do
369
+ it 'encodes an array of timestamps with sub arrays' do
370
+ expect( textenc_timestamp_array.encode([Time.new(2014,12,31),[nil, Time.new(2016,01,02, 23, 23, 59.99)]]) ).
371
+ to eq( %[{2014-12-31 00:00:00.000000000,{NULL,2016-01-02 23:23:59.990000000}}] )
372
+ end
373
+ end
374
+ context 'one dimensional array' do
375
+ it 'can encode empty arrays' do
376
+ expect( textenc_int_array.encode([]) ).to eq( '{}' )
377
+ expect( textenc_string_array.encode([]) ).to eq( '{}' )
378
+ end
379
+ it 'encodes an array of NULL strings w/wo quotes' do
380
+ expect( textenc_string_array.encode(['NUL', 'NULL', 'NULLL', 'nul', 'null', 'nulll']) ).to eq( %[{NUL,"NULL",NULLL,nul,"null",nulll}] )
381
+ end
382
+ it 'respects a different delimiter' do
383
+ expect( textenc_string_array_with_delimiter.encode(['a','b,','c']) ).to eq( '{a;b,;c}' )
384
+ end
385
+ end
386
+
387
+ context 'array of types with encoder in ruby space' do
388
+ it 'encodes with quotation' do
389
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: true
390
+ expect( array_type.encode([3,4]) ).to eq( %[{"4 ","5 "}] )
391
+ end
392
+
393
+ it 'encodes without quotation' do
394
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: false
395
+ expect( array_type.encode([3,4]) ).to eq( %[{4 ,5 }] )
396
+ end
397
+
398
+ it "should raise when ruby encoder returns non string values" do
399
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_int_result, needs_quotation: false
400
+ expect{ array_type.encode([3,4]) }.to raise_error(TypeError)
401
+ end
402
+ end
403
+
404
+ it "should pass through non Array inputs" do
405
+ expect( textenc_float_array.encode("text") ).to eq( "text" )
406
+ expect( textenc_float_array.encode(1234) ).to eq( "1234" )
407
+ end
408
+
409
+ context 'identifier quotation' do
410
+ it 'should quote and escape identifier' do
411
+ quoted_type = PG::TextEncoder::Identifier.new elements_type: textenc_string
412
+ expect( quoted_type.encode(['schema','table','col']) ).to eq( %["schema"."table"."col"] )
413
+ expect( quoted_type.encode(['A.','.B']) ).to eq( %["A.".".B"] )
414
+ expect( quoted_type.encode(%['A"."B']) ).to eq( %["'A"".""B'"] )
415
+ end
416
+
417
+ it 'shouldn\'t quote or escape identifier if requested to not do' do
418
+ quoted_type = PG::TextEncoder::Identifier.new elements_type: textenc_string,
419
+ needs_quotation: false
420
+ expect( quoted_type.encode(['a','b']) ).to eq( %[a.b] )
421
+ expect( quoted_type.encode(%[a.b]) ).to eq( %[a.b] )
422
+ end
423
+ end
424
+
425
+ context 'literal quotation' do
426
+ it 'should quote and escape literals' do
427
+ quoted_type = PG::TextEncoder::QuotedLiteral.new elements_type: textenc_string_array
428
+ expect( quoted_type.encode(["'A\",","\\B'"]) ).to eq( %['{"''A\\",","\\\\B''"}'] )
429
+ end
430
+ end
431
+ end
432
+
433
+ it "should be possible to marshal encoders" do
434
+ mt = Marshal.dump(textenc_int_array)
435
+ lt = Marshal.load(mt)
436
+ expect( lt.to_h ).to eq( textenc_int_array.to_h )
437
+ end
438
+
439
+ it "should be possible to marshal encoders" do
440
+ mt = Marshal.dump(textdec_int_array)
441
+ lt = Marshal.load(mt)
442
+ expect( lt.to_h ).to eq( textdec_int_array.to_h )
443
+ end
444
+
445
+ it "should respond to to_h" do
446
+ expect( textenc_int_array.to_h ).to eq( {
447
+ name: nil, oid: 0, format: 0,
448
+ elements_type: textenc_int, needs_quotation: false, delimiter: ','
449
+ } )
450
+ end
451
+
452
+ it "shouldn't accept invalid elements_types" do
453
+ expect{ PG::TextEncoder::Array.new elements_type: false }.to raise_error(TypeError)
454
+ end
455
+
456
+ it "should have reasonable default values" do
457
+ t = PG::TextEncoder::Array.new
458
+ expect( t.format ).to eq( 0 )
459
+ expect( t.oid ).to eq( 0 )
460
+ expect( t.name ).to be_nil
461
+ expect( t.needs_quotation? ).to eq( true )
462
+ expect( t.delimiter ).to eq( ',' )
463
+ expect( t.elements_type ).to be_nil
464
+ end
465
+ end
466
+
467
+ it "should encode Strings as base64 in TextEncoder" do
468
+ e = PG::TextEncoder::ToBase64.new
469
+ expect( e.encode("") ).to eq("")
470
+ expect( e.encode("x") ).to eq("eA==")
471
+ expect( e.encode("xx") ).to eq("eHg=")
472
+ expect( e.encode("xxx") ).to eq("eHh4")
473
+ expect( e.encode("xxxx") ).to eq("eHh4eA==")
474
+ expect( e.encode("xxxxx") ).to eq("eHh4eHg=")
475
+ expect( e.encode("\0\n\t") ).to eq("AAoJ")
476
+ end
477
+
478
+ it "should encode Strings as base64 in BinaryDecoder" do
479
+ e = PG::BinaryDecoder::ToBase64.new
480
+ expect( e.decode("x") ).to eq("eA==")
481
+ end
482
+
483
+ it "should encode Integers as base64" do
484
+ # Not really useful, but ensures that two-pass element and composite element encoders work.
485
+ e = PG::TextEncoder::ToBase64.new( elements_type: PG::TextEncoder::Array.new( elements_type: PG::TextEncoder::Integer.new, needs_quotation: false ))
486
+ expect( e.encode([1]) ).to eq(["{1}"].pack("m").chomp)
487
+ expect( e.encode([12]) ).to eq(["{12}"].pack("m").chomp)
488
+ expect( e.encode([123]) ).to eq(["{123}"].pack("m").chomp)
489
+ expect( e.encode([1234]) ).to eq(["{1234}"].pack("m").chomp)
490
+ expect( e.encode([12345]) ).to eq(["{12345}"].pack("m").chomp)
491
+ expect( e.encode([123456]) ).to eq(["{123456}"].pack("m").chomp)
492
+ expect( e.encode([1234567]) ).to eq(["{1234567}"].pack("m").chomp)
493
+ end
494
+
495
+ it "should decode base64 to Strings in TextDecoder" do
496
+ e = PG::TextDecoder::FromBase64.new
497
+ expect( e.decode("") ).to eq("")
498
+ expect( e.decode("eA==") ).to eq("x")
499
+ expect( e.decode("eHg=") ).to eq("xx")
500
+ expect( e.decode("eHh4") ).to eq("xxx")
501
+ expect( e.decode("eHh4eA==") ).to eq("xxxx")
502
+ expect( e.decode("eHh4eHg=") ).to eq("xxxxx")
503
+ expect( e.decode("AAoJ") ).to eq("\0\n\t")
504
+ end
505
+
506
+ it "should decode base64 in BinaryEncoder" do
507
+ e = PG::BinaryEncoder::FromBase64.new
508
+ expect( e.encode("eA==") ).to eq("x")
509
+
510
+ e = PG::BinaryEncoder::FromBase64.new( elements_type: PG::TextEncoder::Integer.new )
511
+ expect( e.encode(124) ).to eq("124=".unpack("m")[0])
512
+ end
513
+
514
+ it "should decode base64 to Integers" do
515
+ # Not really useful, but ensures that composite element encoders work.
516
+ e = PG::TextDecoder::FromBase64.new( elements_type: PG::TextDecoder::Array.new( elements_type: PG::TextDecoder::Integer.new ))
517
+ expect( e.decode(["{1}"].pack("m")) ).to eq([1])
518
+ expect( e.decode(["{12}"].pack("m")) ).to eq([12])
519
+ expect( e.decode(["{123}"].pack("m")) ).to eq([123])
520
+ expect( e.decode(["{1234}"].pack("m")) ).to eq([1234])
521
+ expect( e.decode(["{12345}"].pack("m")) ).to eq([12345])
522
+ expect( e.decode(["{123456}"].pack("m")) ).to eq([123456])
523
+ expect( e.decode(["{1234567}"].pack("m")) ).to eq([1234567])
524
+ expect( e.decode(["{12345678}"].pack("m")) ).to eq([12345678])
525
+
526
+ e = PG::TextDecoder::FromBase64.new( elements_type: PG::BinaryDecoder::Integer.new )
527
+ expect( e.decode("ALxhTg==") ).to eq(12345678)
528
+ end
529
+
530
+ it "should decode base64 with garbage" do
531
+ e = PG::TextDecoder::FromBase64.new format: 1
532
+ expect( e.decode("=") ).to eq("=".unpack("m")[0])
533
+ expect( e.decode("==") ).to eq("==".unpack("m")[0])
534
+ expect( e.decode("===") ).to eq("===".unpack("m")[0])
535
+ expect( e.decode("====") ).to eq("====".unpack("m")[0])
536
+ expect( e.decode("a=") ).to eq("a=".unpack("m")[0])
537
+ expect( e.decode("a==") ).to eq("a==".unpack("m")[0])
538
+ expect( e.decode("a===") ).to eq("a===".unpack("m")[0])
539
+ expect( e.decode("a====") ).to eq("a====".unpack("m")[0])
540
+ expect( e.decode("aa=") ).to eq("aa=".unpack("m")[0])
541
+ expect( e.decode("aa==") ).to eq("aa==".unpack("m")[0])
542
+ expect( e.decode("aa===") ).to eq("aa===".unpack("m")[0])
543
+ expect( e.decode("aa====") ).to eq("aa====".unpack("m")[0])
544
+ expect( e.decode("aaa=") ).to eq("aaa=".unpack("m")[0])
545
+ expect( e.decode("aaa==") ).to eq("aaa==".unpack("m")[0])
546
+ expect( e.decode("aaa===") ).to eq("aaa===".unpack("m")[0])
547
+ expect( e.decode("aaa====") ).to eq("aaa====".unpack("m")[0])
548
+ expect( e.decode("=aa") ).to eq("=aa=".unpack("m")[0])
549
+ expect( e.decode("=aa=") ).to eq("=aa=".unpack("m")[0])
550
+ expect( e.decode("=aa==") ).to eq("=aa==".unpack("m")[0])
551
+ expect( e.decode("=aa===") ).to eq("=aa===".unpack("m")[0])
552
+ end
553
+ end
554
+
555
+ describe PG::CopyCoder do
556
+ describe PG::TextEncoder::CopyRow do
557
+ context "with default typemap" do
558
+ let!(:encoder) do
559
+ PG::TextEncoder::CopyRow.new
560
+ end
561
+
562
+ it "should encode different types of Ruby objects" do
563
+ expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
564
+ to eq("xyz\t123\t2456\t34567\t456789\t5678901\t[1, 2, 3]\t12.1\tabcdefg\t\\N\n")
565
+ end
566
+ end
567
+
568
+ context "with TypeMapByMriType" do
569
+ let!(:tm) do
570
+ tm = PG::TypeMapByMriType.new
571
+ tm['T_FIXNUM'] = textenc_int
572
+ tm['T_FLOAT'] = intenc_incrementer
573
+ tm['T_ARRAY'] = PG::TextEncoder::Array.new elements_type: textenc_string
574
+ tm
575
+ end
576
+ let!(:encoder) do
577
+ PG::TextEncoder::CopyRow.new type_map: tm
578
+ end
579
+
580
+ it "should have reasonable default values" do
581
+ expect( encoder.name ).to be_nil
582
+ expect( encoder.delimiter ).to eq( "\t" )
583
+ expect( encoder.null_string ).to eq( "\\N" )
584
+ end
585
+
586
+ it "copies all attributes with #dup" do
587
+ encoder.name = "test"
588
+ encoder.delimiter = "#"
589
+ encoder.null_string = "NULL"
590
+ encoder.type_map = PG::TypeMapByColumn.new []
591
+ encoder2 = encoder.dup
592
+ expect( encoder.object_id ).to_not eq( encoder2.object_id )
593
+ expect( encoder2.name ).to eq( "test" )
594
+ expect( encoder2.delimiter ).to eq( "#" )
595
+ expect( encoder2.null_string ).to eq( "NULL" )
596
+ expect( encoder2.type_map ).to be_a_kind_of( PG::TypeMapByColumn )
597
+ end
598
+
599
+ describe '#encode' do
600
+ it "should encode different types of Ruby objects" do
601
+ expect( encoder.encode([]) ).to eq("\n")
602
+ expect( encoder.encode(["a"]) ).to eq("a\n")
603
+ expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
604
+ to eq("xyz\t123\t2456\t34567\t456789\t5678901\t{1,2,3}\t13 \tabcdefg\t\\N\n")
605
+ end
606
+
607
+ it "should escape special characters" do
608
+ expect( encoder.encode([" \0\t\n\r\\"]) ).to eq(" \0#\t#\n#\r#\\\n".gsub("#", "\\"))
609
+ end
610
+
611
+ it "should escape with different delimiter" do
612
+ encoder.delimiter = " "
613
+ encoder.null_string = "NULL"
614
+ expect( encoder.encode([nil, " ", "\0", "\t", "\n", "\r", "\\"]) ).to eq("NULL # \0 \t #\n #\r #\\\n".gsub("#", "\\"))
615
+ end
616
+ end
617
+ end
618
+ end
619
+
620
+ describe PG::TextDecoder::CopyRow do
621
+ context "with default typemap" do
622
+ let!(:decoder) do
623
+ PG::TextDecoder::CopyRow.new
624
+ end
625
+
626
+ describe '#decode' do
627
+ it "should decode different types of Ruby objects" do
628
+ 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"] )
629
+ end
630
+ end
631
+ end
632
+
633
+ context "with TypeMapByColumn" do
634
+ let!(:tm) do
635
+ PG::TypeMapByColumn.new [textdec_int, textdec_string, intdec_incrementer, nil]
636
+ end
637
+ let!(:decoder) do
638
+ PG::TextDecoder::CopyRow.new type_map: tm
639
+ end
640
+
641
+ describe '#decode' do
642
+ it "should decode different types of Ruby objects" do
643
+ expect( decoder.decode("123\t \0#\t#\n#\r#\\ \t234\t#\x01#\002\n".gsub("#", "\\"))).to eq( [123, " \0\t\n\r\\ ", 235, "\x01\x02"] )
644
+ end
645
+ end
646
+ end
647
+ end
648
+ end
649
+ end