red-arrow 10.0.0 → 16.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/ext/arrow/arrow.cpp +31 -0
  4. data/ext/arrow/converters.hpp +45 -41
  5. data/ext/arrow/extconf.rb +16 -4
  6. data/ext/arrow/raw-records.cpp +155 -2
  7. data/ext/arrow/red-arrow.hpp +2 -0
  8. data/ext/arrow/values.cpp +1 -2
  9. data/lib/arrow/array-computable.rb +13 -0
  10. data/lib/arrow/array.rb +6 -1
  11. data/lib/arrow/chunked-array.rb +35 -1
  12. data/lib/arrow/column-containable.rb +9 -0
  13. data/lib/arrow/column.rb +1 -0
  14. data/lib/arrow/data-type.rb +9 -0
  15. data/lib/arrow/dense-union-array-builder.rb +49 -0
  16. data/lib/arrow/dense-union-array.rb +26 -0
  17. data/lib/arrow/expression.rb +6 -2
  18. data/lib/arrow/function.rb +0 -1
  19. data/lib/arrow/half-float-array-builder.rb +32 -0
  20. data/lib/arrow/half-float-array.rb +24 -0
  21. data/lib/arrow/half-float.rb +118 -0
  22. data/lib/arrow/input-referable.rb +29 -0
  23. data/lib/arrow/loader.rb +11 -0
  24. data/lib/arrow/raw-table-converter.rb +7 -5
  25. data/lib/arrow/record-batch-file-reader.rb +2 -0
  26. data/lib/arrow/record-batch-stream-reader.rb +2 -0
  27. data/lib/arrow/record-batch.rb +6 -2
  28. data/lib/arrow/scalar.rb +67 -0
  29. data/lib/arrow/slicer.rb +61 -0
  30. data/lib/arrow/sort-key.rb +3 -3
  31. data/lib/arrow/sparse-union-array-builder.rb +56 -0
  32. data/lib/arrow/sparse-union-array.rb +26 -0
  33. data/lib/arrow/struct-array-builder.rb +0 -5
  34. data/lib/arrow/table-loader.rb +11 -5
  35. data/lib/arrow/table-saver.rb +1 -0
  36. data/lib/arrow/table.rb +180 -33
  37. data/lib/arrow/tensor.rb +4 -0
  38. data/lib/arrow/timestamp-parser.rb +33 -0
  39. data/lib/arrow/union-array-builder.rb +59 -0
  40. data/lib/arrow/version.rb +1 -1
  41. data/red-arrow.gemspec +2 -1
  42. data/test/each-raw-record/test-basic-arrays.rb +411 -0
  43. data/test/each-raw-record/test-dense-union-array.rb +566 -0
  44. data/test/each-raw-record/test-dictionary-array.rb +341 -0
  45. data/test/each-raw-record/test-list-array.rb +628 -0
  46. data/test/each-raw-record/test-map-array.rb +507 -0
  47. data/test/each-raw-record/test-multiple-columns.rb +72 -0
  48. data/test/each-raw-record/test-sparse-union-array.rb +528 -0
  49. data/test/each-raw-record/test-struct-array.rb +529 -0
  50. data/test/each-raw-record/test-table.rb +47 -0
  51. data/test/helper/omittable.rb +13 -0
  52. data/test/helper.rb +1 -0
  53. data/test/raw-records/test-basic-arrays.rb +11 -1
  54. data/test/raw-records/test-dense-union-array.rb +90 -45
  55. data/test/raw-records/test-list-array.rb +28 -10
  56. data/test/raw-records/test-map-array.rb +39 -10
  57. data/test/raw-records/test-sparse-union-array.rb +86 -41
  58. data/test/raw-records/test-struct-array.rb +22 -8
  59. data/test/test-array.rb +7 -0
  60. data/test/test-chunked-array.rb +9 -0
  61. data/test/test-csv-loader.rb +39 -0
  62. data/test/test-data-type.rb +2 -1
  63. data/test/test-dense-union-array.rb +42 -0
  64. data/test/test-dense-union-data-type.rb +1 -1
  65. data/test/test-expression.rb +11 -0
  66. data/test/test-function.rb +7 -7
  67. data/test/test-group.rb +58 -58
  68. data/test/test-half-float-array.rb +43 -0
  69. data/test/test-half-float.rb +130 -0
  70. data/test/test-ractor.rb +34 -0
  71. data/test/test-record-batch-file-reader.rb +21 -0
  72. data/test/test-record-batch-stream-reader.rb +129 -0
  73. data/test/test-scalar.rb +65 -0
  74. data/test/test-slicer.rb +194 -129
  75. data/test/test-sparse-union-array.rb +38 -0
  76. data/test/test-table.rb +356 -40
  77. data/test/values/test-basic-arrays.rb +10 -0
  78. data/test/values/test-dense-union-array.rb +88 -45
  79. data/test/values/test-list-array.rb +26 -10
  80. data/test/values/test-map-array.rb +33 -10
  81. data/test/values/test-sparse-union-array.rb +84 -41
  82. data/test/values/test-struct-array.rb +20 -8
  83. metadata +62 -9
@@ -0,0 +1,529 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ module EachRawRecordStructArrayTests
19
+ def build_schema(type)
20
+ field_description = {
21
+ name: :field,
22
+ }
23
+ if type.is_a?(Hash)
24
+ field_description = field_description.merge(type)
25
+ else
26
+ field_description[:type] = type
27
+ end
28
+ {
29
+ column: {
30
+ type: :struct,
31
+ fields: [
32
+ field_description,
33
+ ],
34
+ },
35
+ }
36
+ end
37
+
38
+ def test_null
39
+ records = [
40
+ [{"field" => nil}],
41
+ [nil],
42
+ ]
43
+ target = build(:null, records)
44
+ assert_equal(records, target.each_raw_record.to_a)
45
+ end
46
+
47
+ def test_boolean
48
+ records = [
49
+ [{"field" => true}],
50
+ [nil],
51
+ [{"field" => nil}],
52
+ ]
53
+ target = build(:boolean, records)
54
+ assert_equal(records, target.each_raw_record.to_a)
55
+ end
56
+
57
+ def test_int8
58
+ records = [
59
+ [{"field" => -(2 ** 7)}],
60
+ [nil],
61
+ [{"field" => nil}],
62
+ ]
63
+ target = build(:int8, records)
64
+ assert_equal(records, target.each_raw_record.to_a)
65
+ end
66
+
67
+ def test_uint8
68
+ records = [
69
+ [{"field" => (2 ** 8) - 1}],
70
+ [nil],
71
+ [{"field" => nil}],
72
+ ]
73
+ target = build(:uint8, records)
74
+ assert_equal(records, target.each_raw_record.to_a)
75
+ end
76
+
77
+ def test_int16
78
+ records = [
79
+ [{"field" => -(2 ** 15)}],
80
+ [nil],
81
+ [{"field" => nil}],
82
+ ]
83
+ target = build(:int16, records)
84
+ assert_equal(records, target.each_raw_record.to_a)
85
+ end
86
+
87
+ def test_uint16
88
+ records = [
89
+ [{"field" => (2 ** 16) - 1}],
90
+ [nil],
91
+ [{"field" => nil}],
92
+ ]
93
+ target = build(:uint16, records)
94
+ assert_equal(records, target.each_raw_record.to_a)
95
+ end
96
+
97
+ def test_int32
98
+ records = [
99
+ [{"field" => -(2 ** 31)}],
100
+ [nil],
101
+ [{"field" => nil}],
102
+ ]
103
+ target = build(:int32, records)
104
+ assert_equal(records, target.each_raw_record.to_a)
105
+ end
106
+
107
+ def test_uint32
108
+ records = [
109
+ [{"field" => (2 ** 32) - 1}],
110
+ [nil],
111
+ [{"field" => nil}],
112
+ ]
113
+ target = build(:uint32, records)
114
+ assert_equal(records, target.each_raw_record.to_a)
115
+ end
116
+
117
+ def test_int64
118
+ records = [
119
+ [{"field" => -(2 ** 63)}],
120
+ [nil],
121
+ [{"field" => nil}],
122
+ ]
123
+ target = build(:int64, records)
124
+ assert_equal(records, target.each_raw_record.to_a)
125
+ end
126
+
127
+ def test_uint64
128
+ records = [
129
+ [{"field" => (2 ** 64) - 1}],
130
+ [nil],
131
+ [{"field" => nil}],
132
+ ]
133
+ target = build(:uint64, records)
134
+ assert_equal(records, target.each_raw_record.to_a)
135
+ end
136
+
137
+ def test_float
138
+ records = [
139
+ [{"field" => -1.0}],
140
+ [nil],
141
+ [{"field" => nil}],
142
+ ]
143
+ target = build(:float, records)
144
+ assert_equal(records, target.each_raw_record.to_a)
145
+ end
146
+
147
+ def test_double
148
+ records = [
149
+ [{"field" => -1.0}],
150
+ [nil],
151
+ [{"field" => nil}],
152
+ ]
153
+ target = build(:double, records)
154
+ assert_equal(records, target.each_raw_record.to_a)
155
+ end
156
+
157
+ def test_binary
158
+ records = [
159
+ [{"field" => "\xff".b}],
160
+ [nil],
161
+ [{"field" => nil}],
162
+ ]
163
+ target = build(:binary, records)
164
+ assert_equal(records, target.each_raw_record.to_a)
165
+ end
166
+
167
+ def test_string
168
+ records = [
169
+ [{"field" => "Ruby"}],
170
+ [nil],
171
+ [{"field" => nil}],
172
+ ]
173
+ target = build(:string, records)
174
+ assert_equal(records, target.each_raw_record.to_a)
175
+ end
176
+
177
+ def test_date32
178
+ records = [
179
+ [{"field" => Date.new(1960, 1, 1)}],
180
+ [nil],
181
+ [{"field" => nil}],
182
+ ]
183
+ target = build(:date32, records)
184
+ assert_equal(records, target.each_raw_record.to_a)
185
+ end
186
+
187
+ def test_date64
188
+ records = [
189
+ [{"field" => DateTime.new(1960, 1, 1, 2, 9, 30)}],
190
+ [nil],
191
+ [{"field" => nil}],
192
+ ]
193
+ target = build(:date64, records)
194
+ assert_equal(records, target.each_raw_record.to_a)
195
+ end
196
+
197
+ def test_timestamp_second
198
+ records = [
199
+ [{"field" => Time.parse("1960-01-01T02:09:30Z")}],
200
+ [nil],
201
+ [{"field" => nil}],
202
+ ]
203
+ target = build({
204
+ type: :timestamp,
205
+ unit: :second,
206
+ },
207
+ records)
208
+ assert_equal(records, target.each_raw_record.to_a)
209
+ end
210
+
211
+ def test_timestamp_milli
212
+ records = [
213
+ [{"field" => Time.parse("1960-01-01T02:09:30.123Z")}],
214
+ [nil],
215
+ [{"field" => nil}],
216
+ ]
217
+ target = build({
218
+ type: :timestamp,
219
+ unit: :milli,
220
+ },
221
+ records)
222
+ assert_equal(records, target.each_raw_record.to_a)
223
+ end
224
+
225
+ def test_timestamp_micro
226
+ records = [
227
+ [{"field" => Time.parse("1960-01-01T02:09:30.123456Z")}],
228
+ [nil],
229
+ [{"field" => nil}],
230
+ ]
231
+ target = build({
232
+ type: :timestamp,
233
+ unit: :micro,
234
+ },
235
+ records)
236
+ assert_equal(records, target.each_raw_record.to_a)
237
+ end
238
+
239
+ def test_timestamp_nano
240
+ records = [
241
+ [{"field" => Time.parse("1960-01-01T02:09:30.123456789Z")}],
242
+ [nil],
243
+ [{"field" => nil}],
244
+ ]
245
+ target = build({
246
+ type: :timestamp,
247
+ unit: :nano,
248
+ },
249
+ records)
250
+ assert_equal(records, target.each_raw_record.to_a)
251
+ end
252
+
253
+ def test_time32_second
254
+ unit = Arrow::TimeUnit::SECOND
255
+ records = [
256
+ # 00:10:00
257
+ [{"field" => Arrow::Time.new(unit, 60 * 10)}],
258
+ [nil],
259
+ [{"field" => nil}],
260
+ ]
261
+ target = build({
262
+ type: :time32,
263
+ unit: :second,
264
+ },
265
+ records)
266
+ assert_equal(records, target.each_raw_record.to_a)
267
+ end
268
+
269
+ def test_time32_milli
270
+ unit = Arrow::TimeUnit::MILLI
271
+ records = [
272
+ # 00:10:00.123
273
+ [{"field" => Arrow::Time.new(unit, (60 * 10) * 1000 + 123)}],
274
+ [nil],
275
+ [{"field" => nil}],
276
+ ]
277
+ target = build({
278
+ type: :time32,
279
+ unit: :milli,
280
+ },
281
+ records)
282
+ assert_equal(records, target.each_raw_record.to_a)
283
+ end
284
+
285
+ def test_time64_micro
286
+ unit = Arrow::TimeUnit::MICRO
287
+ records = [
288
+ # 00:10:00.123456
289
+ [{"field" => Arrow::Time.new(unit, (60 * 10) * 1_000_000 + 123_456)}],
290
+ [nil],
291
+ [{"field" => nil}],
292
+ ]
293
+ target = build({
294
+ type: :time64,
295
+ unit: :micro,
296
+ },
297
+ records)
298
+ assert_equal(records, target.each_raw_record.to_a)
299
+ end
300
+
301
+ def test_time64_nano
302
+ unit = Arrow::TimeUnit::NANO
303
+ records = [
304
+ # 00:10:00.123456789
305
+ [{"field" => Arrow::Time.new(unit, (60 * 10) * 1_000_000_000 + 123_456_789)}],
306
+ [nil],
307
+ [{"field" => nil}],
308
+ ]
309
+ target = build({
310
+ type: :time64,
311
+ unit: :nano,
312
+ },
313
+ records)
314
+ assert_equal(records, target.each_raw_record.to_a)
315
+ end
316
+
317
+ def test_decimal128
318
+ records = [
319
+ [{"field" => BigDecimal("92.92")}],
320
+ [nil],
321
+ [{"field" => nil}],
322
+ ]
323
+ target = build({
324
+ type: :decimal128,
325
+ precision: 8,
326
+ scale: 2,
327
+ },
328
+ records)
329
+ assert_equal(records, target.each_raw_record.to_a)
330
+ end
331
+
332
+ def test_decimal256
333
+ records = [
334
+ [{"field" => BigDecimal("92.92")}],
335
+ [nil],
336
+ [{"field" => nil}],
337
+ ]
338
+ target = build({
339
+ type: :decimal256,
340
+ precision: 38,
341
+ scale: 2,
342
+ },
343
+ records)
344
+ assert_equal(records, target.each_raw_record.to_a)
345
+ end
346
+
347
+ def test_month_interval
348
+ records = [
349
+ [{"field" => 1}],
350
+ [nil],
351
+ [{"field" => nil}],
352
+ ]
353
+ target = build(:month_interval, records)
354
+ assert_equal(records, target.each_raw_record.to_a)
355
+ end
356
+
357
+ def test_day_time_interval
358
+ records = [
359
+ [{"field" => {day: 1, millisecond: 100}}],
360
+ [nil],
361
+ [{"field" => nil}],
362
+ ]
363
+ target = build(:day_time_interval, records)
364
+ assert_equal(records, target.each_raw_record.to_a)
365
+ end
366
+
367
+ def test_month_day_nano_interval
368
+ records = [
369
+ [{"field" => {month: 1, day: 1, nanosecond: 100}}],
370
+ [nil],
371
+ [{"field" => nil}],
372
+ ]
373
+ target = build(:month_day_nano_interval, records)
374
+ assert_equal(records, target.each_raw_record.to_a)
375
+ end
376
+
377
+ def test_list
378
+ records = [
379
+ [{"field" => [true, nil, false]}],
380
+ [nil],
381
+ [{"field" => nil}],
382
+ ]
383
+ target = build({
384
+ type: :list,
385
+ field: {
386
+ name: :sub_element,
387
+ type: :boolean,
388
+ },
389
+ },
390
+ records)
391
+ assert_equal(records, target.each_raw_record.to_a)
392
+ end
393
+
394
+ def test_struct
395
+ records = [
396
+ [{"field" => {"sub_field" => true}}],
397
+ [nil],
398
+ [{"field" => nil}],
399
+ [{"field" => {"sub_field" => nil}}],
400
+ ]
401
+ target = build({
402
+ type: :struct,
403
+ fields: [
404
+ {
405
+ name: :sub_field,
406
+ type: :boolean,
407
+ },
408
+ ],
409
+ },
410
+ records)
411
+ assert_equal(records, target.each_raw_record.to_a)
412
+ end
413
+
414
+ def test_map
415
+ records = [
416
+ [{"field" => {"key1" => true, "key2" => nil}}],
417
+ [nil],
418
+ [{"field" => nil}],
419
+ ]
420
+ target = build({
421
+ type: :map,
422
+ key: :string,
423
+ item: :boolean,
424
+ },
425
+ records)
426
+ assert_equal(records, target.each_raw_record.to_a)
427
+ end
428
+
429
+ def remove_union_field_names(records)
430
+ records.collect do |record|
431
+ record.collect do |column|
432
+ if column.nil?
433
+ column
434
+ else
435
+ value = column["field"]
436
+ value = value.values[0] unless value.nil?
437
+ {"field" => value}
438
+ end
439
+ end
440
+ end
441
+ end
442
+
443
+ def test_sparse_union
444
+ records = [
445
+ [{"field" => {"field1" => true}}],
446
+ [nil],
447
+ [{"field" => nil}],
448
+ [{"field" => {"field2" => 29}}],
449
+ [{"field" => {"field2" => nil}}],
450
+ ]
451
+ target = build({
452
+ type: :sparse_union,
453
+ fields: [
454
+ {
455
+ name: :field1,
456
+ type: :boolean,
457
+ },
458
+ {
459
+ name: :field2,
460
+ type: :uint8,
461
+ },
462
+ ],
463
+ type_codes: [0, 1],
464
+ },
465
+ records)
466
+ assert_equal(remove_union_field_names(records),
467
+ target.each_raw_record.to_a)
468
+ end
469
+
470
+ def test_dense_union
471
+ records = [
472
+ [{"field" => {"field1" => true}}],
473
+ [nil],
474
+ [{"field" => nil}],
475
+ [{"field" => {"field2" => 29}}],
476
+ [{"field" => {"field2" => nil}}],
477
+ ]
478
+ target = build({
479
+ type: :dense_union,
480
+ fields: [
481
+ {
482
+ name: :field1,
483
+ type: :boolean,
484
+ },
485
+ {
486
+ name: :field2,
487
+ type: :uint8,
488
+ },
489
+ ],
490
+ type_codes: [0, 1],
491
+ },
492
+ records)
493
+ assert_equal(remove_union_field_names(records),
494
+ target.each_raw_record.to_a)
495
+ end
496
+
497
+ def test_dictionary
498
+ records = [
499
+ [{"field" => "Ruby"}],
500
+ [nil],
501
+ [{"field" => nil}],
502
+ [{"field" => "GLib"}],
503
+ ]
504
+ target = build({
505
+ type: :dictionary,
506
+ index_data_type: :int8,
507
+ value_data_type: :string,
508
+ ordered: false,
509
+ },
510
+ records)
511
+ assert_equal(records, target.each_raw_record.to_a)
512
+ end
513
+ end
514
+
515
+ class EachRawRecordRecordBatchStructArrayTest < Test::Unit::TestCase
516
+ include EachRawRecordStructArrayTests
517
+
518
+ def build(type, records)
519
+ Arrow::RecordBatch.new(build_schema(type), records)
520
+ end
521
+ end
522
+
523
+ class EachRawRecordTableStructArrayTest < Test::Unit::TestCase
524
+ include EachRawRecordStructArrayTests
525
+
526
+ def build(type, records)
527
+ Arrow::Table.new(build_schema(type), records)
528
+ end
529
+ end
@@ -0,0 +1,47 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ class EachRawRecordTableTest < Test::Unit::TestCase
19
+ test("2 arrays") do
20
+ raw_record_batches = [
21
+ [
22
+ [true, nil, "Ruby"],
23
+ [nil, 0, "GLib"],
24
+ [false, 2 ** 8 - 1, nil],
25
+ ],
26
+ [
27
+ [nil, 10, "A"],
28
+ [true, 20, "B"],
29
+ [false, nil, "C"],
30
+ [nil, 40, nil],
31
+ ]
32
+ ]
33
+ raw_records = raw_record_batches.inject do |all_records, record_batch|
34
+ all_records + record_batch
35
+ end
36
+ schema = [
37
+ {name: :column0, type: :boolean},
38
+ {name: :column1, type: :uint8},
39
+ {name: :column2, type: :string},
40
+ ]
41
+ record_batches = raw_record_batches.collect do |record_batch|
42
+ Arrow::RecordBatch.new(schema, record_batch)
43
+ end
44
+ table = Arrow::Table.new(schema, record_batches)
45
+ assert_equal(raw_records, table.each_raw_record.to_a)
46
+ end
47
+ end
@@ -17,6 +17,11 @@
17
17
 
18
18
  module Helper
19
19
  module Omittable
20
+ def require_ruby(major, minor, micro=0)
21
+ return if (RUBY_VERSION <=> "#{major}.#{minor}.#{micro}") >= 0
22
+ omit("Require Ruby #{major}.#{minor}.#{micro} or later: #{RUBY_VERSION}")
23
+ end
24
+
20
25
  def require_gi_bindings(major, minor, micro)
21
26
  return if GLib.check_binding_version?(major, minor, micro)
22
27
  message =
@@ -32,5 +37,13 @@ module Helper
32
37
  GObjectIntrospection::Version::STRING
33
38
  omit(message)
34
39
  end
40
+
41
+ def require_glib(major, minor, micro)
42
+ return if GLib::Version.or_later?(major, minor, micro)
43
+ message =
44
+ "Require GLib #{major}.#{minor}.#{micro} or later: " +
45
+ GLib::Version::STRING
46
+ omit(message)
47
+ end
35
48
  end
36
49
  end
data/test/helper.rb CHANGED
@@ -18,6 +18,7 @@
18
18
  require "arrow"
19
19
 
20
20
  require "fiddle"
21
+ require "json"
21
22
  require "pathname"
22
23
  require "tempfile"
23
24
  require "timeout"
@@ -117,6 +117,16 @@ module RawRecordsBasicArraysTests
117
117
  assert_equal(records, target.raw_records)
118
118
  end
119
119
 
120
+ def test_half_float
121
+ records = [
122
+ [-1.5],
123
+ [nil],
124
+ [1.5],
125
+ ]
126
+ target = build({column: :half_float}, records)
127
+ assert_equal(records, target.raw_records)
128
+ end
129
+
120
130
  def test_float
121
131
  records = [
122
132
  [-1.0],
@@ -147,7 +157,7 @@ module RawRecordsBasicArraysTests
147
157
  assert_equal(records, target.raw_records)
148
158
  end
149
159
 
150
- def test_tring
160
+ def test_string
151
161
  records = [
152
162
  ["Ruby"],
153
163
  [nil],