vertica 0.12.0 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -20
- data/lib/vertica.rb +15 -8
- data/lib/vertica/column.rb +32 -13
- data/lib/vertica/connection.rb +143 -119
- data/lib/vertica/{messages/backend_messages → protocol/backend}/authentication.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/backend_key_data.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/bind_complete.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/close_complete.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/command_complete.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/copy_in_response.rb +2 -2
- data/lib/vertica/{messages/backend_messages → protocol/backend}/data_row.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/empty_query_response.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/error_response.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/no_data.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/notice_response.rb +6 -6
- data/lib/vertica/{messages/backend_messages → protocol/backend}/parameter_description.rb +2 -2
- data/lib/vertica/{messages/backend_messages → protocol/backend}/parameter_status.rb +3 -3
- data/lib/vertica/{messages/backend_messages → protocol/backend}/parse_complete.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/portal_suspended.rb +1 -1
- data/lib/vertica/{messages/backend_messages → protocol/backend}/ready_for_query.rb +2 -2
- data/lib/vertica/{messages/backend_messages → protocol/backend}/row_description.rb +9 -9
- data/lib/vertica/{messages/backend_messages → protocol/backend}/unknown.rb +1 -1
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/bind.rb +2 -3
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/cancel_request.rb +3 -4
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/close.rb +3 -3
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/copy_data.rb +6 -6
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/copy_done.rb +1 -1
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/copy_fail.rb +5 -5
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/describe.rb +3 -3
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/execute.rb +3 -3
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/flush.rb +1 -1
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/parse.rb +3 -3
- data/lib/vertica/protocol/frontend/password.rb +32 -0
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/query.rb +3 -4
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/ssl_request.rb +2 -2
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/startup.rb +2 -3
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/sync.rb +1 -1
- data/lib/vertica/{messages/frontend_messages → protocol/frontend}/terminate.rb +1 -1
- data/lib/vertica/protocol/message.rb +86 -0
- data/lib/vertica/query.rb +63 -44
- data/lib/vertica/result.rb +25 -47
- data/lib/vertica/row.rb +44 -0
- data/lib/vertica/row_description.rb +102 -0
- data/lib/vertica/version.rb +1 -1
- data/test/connection.yml.example +8 -8
- data/test/functional/functional_connection_test.rb +178 -0
- data/test/functional/{query_test.rb → functional_query_test.rb} +64 -67
- data/test/functional/functional_value_conversion_test.rb +117 -0
- data/test/test_helper.rb +1 -1
- data/test/unit/backend_message_test.rb +57 -57
- data/test/unit/column_test.rb +23 -23
- data/test/unit/frontend_message_test.rb +5 -5
- data/test/unit/quoting_test.rb +16 -5
- data/test/unit/result_test.rb +61 -0
- data/test/unit/row_description_test.rb +56 -0
- data/test/unit/row_test.rb +31 -0
- data/vertica.gemspec +1 -0
- metadata +67 -45
- data/lib/vertica/messages/frontend_messages/password.rb +0 -34
- data/lib/vertica/messages/message.rb +0 -79
- data/test/functional/connection_test.rb +0 -128
- data/test/functional/value_conversion_test.rb +0 -88
@@ -1,5 +1,5 @@
|
|
1
1
|
module Vertica
|
2
|
-
module
|
2
|
+
module Protocol
|
3
3
|
|
4
4
|
class Startup < FrontendMessage
|
5
5
|
message_id nil
|
@@ -10,13 +10,12 @@ module Vertica
|
|
10
10
|
@options = options
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def message_body
|
14
14
|
str = [Vertica::PROTOCOL_VERSION].pack('N')
|
15
15
|
str << ["user", @user].pack('Z*Z*') if @user
|
16
16
|
str << ["database", @database].pack('Z*Z*') if @database
|
17
17
|
str << ["options", @options].pack('Z*Z*') if @options
|
18
18
|
str << [].pack('x')
|
19
|
-
message_string str
|
20
19
|
end
|
21
20
|
end
|
22
21
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Vertica
|
2
|
+
module Protocol
|
3
|
+
|
4
|
+
class Message
|
5
|
+
def self.message_id(message_id)
|
6
|
+
self.send(:define_method, :message_id) { message_id }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class BackendMessage < Message
|
11
|
+
MessageIdMap = {}
|
12
|
+
|
13
|
+
def self.factory(type, data)
|
14
|
+
if klass = MessageIdMap[type]
|
15
|
+
klass.new(data)
|
16
|
+
else
|
17
|
+
Protocol::Unknown.new(type, data)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.message_id(message_id)
|
22
|
+
super
|
23
|
+
MessageIdMap[message_id] = self
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(_data)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class FrontendMessage < Message
|
31
|
+
def to_bytes
|
32
|
+
prepend_message_header(message_body)
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def message_body
|
38
|
+
''
|
39
|
+
end
|
40
|
+
|
41
|
+
def prepend_message_header(msg)
|
42
|
+
if message_id
|
43
|
+
[message_id, 4 + msg.bytesize, msg].pack('aNa*')
|
44
|
+
else
|
45
|
+
[4 + msg.bytesize, msg].pack('Na*')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
require 'vertica/protocol/backend/authentication'
|
53
|
+
require 'vertica/protocol/backend/backend_key_data'
|
54
|
+
require 'vertica/protocol/backend/bind_complete'
|
55
|
+
require 'vertica/protocol/backend/close_complete'
|
56
|
+
require 'vertica/protocol/backend/command_complete'
|
57
|
+
require 'vertica/protocol/backend/data_row'
|
58
|
+
require 'vertica/protocol/backend/empty_query_response'
|
59
|
+
require 'vertica/protocol/backend/notice_response'
|
60
|
+
require 'vertica/protocol/backend/error_response'
|
61
|
+
require 'vertica/protocol/backend/no_data'
|
62
|
+
require 'vertica/protocol/backend/parameter_description'
|
63
|
+
require 'vertica/protocol/backend/parameter_status'
|
64
|
+
require 'vertica/protocol/backend/parse_complete'
|
65
|
+
require 'vertica/protocol/backend/portal_suspended'
|
66
|
+
require 'vertica/protocol/backend/ready_for_query'
|
67
|
+
require 'vertica/protocol/backend/row_description'
|
68
|
+
require 'vertica/protocol/backend/copy_in_response'
|
69
|
+
require 'vertica/protocol/backend/unknown'
|
70
|
+
|
71
|
+
require 'vertica/protocol/frontend/bind'
|
72
|
+
require 'vertica/protocol/frontend/cancel_request'
|
73
|
+
require 'vertica/protocol/frontend/close'
|
74
|
+
require 'vertica/protocol/frontend/describe'
|
75
|
+
require 'vertica/protocol/frontend/execute'
|
76
|
+
require 'vertica/protocol/frontend/flush'
|
77
|
+
require 'vertica/protocol/frontend/parse'
|
78
|
+
require 'vertica/protocol/frontend/password'
|
79
|
+
require 'vertica/protocol/frontend/query'
|
80
|
+
require 'vertica/protocol/frontend/ssl_request'
|
81
|
+
require 'vertica/protocol/frontend/startup'
|
82
|
+
require 'vertica/protocol/frontend/sync'
|
83
|
+
require 'vertica/protocol/frontend/terminate'
|
84
|
+
require 'vertica/protocol/frontend/copy_done'
|
85
|
+
require 'vertica/protocol/frontend/copy_fail'
|
86
|
+
require 'vertica/protocol/frontend/copy_data'
|
data/lib/vertica/query.rb
CHANGED
@@ -1,85 +1,104 @@
|
|
1
1
|
class Vertica::Query
|
2
2
|
|
3
|
-
attr_reader :connection, :sql, :
|
4
|
-
attr_accessor :row_handler, :copy_handler, :row_style
|
3
|
+
attr_reader :connection, :sql, :error, :result
|
5
4
|
|
6
|
-
def initialize(connection, sql,
|
5
|
+
def initialize(connection, sql, row_handler: nil, copy_handler: nil)
|
7
6
|
@connection, @sql = connection, sql
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
7
|
+
if row_handler.nil?
|
8
|
+
@buffer = []
|
9
|
+
@row_handler = lambda { |row| buffer_row(row) }
|
10
|
+
else
|
11
|
+
@row_handler = row_handler
|
12
|
+
end
|
13
|
+
@copy_handler = copy_handler
|
14
|
+
@error = nil
|
15
15
|
end
|
16
16
|
|
17
17
|
def run
|
18
|
-
@connection.write_message(Vertica::
|
18
|
+
@connection.write_message(Vertica::Protocol::Query.new(sql))
|
19
19
|
|
20
20
|
begin
|
21
21
|
process_message(message = @connection.read_message)
|
22
|
-
end until message.kind_of?(Vertica::
|
22
|
+
end until message.kind_of?(Vertica::Protocol::ReadyForQuery)
|
23
23
|
|
24
24
|
raise error unless error.nil?
|
25
25
|
return result
|
26
26
|
end
|
27
27
|
|
28
|
-
def write(data)
|
29
|
-
@connection.write_message(Vertica::Messages::CopyData.new(data))
|
30
|
-
return self
|
31
|
-
end
|
32
|
-
|
33
|
-
alias_method :<<, :write
|
34
|
-
|
35
28
|
def to_s
|
36
29
|
@sql
|
37
30
|
end
|
38
31
|
|
39
32
|
protected
|
40
33
|
|
34
|
+
def buffer_rows?
|
35
|
+
!!@buffer
|
36
|
+
end
|
37
|
+
|
41
38
|
def process_message(message)
|
42
39
|
case message
|
43
|
-
when Vertica::
|
40
|
+
when Vertica::Protocol::ErrorResponse
|
44
41
|
@error = Vertica::Error::QueryError.from_error_response(message, @sql)
|
45
|
-
when Vertica::
|
42
|
+
when Vertica::Protocol::EmptyQueryResponse
|
46
43
|
@error = Vertica::Error::EmptyQueryError.new("A SQL string was expected, but the given string was blank or only contained SQL comments.")
|
47
|
-
when Vertica::
|
48
|
-
|
49
|
-
when Vertica::
|
50
|
-
|
51
|
-
when Vertica::
|
52
|
-
|
53
|
-
when Vertica::
|
54
|
-
|
44
|
+
when Vertica::Protocol::CopyInResponse
|
45
|
+
handle_copy_in_response(message)
|
46
|
+
when Vertica::Protocol::RowDescription
|
47
|
+
handle_row_description(message)
|
48
|
+
when Vertica::Protocol::DataRow
|
49
|
+
handle_data_row(message)
|
50
|
+
when Vertica::Protocol::CommandComplete
|
51
|
+
handle_command_complete(message)
|
55
52
|
else
|
56
53
|
@connection.process_message(message)
|
57
54
|
end
|
58
55
|
end
|
59
56
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
57
|
+
def handle_row_description(message)
|
58
|
+
@row_description = Vertica::RowDescription.build(message)
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_data_row(message)
|
62
|
+
@row_handler.call(@row_description.build_row(message))
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle_command_complete(message)
|
66
|
+
if buffer_rows?
|
67
|
+
@result = Vertica::Result.build(row_description: @row_description, rows: @buffer, tag: message.tag)
|
68
|
+
@row_description, @buffer = nil, nil
|
69
|
+
else
|
70
|
+
@result = message.tag
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_copy_in_response(_message)
|
75
|
+
if @copy_handler.nil?
|
76
|
+
@connection.write_message(Vertica::Protocol::CopyFail.new('no handler provided'))
|
63
77
|
else
|
64
78
|
begin
|
65
|
-
|
66
|
-
|
67
|
-
else
|
68
|
-
@connection.write_message(Vertica::Messages::CopyDone.new)
|
69
|
-
end
|
79
|
+
@copy_handler.call(CopyFromStdinWriter.new(connection))
|
80
|
+
@connection.write_message(Vertica::Protocol::CopyDone.new)
|
70
81
|
rescue => e
|
71
|
-
@connection.write_message(Vertica::
|
82
|
+
@connection.write_message(Vertica::Protocol::CopyFail.new(e.message))
|
72
83
|
end
|
73
84
|
end
|
74
85
|
end
|
75
86
|
|
76
|
-
def
|
77
|
-
|
78
|
-
result.add_row(record) if buffer_rows?
|
79
|
-
row_handler.call(record) if row_handler
|
87
|
+
def buffer_row(row)
|
88
|
+
@buffer << row
|
80
89
|
end
|
81
90
|
|
82
|
-
|
83
|
-
|
91
|
+
class CopyFromStdinWriter
|
92
|
+
def initialize(connection)
|
93
|
+
@connection = connection
|
94
|
+
end
|
95
|
+
|
96
|
+
def write(data)
|
97
|
+
@connection.write_message(Vertica::Protocol::CopyData.new(data))
|
98
|
+
return self
|
99
|
+
end
|
100
|
+
|
101
|
+
alias_method :<<, :write
|
84
102
|
end
|
103
|
+
private_constant :CopyFromStdinWriter
|
85
104
|
end
|
data/lib/vertica/result.rb
CHANGED
@@ -1,70 +1,48 @@
|
|
1
1
|
class Vertica::Result
|
2
2
|
include Enumerable
|
3
3
|
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :row_description
|
5
5
|
attr_reader :rows
|
6
|
-
|
6
|
+
attr_reader :tag
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@rows = []
|
8
|
+
def initialize(row_description: nil, rows: nil, tag: nil)
|
9
|
+
@row_description, @rows, @tag = row_description, rows, tag
|
11
10
|
end
|
12
11
|
|
13
|
-
def
|
14
|
-
@
|
15
|
-
end
|
16
|
-
|
17
|
-
def format_row_as_hash(row_data)
|
18
|
-
row = {}
|
19
|
-
row_data.values.each_with_index do |value, idx|
|
20
|
-
col = columns.fetch(idx)
|
21
|
-
row[col.name] = col.convert(value)
|
22
|
-
end
|
23
|
-
row
|
12
|
+
def each(&block)
|
13
|
+
@rows.each(&block)
|
24
14
|
end
|
25
15
|
|
26
|
-
def
|
27
|
-
|
16
|
+
def empty?
|
17
|
+
@rows.empty?
|
28
18
|
end
|
29
19
|
|
30
|
-
def
|
31
|
-
|
32
|
-
row_data.values.each_with_index do |value, idx|
|
33
|
-
row << columns.fetch(idx).convert(value)
|
34
|
-
end
|
35
|
-
row
|
20
|
+
def size
|
21
|
+
@rows.length
|
36
22
|
end
|
37
23
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
24
|
+
alias_method :count, :size
|
25
|
+
alias_method :length, :size
|
41
26
|
|
42
|
-
def
|
43
|
-
|
27
|
+
def fetch(row_index, col = nil)
|
28
|
+
row = rows.fetch(row_index)
|
29
|
+
return row if col.nil?
|
30
|
+
row.fetch(col)
|
44
31
|
end
|
45
32
|
|
46
|
-
|
47
|
-
@rows.empty?
|
48
|
-
end
|
33
|
+
alias_method :[], :fetch
|
49
34
|
|
50
|
-
def
|
51
|
-
|
52
|
-
nil
|
53
|
-
else
|
54
|
-
@row_style == :array ? rows[0][0] : rows[0][columns[0].name]
|
55
|
-
end
|
35
|
+
def value
|
36
|
+
fetch(0, 0)
|
56
37
|
end
|
57
38
|
|
58
|
-
|
59
|
-
col.nil? ? row[row] : rows[row][col]
|
60
|
-
end
|
39
|
+
alias_method :the_value, :value
|
61
40
|
|
62
|
-
alias_method :
|
41
|
+
alias_method :columns, :row_description
|
63
42
|
|
64
|
-
def
|
65
|
-
|
43
|
+
def self.build(row_description: nil, rows: [], tag: nil)
|
44
|
+
row_description = Vertica::RowDescription.build(row_description)
|
45
|
+
rows = rows.map { |values| row_description.build_row(values) }
|
46
|
+
new(row_description: row_description, rows: rows, tag: tag)
|
66
47
|
end
|
67
|
-
|
68
|
-
alias_method :size, :row_count
|
69
|
-
alias_method :length, :row_count
|
70
48
|
end
|
data/lib/vertica/row.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
class Vertica::Row
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
def initialize(row_description, values)
|
5
|
+
@row_description, @values = row_description, values
|
6
|
+
end
|
7
|
+
|
8
|
+
def each(&block)
|
9
|
+
@values.each(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch(name_or_index)
|
13
|
+
@values.fetch(column_index(name_or_index))
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"<Vertica::Row#{@values.inspect}>"
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :[], :fetch
|
21
|
+
|
22
|
+
def to_a
|
23
|
+
@values.to_a
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_h(symbolize_keys: false)
|
27
|
+
@row_description.inject({}) do |carry, column|
|
28
|
+
key = symbolize_keys ? column.name.to_sym : column.name
|
29
|
+
carry.merge(key => fetch(column.name))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def column(name_or_index)
|
34
|
+
column_with_index(name_or_index).fetch(0)
|
35
|
+
end
|
36
|
+
|
37
|
+
def column_index(name_or_index)
|
38
|
+
column_with_index(name_or_index).fetch(1)
|
39
|
+
end
|
40
|
+
|
41
|
+
def column_with_index(name_or_index)
|
42
|
+
@row_description.column_with_index(name_or_index)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class Vertica::RowDescription
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
def self.build(columns)
|
5
|
+
case columns
|
6
|
+
when Vertica::Protocol::RowDescription
|
7
|
+
new(columns.fields.map { |fd| Vertica::Column.new(fd) })
|
8
|
+
when Vertica::RowDescription
|
9
|
+
columns
|
10
|
+
when Array
|
11
|
+
new(columns)
|
12
|
+
when nil
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Invalid list of columns: #{columns.inspect}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(columns)
|
20
|
+
@columns = columns
|
21
|
+
end
|
22
|
+
|
23
|
+
def column(name_or_index)
|
24
|
+
column_with_index(name_or_index).first
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :[], :column
|
28
|
+
|
29
|
+
def column_with_index(name_or_index)
|
30
|
+
columns_index.fetch(name_or_index)
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
@columns.each(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def size
|
38
|
+
@columns.size
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :length, :size
|
42
|
+
|
43
|
+
def to_a
|
44
|
+
@columns.clone
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_h(symbolize_keys: false)
|
48
|
+
@columns.inject({}) do |carry, column|
|
49
|
+
key = symbolize_keys ? column.name.to_sym : column.name
|
50
|
+
carry.merge(key => column)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_row(values)
|
55
|
+
case values
|
56
|
+
when Vertica::Row
|
57
|
+
values
|
58
|
+
|
59
|
+
when Vertica::Protocol::DataRow
|
60
|
+
converted_values = @columns.map.with_index do |column, index|
|
61
|
+
column.convert(values.values.fetch(index))
|
62
|
+
end
|
63
|
+
Vertica::Row.new(self, converted_values)
|
64
|
+
|
65
|
+
when Array
|
66
|
+
raise ArgumentError, "Number of values does not match row description" if values.size != size
|
67
|
+
Vertica::Row.new(self, values)
|
68
|
+
|
69
|
+
when Hash
|
70
|
+
raise ArgumentError, "Number of values does not match row description" if values.size != size
|
71
|
+
values_as_array = @columns.map { |column| values[column.name] || values[column.name.to_sym] }
|
72
|
+
Vertica::Row.new(self, values_as_array)
|
73
|
+
|
74
|
+
else
|
75
|
+
raise ArgumentError, "Don't know how to build a row from a #{values.class.name} instance"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def eql?(other)
|
80
|
+
self.class === other && other.to_a == self.to_a
|
81
|
+
end
|
82
|
+
|
83
|
+
alias_method :==, :eql?
|
84
|
+
|
85
|
+
def hash
|
86
|
+
@columns.hash
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def columns_index
|
92
|
+
@columns_index ||= begin
|
93
|
+
result = {}
|
94
|
+
@columns.each_with_index do |column, index|
|
95
|
+
result[index] = [column, index]
|
96
|
+
result[column.name.to_s] = [column, index]
|
97
|
+
result[column.name.to_sym] = [column, index]
|
98
|
+
end
|
99
|
+
result
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|