vertica 1.0.0.rc1 → 1.0.0.rc2

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.
@@ -0,0 +1,146 @@
1
+ # Class that represents a data type of a column.
2
+ #
3
+ # This gem is only able to handle registered types. Types are registered using {.register}.
4
+ # If an unregistered type is encountered, the library will raise {Vertica::Error::UnknownTypeError}.
5
+ #
6
+ # @example Handling an unknown OID:
7
+ # Vertica::DataType.register 12345, 'fancy_type', lambda { |bytes| ... }
8
+ #
9
+ # @attr_reader oid [Integer] The object ID of the type.
10
+ # @attr_reader name [String] The name of the type as it can be used in SQL.
11
+ # @attr_reader size [Integer] The size of the type.
12
+ # @attr_reader modifier [Integer] A modifier of the type.
13
+ # @attr_reader format [Integer] The serialization format of this type.
14
+ # @attr_reader deserializer [Proc] Proc that can deserialize values of this type coming from the database.
15
+ #
16
+ # @see Vertica::Column
17
+ class Vertica::DataType
18
+
19
+ class << self
20
+ # @return [Hash<Integer, Hash>] The Vertica types that are registered with this library, indexed by OID.
21
+ # @see .register
22
+ attr_accessor :registered_types
23
+
24
+ # Registers a new type by OID.
25
+ #
26
+ # @param oid [Integer] The object ID of the type.
27
+ # @param name [String] The name of the type as it can be used in SQL.
28
+ # @param deserializer [Proc] Proc that can deserialize values of this type coming
29
+ # from the database.
30
+ # @return [void]
31
+ def register(oid, name, deserializer = self.default_deserializer)
32
+ self.registered_types ||= {}
33
+ self.registered_types[oid] = { oid: oid, name: name, deserializer: TYPE_DESERIALIZERS.fetch(deserializer) }
34
+ end
35
+
36
+ # Builds a new type instance based on an OID.
37
+ # @param (see Vertica::DataType#initialize)
38
+ # @return [Vertica::DataType]
39
+ # @raise [Vertica::Error::UnknownTypeError] if the OID is not registered.
40
+ def build(oid: nil, **kwargs)
41
+ args = registered_types.fetch(oid) do |unknown_oid|
42
+ raise Vertica::Error::UnknownTypeError, "Unknown type OID: #{unknown_oid}"
43
+ end
44
+
45
+ new(args.merge(kwargs))
46
+ end
47
+
48
+ # The name of the default deserializer proc.
49
+ # @return [Symbol]
50
+ def default_deserializer
51
+ :generic
52
+ end
53
+ end
54
+
55
+ attr_reader :oid, :name, :size, :modifier, :format, :deserializer
56
+
57
+ # Instantiates a new DataType.
58
+ #
59
+ # @param oid [Integer] The object ID of the type.
60
+ # @param name [String] The name of the type as it can be used in SQL.
61
+ # @param size [Integer] The size of the type.
62
+ # @param modifier [Integer] A modifier of the type.
63
+ # @param format [Integer] The serialization format of this type.
64
+ # @param deserializer [Proc] Proc that can deserialize values of this type coming
65
+ # from the database.
66
+ # @see .build
67
+ def initialize(oid: nil, name: nil, size: nil, modifier: nil, format: 0, deserializer: nil)
68
+ @oid, @name, @size, @modifier, @format, @deserializer = oid, name, size, modifier, format, deserializer
69
+ end
70
+
71
+ # @return [Integer] Returns a hash digtest of this object.
72
+ def hash
73
+ [oid, size, modifier, format].hash
74
+ end
75
+
76
+ # @return [Boolean] Returns true iff this record is equal to the other provided object
77
+ def eql?(other)
78
+ other.kind_of?(Vertica::DataType) && oid == other.oid && size == other.size &&
79
+ modifier == other.modifier && other.format == format
80
+ end
81
+
82
+ alias_method :==, :eql?
83
+
84
+ # Deserializes a value of this type as returned by the server.
85
+ # @param bytes [String, nil] The representation of the value returned by the server.
86
+ # @return [Object] The Ruby-value taht repesents the value returned from the DB.
87
+ # @see Vertica::Protocol::DataRow
88
+ def deserialize(bytes)
89
+ return nil if bytes.nil?
90
+ deserializer.call(bytes)
91
+ end
92
+
93
+ # @return [String] Returns a user-consumable string representation of this type.
94
+ def inspect
95
+ "#<#{self.class.name}:#{oid} #{sql.inspect}>"
96
+ end
97
+
98
+ # Returns a SQL representation of this type.
99
+ # @return [String]
100
+ # @todo Take size and modifier into account.
101
+ def sql
102
+ name
103
+ end
104
+
105
+ TYPE_DESERIALIZERS = {
106
+ generic: lambda { |bytes| bytes },
107
+ bool: lambda { |bytes|
108
+ case bytes
109
+ when 't'; true
110
+ when 'f'; false
111
+ else raise ArgumentError, "Cannot convert #{bytes.inspect} to a boolean value"
112
+ end
113
+ },
114
+ integer: lambda { |bytes| Integer(bytes) },
115
+ float: lambda { |bytes|
116
+ case bytes
117
+ when 'Infinity'; Float::INFINITY
118
+ when '-Infinity'; -Float::INFINITY
119
+ when 'NaN'; Float::NAN
120
+ else Float(bytes)
121
+ end
122
+ },
123
+ bigdecimal: lambda { |bytes| BigDecimal(bytes) },
124
+ unicode_string: lambda { |bytes| bytes.force_encoding(Encoding::UTF_8) },
125
+ binary_string: lambda { |bytes| bytes.gsub(/\\([0-3][0-7][0-7])/) { $1.to_i(8).chr }.force_encoding(Encoding::BINARY) },
126
+ date: lambda { |bytes| Date.parse(bytes) },
127
+ timestamp: lambda { |bytes| Time.parse(bytes) },
128
+ }.freeze
129
+
130
+ private_constant :TYPE_DESERIALIZERS
131
+ end
132
+
133
+ Vertica::DataType.register 5, 'bool', :bool
134
+ Vertica::DataType.register 6, 'integer', :integer
135
+ Vertica::DataType.register 7, 'float', :float
136
+ Vertica::DataType.register 8, 'char', :unicode_string
137
+ Vertica::DataType.register 9, 'varchar', :unicode_string
138
+ Vertica::DataType.register 10, 'date', :date
139
+ Vertica::DataType.register 11, 'time'
140
+ Vertica::DataType.register 12, 'timestamp', :timestamp
141
+ Vertica::DataType.register 13, 'timestamp_tz', :timestamp
142
+ Vertica::DataType.register 14, 'time_tz'
143
+ Vertica::DataType.register 15, 'interval'
144
+ Vertica::DataType.register 16, 'numeric', :bigdecimal
145
+ Vertica::DataType.register 17, 'bytes', :binary_string
146
+ Vertica::DataType.register 115, 'long varchar', :unicode_string
data/lib/vertica/error.rb CHANGED
@@ -8,6 +8,7 @@ class Vertica::Error < StandardError
8
8
  class EmptyQueryError < Vertica::Error; end
9
9
  class TimedOutError < ConnectionError; end
10
10
  class UnknownTypeError < Vertica::Error; end
11
+ class DuplicateColumnName < Vertica::Error; end
11
12
 
12
13
  class SynchronizeError < Vertica::Error
13
14
  attr_reader :running_job, :requested_job
@@ -3,19 +3,10 @@ module Vertica
3
3
  class CommandComplete < BackendMessage
4
4
  message_id 'C'
5
5
 
6
- attr_reader :tag, :rows, :oid
6
+ attr_reader :tag
7
7
 
8
8
  def initialize(data)
9
- case data = data.unpack('Z*').first
10
- when /^INSERT /
11
- @tag, oid, rows = data.split(' ', 3)
12
- @oid, @rows = oid.to_i, rows.to_i
13
- when /^DELETE /, /^UPDATE /, /^MOVE /, /^FETCH /, /^COPY /
14
- @tag, @rows = data.split(' ', 2)
15
- @rows = rows.to_i
16
- else
17
- @tag = data
18
- end
9
+ @tag = data.unpack('Z*').first
19
10
  end
20
11
  end
21
12
  end
@@ -18,7 +18,7 @@ module Vertica
18
18
  :data_type_oid => field_info[3],
19
19
  :data_type_size => field_info[4],
20
20
  :data_type_modifier => field_info[5],
21
- :format_code => field_info[6],
21
+ :data_format => field_info[6],
22
22
  }
23
23
 
24
24
  pos += 19 + field_info[0].size
data/lib/vertica/query.rb CHANGED
@@ -1,38 +1,56 @@
1
+ # The Query class handles the state of the connection while a SQL query is being executed.
2
+ # The connection should call {#run} and it will block until the query has been handled by
3
+ # the connection, after which control will be given back to the {Connection} instance.
4
+ #
5
+ # @note This class is for internal use only, you should never interact with this class directly.
6
+ #
7
+ # @see Vertica::Connection#query
8
+ # @see Vertica::Connection#copy
1
9
  class Vertica::Query
2
10
 
3
- attr_reader :connection, :sql, :error, :result
4
-
11
+ # Instantiates a new query
12
+ # @param connection [Vertica::Connection] The connection to use for the query
13
+ # @param sql [String] The SQL statement to execute.
14
+ # @param row_handler [Proc, nil] Callback that will be called for every row that is returned.
15
+ # If no handler is provided, all rows will be buffered so a {Vertica::Result} can be returned.
16
+ #
17
+ # @param copy_handler [Proc, nil] Callback that will be called when the connection is ready
18
+ # to receive data for a `COPY ... FROM STDIN` statement.
5
19
  def initialize(connection, sql, row_handler: nil, copy_handler: nil)
6
20
  @connection, @sql = connection, sql
7
- if row_handler.nil?
8
- @buffer = []
9
- @row_handler = lambda { |row| buffer_row(row) }
10
- else
11
- @row_handler = row_handler
12
- end
21
+ @buffer = row_handler.nil? && copy_handler.nil? ? [] : nil
22
+ @row_handler = row_handler || lambda { |row| buffer_row(row) }
13
23
  @copy_handler = copy_handler
14
- @error = nil
24
+ @row_description, @error = nil, nil
15
25
  end
16
26
 
27
+ # Sends the query to the server, and processes the results.
28
+ # @return [String] For an unbuffered query, the type of SQL command will be return as a string
29
+ # (e.g. `"SELECT"` or `"COPY"`).
30
+ # @return [Vertica::Result] For a buffered query, this method will return a {Vertica::Result} instance
31
+ # @raise [Vertica::Error::ConnectionError] if the connection between client and
32
+ # server fails.
33
+ # @raise [Vertica::Error::QueryError] if the server cannot evaluate the query.
17
34
  def run
18
- @connection.write_message(Vertica::Protocol::Query.new(sql))
35
+ @connection.write_message(Vertica::Protocol::Query.new(@sql))
19
36
 
20
37
  begin
21
38
  process_message(message = @connection.read_message)
22
39
  end until message.kind_of?(Vertica::Protocol::ReadyForQuery)
23
40
 
24
- raise error unless error.nil?
25
- return result
41
+ raise @error unless @error.nil?
42
+ return @result
26
43
  end
27
44
 
28
- def to_s
29
- @sql
45
+ # @return [String] Returns a user-consumable string representation of this query instance.
46
+ def inspect
47
+ "#<Vertica::Query:#{object_id} sql=#{@sql.inspect}>"
30
48
  end
31
49
 
32
- protected
50
+ private
33
51
 
34
52
  def buffer_rows?
35
- !!@buffer
53
+ @buffer.is_a?(Array)
36
54
  end
37
55
 
38
56
  def process_message(message)
@@ -64,7 +82,7 @@ class Vertica::Query
64
82
 
65
83
  def handle_command_complete(message)
66
84
  if buffer_rows?
67
- @result = Vertica::Result.build(row_description: @row_description, rows: @buffer, tag: message.tag)
85
+ @result = Vertica::Result.new(row_description: @row_description, rows: @buffer, tag: message.tag)
68
86
  @row_description, @buffer = nil, nil
69
87
  else
70
88
  @result = message.tag
@@ -76,7 +94,7 @@ class Vertica::Query
76
94
  @connection.write_message(Vertica::Protocol::CopyFail.new('no handler provided'))
77
95
  else
78
96
  begin
79
- @copy_handler.call(CopyFromStdinWriter.new(connection))
97
+ @copy_handler.call(CopyFromStdinWriter.new(@connection))
80
98
  @connection.write_message(Vertica::Protocol::CopyDone.new)
81
99
  rescue => e
82
100
  @connection.write_message(Vertica::Protocol::CopyFail.new(e.message))
@@ -1,22 +1,62 @@
1
+ # Class that represents a buffered resultset.
2
+ #
3
+ # This class implements the Enumerable interface for easy iteration through
4
+ # the rows. You can also address specific values in the result using {#fetch}.
5
+ # To leanr more about the shape of the result {#row_description} will give you
6
+ # an ordered list of all the {Vertica::Column}s in this result.
7
+ #
8
+ # @example Iterating over the rows
9
+ # result = connection.query("SELECT name, id")
10
+ # result.each do |row|
11
+ # puts "#{row[:id]}: #{row[:name]}"
12
+ # end
13
+ #
14
+ # @example Fetching specific values in the result
15
+ # result = connection.query('SELECT id, name FROM people WHERE id = 1')
16
+ # name = result.fetch(0, 'name')
17
+ # id = result[0,0]
18
+ #
19
+ # @example Retrieving the only value in a result
20
+ # average_salery = connection.query("SELECT AVG(salery) FROM employees").the_value
21
+ #
22
+ # @attr_reader [Vertica::RowDescription] row_description The columns in the result.
23
+ # @attr_reader [String] tag The kind of SQL command that produced this reuslt.
24
+ #
25
+ # @see Vertica::Connection#query
1
26
  class Vertica::Result
2
27
  include Enumerable
3
28
 
4
- attr_reader :row_description
5
- attr_reader :rows
6
- attr_reader :tag
29
+ attr_reader :row_description, :tag
7
30
 
31
+ # Initializes a new Vertica::Result instance.
32
+ #
33
+ # The constructor assumes that the row description, and the list of rows match up.
34
+ # If you're unsure, use {Vertica::Result.build} which will assert this is the case.
35
+ #
36
+ # @param row_description [Vertica::RowDescription] The description of the rows
37
+ # @param rows [Array<Vertica::Row>] The array of rows
38
+ # @param tag [String] The kind of command that returned this result.
39
+ # @see Vertica::Result.build
8
40
  def initialize(row_description: nil, rows: nil, tag: nil)
9
41
  @row_description, @rows, @tag = row_description, rows, tag
10
42
  end
11
43
 
44
+ # Iterates through the resultset. This class also includes the `Enumerable`
45
+ # interface, which means you can use all the `Enumerable` methods like `map`
46
+ # and `inject` as well.
47
+ # @yield The provided block will be called for every row in the resutset.
48
+ # @yieldparam row [Vertica::Row]
49
+ # @return [void]
12
50
  def each(&block)
13
51
  @rows.each(&block)
14
52
  end
15
53
 
54
+ # @return [Boolean] Returns `true` if the result has no rows.
16
55
  def empty?
17
56
  @rows.empty?
18
57
  end
19
58
 
59
+ # @return [Integer] The number of rows in this result
20
60
  def size
21
61
  @rows.length
22
62
  end
@@ -24,14 +64,31 @@ class Vertica::Result
24
64
  alias_method :count, :size
25
65
  alias_method :length, :size
26
66
 
27
- def fetch(row_index, col = nil)
28
- row = rows.fetch(row_index)
67
+ # Retrieves a row or value from the result.
68
+ # @return [Vertica::Row, Object]
69
+ #
70
+ # @overload fetch(row)
71
+ # Returns a row from the result.
72
+ # @param row [Integer] The 0-indexed row number.
73
+ # @raise [IndexError] if the row index is out of bounds.
74
+ # @return [Vertica::Row]
75
+ # @overload fetch(row, column)
76
+ # Returns a singular value from the result.
77
+ # @param row [Integer] The 0-indexed row number.
78
+ # @param col [Symbol, String, Integer] The name or index of the column.
79
+ # @raise [IndexError] if the row index is out of bounds.
80
+ # @raise [KeyError] if the requested column is not part of the result
81
+ # @return The value at the given row and column in the result.
82
+ def fetch(row, col = nil)
83
+ row = @rows.fetch(row)
29
84
  return row if col.nil?
30
85
  row.fetch(col)
31
86
  end
32
87
 
33
88
  alias_method :[], :fetch
34
89
 
90
+ # Shorthand to return the value of a query that only returns a single value.
91
+ # @return The first value of the first row, i.e. `fetch(0, 0)`.
35
92
  def value
36
93
  fetch(0, 0)
37
94
  end
@@ -40,6 +97,13 @@ class Vertica::Result
40
97
 
41
98
  alias_method :columns, :row_description
42
99
 
100
+ # Builds a {Vertica::Result} from a row description and a list of compatible rows.
101
+ # @param row_description An object that can be built into a {Vertica::RowDescription}.
102
+ # See {Vertica::RowDescription.build} for more info
103
+ # @param rows [Array] An array of objects that can be turned into a {Vertica::Row}.
104
+ # See {Vertica::RowDescription#build_row} for more info
105
+ # @param tag [String] The type of SQL command that yielded the result.
106
+ # @return [Vertica::Result]
43
107
  def self.build(row_description: nil, rows: [], tag: nil)
44
108
  row_description = Vertica::RowDescription.build(row_description)
45
109
  rows = rows.map { |values| row_description.build_row(values) }
data/lib/vertica/row.rb CHANGED
@@ -1,35 +1,86 @@
1
+ # Class to represent a row returns by a query.
2
+ #
3
+ # Row instances can either be yielded to the block passed to {Vertica::Connection#query},
4
+ # or be part of a buffered {Vertica::Result} returned by {Vertica::Connection#query}.
5
+ #
6
+ # @attr_reader row_description [Vertica::RowDescription] The ordered list of columns this
7
+ # row conforms to.
8
+ #
9
+ # @see Vertica::RowDescription#build_row
1
10
  class Vertica::Row
2
11
  include Enumerable
3
12
 
13
+ attr_reader :row_description
14
+
15
+ # Initializes a new row instance for a given row description and values. The
16
+ # number of values MUST match the number of columns in the row description.
17
+ # @param row_description [Vertica::RowDescription] The row description the
18
+ # values will conform to.
19
+ # @param values [Array] The values for the columns in the row description
20
+ # @see Vertica::RowDescription#build_row
4
21
  def initialize(row_description, values)
5
22
  @row_description, @values = row_description, values
6
23
  end
7
24
 
25
+ # Iterates over the values in this row.
26
+ # @yield [value] The provided block will get called with the values in the order of
27
+ # the columns in the row_description.
8
28
  def each(&block)
9
29
  @values.each(&block)
10
30
  end
11
31
 
32
+ # Fetches a value from this row.
33
+ # @param name_or_index [Symbol, String, Integer] The name of the column as string or symbol,
34
+ # or the index of the column in the row description.
35
+ # @raise KeyError A KeyError is raised if the field connot be found.
12
36
  def fetch(name_or_index)
13
37
  @values.fetch(column_index(name_or_index))
14
38
  end
15
39
 
16
- def inspect
17
- "<Vertica::Row#{@values.inspect}>"
18
- end
19
-
20
40
  alias_method :[], :fetch
21
41
 
42
+ # Returns an array representation of the row. The values will
43
+ # be ordered like the order of the fields in the {#row_description}.
44
+ # @return [Array]
22
45
  def to_a
23
- @values.to_a
46
+ @values
24
47
  end
25
48
 
49
+ # Returns a hash representation of this rows, using the name of the
50
+ # fields as keys.
51
+ # @param symbolize_keys [true, false] Set to true to use symbols instead
52
+ # of strings for the field names.
53
+ # @return [Hash]
54
+ # @raise [Vertica::Error::DuplicateColumnName] If the row contains multiple
55
+ # columns with the same name
26
56
  def to_h(symbolize_keys: false)
27
57
  @row_description.inject({}) do |carry, column|
28
58
  key = symbolize_keys ? column.name.to_sym : column.name
29
- carry.merge(key => fetch(column.name))
59
+ raise Vertica::Error::DuplicateColumnName, "Column with name #{key} occurs more than once in this row." if carry.key?(key)
60
+ carry[key] = fetch(column.name)
61
+ carry
30
62
  end
31
63
  end
32
64
 
65
+ # @return [Boolean] Returns true iff this record is equal to the other provided object
66
+ def eql?(other)
67
+ other.kind_of?(Vertica::Row) && other.row_description == row_description && other.to_a == to_a
68
+ end
69
+
70
+ alias_method :==, :eql?
71
+
72
+ # @return [Integer] Returns a hash digtest of this object.
73
+ def hash
74
+ [row_description, values].hash
75
+ end
76
+
77
+ # @return [String] Returns a user-consumable string representation of this row.
78
+ def inspect
79
+ "#<#{self.class.name}#{@values.inspect}>"
80
+ end
81
+
82
+ private
83
+
33
84
  def column(name_or_index)
34
85
  column_with_index(name_or_index).fetch(0)
35
86
  end