vertica 0.7.4

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 (48) hide show
  1. data/LICENSE +19 -0
  2. data/README.textile +69 -0
  3. data/Rakefile +44 -0
  4. data/lib/vertica/bit_helper.rb +51 -0
  5. data/lib/vertica/column.rb +68 -0
  6. data/lib/vertica/connection.rb +247 -0
  7. data/lib/vertica/messages/authentication.rb +33 -0
  8. data/lib/vertica/messages/backend_key_data.rb +16 -0
  9. data/lib/vertica/messages/bind.rb +36 -0
  10. data/lib/vertica/messages/bind_complete.rb +8 -0
  11. data/lib/vertica/messages/cancel_request.rb +25 -0
  12. data/lib/vertica/messages/close.rb +30 -0
  13. data/lib/vertica/messages/close_complete.rb +8 -0
  14. data/lib/vertica/messages/command_complete.rb +16 -0
  15. data/lib/vertica/messages/data_row.rb +23 -0
  16. data/lib/vertica/messages/describe.rb +30 -0
  17. data/lib/vertica/messages/empty_query_response.rb +8 -0
  18. data/lib/vertica/messages/error_response.rb +59 -0
  19. data/lib/vertica/messages/execute.rb +24 -0
  20. data/lib/vertica/messages/flush.rb +15 -0
  21. data/lib/vertica/messages/message.rb +85 -0
  22. data/lib/vertica/messages/no_data.rb +8 -0
  23. data/lib/vertica/messages/notice_response.rb +21 -0
  24. data/lib/vertica/messages/notification_response.rb +18 -0
  25. data/lib/vertica/messages/parameter_description.rb +19 -0
  26. data/lib/vertica/messages/parameter_status.rb +17 -0
  27. data/lib/vertica/messages/parse.rb +31 -0
  28. data/lib/vertica/messages/parse_complete.rb +8 -0
  29. data/lib/vertica/messages/password.rb +33 -0
  30. data/lib/vertica/messages/portal_suspended.rb +8 -0
  31. data/lib/vertica/messages/query.rb +20 -0
  32. data/lib/vertica/messages/ready_for_query.rb +14 -0
  33. data/lib/vertica/messages/row_description.rb +29 -0
  34. data/lib/vertica/messages/ssl_request.rb +14 -0
  35. data/lib/vertica/messages/startup.rb +38 -0
  36. data/lib/vertica/messages/sync.rb +15 -0
  37. data/lib/vertica/messages/terminate.rb +14 -0
  38. data/lib/vertica/messages/unknown.rb +11 -0
  39. data/lib/vertica/notice.rb +11 -0
  40. data/lib/vertica/notification.rb +13 -0
  41. data/lib/vertica/result.rb +28 -0
  42. data/lib/vertica/vertica_socket.rb +8 -0
  43. data/lib/vertica.rb +19 -0
  44. data/test/connection_test.rb +191 -0
  45. data/test/create_schema.sql +4 -0
  46. data/test/test_helper.rb +25 -0
  47. data/vertica.gemspec +64 -0
  48. metadata +112 -0
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Matt Bauer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,69 @@
1
+ h1. Vertica
2
+
3
+ by Matt Bauer
4
+
5
+ h2. Description
6
+
7
+ Vertica is a pure Ruby library for connecting to Vertica databases. You can learn more
8
+ about Vertica at http://www.vertica.com. This library currently supports queries and
9
+ prepared statements.
10
+
11
+ h2. Install
12
+
13
+ $ gem install mattbauer-vertica --source http://gems.github.com
14
+
15
+ h2. Source
16
+
17
+ Vertica's git repo is available on GitHub, which can be browsed at:
18
+
19
+ http://github.com/mattbauer/vertica
20
+
21
+ and cloned from:
22
+
23
+ git://github.com/mattbauer/vertica.git
24
+
25
+ h2. Usage
26
+
27
+ h4. Example Query
28
+
29
+ <pre>
30
+ <code>
31
+ c = Vertica::Connection.new(
32
+ :user => 'user',
33
+ :password => 'password',
34
+ :host => 'db_server',
35
+ :port => '5433',
36
+ :database => 'db
37
+ )
38
+ r = c.query("SELECT * FROM my_table")
39
+ puts r.row_count
40
+ puts r.columns[0].name
41
+ puts r.rows
42
+ c.close
43
+ </code>
44
+ </pre>
45
+
46
+ h4. Example Prepared Statement
47
+
48
+ <pre>
49
+ <code>
50
+ c = Vertica::Connection.new(
51
+ :user => 'user',
52
+ :password => 'password',
53
+ :host => 'db_server',
54
+ :port => '5433',
55
+ :database => 'db
56
+ )
57
+ c.prepare("my_prepared_statement", "SELECT * FROM my_table WHERE id = ?", 1)
58
+ r = c.execute_prepared("my_prepared_statement", 13)
59
+ puts r.row_count
60
+ puts r.columns[0].name
61
+ puts r.rows
62
+ c.close
63
+ </code>
64
+ </pre>
65
+
66
+ h2. Running The Tests
67
+
68
+ To run the tests, change the values in test_helper.rb to match your db configuration. Then
69
+ execute the create_schema.sql on the database. Then you may run the tests.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/testtask'
5
+
6
+ load 'vertica.gemspec'
7
+
8
+ Rake::GemPackageTask.new(VERTICA_SPEC) do |pkg|
9
+ pkg.need_tar = true
10
+ end
11
+
12
+ task :default => "test"
13
+
14
+ desc "Clean"
15
+ task :clean do
16
+ include FileUtils
17
+ rm_rf 'pkg'
18
+ end
19
+
20
+ desc "Run tests"
21
+ Rake::TestTask.new("test") do |t|
22
+ t.libs << ["test", "ext"]
23
+ t.pattern = 'test/*_test.rb'
24
+ t.verbose = true
25
+ t.warning = true
26
+ end
27
+
28
+ task :doc => [:rdoc]
29
+ namespace :doc do
30
+ Rake::RDocTask.new do |rdoc|
31
+ files = ["README", "lib/**/*.rb"]
32
+ rdoc.rdoc_files.add(files)
33
+ rdoc.main = "README.textile"
34
+ rdoc.title = "Vertica Docs"
35
+ rdoc.rdoc_dir = "doc"
36
+ rdoc.options << "--line-numbers" << "--inline-source"
37
+ end
38
+ end
39
+
40
+ desc "Run rcov on current app"
41
+ task :rcov do
42
+ system "rm -rf coverage && rcov -o coverage -x rcov.rb test/*_test.rb"
43
+ system("open coverage/index.html") if PLATFORM['darwin']
44
+ end
@@ -0,0 +1,51 @@
1
+ module Vertica
2
+ module BitHelper
3
+
4
+ def readn(n)
5
+ s = read(n)
6
+ raise "couldn't read #{n} characters" if s.nil? or s.size != n # TODO make into a Vertica Exception
7
+ s
8
+ end
9
+
10
+ def write_byte(value)
11
+ write [value].pack('C')
12
+ end
13
+
14
+ def read_byte
15
+ readn(1).unpack('C').first
16
+ end
17
+
18
+ def write_network_int16(value)
19
+ write [value].pack('n')
20
+ end
21
+
22
+ def read_network_int16
23
+ readn(2).unpack('n').first
24
+ end
25
+
26
+ def write_network_int32(value)
27
+ write [value].pack('N')
28
+ end
29
+
30
+ def read_network_int32
31
+ handle_endian_flavor(readn(4)).unpack('l').first
32
+ end
33
+
34
+ def write_cstring(value)
35
+ raise ArgumentError, "Invalid cstring" if value.include?("\000")
36
+ write "#{value}\000"
37
+ end
38
+
39
+ def read_cstring
40
+ readline("\000")[0..-2]
41
+ end
42
+
43
+ def handle_endian_flavor(s)
44
+ little_endian? ? s.reverse : s
45
+ end
46
+
47
+ def little_endian?
48
+ @little_endian ||= ([0x12345678].pack("L") == "\x12\x34\x56\x78" ? false : true)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,68 @@
1
+ module Vertica
2
+ class Column
3
+ attr_reader :name
4
+ attr_reader :table_oid
5
+ attr_reader :type_modifier
6
+ attr_reader :size
7
+ attr_reader :data_type
8
+
9
+ DATA_TYPES = [
10
+ :unspecified,
11
+ :tuple,
12
+ :pos,
13
+ :record,
14
+ :unknown,
15
+ :bool,
16
+ :in,
17
+ :float,
18
+ :char,
19
+ :varchar,
20
+ :date,
21
+ :time,
22
+ :timestamp,
23
+ :timestamp_tz,
24
+ :interval,
25
+ :time_tz,
26
+ :numberic,
27
+ :bytea,
28
+ :rle_tuple
29
+ ]
30
+
31
+ DATA_TYPE_CONVERSIONS = {
32
+ :unspecified => nil,
33
+ :tuple => nil,
34
+ :pos => nil,
35
+ :record => nil,
36
+ :unknown => nil,
37
+ :bool => lambda { |s| s == 't' },
38
+ :in => lambda { |s| s.to_i },
39
+ :float => lambda { |s| s.to_f },
40
+ :char => nil,
41
+ :varchar => nil,
42
+ :date => lambda { |s| Date.new(*s.split("-").map{|x| x.to_i}) },
43
+ :time => lambda { |s| Time.parse(s) },
44
+ :timestamp => lambda { |s| DateTime.parse(s, true) },
45
+ :timestamp_tz => lambda { |s| DateTime.parse(s, true) },
46
+ :interval => nil,
47
+ :time_tz => lambda { |s| Time.parse(s) },
48
+ :numberic => lambda { |s| s.to_d },
49
+ :bytea => nil,
50
+ :rle_tuple => nil
51
+ }
52
+
53
+ def initialize(type_modifier, format_code, table_oid, name, attribute_number, data_type_oid, size)
54
+ @type_modifier = type_modifier
55
+ @format = (format_code == 0 ? :text : :binary)
56
+ @table_oid = table_oid
57
+ @name = name
58
+ @attribute_number = attribute_number
59
+ @data_type = DATA_TYPES[data_type_oid]
60
+ @size = size
61
+ end
62
+
63
+ def convert(s)
64
+ l = DATA_TYPE_CONVERSIONS[@data_type]
65
+ l ? l.call(s) : s
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,247 @@
1
+ require 'uri'
2
+ require 'stringio'
3
+ require 'vertica/vertica_socket'
4
+ require 'vertica/messages/message'
5
+ require 'openssl/ssl'
6
+
7
+ module Vertica
8
+
9
+ class Connection
10
+
11
+ def initialize(options = {})
12
+ reset_values
13
+
14
+ @options = options
15
+ establish_connection
16
+
17
+ unless options[:skip_startup]
18
+ Messages::Startup.new(@options[:user], @options[:database]).to_bytes(@conn)
19
+ process
20
+ end
21
+ end
22
+
23
+ def close
24
+ raise_if_not_open
25
+ Messages::Terminate.new.to_bytes(@conn)
26
+ @conn.shutdown
27
+ rescue Errno::ENOTCONN
28
+ # the backend closed the connection already
29
+ ensure
30
+ reset_values
31
+ end
32
+
33
+ def reset
34
+ close if opened?
35
+ reset_values
36
+ establish_connection
37
+ end
38
+
39
+ def options
40
+ @options.dup
41
+ end
42
+
43
+ def transaction_status
44
+ @transaction_status
45
+ end
46
+
47
+ def backend_pid
48
+ @backend_pid
49
+ end
50
+
51
+ def backend_key
52
+ @backend_key
53
+ end
54
+
55
+ def notifications
56
+ @notifications
57
+ end
58
+
59
+ def parameters
60
+ @parameters.dup
61
+ end
62
+
63
+ def put_copy_data; raise NotImplementedError.new; end
64
+ def put_copy_end; raise NotImplementedError.new; end
65
+ def get_copy_data; raise NotImplementedError.new; end
66
+
67
+ def opened?
68
+ @conn && @backend_pid && @transaction_status
69
+ end
70
+
71
+ def closed?
72
+ !opened?
73
+ end
74
+
75
+ def query(query_string)
76
+ raise ArgumentError.new("Query string cannot be blank or empty.") if query_string.nil? || query_string.empty?
77
+ raise_if_not_open
78
+ reset_result
79
+
80
+ Messages::Query.new(query_string).to_bytes(@conn)
81
+ process(true)
82
+ end
83
+
84
+ def prepare(name, query, params_count = 0)
85
+ raise_if_not_open
86
+
87
+ param_types = Array.new(params_count).fill(0)
88
+
89
+ Messages::Parse.new(name, query, param_types).to_bytes(@conn)
90
+ Messages::Describe.new(:prepared_statement, name).to_bytes(@conn)
91
+ Messages::Sync.new.to_bytes(@conn)
92
+ Messages::Flush.new.to_bytes(@conn)
93
+
94
+ process
95
+ end
96
+
97
+ def execute_prepared(name, *param_values)
98
+ raise_if_not_open
99
+
100
+ portal_name = "" # use the unnamed portal
101
+ max_rows = 0 # return all rows
102
+
103
+ reset_result
104
+
105
+ Messages::Bind.new(portal_name, name, param_values).to_bytes(@conn)
106
+ Messages::Execute.new(portal_name, max_rows).to_bytes(@conn)
107
+ Messages::Sync.new.to_bytes(@conn)
108
+
109
+ result = process(true)
110
+
111
+ # Close the portal
112
+ Messages::Close.new(:portal, portal_name).to_bytes(@conn)
113
+ Messages::Flush.new.to_bytes(@conn)
114
+
115
+ process
116
+
117
+ # Return the result from the prepared statement
118
+ result
119
+ end
120
+
121
+ def self.cancel(existing_conn)
122
+ conn = new(existing_conn.options.merge(:skip_startup => true))
123
+ Messages::CancelRequest.new(existing_conn.backend_pid, existing_conn.backend_key).to_bytes(conn.send(:conn))
124
+ Messages::Flush.new.to_bytes(conn.send(:conn))
125
+ conn.close
126
+ end
127
+
128
+ protected
129
+
130
+ def establish_connection
131
+ @conn = VerticaSocket.new(@options[:host], @options[:port].to_s)
132
+
133
+ if @options[:ssl]
134
+ Messages::SslRequest.new.to_bytes(@conn)
135
+ if @conn.read_byte == ?S
136
+ @conn = OpenSSL::SSL::SSLSocket.new(@conn, OpenSSL::SSL::SSLContext.new)
137
+ @conn.sync = true
138
+ @conn.connect
139
+ else
140
+ raise Error::ConnectionError.new("SSL requested but server doesn't support it.")
141
+ end
142
+ end
143
+ end
144
+
145
+ def process(return_result = false)
146
+ loop do
147
+ message = Messages::BackendMessage.read(@conn)
148
+
149
+ case message
150
+ when Messages::Authentication
151
+ if message.code != Messages::Authentication::OK
152
+ Messages::Password.new(@options[:password], message.code, {:user => @options[:user], :salt => message.salt}).to_bytes(@conn)
153
+ end
154
+ when Messages::BackendKeyData
155
+ @backend_pid = message.pid
156
+ @backend_key = message.key
157
+ when Messages::BindComplete
158
+ :nothing
159
+ when Messages::CloseComplete
160
+ break
161
+ when Messages::CommandComplete
162
+ break
163
+ # when Messages::CopyData
164
+ # # nothing
165
+ # when Messages::CopyDone
166
+ # # nothing
167
+ # when Messages::CopyInResponse
168
+ # raise 'not done'
169
+ # when Messages::CopyOutResponse
170
+ # raise 'not done'
171
+ when Messages::DataRow
172
+ @field_values << message.fields
173
+ when Messages::EmptyQueryResponse
174
+ break
175
+ when Messages::ErrorResponse
176
+ raise Error::MessageError.new(message.error)
177
+ when Messages::NoData
178
+ :nothing
179
+ when Messages::NoticeResponse
180
+ message.notices.each do |notice|
181
+ @notices << Notice.new(notice[0], notice[1])
182
+ end
183
+ when Messages::NotificationResponse
184
+ @notifications << Notification.new(message.pid, message.condition, message.additional_info)
185
+ when Messages::ParameterDescription
186
+ :nothing
187
+ when Messages::ParameterStatus
188
+ @parameters[message.name] = message.value
189
+ when Messages::ParseComplete
190
+ break
191
+ when Messages::PortalSuspended
192
+ break
193
+ when Messages::ReadyForQuery
194
+ @transaction_status = convert_transaction_status_to_sym(message.transaction_status)
195
+ break unless return_result
196
+ when Messages::RowDescription
197
+ @field_descriptions = message.fields
198
+ when Messages::Unknown
199
+ raise Error::MessageError.new("Unknown message type: #{message.message_id}")
200
+ end
201
+ end
202
+
203
+ return_result ? Result.new(@field_descriptions, @field_values) : nil
204
+ end
205
+
206
+ def raise_if_not_open
207
+ raise ConnectionError.new("connection doesn't exist or is already closed") if @conn.nil?
208
+ end
209
+
210
+ def reset_values
211
+ reset_notifications
212
+ reset_result
213
+ @parameters = {}
214
+ @backend_pid = nil
215
+ @backend_key = nil
216
+ @transaction_status = nil
217
+ @conn = nil
218
+ end
219
+
220
+ def reset_notifications
221
+ @notifications = []
222
+ end
223
+
224
+ def reset_result
225
+ @field_descriptions = []
226
+ @field_values = []
227
+ end
228
+
229
+ def convert_transaction_status_to_sym(status)
230
+ case status
231
+ when ?I
232
+ :no_transaction
233
+ when ?T
234
+ :in_transaction
235
+ when ?E
236
+ :failed_transaction
237
+ else
238
+ nil
239
+ end
240
+ end
241
+
242
+ def conn
243
+ @conn
244
+ end
245
+
246
+ end
247
+ end
@@ -0,0 +1,33 @@
1
+ module Vertica
2
+ module Messages
3
+ class Authentication < BackendMessage
4
+ message_id ?R
5
+
6
+ OK = 0
7
+ KERBEROS_V5 = 2
8
+ CLEARTEXT_PASSWORD = 3
9
+ CRYPT_PASSWORD = 4
10
+ MD5_PASSWORD = 5
11
+ SCM_CREDENTIAL = 6
12
+ GSS = 7
13
+ GSS_CONTINUE = 8
14
+ SSPI = 9
15
+
16
+ attr_reader :code
17
+ attr_reader :salt
18
+ attr_reader :auth_data
19
+
20
+ def initialize(stream, size)
21
+ super
22
+ @code = stream.read_network_int32
23
+ if @code == CRYPT_PASSWORD
24
+ @salt = stream.readn(2)
25
+ elsif @code == MD5_PASSWORD
26
+ @salt = stream.readn(4)
27
+ elsif @code == GSS_CONTINUE
28
+ @auth_data = stream.readn(size - 9)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ module Vertica
2
+ module Messages
3
+ class BackendKeyData < BackendMessage
4
+ message_id ?K
5
+
6
+ attr_reader :pid
7
+ attr_reader :key
8
+
9
+ def initialize(stream, size)
10
+ super
11
+ @pid = stream.read_network_int32
12
+ @key = stream.read_network_int32
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ module Vertica
2
+ module Messages
3
+ class Bind < FrontendMessage
4
+ message_id ?B
5
+
6
+ def initialize(portal_name, prepared_statement_name, parameter_values)
7
+ @portal_name = portal_name
8
+ @prepared_statement_name = prepared_statement_name
9
+ @parameter_values = parameter_values.map { |pv| pv.to_s }
10
+ end
11
+
12
+ def to_bytes(stream)
13
+ size = LENGTH_SIZE
14
+ size += @portal_name.length + 1
15
+ size += @prepared_statement_name.length + 1
16
+ size += 2 # parameter format code (0)
17
+ size += 2 # number of parameter values
18
+ size += @parameter_values.inject(0) { |sum, e| sum += (e.length + 4) }
19
+ size += 2
20
+
21
+ stream.write_byte(message_id)
22
+ stream.write_network_int32(size) # size
23
+ stream.write_cstring(@portal_name) # portal name ("")
24
+ stream.write_cstring(@prepared_statement_name) # prep
25
+ stream.write_network_int16(0) # format codes (0 - default text format)
26
+ stream.write_network_int16(@parameter_values.length) # number of parameters
27
+ @parameter_values.each do |parameter_value|
28
+ stream.write_network_int32(parameter_value.length) # parameter value (which is represented as a string) length
29
+ stream.write(parameter_value) # parameter value written out in text representation
30
+ end
31
+ stream.write_network_int16(0)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ module Vertica
2
+ module Messages
3
+ class BindComplete < BackendMessage
4
+ message_id ?2
5
+
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ module Vertica
2
+ module Messages
3
+ class CancelRequest < FrontendMessage
4
+ message_id nil
5
+
6
+ def initialize(backend_pid, backend_key)
7
+ @backend_pid = backend_pid
8
+ @backend_key = backend_key
9
+ end
10
+
11
+ def to_bytes(stream)
12
+ size = LENGTH_SIZE
13
+ size += 4
14
+ size += 4
15
+ size += 4
16
+
17
+ stream.write_network_int32(size) # size
18
+ stream.write_network_int32(80877102)
19
+ stream.write_network_int32(@backend_pid)
20
+ stream.write_network_int32(@backend_key)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module Vertica
2
+ module Messages
3
+ class Close < FrontendMessage
4
+ message_id ?C
5
+
6
+ def initialize(close_type, close_name)
7
+ if close_type == :portal
8
+ @close_type = ?P
9
+ elsif close_type == :prepared_statement
10
+ @close_type = ?S
11
+ else
12
+ raise ArgumentError.new("#{close_type} is not a valid close_type. Must be either :portal or :prepared_statement.")
13
+ end
14
+ @close_name = close_name
15
+ end
16
+
17
+ def to_bytes(stream)
18
+ size = LENGTH_SIZE
19
+ size += 1
20
+ size += @close_name.length + 1
21
+
22
+ stream.write_byte(message_id)
23
+ stream.write_network_int32(size) # size
24
+ stream.write_byte(@close_type)
25
+ stream.write_cstring(@close_name)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,8 @@
1
+ module Vertica
2
+ module Messages
3
+ class CloseComplete < BackendMessage
4
+ message_id ?3
5
+
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ module Vertica
2
+ module Messages
3
+ class CommandComplete < BackendMessage
4
+ message_id ?C
5
+
6
+ attr_reader :tag
7
+ attr_reader :rows
8
+
9
+ def initialize(stream, size)
10
+ @tag = stream.read_cstring
11
+ @rows = @tag.split[-1].to_i
12
+ end
13
+
14
+ end
15
+ end
16
+ end