sequel-impala 1.0.0
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 +7 -0
- data/CHANGELOG +3 -0
- data/LICENSE +462 -0
- data/README.rdoc +39 -0
- data/Rakefile +39 -0
- data/lib/driver/commons-logging-1.2.jar +0 -0
- data/lib/driver/hadoop-common-2.6.0.jar +0 -0
- data/lib/driver/hadoop-core-2.6.0.jar +0 -0
- data/lib/driver/hive-exec-1.1.0.jar +0 -0
- data/lib/driver/hive-jdbc-1.1.0.jar +0 -0
- data/lib/driver/hive-metastore-1.1.0.jar +0 -0
- data/lib/driver/hive-service-1.1.0.jar +0 -0
- data/lib/driver/httpclient-4.3.jar +0 -0
- data/lib/driver/httpcore-4.3.jar +0 -0
- data/lib/driver/libfb303-0.9.0.jar +0 -0
- data/lib/driver/slf4j-api-1.7.5.jar +0 -0
- data/lib/impala.rb +47 -0
- data/lib/impala/connection.rb +117 -0
- data/lib/impala/cursor.rb +157 -0
- data/lib/impala/protocol.rb +8 -0
- data/lib/impala/protocol/beeswax_constants.rb +15 -0
- data/lib/impala/protocol/beeswax_service.rb +766 -0
- data/lib/impala/protocol/beeswax_types.rb +193 -0
- data/lib/impala/protocol/cli_service_constants.rb +60 -0
- data/lib/impala/protocol/cli_service_types.rb +1452 -0
- data/lib/impala/protocol/facebook_service.rb +706 -0
- data/lib/impala/protocol/fb303_constants.rb +15 -0
- data/lib/impala/protocol/fb303_types.rb +25 -0
- data/lib/impala/protocol/hive_metastore_constants.rb +53 -0
- data/lib/impala/protocol/hive_metastore_types.rb +698 -0
- data/lib/impala/protocol/impala_hive_server2_service.rb +29 -0
- data/lib/impala/protocol/impala_service.rb +377 -0
- data/lib/impala/protocol/impala_service_constants.rb +13 -0
- data/lib/impala/protocol/impala_service_types.rb +90 -0
- data/lib/impala/protocol/status_constants.rb +13 -0
- data/lib/impala/protocol/status_types.rb +46 -0
- data/lib/impala/protocol/t_c_l_i_service.rb +948 -0
- data/lib/impala/protocol/thrift_hive_metastore.rb +4707 -0
- data/lib/impala/version.rb +3 -0
- data/lib/jdbc/hive2.rb +46 -0
- data/lib/sequel/adapters/impala.rb +123 -0
- data/lib/sequel/adapters/jdbc/hive2.rb +26 -0
- data/lib/sequel/adapters/shared/impala.rb +635 -0
- data/lib/sequel/extensions/csv_to_parquet.rb +112 -0
- data/spec/database_test.rb +56 -0
- data/spec/dataset_test.rb +1268 -0
- data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
- data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
- data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
- data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
- data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
- data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
- data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
- data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
- data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
- data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
- data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/reversible_migrations/001_reversible.rb +5 -0
- data/spec/files/reversible_migrations/002_reversible.rb +5 -0
- data/spec/files/reversible_migrations/003_reversible.rb +5 -0
- data/spec/files/reversible_migrations/004_reversible.rb +5 -0
- data/spec/files/reversible_migrations/005_reversible.rb +10 -0
- data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/impala_test.rb +285 -0
- data/spec/migrator_test.rb +240 -0
- data/spec/plugin_test.rb +91 -0
- data/spec/prepared_statement_test.rb +327 -0
- data/spec/schema_test.rb +356 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/timezone_test.rb +86 -0
- data/spec/type_test.rb +99 -0
- metadata +239 -0
data/spec/impala_test.rb
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
|
|
2
|
+
|
|
3
|
+
describe "Impala column/table comments and describe" do
|
|
4
|
+
before do
|
|
5
|
+
@db = DB
|
|
6
|
+
end
|
|
7
|
+
after do
|
|
8
|
+
@db.drop_table?(:items)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "should set table and column comments correctly" do
|
|
12
|
+
@db.create_table!(:items, :comment=>'tab_com') do
|
|
13
|
+
Integer :i, :comment=>'col_com'
|
|
14
|
+
end
|
|
15
|
+
@db.describe(:items).first[:comment].must_equal 'col_com'
|
|
16
|
+
@db.describe(:items, :formatted=>true).find{|r| r[:type].to_s.strip == 'comment' && r[:name] == ''}[:comment].strip.must_equal 'tab_com'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "Impala dataset" do
|
|
21
|
+
before do
|
|
22
|
+
@db = DB
|
|
23
|
+
@db.create_table!(:items) do
|
|
24
|
+
Integer :id
|
|
25
|
+
Integer :number
|
|
26
|
+
String :name
|
|
27
|
+
end
|
|
28
|
+
@ds = @db[:items].order(:id)
|
|
29
|
+
@ds.insert(1, 10, 'a')
|
|
30
|
+
end
|
|
31
|
+
after do
|
|
32
|
+
@db.drop_table?(:items)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "#update should emulate UPDATE" do
|
|
36
|
+
@ds.update(:number=>20, :name=>'b')
|
|
37
|
+
@ds.all.must_equal [{:id=>1, :number=>20, :name=>'b'}]
|
|
38
|
+
@ds.where(:id=>1).update(:number=>30, :name=>'c')
|
|
39
|
+
@ds.all.must_equal [{:id=>1, :number=>30, :name=>'c'}]
|
|
40
|
+
@ds.where(:id=>2).update(:number=>40, :name=>'d')
|
|
41
|
+
@ds.all.must_equal [{:id=>1, :number=>30, :name=>'c'}]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "Impala dataset" do
|
|
46
|
+
before do
|
|
47
|
+
@db = DB
|
|
48
|
+
@db.create_table!(:items){Integer :number}
|
|
49
|
+
@ds = @db[:items]
|
|
50
|
+
@ds.insert(1)
|
|
51
|
+
end
|
|
52
|
+
after do
|
|
53
|
+
@db.drop_table?(:items)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "#delete should emulate DELETE" do
|
|
57
|
+
@ds.where(:number=>2).delete.must_equal nil
|
|
58
|
+
@ds.count.must_equal 1
|
|
59
|
+
@ds.where(:number=>1).delete.must_equal nil
|
|
60
|
+
@ds.count.must_equal 0
|
|
61
|
+
|
|
62
|
+
@ds.insert(1)
|
|
63
|
+
@ds.count.must_equal 1
|
|
64
|
+
@ds.delete.must_equal nil
|
|
65
|
+
@ds.count.must_equal 0
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "#truncate should emulate TRUNCATE" do
|
|
69
|
+
@ds.truncate.must_equal nil
|
|
70
|
+
@ds.count.must_equal 0
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "#insert_overwrite should overwrite tables" do
|
|
74
|
+
@ds.insert_overwrite.insert(2)
|
|
75
|
+
@ds.select_map(:number).must_equal [2]
|
|
76
|
+
@ds.insert(4)
|
|
77
|
+
@ds.select_order_map(:number).must_equal [2, 4]
|
|
78
|
+
@ds.insert_overwrite.insert(3)
|
|
79
|
+
@ds.select_map(:number).must_equal [3]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe "Impala string comparisons" do
|
|
84
|
+
before do
|
|
85
|
+
@db = DB
|
|
86
|
+
@db.create_table!(:items){String :name}
|
|
87
|
+
@ds = @db[:items]
|
|
88
|
+
@ds.insert('m')
|
|
89
|
+
end
|
|
90
|
+
after do
|
|
91
|
+
@db.drop_table?(:items)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "should work for equality and inequality" do
|
|
95
|
+
@ds.where(:name => 'm').all.must_equal [{:name=>'m'}]
|
|
96
|
+
@ds.where(:name => 'j').all.must_equal []
|
|
97
|
+
@ds.exclude(:name => 'j').all.must_equal [{:name=>'m'}]
|
|
98
|
+
@ds.exclude(:name => 'm').all.must_equal []
|
|
99
|
+
@ds.where{name > 'l'}.all.must_equal [{:name=>'m'}]
|
|
100
|
+
@ds.where{name > 'm'}.all.must_equal []
|
|
101
|
+
@ds.where{name < 'n'}.all.must_equal [{:name=>'m'}]
|
|
102
|
+
@ds.where{name < 'm'}.all.must_equal []
|
|
103
|
+
@ds.where{name >= 'm'}.all.must_equal [{:name=>'m'}]
|
|
104
|
+
@ds.where{name >= 'n'}.all.must_equal []
|
|
105
|
+
@ds.where{name <= 'm'}.all.must_equal [{:name=>'m'}]
|
|
106
|
+
@ds.where{name <= 'l'}.all.must_equal []
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "Impala date manipulation functions" do
|
|
111
|
+
before do
|
|
112
|
+
@db = DB
|
|
113
|
+
@db.create_table!(:items){Time :t}
|
|
114
|
+
@ds = @db[:items]
|
|
115
|
+
@ds.insert(Date.today)
|
|
116
|
+
end
|
|
117
|
+
after do
|
|
118
|
+
@db.drop_table?(:items)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "date_add should work correctly" do
|
|
122
|
+
@ds.get{date_add(t, 0)}.to_date.must_equal Date.today
|
|
123
|
+
@ds.get{date_add(t, Sequel.lit('interval 0 days'))}.to_date.must_equal Date.today
|
|
124
|
+
@ds.get{date_add(t, 1)}.to_date.must_equal(Date.today+1)
|
|
125
|
+
@ds.get{date_add(t, Sequel.lit('interval 1 day'))}.to_date.must_equal(Date.today+1)
|
|
126
|
+
@ds.get{date_add(t, -1)}.to_date.must_equal(Date.today-1)
|
|
127
|
+
@ds.get{date_add(t, Sequel.lit('interval -1 day'))}.to_date.must_equal(Date.today-1)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "should work with Sequel date_arithmetic extension" do
|
|
131
|
+
@ds.extension!(:date_arithmetic)
|
|
132
|
+
@ds.get(Sequel.date_add(:t, :days=>1)).to_date.must_equal(Date.today+1)
|
|
133
|
+
@ds.get(Sequel.date_sub(:t, :days=>1)).to_date.must_equal(Date.today-1)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe "Impala syntax" do
|
|
138
|
+
def ct_sql(opts)
|
|
139
|
+
DB.send(:create_table_sql, :t, Sequel::Schema::CreateTableGenerator.new(DB){}, opts)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "should produce correct sql for create_table" do
|
|
143
|
+
ct_sql(:external=>true).must_equal 'CREATE EXTERNAL TABLE `t` ()'
|
|
144
|
+
ct_sql(:stored_as=>:parquet).must_equal 'CREATE TABLE `t` () STORED AS parquet'
|
|
145
|
+
ct_sql(:location=>'/a/b').must_equal "CREATE TABLE `t` () LOCATION '/a/b'"
|
|
146
|
+
ct_sql(:field_term=>"\02").must_equal "CREATE TABLE `t` () ROW FORMAT DELIMITED FIELDS TERMINATED BY '\02'"
|
|
147
|
+
ct_sql(:field_term=>"\02", :field_escape=>"\a").must_equal "CREATE TABLE `t` () ROW FORMAT DELIMITED FIELDS TERMINATED BY '\02' ESCAPED BY '\a'"
|
|
148
|
+
ct_sql(:line_term=>"\01").must_equal "CREATE TABLE `t` () ROW FORMAT DELIMITED LINES TERMINATED BY '\01'"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "should produce correct sql for load_data" do
|
|
152
|
+
DB.send(:load_data_sql, '/a/b', :c, {}).must_equal "LOAD DATA INPATH '/a/b' INTO TABLE `c`"
|
|
153
|
+
DB.send(:load_data_sql, '/a/b', :c, :overwrite=>true).must_equal "LOAD DATA INPATH '/a/b' OVERWRITE INTO TABLE `c`"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe "Impala create_table" do
|
|
158
|
+
before do
|
|
159
|
+
@db = DB
|
|
160
|
+
end
|
|
161
|
+
after do
|
|
162
|
+
@db.drop_table?(:items)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "should handle row format options" do
|
|
166
|
+
DB.create_table(:items, :field_term=>"\001", :field_escape=>"\002", :line_term=>"\003"){Integer :a; Integer :b}
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe "Impala parquet support" do
|
|
171
|
+
before do
|
|
172
|
+
@db = DB
|
|
173
|
+
end
|
|
174
|
+
after do
|
|
175
|
+
@db.drop_table?(:items)
|
|
176
|
+
@db.drop_table?(:items2)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "should support parquet format using create_table :stored_as option" do
|
|
180
|
+
@db.create_table!(:items, :stored_as=>:parquet){Integer :number}
|
|
181
|
+
@ds = @db[:items]
|
|
182
|
+
@ds.insert(1)
|
|
183
|
+
@ds.all.must_equal [{:number=>1}]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "should support parquet format via Dataset#to_parquet" do
|
|
187
|
+
@db.create_table!(:items){Integer :number}
|
|
188
|
+
@ds = @db[:items]
|
|
189
|
+
@ds.insert(1)
|
|
190
|
+
@ds.to_parquet(:items2)
|
|
191
|
+
@db[:items2].all.must_equal [{:number=>1}]
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
describe "Impala create/drop schemas" do
|
|
196
|
+
it "should use correct SQL" do
|
|
197
|
+
DB.send(:create_schema_sql, :s1, {}).must_equal "CREATE SCHEMA `s1`"
|
|
198
|
+
DB.send(:create_schema_sql, :s1, :if_not_exists=>true).must_equal "CREATE SCHEMA IF NOT EXISTS `s1`"
|
|
199
|
+
DB.send(:create_schema_sql, :s1, :location=>'/a/b').must_equal "CREATE SCHEMA `s1` LOCATION '/a/b'"
|
|
200
|
+
|
|
201
|
+
DB.send(:drop_schema_sql, :s1, {}).must_equal "DROP SCHEMA `s1`"
|
|
202
|
+
DB.send(:drop_schema_sql, :s1, :if_exists=>true).must_equal "DROP SCHEMA IF EXISTS `s1`"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "should support create_schema and drop_schema" do
|
|
206
|
+
DB.create_schema(:s1)
|
|
207
|
+
DB.create_schema(:s1, :if_not_exists=>true)
|
|
208
|
+
proc{DB.create_schema(:s1)}.must_raise Sequel::DatabaseError
|
|
209
|
+
DB.create_table(:s1__items){Integer :number}
|
|
210
|
+
DB[:s1__items].insert(1)
|
|
211
|
+
DB[:s1__items].all.must_equal [{:number=>1}]
|
|
212
|
+
DB.drop_table(:s1__items)
|
|
213
|
+
DB.drop_schema(:s1)
|
|
214
|
+
proc{DB.drop_schema(:s1)}.must_raise Sequel::DatabaseError
|
|
215
|
+
DB.drop_schema(:s1, :if_exists=>true)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
describe "Impala :search_path option" do
|
|
220
|
+
before do
|
|
221
|
+
DB.create_schema(:s1, :if_not_exists=>true)
|
|
222
|
+
DB.create_schema(:s2, :if_not_exists=>true)
|
|
223
|
+
DB.create_schema(:s3, :if_not_exists=>true)
|
|
224
|
+
DB.create_table(:s1__t1){Integer :a}
|
|
225
|
+
DB.create_table(:s2__t1){Integer :a}
|
|
226
|
+
DB.create_table(:s3__t1){Integer :a}
|
|
227
|
+
DB.create_table(:s2__t2){Integer :a}
|
|
228
|
+
DB.create_table(:s3__t2){Integer :a}
|
|
229
|
+
DB.create_table(:s3__t3){Integer :a}
|
|
230
|
+
DB.opts[:search_path] = 's1,s2,s3'
|
|
231
|
+
end
|
|
232
|
+
after do
|
|
233
|
+
DB.opts.delete(:search_path)
|
|
234
|
+
DB.drop_table?(:s3__t3, :s3__t2, :s2__t2, :s3__t1, :s2__t1, :s1__t1)
|
|
235
|
+
DB.drop_schema(:s3)
|
|
236
|
+
DB.drop_schema(:s2)
|
|
237
|
+
DB.drop_schema(:s1)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it "should use the search_path to implicitly qualify tables" do
|
|
241
|
+
DB[:t1].insert(1)
|
|
242
|
+
DB[:t2].insert(1)
|
|
243
|
+
DB[:t3].insert(1)
|
|
244
|
+
DB[:s1__t1].count.must_equal 1
|
|
245
|
+
DB[:s2__t2].count.must_equal 1
|
|
246
|
+
DB[:s3__t3].count.must_equal 1
|
|
247
|
+
DB[:t1].select_map(:a).must_equal [1]
|
|
248
|
+
DB[:t1].join(:t2, [:a]).select_map([:t1__a, :t2__a]).must_equal [[1, 1]]
|
|
249
|
+
DB[:t1].join(:t2, [:a]).join(:t3, [:a]).select_map([:t1__a, :t2__a, :t3__a]).must_equal [[1, 1, 1]]
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
describe "Impala except/intersect" do
|
|
254
|
+
before do
|
|
255
|
+
DB.create_table!(:a){Integer :a; Integer :b; Integer :c}
|
|
256
|
+
DB.create_table!(:b){Integer :d; Integer :e; Integer :f}
|
|
257
|
+
end
|
|
258
|
+
after do
|
|
259
|
+
DB.drop_table?(:a)
|
|
260
|
+
DB.drop_table?(:b)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
it "should be emulated correctly" do
|
|
264
|
+
DB[:a].import([:a, :b, :c], [[1,2,3], [1,2,3], [2,3,4], [3,4,5], [3,4,5]])
|
|
265
|
+
DB[:b].import([:d, :e, :f], [[1,2,3], [2,3,4], [2,3,4], [4,5,6], [4,5,6]])
|
|
266
|
+
|
|
267
|
+
DB[:a].intersect(DB[:b]).select_order_map([:a, :b, :c]).must_equal [[1,2,3], [2,3,4]]
|
|
268
|
+
DB[:b].intersect(DB[:a]).select_order_map([:d, :e, :f]).must_equal [[1,2,3], [2,3,4]]
|
|
269
|
+
DB[:a].intersect(DB[:a]).select_order_map([:a, :b, :c]).must_equal [[1,2,3], [2,3,4], [3,4,5]]
|
|
270
|
+
DB[:b].intersect(DB[:b]).select_order_map([:d, :e, :f]).must_equal [[1,2,3], [2,3,4], [4,5,6]]
|
|
271
|
+
|
|
272
|
+
DB[:a].except(DB[:b]).select_order_map([:a, :b, :c]).must_equal [[3,4,5]]
|
|
273
|
+
DB[:b].except(DB[:a]).select_order_map([:d, :e, :f]).must_equal [[4,5,6]]
|
|
274
|
+
DB[:a].except(DB[:a]).select_order_map([:a, :b, :c]).must_equal []
|
|
275
|
+
DB[:b].except(DB[:b]).select_order_map([:d, :e, :f]).must_equal []
|
|
276
|
+
|
|
277
|
+
DB[:a].intersect(DB[:b]).unfiltered.select_order_map([:a, :b, :c]).must_equal [[1,2,3], [2,3,4]]
|
|
278
|
+
DB[:a].except(DB[:b]).unfiltered.select_order_map([:a, :b, :c]).must_equal [[3,4,5]]
|
|
279
|
+
|
|
280
|
+
DB[:a].intersect(DB[:b], :alias=>:q).where(:q__b=>2).select_order_map([:a, :b, :c]).must_equal [[1,2,3]]
|
|
281
|
+
DB[:a].except(DB[:b], :alias=>:q).where(:q__b=>2).select_order_map([:a, :b, :c]).must_equal []
|
|
282
|
+
DB[:a].except(DB[:b], :alias=>:q).where(:q__b=>4).select_order_map([:a, :b, :c]).must_equal [[3,4,5]]
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|