vertica 0.12.0 → 1.0.0.rc1

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -20
  3. data/lib/vertica.rb +15 -8
  4. data/lib/vertica/column.rb +32 -13
  5. data/lib/vertica/connection.rb +143 -119
  6. data/lib/vertica/{messages/backend_messages → protocol/backend}/authentication.rb +1 -1
  7. data/lib/vertica/{messages/backend_messages → protocol/backend}/backend_key_data.rb +1 -1
  8. data/lib/vertica/{messages/backend_messages → protocol/backend}/bind_complete.rb +1 -1
  9. data/lib/vertica/{messages/backend_messages → protocol/backend}/close_complete.rb +1 -1
  10. data/lib/vertica/{messages/backend_messages → protocol/backend}/command_complete.rb +1 -1
  11. data/lib/vertica/{messages/backend_messages → protocol/backend}/copy_in_response.rb +2 -2
  12. data/lib/vertica/{messages/backend_messages → protocol/backend}/data_row.rb +1 -1
  13. data/lib/vertica/{messages/backend_messages → protocol/backend}/empty_query_response.rb +1 -1
  14. data/lib/vertica/{messages/backend_messages → protocol/backend}/error_response.rb +1 -1
  15. data/lib/vertica/{messages/backend_messages → protocol/backend}/no_data.rb +1 -1
  16. data/lib/vertica/{messages/backend_messages → protocol/backend}/notice_response.rb +6 -6
  17. data/lib/vertica/{messages/backend_messages → protocol/backend}/parameter_description.rb +2 -2
  18. data/lib/vertica/{messages/backend_messages → protocol/backend}/parameter_status.rb +3 -3
  19. data/lib/vertica/{messages/backend_messages → protocol/backend}/parse_complete.rb +1 -1
  20. data/lib/vertica/{messages/backend_messages → protocol/backend}/portal_suspended.rb +1 -1
  21. data/lib/vertica/{messages/backend_messages → protocol/backend}/ready_for_query.rb +2 -2
  22. data/lib/vertica/{messages/backend_messages → protocol/backend}/row_description.rb +9 -9
  23. data/lib/vertica/{messages/backend_messages → protocol/backend}/unknown.rb +1 -1
  24. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/bind.rb +2 -3
  25. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/cancel_request.rb +3 -4
  26. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/close.rb +3 -3
  27. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/copy_data.rb +6 -6
  28. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/copy_done.rb +1 -1
  29. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/copy_fail.rb +5 -5
  30. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/describe.rb +3 -3
  31. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/execute.rb +3 -3
  32. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/flush.rb +1 -1
  33. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/parse.rb +3 -3
  34. data/lib/vertica/protocol/frontend/password.rb +32 -0
  35. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/query.rb +3 -4
  36. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/ssl_request.rb +2 -2
  37. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/startup.rb +2 -3
  38. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/sync.rb +1 -1
  39. data/lib/vertica/{messages/frontend_messages → protocol/frontend}/terminate.rb +1 -1
  40. data/lib/vertica/protocol/message.rb +86 -0
  41. data/lib/vertica/query.rb +63 -44
  42. data/lib/vertica/result.rb +25 -47
  43. data/lib/vertica/row.rb +44 -0
  44. data/lib/vertica/row_description.rb +102 -0
  45. data/lib/vertica/version.rb +1 -1
  46. data/test/connection.yml.example +8 -8
  47. data/test/functional/functional_connection_test.rb +178 -0
  48. data/test/functional/{query_test.rb → functional_query_test.rb} +64 -67
  49. data/test/functional/functional_value_conversion_test.rb +117 -0
  50. data/test/test_helper.rb +1 -1
  51. data/test/unit/backend_message_test.rb +57 -57
  52. data/test/unit/column_test.rb +23 -23
  53. data/test/unit/frontend_message_test.rb +5 -5
  54. data/test/unit/quoting_test.rb +16 -5
  55. data/test/unit/result_test.rb +61 -0
  56. data/test/unit/row_description_test.rb +56 -0
  57. data/test/unit/row_test.rb +31 -0
  58. data/vertica.gemspec +1 -0
  59. metadata +67 -45
  60. data/lib/vertica/messages/frontend_messages/password.rb +0 -34
  61. data/lib/vertica/messages/message.rb +0 -79
  62. data/test/functional/connection_test.rb +0 -128
  63. data/test/functional/value_conversion_test.rb +0 -88
@@ -1,5 +1,5 @@
1
1
  module Vertica
2
- module Messages
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 to_bytes
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
@@ -1,5 +1,5 @@
1
1
  module Vertica
2
- module Messages
2
+ module Protocol
3
3
  class Sync < FrontendMessage
4
4
  message_id 'S'
5
5
  end
@@ -1,5 +1,5 @@
1
1
  module Vertica
2
- module Messages
2
+ module Protocol
3
3
  class Terminate < FrontendMessage
4
4
  message_id 'X'
5
5
  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'
@@ -1,85 +1,104 @@
1
1
  class Vertica::Query
2
2
 
3
- attr_reader :connection, :sql, :result, :error
4
- attr_accessor :row_handler, :copy_handler, :row_style
3
+ attr_reader :connection, :sql, :error, :result
5
4
 
6
- def initialize(connection, sql, options = {})
5
+ def initialize(connection, sql, row_handler: nil, copy_handler: nil)
7
6
  @connection, @sql = connection, sql
8
-
9
- @row_style = options[:row_style] || @connection.row_style || :hash
10
- @row_handler = options[:row_handler]
11
- @copy_handler = options[:copy_handler]
12
-
13
- @error = nil
14
- @result = Vertica::Result.new(row_style)
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::Messages::Query.new(sql))
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::Messages::ReadyForQuery)
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::Messages::ErrorResponse
40
+ when Vertica::Protocol::ErrorResponse
44
41
  @error = Vertica::Error::QueryError.from_error_response(message, @sql)
45
- when Vertica::Messages::EmptyQueryResponse
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::Messages::CopyInResponse
48
- handle_copy_from_stdin
49
- when Vertica::Messages::RowDescription
50
- result.descriptions = message
51
- when Vertica::Messages::DataRow
52
- handle_datarow(message)
53
- when Vertica::Messages::CommandComplete
54
- result.tag = message.tag
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 handle_copy_from_stdin
61
- if copy_handler.nil?
62
- @connection.write_message(Vertica::Messages::CopyFail.new('no handler provided'))
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
- if copy_handler.call(self) == :rollback
66
- @connection.write_message(Vertica::Messages::CopyFail.new("rollback"))
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::Messages::CopyFail.new(e.message))
82
+ @connection.write_message(Vertica::Protocol::CopyFail.new(e.message))
72
83
  end
73
84
  end
74
85
  end
75
86
 
76
- def handle_datarow(datarow_message)
77
- record = result.format_row(datarow_message)
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
- def buffer_rows?
83
- row_handler.nil? && copy_handler.nil?
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
@@ -1,70 +1,48 @@
1
1
  class Vertica::Result
2
2
  include Enumerable
3
3
 
4
- attr_reader :columns
4
+ attr_reader :row_description
5
5
  attr_reader :rows
6
- attr_accessor :tag, :notice
6
+ attr_reader :tag
7
7
 
8
- def initialize(row_style = :hash)
9
- @row_style = row_style
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 descriptions=(message)
14
- @columns = message.fields.map { |fd| Vertica::Column.new(fd) }
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 format_row(row_data)
27
- send("format_row_as_#{@row_style}", row_data)
16
+ def empty?
17
+ @rows.empty?
28
18
  end
29
19
 
30
- def format_row_as_array(row_data)
31
- row = []
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
- def add_row(row)
39
- @rows << row
40
- end
24
+ alias_method :count, :size
25
+ alias_method :length, :size
41
26
 
42
- def each_row(&block)
43
- @rows.each(&block)
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
- def empty?
47
- @rows.empty?
48
- end
33
+ alias_method :[], :fetch
49
34
 
50
- def the_value
51
- if empty?
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
- def [](row, col = nil)
59
- col.nil? ? row[row] : rows[row][col]
60
- end
39
+ alias_method :the_value, :value
61
40
 
62
- alias_method :each, :each_row
41
+ alias_method :columns, :row_description
63
42
 
64
- def row_count
65
- @rows.size
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
@@ -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