pg 0.18.0.pre20140820094244 → 0.18.0.pre20141017155815

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