vertica 1.0.0.rc1 → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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