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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +1573 -2
- data/History.rdoc +3 -11
- data/Manifest.txt +24 -0
- data/README.rdoc +51 -4
- data/Rakefile +20 -14
- data/Rakefile.cross +39 -32
- data/ext/extconf.rb +27 -26
- data/ext/pg.c +75 -21
- data/ext/pg.h +194 -6
- data/ext/pg_binary_decoder.c +160 -0
- data/ext/pg_binary_encoder.c +160 -0
- data/ext/pg_coder.c +454 -0
- data/ext/pg_connection.c +815 -518
- data/ext/pg_copy_coder.c +557 -0
- data/ext/pg_result.c +258 -103
- data/ext/pg_text_decoder.c +424 -0
- data/ext/pg_text_encoder.c +608 -0
- data/ext/pg_type_map.c +113 -0
- data/ext/pg_type_map_all_strings.c +113 -0
- data/ext/pg_type_map_by_column.c +254 -0
- data/ext/pg_type_map_by_mri_type.c +266 -0
- data/ext/pg_type_map_by_oid.c +341 -0
- data/ext/util.c +121 -0
- data/ext/util.h +63 -0
- data/lib/pg.rb +11 -1
- data/lib/pg/basic_type_mapping.rb +377 -0
- data/lib/pg/coder.rb +74 -0
- data/lib/pg/connection.rb +38 -5
- data/lib/pg/result.rb +13 -3
- data/lib/pg/text_decoder.rb +42 -0
- data/lib/pg/text_encoder.rb +27 -0
- data/lib/pg/type_map_by_column.rb +15 -0
- data/spec/helpers.rb +9 -1
- data/spec/pg/basic_type_mapping_spec.rb +251 -0
- data/spec/pg/connection_spec.rb +232 -13
- data/spec/pg/result_spec.rb +52 -0
- data/spec/pg/type_map_by_column_spec.rb +135 -0
- data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
- data/spec/pg/type_map_by_oid_spec.rb +133 -0
- data/spec/pg/type_map_spec.rb +39 -0
- data/spec/pg/type_spec.rb +620 -0
- metadata +40 -4
- 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
|