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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d40466055bc43ffb3c72810432f2cf6738295399
4
- data.tar.gz: c4c4c43a8cf12c22ce7cc3b46eadc68d90931453
3
+ metadata.gz: fd8271cb188eef45ec983912494a8ebb72209567
4
+ data.tar.gz: 730d859a30f294766df5e0a789ecb79de550771c
5
5
  SHA512:
6
- metadata.gz: 5d9941765704d6e4c29efa04d1f7dbdf7f79ca787a6c8786019f8659c47f024f7245673cb7185cadce1ea6035bd8bbd2dfa4f526404ab0c922824309ce427c6b
7
- data.tar.gz: aee68c7643bbafd0399cc3c8ff44b0ea13d9490a1ce553f426579ad87a0e28bf4fb0bc21f73f19bc9ebca85755195a4be485099775336b38a49e69127e4dd3a6
6
+ metadata.gz: 504edb900db1f059bb19cb9f619f8d5cf51586bbd8184fb0bfe1bad1f9b208cb49704c21f0e5b2c76de502103035e2f90d7d3086b7dcebc699f5e6b911c2172e
7
+ data.tar.gz: 4560a75f5a0f2926cf95f95ea3514b578c7779a082148e3af86b84e22091f392a788f084d46b0bcdda309c6a0040628db9e79e5ff44b28b162815312901b5325
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ --hide-void-return
2
+ --markup markdown
3
+ --readme README.md
4
+ --no-private
data/README.md CHANGED
@@ -6,50 +6,56 @@ about Vertica at http://www.vertica.com.
6
6
  - Connecting, including over SSL.
7
7
  - Executing queries, with results as streaming rows or buffered resultsets.
8
8
  - `COPY table FROM STDIN` statement to load data from your application.
9
- - Confirmed to work with Ruby 1.9, 2.0, and 2.1; JRuby 1.7.23 and 9.0.4.0; and
10
- with Vertica version 6.x, and 7.x.
11
9
  - The library is thread-safe as of version 0.11. However, you can only run one
12
- statement at the time per connection, because the protocol is stateful.
10
+ statement at the time per connection, because the protocol is stateful. In a
11
+ multi-threaded environment, you may want to tthink about setting up a
12
+ connection pool.
13
13
 
14
14
 
15
15
  ## Installation
16
16
 
17
- $ gem install vertica
17
+ Add it to the Gemfile of your project:
18
18
 
19
- Or add it to your Gemfile:
20
-
21
- gem 'vertica'
19
+ gem 'vertica', '~> 1.0'
22
20
  # gem 'vertica', git: 'git://github.com/wvanbergen/vertica.git' # HEAD version
23
21
 
24
- ### Compatiblity
22
+ Now, run `bundle install`.
25
23
 
26
- - Ruby 1.8 is no longer supported, but version 0.9.x should still support it.
27
- - Vertica versions 4.x, and 5.x worked with at some point with this gem, but
28
- compatibility is no longer tested. It probably still works as the protocol hasn't
29
- changed as far as I am aware.
24
+ ### Compatiblity
30
25
 
26
+ - Ruby 2.0 or higher is required.
27
+ - Compatibility is tested with Vertica 7.x community edition. Vertica versions 4.x, 5.x,
28
+ and 6.x worked with at some point with this gem, but compatibility is no longer tested.
31
29
 
32
30
  ## Usage
33
31
 
34
32
  ### Connecting
35
33
 
36
- The <code>Vertica.connect</code> methods takes a connection parameter hash and returns a
37
- connection object. For most options, the gem will use a default value if no value is provided.
38
-
39
- connection = Vertica.connect(
40
- :host => 'db_server',
41
- :username => 'user',
42
- :password => 'password',
43
- # :ssl => false, # use SSL for the connection
44
- # :port => 5433, # default Vertica port: 5433
45
- # :database => 'db', # there is only one database
46
- # :role => nil, # the (additional) role(s) to enable for the user.
47
- # :search_path => nil, # default: <user>,public,v_catalog
48
- )
49
-
50
- To close the connection when you're done with it, run <code>connection.close</code>.
51
-
52
- You can pass `OpenSSL::SSL::SSLContext` in `:ssl` to customize SSL connection options.
34
+ The `Vertica.connect` methods takes keyword arguments and returns a connection
35
+ instance. For most options, the gem will use a default value if no value is provided.
36
+
37
+ ``` ruby
38
+ connection = Vertica.connect(
39
+ host: 'db_server',
40
+ username: 'user',
41
+ password: 'password',
42
+
43
+ # ssl: false, # use SSL for the connection
44
+ # port: 5433, # default Vertica port: 5433
45
+ # database: nil, # there is only one database
46
+ # role: nil, # the (additional) role(s) to enable for the user.
47
+ # search_path: nil, # default: <user>,public,v_catalog
48
+ # timezone: nil, # the timezone for the connection to convert timestamps
49
+ # autocommit: false, # automatically commit INSERT/UPDATE/DELETE queries
50
+ # interruptable: false, # set to true to allow sessions to be interrupted.
51
+ # read_timeout: 600, # timeout in seconds when reading data
52
+ # debug: false, # print all the messages back and forth to STDOUT.
53
+ )
54
+ ```
55
+
56
+ - To close the connection when you're done with it, run `connection.close`.
57
+ - You can pass `OpenSSL::SSL::SSLContext` in `:ssl` to customize SSL connection options,
58
+ or `true` to use the default (`OpenSSL::SSL::SSLContext.new`).
53
59
 
54
60
  ### Running queries
55
61
 
@@ -59,10 +65,12 @@ because buffering the entire result may require a lot of memory.
59
65
 
60
66
  Get all the result rows without buffering by providing a block:
61
67
 
62
- connection.query("SELECT id, name FROM my_table") do |row|
63
- puts row['id'] # => 123
64
- puts row['name'] # => 'Jim Bob'
65
- end
68
+ ``` ruby
69
+ connection.query("SELECT id, name FROM my_table") do |row|
70
+ puts row['id'] # => 123
71
+ puts row['name'] # => 'Unicorn'
72
+ end
73
+ ```
66
74
 
67
75
  Note: you can only use the connection for one query at the time. If you try to run another
68
76
  query when the connection is still busy delivering the results of a previous query, a
@@ -71,42 +79,70 @@ problem.
71
79
 
72
80
  Store the result of the query method as a variable to get a buffered resultset:
73
81
 
74
- result = connection.query("SELECT id, name FROM my_table")
75
- connection.close
82
+ ``` ruby
83
+ result = connection.query("SELECT id, name FROM my_table")
84
+ connection.close # buffered result will be available even after closing the connection.
76
85
 
77
- result.size # => 2
86
+ result.size # => 2
78
87
 
79
- result.each do |row|
80
- puts row # => Vertica::Row[123, "Jim Bob"]>
81
- end
88
+ result.each do |row|
89
+ puts "Hello #{row['name']}, your ID is #{row['id']}."
90
+ end
91
+ ```
82
92
 
83
93
  Rows are provided as `Vertica::Row` instances. You can access the individial fields by
84
94
  referring to their name as String or Symbol, or the index of the field in the result.
85
95
 
86
- result.each do |row|
87
- puts row # => Vertica::Row[123, "Jim Bob"]>
96
+ ``` ruby
97
+ result.each do |row|
98
+ p row # => Vertica::Row[123, "Unicorn"]>
88
99
 
89
- puts row['id'], row[:id], row[] # Three times '123'
90
- puts row['name'], row[:name], row[1] # Three times 'Jim Bob'
91
- end
100
+ puts row['id'], row[:id], row[0] # Three times 123
101
+ puts row['name'], row[:name], row[1] # Three times 'Unicorn'
102
+ end
103
+ ```
92
104
 
93
105
  ### Loading data into Vertica using COPY statements
94
106
 
95
- Using the COPY statement, you can load arbitrary data from your ruby script to the database.
107
+ Using the `COPY FROM STDIN` statement, you can load arbitrary data from your ruby script to the database.
96
108
 
97
- connection.copy("COPY table FROM STDIN ...") do |stdin|
98
- File.open('data.tsv', 'r') do |f|
99
- begin
100
- stdin << f.gets
101
- end until f.eof?
102
- end
103
- end
109
+ ``` ruby
110
+ connection.copy("COPY table FROM STDIN ...") do |stdin|
111
+ File.open('data.tsv', 'r') do |f|
112
+ begin
113
+ stdin << f.gets
114
+ end until f.eof?
115
+ end
116
+ end
117
+ ```
104
118
 
105
119
  You can also provide a filename or an IO object:
106
120
 
107
- connection.copy("COPY table FROM STDIN ...", "data.csv")
108
- connection.copy("COPY table FROM STDIN ...", io)
121
+ ``` ruby
122
+ connection.copy("COPY table FROM STDIN ...", source: "data.csv")
123
+ File.open('file.csv') do |io|
124
+ connection.copy("COPY table FROM STDIN ...", source: io)
125
+ end
126
+ ```
109
127
 
128
+ For more information, see [the Vertica documentation](https://my.vertica.com/docs/7.1.x/HTML/Content/Authoring/SQLReferenceManual/Statements/COPY/COPY.htm)
129
+
130
+ ### Interrupting sessions
131
+
132
+ ``` ruby
133
+ connection = Vertica.connect(host: 'dbserver', ...)
134
+
135
+ Thread.new do
136
+ sleep(60)
137
+ connection.interrupt
138
+ end
139
+
140
+ begin
141
+ result = connection.query('SELECT complicated_query FROM huge_table')
142
+ rescue Vertica::Error::ConnectionError
143
+ # ...
144
+ end
145
+ ```
110
146
 
111
147
  ## About
112
148
 
@@ -114,17 +150,15 @@ This package is MIT licensed. See the LICENSE file for more information.
114
150
 
115
151
  ### Development
116
152
 
117
- This project comes with a test suite. The unit tests in <tt>/test/unit</tt> do not need a database
118
- connection to run, the functional tests in <tt>/test/functional</tt> do need a working
153
+ This project comes with a test suite. The unit tests in `/test/unit` do not need a database
154
+ connection to run, the functional tests in `/test/functional` do need a working
119
155
  database connection. You can specify the connection parameters by copying the file
120
- <tt>/test/connection.yml.example</tt> to <tt>/test/connection.yml</tt> and filling out the
156
+ `/test/connection.yml.example` to `/test/connection.yml` and filling out the
121
157
  necessary fields.
122
158
 
123
- Note that the test suite requires write access to the default schema of the provided connection,
124
- although it tries to be as little invasive as possible: all tables it creates (and drops) are
125
- prefixed with <tt>test_ruby_vertica_</tt>.
126
-
127
- The test suite is also run by Travis CI againast Vertica 7.0.1, and Ruby 1.9.3, 2.0.0, and 2.1.1.
159
+ The `/vagrant` folder contains a Vagrantfile and a setup script to help you set up a development
160
+ database that you can run the functional test suite against. The full test suite is also run by
161
+ Travis CI against Vertica 7 CE, and against several Ruby versions.
128
162
 
129
163
  ### Authors
130
164
 
@@ -137,6 +171,7 @@ The test suite is also run by Travis CI againast Vertica 7.0.1, and Ruby 1.9.3,
137
171
 
138
172
  * [Website](http://vanbergen.org/vertica)
139
173
  * [API Documentation](http://www.rubydoc.info/gems/vertica/frames)
174
+ * [Vertica documentation](https://my.vertica.com/docs/7.1.x/HTML/index.htm)
140
175
  * [sequel-vertica](https://github.com/camilo/sequel-vertica): Sequel integration
141
176
  * [newrelic-vertica](https://github.com/wvanbergen/newrelic-vertica): NewRelic monitoring of queries
142
177
  * [node-vertica](https://github.com/wvanbergen/node-vertica): node.js Vertica driver
data/lib/vertica.rb CHANGED
@@ -23,7 +23,7 @@ module Vertica
23
23
  # This method has quoting rules for common types. Any other object will be converted to
24
24
  # a string using +:to_s+ and then quoted as a string.
25
25
  #
26
- # @param [Object] value The value to quote.
26
+ # @param value [Object] The value to quote.
27
27
  # @return [String] The quoted value that can be safely included in SQL queries.
28
28
  def self.quote(value)
29
29
  case value
@@ -42,7 +42,7 @@ module Vertica
42
42
  end
43
43
 
44
44
  # Quotes an identifier for safe use within SQL queries, using double quotes.
45
- # @param [:to_s] identifier The identifier to quote.
45
+ # @param identifier [:to_s] The identifier to quote.
46
46
  # @return [String] The quoted identifier that can be safely included in SQL queries.
47
47
  def self.quote_identifier(identifier)
48
48
  "\"#{identifier.to_s.gsub(/"/, '""')}\""
@@ -53,6 +53,7 @@ require 'vertica/version'
53
53
  require 'vertica/error'
54
54
  require 'vertica/connection'
55
55
  require 'vertica/query'
56
+ require 'vertica/data_type'
56
57
  require 'vertica/column'
57
58
  require 'vertica/row_description'
58
59
  require 'vertica/row'
@@ -1,92 +1,58 @@
1
- module Vertica
2
- class Column
3
- attr_reader :name
4
- attr_reader :table_oid
5
- attr_reader :attribute_number
6
- attr_reader :format
7
- attr_reader :data_type
8
- attr_reader :data_type_size
9
- attr_reader :data_type_modifier
10
-
11
- STRING_CONVERTER = lambda { |s| s.force_encoding('utf-8') }
12
-
13
- FLOAT_CONVERTER = lambda do |s|
14
- case s
15
- when 'Infinity'
16
- Float::INFINITY
17
- when '-Infinity'
18
- -Float::INFINITY
19
- when 'NaN'
20
- Float::NAN
21
- else
22
- s.to_f
23
- end
24
- end
25
-
26
- DATA_TYPE_CONVERSIONS = {
27
- 0 => [:unspecified, nil],
28
- 1 => [:tuple, nil],
29
- 2 => [:pos, nil],
30
- 3 => [:record, nil],
31
- 4 => [:unknown, nil],
32
- 5 => [:bool, lambda { |s| s == 't' }],
33
- 6 => [:integer, lambda { |s| s.to_i }],
34
- 7 => [:float, FLOAT_CONVERTER],
35
- 8 => [:char, STRING_CONVERTER],
36
- 9 => [:varchar, STRING_CONVERTER],
37
- 10 => [:date, lambda { |s| Date.parse(s) }],
38
- 11 => [:time, nil],
39
- 12 => [:timestamp, lambda { |s| Time.parse(s) }],
40
- 13 => [:timestamp_tz, lambda { |s| Time.parse(s) }],
41
- 14 => [:interval, nil],
42
- 15 => [:time_tz, nil],
43
- 16 => [:numeric, lambda { |s| BigDecimal.new(s) }],
44
- 17 => [:bytea, lambda { |s| s.gsub(/\\([0-3][0-7][0-7])/) { $1.to_i(8).chr }} ],
45
- 18 => [:rle_tuple, nil],
46
- 115 => [:long_varchar, STRING_CONVERTER],
47
- }
48
-
49
- DATA_TYPES = DATA_TYPE_CONVERSIONS.values.map { |t| t[0] }
50
-
51
- def initialize(name: nil, table_oid: nil, attribute_number: nil, format_code: 0, data_type_oid: nil, data_type_size: nil, data_type_modifier: nil)
52
- @name = name
53
- @table_oid = table_oid
54
- @attribute_number = attribute_number
55
-
56
- @format = format_code == 0 ? :text : :binary
57
- @data_type_size = data_type_size
58
- @data_type_modifier = data_type_modifier
59
- @data_type, @converter = column_type_from_oid(data_type_oid)
60
- end
1
+ # Class representing a column in a result.
2
+ #
3
+ # @attr_reader name [String] The name of the column
4
+ # @attr_reader table_oid [Integer, nil] The OID of the table this column originates from. This can
5
+ # be nil if the volumn was computed, and was not soruced from a table
6
+ # @attr_reader attribute_number [Integer, nil] The attribute index in the table this column originates from.
7
+ # This can be nil if the volumn was computed, and was not soruced from a table
8
+ # @attr_reader data_type [Vertica::DataType] The type of the values in this column.
9
+ #
10
+ # @see Vertica::RowDescription
11
+ # @see Vertica::DataType
12
+ class Vertica::Column
13
+
14
+ # Builds a new column instance based on the values provided by a {Vertica::Protocol::RowDescription} message.
15
+ # @param name [String] The name of the column
16
+ # @param table_oid [Integer, nil] The OID of the table this column originates from. This can
17
+ # be nil if the volumn was computed, and was not soruced from a table
18
+ # @param attribute_number [Integer, nil] The attribute index in the table this column originates from.
19
+ # This can be nil if the volumn was computed, and was not soruced from a table
20
+ # @param data_type_oid [Integer] The object ID of the type.
21
+ # @param data_type_size [Integer] The size of the type.
22
+ # @param data_type_modifier [Integer] A modifier of the type.
23
+ # @param data_format [Integer] The serialization format of this type.
24
+ # @return [Vertica::Column]
25
+ def self.build(name: nil, table_oid: nil, attribute_number: nil, data_format: 0, data_type_oid: nil, data_type_size: nil, data_type_modifier: nil)
26
+ data_type = Vertica::DataType.build(oid: data_type_oid, size: data_type_size, modifier: data_type_modifier, format: data_format)
27
+ new(name: name, data_type: data_type, table_oid: table_oid, attribute_number: attribute_number)
28
+ end
61
29
 
62
- def eql?(other)
63
- self.class === other &&
64
- other.name == name &&
65
- other.format == format &&
66
- other.data_type == data_type &&
67
- other.data_type_size == data_type_size &&
68
- other.data_type_modifier == data_type_modifier &&
69
- other.table_oid == table_oid &&
70
- other.attribute_number == attribute_number
71
- end
30
+ attr_reader :name, :data_type, :table_oid, :attribute_number
72
31
 
73
- alias_method :==, :eql?
32
+ # Initializes a new Vertica::Column.
33
+ # @see .build
34
+ def initialize(name: nil, data_type: nil, table_oid: nil, attribute_number: nil)
35
+ @name = name
36
+ @table_oid = table_oid
37
+ @attribute_number = attribute_number
38
+ @data_type = data_type
39
+ end
74
40
 
75
- def hash
76
- [name, format, data_type, data_type_size, data_type_modifier, table_oid, attribute_number].hash
77
- end
41
+ # @return [Boolean] Returns true iff this record is equal to the other provided object
42
+ def eql?(other)
43
+ self.class === other && other.name == name && other.data_type == data_type &&
44
+ other.table_oid == table_oid && other.attribute_number == attribute_number
45
+ end
78
46
 
79
- def convert(s)
80
- return unless s
81
- @converter ? @converter.call(s) : s
82
- end
47
+ alias_method :==, :eql?
83
48
 
84
- private
49
+ # @return [Integer] Returns a hash digtest of this object.
50
+ def hash
51
+ [name, data_type, table_oid, attribute_number].hash
52
+ end
85
53
 
86
- def column_type_from_oid(oid)
87
- DATA_TYPE_CONVERSIONS.fetch(oid) do |unknown_oid|
88
- raise Vertica::Error::UnknownTypeError, "Unknown type OID: #{unknown_oid}"
89
- end
90
- end
54
+ # @return [String] Returns a user-consumable string representation of this column.
55
+ def inspect
56
+ "#<#{self.class.name} name=#{name.inspect} data_type=#{data_type.inspect}>"
91
57
  end
92
58
  end
@@ -1,12 +1,54 @@
1
1
  require 'socket'
2
2
 
3
+ # A client for a Vertica server, which allows you to run queries against it.
4
+ #
5
+ # Use {Vertica.connect} to establish a connection. Then, the {#query} method will allow you
6
+ # to run SQL queries against the database. For `COPY FROM STDIN` queries, use the {#copy} method
7
+ # instead. You can use {#interrupt} to interrupt long running queries. {#close} will close the
8
+ # connection to the server.
9
+ #
10
+ # @attr_reader transaction_status [:no_transaction, :in_transaction, :failed_transaction] The current
11
+ # transaction state of the session. This will be updated after every query.
12
+ # @attr_reader parameters [Hash<String, String>] Connection parameters as provided by the server.
13
+ # @attr_reader options [Hash] The connection options provided to the constructor. See {#initialize}.
14
+ #
15
+ # @example Running a buffered query against the database
16
+ # connection = Vertica.connect(host: 'db_server', username: 'user', password: 'password', ...)
17
+ # result = connection.query("SELECT id, name FROM my_table")
18
+ # result.each do |row|
19
+ # puts "Row: #row['id']: #{row['name']}"
20
+ # end
21
+ # connection.close
22
+ #
23
+ # @see Vertica.connect
24
+ # @see Vertica::Result
3
25
  class Vertica::Connection
4
26
 
5
27
  attr_reader :transaction_status, :parameters, :options
6
28
 
7
- # Opens a connectio the a Vertica server
8
- # @param [Hash] options The connection options to use.
9
- def initialize(host: nil, port: 5433, username: nil, password: nil, database: nil, interruptable: false, ssl: nil, read_timeout: 600, debug: false, role: nil, search_path: nil, timezone: nil, autocommit: false, skip_startup: false, skip_initialize: false, user: nil)
29
+ # Creates a connection the a Vertica server.
30
+ # @param host [String] The hostname to connect to. E.g. `localhost`
31
+ # @param port [Integer] The port to connect to. Defaults to `5433`.
32
+ # @param username [String] The username for the session.
33
+ # @param password [String] The password for the session.
34
+ # @param interruptable [true, false] Whether to make this session interruptible. Setting this to true
35
+ # allows you to interrupt sessions and queries, but requires running a query during startup in order
36
+ # to obtain the session id.
37
+ # @param ssl [OpenSSL::SSL::SSLContext, Boolean] Set this to an OpenSSL::SSL::SSLContext instance to
38
+ # require the connection to be encrypted using SSL/TLS. `true` will use the default SSL options.
39
+ # Not every server has support for SSL encryption. In that case you'll have to leave this to false.
40
+ # @param read_timeout [Integer] The number of seconds to wait for data on the connection. You should
41
+ # set this to a sufficiently high value when executing complicated queries that require a long time
42
+ # to be evaluated.
43
+ # @param role [Array<String>, :all, :none, :default] A list of additional roles to enable for the session. See the
44
+ # [Vertica documentation for `SET ROLE`](https://my.vertica.com/docs/7.1.x/HTML/Content/Authoring/SQLReferenceManual/Statements/SET/SETROLE.htm).
45
+ # @param timezone [String] The timezone to use for the session. See the
46
+ # [Vertica documentation for `SET TIME ZONE`](https://my.vertica.com/docs/7.1.x/HTML/Content/Authoring/SQLReferenceManual/Statements/SET/SETTIMEZONE.htm).
47
+ # @param search_path [Array<String>] A list of schemas to use as search path. See the
48
+ # [Vertica documentation for `SET SEARCH_PATH`](https://my.vertica.com/docs/7.1.x/HTML/Content/Authoring/SQLReferenceManual/Statements/SET/SETSEARCH_PATH.htm).
49
+ # @param debug [Boolean] Setting this to true will log all the communication between client and server
50
+ # to STDOUT. Useful when developing this library.
51
+ def initialize(host: nil, port: 5433, username: nil, password: nil, database: nil, interruptable: false, ssl: false, read_timeout: 600, debug: false, role: nil, search_path: nil, timezone: nil, autocommit: false, skip_startup: false, skip_initialize: false, user: nil)
10
52
  reset_state
11
53
  @notice_handler = nil
12
54
 
@@ -29,41 +71,110 @@ class Vertica::Connection
29
71
  boot_connection(skip_initialize: skip_initialize) unless skip_startup
30
72
  end
31
73
 
32
- def on_notice(&block)
33
- @notice_handler = block
34
- end
35
-
74
+ # @return [Boolean] Returns true iff the connection is encrypted.
36
75
  def ssl?
37
76
  Object.const_defined?('OpenSSL') && @socket.kind_of?(OpenSSL::SSL::SSLSocket)
38
77
  end
39
78
 
79
+ # @return [Boolean] Returns true iff the connection to the server is opened.
80
+ # @note The connection will be opened automatically if you use it.
40
81
  def opened?
41
82
  @socket && @backend_pid && @transaction_status
42
83
  end
43
84
 
85
+ # @return [Boolean] Returns false iff the connection to the server is opened.
86
+ # @note Even if the connection is closed, it will be opened automatically if you use it.
44
87
  def closed?
45
88
  !opened?
46
89
  end
47
90
 
91
+ # @return [Boolean] Returns true iff the connection is in use.
48
92
  def busy?
49
93
  @mutex.locked?
50
94
  end
51
95
 
96
+ # @return [Boolean] Returns true iff the connection is ready to handle queries.
52
97
  def ready_for_query?
53
98
  !busy?
54
99
  end
55
100
 
101
+ # Returns true iff the connection can be interrupted.
102
+ #
103
+ # Connections can only be interrupted if the session ID is known, so it can
104
+ # run `SELECT CLOSE_SESSION(session_id)` using a separate connection. By passing
105
+ # `interruptable: true` as a connection parameter (see {#initialize}), the connection
106
+ # will discover its session id before you can use it, allowing it to be interrupted.
107
+ #
108
+ # @return [Boolean] Returns true iff the connection can be interrupted.
109
+ # @see {#interrupt}
56
110
  def interruptable?
57
111
  !session_id.nil?
58
112
  end
59
113
 
60
- def query(sql, **kwargs, &block)
61
- row_handler = block_given? ? block : nil
62
- job = Vertica::Query.new(self, sql, row_handler: row_handler, **kwargs)
63
- run_with_mutex(job)
64
- end
65
-
66
- def copy(sql, source: nil, **kwargs, &block)
114
+ # Runs a SQL query against the database.
115
+ #
116
+ # @overload query(sql)
117
+ # Runs a query against the database, and return the full result as a {Vertica::Result}
118
+ # instance.
119
+ #
120
+ # @note This requires the entire result to be buffered in memory, which may cause problems
121
+ # for queries with large results. Consider using the unbuffered version instead.
122
+ #
123
+ # @param sql [String] The SQL command to run.
124
+ # @return [Vertica::Result]
125
+ # @raise [Vertica::Error::ConnectionError] The connection to the server failed.
126
+ # @raise [Vertica::Error::QueryError] The server sent an error response indicating that
127
+ # the provided query cannot be evaluated.
128
+ #
129
+ # @overload query(sql, &block)
130
+ # Runs a query against the database, and yield every {Vertica::Row row} to the provided
131
+ # block.
132
+ #
133
+ # @param sql [String] The SQL command to run.
134
+ # @yield The provided block will be called for every row in the result.
135
+ # @yieldparam row [Vertica::Row]
136
+ # @return [String] The kind of command that was executed, e.g. `"SELECT"`.
137
+ # @raise [Vertica::Error::ConnectionError] The connection to the server failed.
138
+ # @raise [Vertica::Error::QueryError] The server sent an error response indicating that
139
+ # the provided query cannot be evaluated.
140
+ #
141
+ # @see https://my.vertica.com/docs/7.1.x/HTML/Content/Authoring/SQLReferenceManual/Statements/SELECT/SELECT.htm
142
+ # Vertica's documentation for SELECT.
143
+ def query(sql, &block)
144
+ run_in_mutex(Vertica::Query.new(self, sql, row_handler: block))
145
+ end
146
+
147
+ # Loads data into Vertica using a `COPY table FROM STDIN` query.
148
+ #
149
+ # @param sql [String] The `COPY ... FROM STDIN` SQL command to run.
150
+ # @param source [String, IO] The source of the data to be copied. This can either be a filename, or
151
+ # an IO object. If you don't specify a source, you'll need to provide a block that will provide the
152
+ # data to be copied.
153
+ # @yield A block that will be called with a writer that you can provided data to. If an exception is
154
+ # raised in the block, the `COPY` command will be cancelled.
155
+ # @yieldparam io [:write] An object that you can call write on to provide data to be loaded.
156
+ # @return [String] The kind of command that was executed on the server. This should always be `"COPY"`.
157
+ #
158
+ # @example Loading data using an IO object as source
159
+ # connection = Vertica.connect(host: 'db_server', username: 'user', password: 'password', ...)
160
+ # File.open("filename.csv", "r") do |io|
161
+ # connection.copy("COPY my_table FROM STDIN ...", source: io)
162
+ # end
163
+ #
164
+ # @example Loading data using a filename as source
165
+ # connection = Vertica.connect(host: 'db_server', username: 'user', password: 'password', ...)
166
+ # connection.copy("COPY my_table FROM STDIN ...", source: "filename.csv")
167
+ #
168
+ # @example Loading data using a callback
169
+ # connection = Vertica.connect(host: 'db_server', username: 'user', password: 'password', ...)
170
+ # connection.copy("COPY my_table FROM STDIN ...") do |io|
171
+ # io.write("my data")
172
+ # io.write("more data")
173
+ # end
174
+ #
175
+ # @see https://my.vertica.com/docs/7.1.x/HTML/Content/Authoring/SQLReferenceManual/Statements/COPY/COPY.htm
176
+ # Vertica's documentation for COPY.
177
+ def copy(sql, source: nil, &block)
67
178
  copy_handler = if block_given?
68
179
  block
69
180
  elsif source && File.exist?(source.to_s)
@@ -72,22 +183,33 @@ class Vertica::Connection
72
183
  lambda { |data| io_copy_handler(source, data) }
73
184
  end
74
185
 
75
- job = Vertica::Query.new(self, sql, copy_handler: copy_handler, **kwargs)
76
-
77
- run_with_mutex(job)
186
+ run_in_mutex(Vertica::Query.new(self, sql, copy_handler: copy_handler))
78
187
  end
79
188
 
189
+ # Returns a user-consumable string representation of this row.
190
+ # @return [String]
80
191
  def inspect
81
192
  safe_options = @options.reject { |name, _| name == :password }
82
193
  "#<Vertica::Connection:#{object_id} @parameters=#{@parameters.inspect} @backend_pid=#{@backend_pid}, @backend_key=#{@backend_key}, @transaction_status=#{@transaction_status}, @socket=#{@socket}, @options=#{safe_options.inspect}>"
83
194
  end
84
195
 
196
+ # Closes the connection to the Vertica server.
197
+ # @return [void]
85
198
  def close
86
199
  write_message(Vertica::Protocol::Terminate.new)
87
200
  ensure
88
201
  close_socket
89
202
  end
90
203
 
204
+ # Cancels the current query.
205
+ #
206
+ # @note Vertica's protocol is based on the PostgreSQL protocol. This method to cancel sessions
207
+ # in PostgreSQL is accepted by the Vertica server, but I haven't actually observed queries
208
+ # actually being cancelled when using this method. Vertica provides an alternative method, by
209
+ # running `SELECT CLOSE_SESSION(session_id)` as a query on a different connection. See {#interrupt}.
210
+ #
211
+ # @return [void]
212
+ # @see #interrupt
91
213
  def cancel
92
214
  conn = self.class.new(skip_startup: true, **options)
93
215
  conn.write_message(Vertica::Protocol::CancelRequest.new(backend_pid, backend_key))
@@ -95,6 +217,18 @@ class Vertica::Connection
95
217
  conn.close_socket
96
218
  end
97
219
 
220
+ # Interrupts this session to the Vertica server, which will cancel the running query.
221
+ #
222
+ # You'll have to call this method in a separate thread. It will open up a separate connection, and run
223
+ # `SELECT CLOSE_SESSION(current_session_id)` to close the current session. In order to be able to do this
224
+ # the client needs to know its session ID. You'll have to pass `interruptable: true` as a connection
225
+ # parameter (see {#initialize}) to make sure the connection will request its session id, by running
226
+ # `SELECT session_id FROM v_monitor.current_session` right after the connection is opened.
227
+ #
228
+ # @return [void]
229
+ # @see #interruptable?
230
+ # @see https://my.vertica.com/docs/7.1.x/HTML/Content/Authoring/SQLReferenceManual/Functions/VerticaFunctions/CLOSE_SESSION.htm
231
+ # Vertica's documentation for CLOSE_SESSION
98
232
  def interrupt
99
233
  raise Vertica::Error::InterruptImpossible, "Session cannopt be interrupted because the session ID is not known!" if session_id.nil?
100
234
  conn = self.class.new(skip_initialize: true, **options)
@@ -103,6 +237,20 @@ class Vertica::Connection
103
237
  conn.close if conn
104
238
  end
105
239
 
240
+ # Installs a hanlder for notices that may be sent from the server to the client.
241
+ #
242
+ # You can only install one handler; if you call this method again it will replace the
243
+ # previous handler.
244
+ #
245
+ # @return [void]
246
+ def on_notice(&block)
247
+ @notice_handler = block
248
+ end
249
+
250
+ # Writes a frontend message to the socket.
251
+ # @note This method is for internal use only; you should not call it directly.
252
+ # @return [void]
253
+ # @raise [Vertica::Error::ConnectionError]
106
254
  # @private
107
255
  def write_message(message)
108
256
  puts "=> #{message.inspect}" if options.fetch(:debug)
@@ -112,6 +260,10 @@ class Vertica::Connection
112
260
  raise Vertica::Error::ConnectionError.new(e.message)
113
261
  end
114
262
 
263
+ # Reads a backend message from the socket.
264
+ # @note This method is for internal use only; you should not call it directly.
265
+ # @return [Vertica::Protocol::BackendMessage]
266
+ # @raise [Vertica::Error::ConnectionError]
115
267
  # @private
116
268
  def read_message
117
269
  type, size = read_bytes(5).unpack('aN')
@@ -124,6 +276,9 @@ class Vertica::Connection
124
276
  raise Vertica::Error::ConnectionError.new(e.message)
125
277
  end
126
278
 
279
+ # Processes a backend message that was received from the socket.
280
+ # @note This method is for internal use only; you should not call it directly.
281
+ # @return [void]
127
282
  # @private
128
283
  def process_message(message)
129
284
  case message
@@ -168,7 +323,7 @@ class Vertica::Connection
168
323
  end
169
324
  end
170
325
 
171
- def run_with_mutex(job)
326
+ def run_in_mutex(job)
172
327
  boot_connection if closed?
173
328
  if @mutex.try_lock
174
329
  begin