red-arrow 16.1.0 → 18.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da8b7abd976ec789610864f01108ed11f819c7f4f3fb89ddc2df953457690932
4
- data.tar.gz: 332e1e80b4b71b34300c473d4e92d4207124205312b520ab6a0f55ca8fbe7ff4
3
+ metadata.gz: 16f24185f6f94f5d5a0179be6d20f289eb2b91fef831c12d1bbd48228c5ee7c0
4
+ data.tar.gz: 2cb5d5b9adfb6db6b21bb05d35e0dbbbbfbf9a21c9df43ea9b8bca51b93ff19b
5
5
  SHA512:
6
- metadata.gz: de80fc835bb774e3eb7e83039447aac4dfccf6b9c12d83d0b4b7e583357665a186d99f2fd8b8bd79890caf87f71c7bcd83c9b09231f598e1b7394d622c51864a
7
- data.tar.gz: f60d05c58897f02dd72770951a482ea79625b44559f8d213b7fabc49e2fef7bc1297985e8dd7f72dfd14cdeff58010bf4ddf688bdca283edc8493e6ecd5a6f0b
6
+ metadata.gz: 9521cbc2dd710008ef9e7d671aa65a739867a0903e5878fbbfe99ef2dc4aba71575c8b90937b9eac0068a5c6dc2caf784551e6b206c3ca8feaf1ebb400f7af7e
7
+ data.tar.gz: 78fa402f9021f9a7511ff518f8fc81e2f748b50a80f60fdb8f7d6cc6ec6a1715ad14c26f2f2c08f76ad1dab0e3a26ca5ce16c710c8868198b806524c8e979f48
data/ext/arrow/extconf.rb CHANGED
@@ -66,6 +66,13 @@ unless required_pkg_config_package([
66
66
  exit(false)
67
67
  end
68
68
 
69
+ # Old re2.pc (e.g. re2.pc on Ubuntu 20.04) may add -std=c++11. It
70
+ # causes a build error because Apache Arrow C++ requires C++17 or
71
+ # later.
72
+ #
73
+ # We can remove this when we drop support for Ubuntu 20.04.
74
+ $CXXFLAGS.gsub!("-std=c++11", "")
75
+
69
76
  [
70
77
  ["glib2", "ext/glib2"],
71
78
  ].each do |name, relative_source_dir|
@@ -84,7 +91,7 @@ when /darwin/
84
91
  symbols_in_external_bundles.each do |symbol|
85
92
  $DLDFLAGS << " -Wl,-U,#{symbol}"
86
93
  end
87
- mmacosx_version_min = "-mmacosx-version-min=10.15"
94
+ mmacosx_version_min = "-mmacosx-version-min=12.0"
88
95
  $CFLAGS << " #{mmacosx_version_min}"
89
96
  $CXXFLAGS << " #{mmacosx_version_min}"
90
97
  end
@@ -29,7 +29,7 @@ module Arrow
29
29
  return nil if index < 0 or index >= n_fields
30
30
  get_field(index)
31
31
  else
32
- message = "field name or index must be String, Symbol or Integer"
32
+ message = +"field name or index must be String, Symbol or Integer"
33
33
  message << ": <#{name_or_index.inspect}>"
34
34
  raise ArgumentError, message
35
35
  end
data/lib/arrow/loader.rb CHANGED
@@ -116,6 +116,8 @@ module Arrow
116
116
  require "arrow/sparse-union-data-type"
117
117
  require "arrow/string-dictionary-array-builder"
118
118
  require "arrow/string-array-builder"
119
+ require "arrow/stream-decoder"
120
+ require "arrow/stream-listener"
119
121
  require "arrow/struct-array"
120
122
  require "arrow/struct-array-builder"
121
123
  require "arrow/struct-data-type"
@@ -168,6 +170,16 @@ module Arrow
168
170
  end
169
171
  end
170
172
 
173
+ def rubyish_class_name(info)
174
+ name = info.name
175
+ case name
176
+ when "StreamListener"
177
+ "StreamListenerRaw"
178
+ else
179
+ super
180
+ end
181
+ end
182
+
171
183
  def load_object_info(info)
172
184
  super
173
185
 
@@ -0,0 +1,29 @@
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 Arrow
19
+ class StreamDecoder
20
+ def consume(data)
21
+ case data
22
+ when Buffer
23
+ consume_buffer(data)
24
+ else
25
+ consume_bytes(data)
26
+ end
27
+ end
28
+ end
29
+ 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
+ module Arrow
19
+ class StreamListener < StreamListenerRaw
20
+ type_register
21
+
22
+ def on_eos
23
+ end
24
+
25
+ def on_record_batch_decoded(record_batch, metadata)
26
+ end
27
+
28
+ def on_schema(schema, filtered_schema)
29
+ end
30
+
31
+ private
32
+ def virtual_do_on_eos
33
+ on_eos
34
+ true
35
+ end
36
+
37
+ def virtual_do_on_record_batch_decoded(record_batch, metadata)
38
+ on_record_batch_decoded(record_batch, metadata)
39
+ true
40
+ end
41
+
42
+ def virtual_do_on_schema_decoded(schema, filtered_schema)
43
+ on_schema_decoded(schema, filtered_schema)
44
+ true
45
+ end
46
+ end
47
+ end
@@ -24,7 +24,8 @@ module Arrow
24
24
  attr_reader :head_values
25
25
  attr_reader :tail_values
26
26
  attr_reader :sample_values
27
- def initialize(column, head_values, tail_values)
27
+ def initialize(table_formatter, column, head_values, tail_values)
28
+ @table_formatter = table_formatter
28
29
  @column = column
29
30
  @head_values = head_values
30
31
  @tail_values = tail_values
@@ -36,6 +37,15 @@ module Arrow
36
37
  @data_type ||= @column.data_type
37
38
  end
38
39
 
40
+ def formatted_data_type_name
41
+ @formatted_data_type_name ||= "(#{data_type.name})"
42
+ end
43
+
44
+ def aligned_data_type_name
45
+ @aligned_data_type_name ||=
46
+ "%*s" % [aligned_name.size, formatted_data_type_name]
47
+ end
48
+
39
49
  def name
40
50
  @name ||= @column.name
41
51
  end
@@ -63,7 +73,7 @@ module Arrow
63
73
  formatted_value = format_value(value[field_name], field_value_width)
64
74
  "#{formatted_name}: #{formatted_value}"
65
75
  end
66
- formatted = "{"
76
+ formatted = +"{"
67
77
  formatted << formatted_values.join(", ")
68
78
  formatted << "}"
69
79
  "%-*s" % [width, formatted]
@@ -90,9 +100,16 @@ module Arrow
90
100
  end
91
101
 
92
102
  def format_aligned_name(name, data_type, sample_values)
103
+ if @table_formatter.show_column_type?
104
+ min_width = formatted_data_type_name.size
105
+ else
106
+ min_width = 0
107
+ end
93
108
  case data_type
94
109
  when TimestampDataType
95
- "%*s" % [::Time.now.iso8601.size, name]
110
+ width = ::Time.now.iso8601.size
111
+ width = min_width if width < min_width
112
+ "%*s" % [width, name]
96
113
  when IntegerDataType
97
114
  have_null = false
98
115
  have_negative = false
@@ -118,9 +135,12 @@ module Arrow
118
135
  end
119
136
  width += 1 if have_negative # Need "-"
120
137
  width = [width, FORMATTED_NULL.size].max if have_null
138
+ width = min_width if width < min_width
121
139
  "%*s" % [width, name]
122
140
  when FloatDataType, DoubleDataType
123
- "%*s" % [FLOAT_N_DIGITS, name]
141
+ width = FLOAT_N_DIGITS
142
+ width = min_width if width < min_width
143
+ "%*s" % [width, name]
124
144
  when StructDataType
125
145
  field_widths = data_type.fields.collect do |field|
126
146
  field_value_width = compute_field_value_width(field, sample_values)
@@ -130,9 +150,11 @@ module Arrow
130
150
  if field_widths.size > 0
131
151
  width += (", ".size * (field_widths.size - 1))
132
152
  end
153
+ width = min_width if width < min_width
133
154
  "%*s" % [width, name]
134
155
  else
135
- name
156
+ width = min_width
157
+ "%*s" % [width, name]
136
158
  end
137
159
  end
138
160
  end
@@ -143,7 +165,7 @@ module Arrow
143
165
  end
144
166
 
145
167
  def format
146
- text = ""
168
+ text = +""
147
169
  n_rows = @table.n_rows
148
170
  border = @options[:border] || 10
149
171
 
@@ -159,7 +181,7 @@ module Arrow
159
181
  else
160
182
  tail_values = []
161
183
  end
162
- ColumnFormatter.new(column, head_values, tail_values)
184
+ ColumnFormatter.new(self, column, head_values, tail_values)
163
185
  end
164
186
 
165
187
  format_header(text, column_formatters)
@@ -186,5 +208,9 @@ module Arrow
186
208
 
187
209
  text
188
210
  end
211
+
212
+ def show_column_type?
213
+ @options.fetch(:show_column_type, true)
214
+ end
189
215
  end
190
216
  end
@@ -27,9 +27,9 @@ module Arrow
27
27
  text << ("=" * 20 + " #{start_offset + nth_row} " + "=" * 20 + "\n")
28
28
  row.each_with_index do |column_value, nth_column|
29
29
  column_formatter = column_formatters[nth_column]
30
- formatted_name = column_formatter.name
31
- formatted_value = column_formatter.format_value(column_value)
32
- text << "#{formatted_name}: #{formatted_value}\n"
30
+ text << column_formatter.name
31
+ text << "(#{column_formatter.data_type.name})" if show_column_type?
32
+ text << ": #{column_formatter.format_value(column_value)}\n"
33
33
  end
34
34
  end
35
35
  end
@@ -26,6 +26,13 @@ module Arrow
26
26
  text << "\t"
27
27
  text << column_formatter.aligned_name
28
28
  end
29
+ if show_column_type?
30
+ text << "\n"
31
+ column_formatters.each do |column_formatter|
32
+ text << "\t"
33
+ text << column_formatter.aligned_data_type_name
34
+ end
35
+ end
29
36
  text << "\n"
30
37
  end
31
38
 
data/lib/arrow/version.rb CHANGED
@@ -16,7 +16,7 @@
16
16
  # under the License.
17
17
 
18
18
  module Arrow
19
- VERSION = "16.1.0"
19
+ VERSION = "18.0.0"
20
20
 
21
21
  module Version
22
22
  numbers, TAG = VERSION.split("-")
data/red-arrow.gemspec CHANGED
@@ -49,7 +49,7 @@ Gem::Specification.new do |spec|
49
49
  spec.add_runtime_dependency("bigdecimal", ">= 3.1.0")
50
50
  spec.add_runtime_dependency("csv")
51
51
  spec.add_runtime_dependency("extpp", ">= 0.1.1")
52
- spec.add_runtime_dependency("gio2", ">= 3.5.0")
52
+ spec.add_runtime_dependency("gio2", ">= 4.2.3")
53
53
  spec.add_runtime_dependency("native-package-installer")
54
54
  spec.add_runtime_dependency("pkg-config")
55
55
 
@@ -27,80 +27,88 @@ class CSVLoaderTest < Test::Unit::TestCase
27
27
  test("String: data: with header") do
28
28
  data = fixture_path("with-header-float.csv").read
29
29
  assert_equal(<<-TABLE, load_csv(data).to_s)
30
- name score
31
- 0 alice 10.100000
32
- 1 bob 29.200000
33
- 2 chris -1.300000
30
+ name score
31
+ (utf8) (double)
32
+ 0 alice 10.100000
33
+ 1 bob 29.200000
34
+ 2 chris -1.300000
34
35
  TABLE
35
36
  end
36
37
 
37
38
  test("String: data: without header") do
38
39
  data = fixture_path("without-header-float.csv").read
39
40
  assert_equal(<<-TABLE, load_csv(data).to_s)
40
- 0 1
41
- 0 alice 10.100000
42
- 1 bob 29.200000
43
- 2 chris -1.300000
41
+ 0 1
42
+ (utf8) (double)
43
+ 0 alice 10.100000
44
+ 1 bob 29.200000
45
+ 2 chris -1.300000
44
46
  TABLE
45
47
  end
46
48
 
47
49
  test("String: path: with header") do
48
50
  path = fixture_path("with-header-float.csv").to_s
49
51
  assert_equal(<<-TABLE, load_csv(path).to_s)
50
- name score
51
- 0 alice 10.100000
52
- 1 bob 29.200000
53
- 2 chris -1.300000
52
+ name score
53
+ (utf8) (double)
54
+ 0 alice 10.100000
55
+ 1 bob 29.200000
56
+ 2 chris -1.300000
54
57
  TABLE
55
58
  end
56
59
 
57
60
  test("String: path: without header") do
58
61
  path = fixture_path("without-header-float.csv").to_s
59
62
  assert_equal(<<-TABLE, load_csv(path).to_s)
60
- 0 1
61
- 0 alice 10.100000
62
- 1 bob 29.200000
63
- 2 chris -1.300000
63
+ 0 1
64
+ (utf8) (double)
65
+ 0 alice 10.100000
66
+ 1 bob 29.200000
67
+ 2 chris -1.300000
64
68
  TABLE
65
69
  end
66
70
 
67
71
  test("Pathname: with header") do
68
72
  path = fixture_path("with-header-float.csv")
69
73
  assert_equal(<<-TABLE, load_csv(path).to_s)
70
- name score
71
- 0 alice 10.100000
72
- 1 bob 29.200000
73
- 2 chris -1.300000
74
+ name score
75
+ (utf8) (double)
76
+ 0 alice 10.100000
77
+ 1 bob 29.200000
78
+ 2 chris -1.300000
74
79
  TABLE
75
80
  end
76
81
 
77
82
  test("Pathname: without header") do
78
83
  path = fixture_path("without-header-float.csv")
79
84
  assert_equal(<<-TABLE, load_csv(path).to_s)
80
- 0 1
81
- 0 alice 10.100000
82
- 1 bob 29.200000
83
- 2 chris -1.300000
85
+ 0 1
86
+ (utf8) (double)
87
+ 0 alice 10.100000
88
+ 1 bob 29.200000
89
+ 2 chris -1.300000
84
90
  TABLE
85
91
  end
86
92
 
87
93
  test("null: with double quote") do
88
94
  path = fixture_path("null-with-double-quote.csv").to_s
89
95
  assert_equal(<<-TABLE, load_csv(path).to_s)
90
- name score
91
- 0 alice 10
92
- 1 bob (null)
93
- 2 chris -1
96
+ name score
97
+ (utf8) (int8)
98
+ 0 alice 10
99
+ 1 bob (null)
100
+ 2 chris -1
94
101
  TABLE
95
102
  end
96
103
 
97
104
  test("null: without double quote") do
98
105
  path = fixture_path("null-without-double-quote.csv").to_s
99
106
  assert_equal(<<-TABLE, load_csv(path).to_s)
100
- name score
101
- 0 alice 10
102
- 1 bob (null)
103
- 2 chris -1
107
+ name score
108
+ (utf8) (int8)
109
+ 0 alice 10
110
+ 1 bob (null)
111
+ 2 chris -1
104
112
  TABLE
105
113
  end
106
114
 
@@ -38,4 +38,10 @@ class Decimal128ArrayTest < Test::Unit::TestCase
38
38
  array.to_a)
39
39
  end
40
40
  end
41
+
42
+ def test_zero
43
+ array = Arrow::Decimal128Array.new({precision: 38, scale: 9},
44
+ [BigDecimal("0")])
45
+ assert_equal(BigDecimal("0"), array[0])
46
+ end
41
47
  end
@@ -38,4 +38,10 @@ class Decimal256ArrayTest < Test::Unit::TestCase
38
38
  array.to_a)
39
39
  end
40
40
  end
41
+
42
+ def test_zero
43
+ array = Arrow::Decimal256Array.new({precision: 38, scale: 9},
44
+ [BigDecimal("0")])
45
+ assert_equal(BigDecimal("0"), array[0])
46
+ end
41
47
  end
data/test/test-group.rb CHANGED
@@ -43,6 +43,7 @@ class GroupTest < Test::Unit::TestCase
43
43
  table = Arrow::Table.new(raw_table)
44
44
  assert_equal(<<-TABLE, table.group(:time).count.to_s)
45
45
  time count(int)
46
+ (timestamp) (int64)
46
47
  0 #{time_values[0].iso8601} 1
47
48
  1 #{time_values[1].iso8601} 1
48
49
  TABLE
@@ -53,6 +54,7 @@ class GroupTest < Test::Unit::TestCase
53
54
  test("single") do
54
55
  assert_equal(<<-TABLE, @table.group(:group_key1).count.to_s)
55
56
  group_key1 count(group_key2) count(int) count(uint) count(float) count(string)
57
+ (uint8) (int64) (int64) (int64) (int64) (int64)
56
58
  0 1 2 2 1 1 2
57
59
  1 2 1 0 1 1 1
58
60
  2 3 3 3 3 3 2
@@ -62,6 +64,7 @@ class GroupTest < Test::Unit::TestCase
62
64
  test("multiple") do
63
65
  assert_equal(<<-TABLE, @table.group(:group_key1, :group_key2).count.to_s)
64
66
  group_key1 group_key2 count(int) count(uint) count(float) count(string)
67
+ (uint8) (uint8) (int64) (int64) (int64) (int64)
65
68
  0 1 1 2 1 1 2
66
69
  1 2 1 0 1 1 1
67
70
  2 3 1 1 1 1 0
@@ -73,6 +76,7 @@ class GroupTest < Test::Unit::TestCase
73
76
  group = @table.group(:group_key1, :group_key2)
74
77
  assert_equal(<<-TABLE, group.count(:int, :uint).to_s)
75
78
  group_key1 group_key2 count(int) count(uint)
79
+ (uint8) (uint8) (int64) (int64)
76
80
  0 1 1 2 1
77
81
  1 2 1 0 1
78
82
  2 3 1 1 1
@@ -85,6 +89,7 @@ class GroupTest < Test::Unit::TestCase
85
89
  test("single") do
86
90
  assert_equal(<<-TABLE, @table.group(:group_key1).sum.to_s)
87
91
  group_key1 sum(group_key2) sum(int) sum(uint) sum(float)
92
+ (uint8) (uint64) (int64) (uint64) (double)
88
93
  0 1 2 -3 1 2.200000
89
94
  1 2 1 (null) 3 3.300000
90
95
  2 3 5 -15 15 16.500000
@@ -94,6 +99,7 @@ class GroupTest < Test::Unit::TestCase
94
99
  test("multiple") do
95
100
  assert_equal(<<-TABLE, @table.group(:group_key1, :group_key2).sum.to_s)
96
101
  group_key1 group_key2 sum(int) sum(uint) sum(float)
102
+ (uint8) (uint8) (int64) (uint64) (double)
97
103
  0 1 1 -3 1 2.200000
98
104
  1 2 1 (null) 3 3.300000
99
105
  2 3 1 -4 4 4.400000
@@ -106,6 +112,7 @@ class GroupTest < Test::Unit::TestCase
106
112
  test("single") do
107
113
  assert_equal(<<-TABLE, @table.group(:group_key1).mean.to_s)
108
114
  group_key1 mean(group_key2) mean(int) mean(uint) mean(float)
115
+ (uint8) (double) (double) (double) (double)
109
116
  0 1 1.000000 -1.500000 1.000000 2.200000
110
117
  1 2 1.000000 (null) 3.000000 3.300000
111
118
  2 3 1.666667 -5.000000 5.000000 5.500000
@@ -115,6 +122,7 @@ class GroupTest < Test::Unit::TestCase
115
122
  test("multiple") do
116
123
  assert_equal(<<-TABLE, @table.group(:group_key1, :group_key2).mean.to_s)
117
124
  group_key1 group_key2 mean(int) mean(uint) mean(float)
125
+ (uint8) (uint8) (double) (double) (double)
118
126
  0 1 1 -1.500000 1.000000 2.200000
119
127
  1 2 1 (null) 3.000000 3.300000
120
128
  2 3 1 -4.000000 4.000000 4.400000
@@ -127,6 +135,7 @@ class GroupTest < Test::Unit::TestCase
127
135
  test("single") do
128
136
  assert_equal(<<-TABLE, @table.group(:group_key1).min.to_s)
129
137
  group_key1 min(group_key2) min(int) min(uint) min(float)
138
+ (uint8) (uint8) (int32) (uint32) (float)
130
139
  0 1 1 -2 1 2.200000
131
140
  1 2 1 (null) 3 3.300000
132
141
  2 3 1 -6 4 4.400000
@@ -136,6 +145,7 @@ class GroupTest < Test::Unit::TestCase
136
145
  test("multiple") do
137
146
  assert_equal(<<-TABLE, @table.group(:group_key1, :group_key2).min.to_s)
138
147
  group_key1 group_key2 min(int) min(uint) min(float)
148
+ (uint8) (uint8) (int32) (uint32) (float)
139
149
  0 1 1 -2 1 2.200000
140
150
  1 2 1 (null) 3 3.300000
141
151
  2 3 1 -4 4 4.400000
@@ -148,6 +158,7 @@ class GroupTest < Test::Unit::TestCase
148
158
  test("single") do
149
159
  assert_equal(<<-TABLE, @table.group(:group_key1).max.to_s)
150
160
  group_key1 max(group_key2) max(int) max(uint) max(float)
161
+ (uint8) (uint8) (int32) (uint32) (float)
151
162
  0 1 1 -1 1 2.200000
152
163
  1 2 1 (null) 3 3.300000
153
164
  2 3 2 -4 6 6.600000
@@ -157,6 +168,7 @@ class GroupTest < Test::Unit::TestCase
157
168
  test("multiple") do
158
169
  assert_equal(<<-TABLE, @table.group(:group_key1, :group_key2).max.to_s)
159
170
  group_key1 group_key2 max(int) max(uint) max(float)
171
+ (uint8) (uint8) (int32) (uint32) (float)
160
172
  0 1 1 -1 1 2.200000
161
173
  1 2 1 (null) 3 3.300000
162
174
  2 3 1 -4 4 4.400000
@@ -170,6 +182,7 @@ class GroupTest < Test::Unit::TestCase
170
182
  group = @table.group(:group_key1, :group_key2)
171
183
  assert_equal(<<-TABLE, group.aggregate("count(int)", "sum(uint)").to_s)
172
184
  group_key1 group_key2 count(int) sum(uint)
185
+ (uint8) (uint8) (int64) (uint64)
173
186
  0 1 1 2 1
174
187
  1 2 1 0 3
175
188
  2 3 1 1 4
data/test/test-schema.rb CHANGED
@@ -95,7 +95,7 @@ class SchemaTest < Test::Unit::TestCase
95
95
 
96
96
  test("[invalid]") do
97
97
  invalid = []
98
- message = "field name or index must be String, Symbol or Integer"
98
+ message = +"field name or index must be String, Symbol or Integer"
99
99
  message << ": <#{invalid.inspect}>"
100
100
  assert_raise(ArgumentError.new(message)) do
101
101
  @schema[invalid]