olaf 0.3.0 → 1.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d1640a5221b0ed5a71b4fbb2ca11d113d69ef7436e7b7c2628497c4767a4245
4
- data.tar.gz: fd816660fe86290c5f5d8148a11823b2c2580e66096817d343b958fcbb1fc30e
3
+ metadata.gz: dd476121e33e97e071c41dc3e75a8128eddee118c93f0ec93acd61fa167b0523
4
+ data.tar.gz: 843da2abba59ade1dc644b47a4575abf77102ed7a58dd0ce1c45e6f7ba6f7968
5
5
  SHA512:
6
- metadata.gz: b1f71e253ff7cb95a7eef3d97324f6615d43c57aaa39e9128fcdce5f8ef683ed2a742d852aff034adf3cbd8fd04ddfa2d58a1c7372edea79843f0ab12581735a
7
- data.tar.gz: db997081fd2f622d354cd7bcb65a73c45ef55eba1bb71d8788b08590cb02e3500cdc428da78697ff838666daac3996d6f8a83cdfd14bbd3750655e308967507c
6
+ metadata.gz: 577d617e9e02f3bbaa858425e70e6d87803af2a9d2764626d30e6d0fda0bce130171b33566e642354e30990ffe19d7e9b4b8c55f48f29d63712a1e81743eb31c
7
+ data.tar.gz: 39da006e922a15ad3fa88a7c7c45979aff8eeb8e13b6e4c5340ee52608f54cf4930b4314e4925c2103ed7559604f664fbb18f2dd8084903605075e1d9f908293
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Olaf
2
2
 
3
- Olaf is a small Ruby wrapper for warehouse queries, running them in Snowflake or
4
- in BigQuery.
3
+ Olaf is a small Ruby wrapper for warehouse queries, running them in BigQuery.
5
4
 
6
5
  ![Olaf](https://user-images.githubusercontent.com/56375/96335285-8c86f080-106f-11eb-9489-999a884f1246.jpg)
7
6
 
@@ -13,7 +12,6 @@ and only when it is used, so add to your `Gemfile` the ones you configure:
13
12
 
14
13
  | Driver | Gems | Also needs |
15
14
  | ----------------- | ----------------------- | ----------------------- |
16
- | `Olaf::Snowflake` | `sequel`, `ruby-odbc` | the ODBC system library |
17
15
  | `Olaf::BigQuery` | `google-cloud-bigquery` | |
18
16
  | `Olaf::Fake` | — | |
19
17
 
@@ -34,7 +32,7 @@ control in the code and in tests.
34
32
  class FetchUsers
35
33
  include Olaf::QueryDefinition
36
34
 
37
- template './snowflake/users_in_department.sql'
35
+ template './big_query/users_in_department.sql'
38
36
 
39
37
  argument :department_id
40
38
 
@@ -52,7 +50,6 @@ Olaf.execute(query)
52
50
  One driver, which every query runs on:
53
51
 
54
52
  ```ruby
55
- Olaf.configure(user: 'olaf') # Olaf::Snowflake, the default
56
53
  Olaf.configure(olaf_driver: Olaf::Fake) # ideal for testing
57
54
  ```
58
55
 
@@ -62,10 +59,10 @@ run on the `default:`, the first one given when it is not specified:
62
59
  ```ruby
63
60
  Olaf.configure(
64
61
  drivers: {
65
- snowflake: Olaf::Snowflake.new(user: 'olaf'),
66
- big_query: Olaf::BigQuery.new(project: 'carwow', labels: { service: 'flatmin' })
62
+ big_query: Olaf::BigQuery.new(project: 'carwow', labels: { service: 'flatmin' }),
63
+ fake: Olaf::Fake.new
67
64
  },
68
- default: :snowflake
65
+ default: :big_query
69
66
  )
70
67
 
71
68
  class FetchUsers
@@ -91,7 +88,8 @@ Olaf.instance.register_result(FetchUsers, [{ id: 42 }], with: { department_id: 1
91
88
  ```
92
89
 
93
90
  Whichever driver runs the query, a failure it reports is wrapped into
94
- `Olaf::QueryExecutionError`, carrying the query metadata.
91
+ `Olaf::QueryExecutionError`, carrying the query metadata. A BigQuery connection
92
+ that keeps dropping can be the exception: see below.
95
93
 
96
94
  ## BigQuery
97
95
 
@@ -100,7 +98,8 @@ Olaf::BigQuery.new(
100
98
  project: 'carwow',
101
99
  credentials: JSON.parse(ENV['BIGQUERY_CREDENTIALS']), # ambient Google credentials when omitted
102
100
  maximum_bytes_billed: 5 * Olaf::BigQuery::GIGABYTE, # default: 1 GiB
103
- labels: { service: 'flatmin', country: 'uk' }
101
+ labels: { service: 'flatmin', country: 'uk' },
102
+ retry_delays: [1, 2] # default: retry twice, after 1s then 2s
104
103
  )
105
104
  ```
106
105
 
@@ -117,6 +116,17 @@ The query runs as a job, so that it can be capped and labelled:
117
116
  ```ruby
118
117
  driver :big_query, maximum_bytes_billed: 10 * Olaf::BigQuery::GIGABYTE
119
118
  ```
119
+ * A call whose connection drops mid-request is retried once after each of the
120
+ `retry_delays`. Waiting for the job is retried on its own, so a dropped status
121
+ check doesn't start a second job. A drop arrives in one of two forms, and both
122
+ are retried:
123
+ * A bare `OpenSSL::SSL::SSLError`, which is how google-apis-core 0.x reports
124
+ an SSL read that ends early. Once the retries run out it is raised as it is,
125
+ not wrapped, so a job runner such as Sidekiq can retry it in turn.
126
+ * A `Google::Cloud::Error` caused by a `Google::Apis::TransmissionError`.
127
+ google-apis-core 1.x reports every dropped connection this way, and 0.x
128
+ does the same for a reset connection. Once the retries run out it is wrapped
129
+ into `Olaf::QueryExecutionError`, like any other error the API reports.
120
130
 
121
131
  `Olaf.execute` returns the rows. `Olaf.instance(:big_query).run(query)` returns
122
132
  the finished job instead, for its statistics:
@@ -1,5 +1,6 @@
1
1
  # Not a dependency of the gem: consumers of this driver add it themselves.
2
2
  require 'google/cloud/bigquery'
3
+ require 'securerandom'
3
4
 
4
5
  # The API client debug-logs whole response bodies, which for a warehouse query
5
6
  # is the entire result set.
@@ -17,14 +18,17 @@ module Olaf
17
18
  LABEL_FORMAT = /[^a-z0-9_-]/.freeze
18
19
  LABEL_MAX_LENGTH = 63
19
20
 
21
+ RETRY_DELAYS = [1, 2].freeze
22
+
20
23
  # Credentials default to the ambient Google ones, and the client is built
21
24
  # from the arguments unless one is given.
22
25
  def initialize(project: nil, credentials: nil, maximum_bytes_billed: DEFAULT_MAXIMUM_BYTES_BILLED,
23
- labels: {}, client: nil)
26
+ labels: {}, retry_delays: RETRY_DELAYS, client: nil)
24
27
  @project = project
25
28
  @credentials = credentials
26
29
  @maximum_bytes_billed = maximum_bytes_billed
27
30
  @labels = labels
31
+ @retry_delays = retry_delays
28
32
  @client = client
29
33
  end
30
34
 
@@ -35,15 +39,11 @@ module Olaf
35
39
  # Returns the finished job, so callers can read its stats (bytes billed,
36
40
  # cache hit) on top of its rows.
37
41
  #
38
- # @raises Olaf::QueryExecutionError
42
+ # @raises Olaf::QueryExecutionError, or the OpenSSL::SSL::SSLError of a
43
+ # connection that keeps dropping, which google-apis-core 0.x doesn't wrap
39
44
  def run(olaf_query)
40
- job = client.query_job(
41
- sql(olaf_query),
42
- params: params(olaf_query),
43
- maximum_bytes_billed: maximum_bytes_billed(olaf_query),
44
- labels: labels(olaf_query)
45
- )
46
- job.wait_until_done!
45
+ job = start_job(olaf_query)
46
+ retrying_dropped_connections { job.wait_until_done! }
47
47
 
48
48
  raise QueryExecutionError.new(job.error.to_h['message'].to_s, olaf_query) if job.failed?
49
49
 
@@ -58,9 +58,51 @@ module Olaf
58
58
  @client ||= Google::Cloud::Bigquery.new(project: @project, credentials: @credentials)
59
59
  end
60
60
 
61
+ # A connection can drop after BigQuery has started the job, so every attempt
62
+ # reuses one job ID: BigQuery refuses the retry as a duplicate, and the job
63
+ # already started is loaded rather than running and billing the query twice.
64
+ # If the lookup can't find the job, as happens for one in a single region,
65
+ # the refusal is re-raised.
66
+ def start_job(olaf_query)
67
+ job_id = "job_#{SecureRandom.urlsafe_base64(21)}"
68
+
69
+ retrying_dropped_connections do
70
+ client.query_job(
71
+ sql(olaf_query),
72
+ params: params(olaf_query),
73
+ maximum_bytes_billed: maximum_bytes_billed(olaf_query),
74
+ labels: labels(olaf_query),
75
+ job_id:
76
+ )
77
+ rescue Google::Cloud::AlreadyExistsError
78
+ client.job(job_id) || raise
79
+ end
80
+ end
81
+
82
+ # google-cloud-bigquery switches off google-apis-core's retries.
83
+ def retrying_dropped_connections
84
+ @retry_delays.each do |delay|
85
+ return yield
86
+ rescue StandardError => error
87
+ raise unless dropped_connection?(error)
88
+
89
+ sleep delay
90
+ end
91
+
92
+ yield
93
+ end
94
+
95
+ # google-apis-core 1.x reports every dropped connection as a TransmissionError,
96
+ # and 0.x does the same for a reset connection; google-cloud-bigquery wraps it.
97
+ def dropped_connection?(error)
98
+ error.is_a?(OpenSSL::SSL::SSLError) ||
99
+ (error.is_a?(Google::Cloud::Error) && error.cause.is_a?(Google::Apis::TransmissionError))
100
+ end
101
+
61
102
  # Google::Cloud::Error covers everything the API reports, Google::Auth::Error
62
103
  # credentials missing or expired before a call is made. The second one only
63
- # exists in googleauth 1.14 and later.
104
+ # exists in googleauth 1.14 and later. A bare OpenSSL::SSL::SSLError is left
105
+ # out, so once its retries run out the caller's job runner can still retry it.
64
106
  def wrapped_errors
65
107
  @wrapped_errors ||= [
66
108
  Google::Cloud::Error,
@@ -3,7 +3,7 @@ module Olaf
3
3
  module ClassMethods
4
4
  # Creates a new instance of the Query defined and validates
5
5
  # the parameters passed, leaving the instance in a ready-to-execute state.
6
- # @return Snowflake::QueryDefinition instance
6
+ # @return Olaf::QueryDefinition instance
7
7
  def prepare(**vars)
8
8
  new(**vars).prepare
9
9
  end
@@ -15,9 +15,9 @@ module Olaf
15
15
  end
16
16
 
17
17
  # Define an argument for the query matching a placeholder in the template.
18
- # The Sequel gem, will fill the placeholders in the SQL query escaping the
19
- # value passed as an argument. Sometimes, we need to pass an argument as
20
- # literal to avoid the single quotes added by Sequel (i.e. sending a table_name)
18
+ # The driver binds the value passed as a query parameter. Sometimes, we need
19
+ # to pass an argument as literal to substitute it into the SQL instead
20
+ # (i.e. sending a table_name)
21
21
  #
22
22
  # options - Hash config for each argument
23
23
  # :as - Argument Type
@@ -26,7 +26,7 @@ module Olaf
26
26
  # Example:
27
27
  #
28
28
  # class OneQuery
29
- # include Snowflake::QueryDefinition
29
+ # include Olaf::QueryDefinition
30
30
  #
31
31
  # template 'reports/one.sql'
32
32
  #
@@ -48,7 +48,7 @@ module Olaf
48
48
  # Example:
49
49
  #
50
50
  # class OneQuery
51
- # include Snowflake::QueryDefinition
51
+ # include Olaf::QueryDefinition
52
52
  #
53
53
  # template 'reports/one.sql'
54
54
  # end
@@ -93,7 +93,7 @@ module Olaf
93
93
  # Example:
94
94
  #
95
95
  # class OneQuery
96
- # include Snowflake::QueryDefinition
96
+ # include Olaf::QueryDefinition
97
97
  #
98
98
  # row_object MyOwnObject
99
99
  # end
@@ -20,8 +20,8 @@ module Olaf
20
20
  #
21
21
  # @return QueryDefinition instance
22
22
  #
23
- # @raises Snowflake::UndefinedArgumentsError
24
- # @raises Snowflake::MissingArgumentsError
23
+ # @raises Olaf::UndefinedArgumentsError
24
+ # @raises Olaf::MissingArgumentsError
25
25
  def prepare
26
26
  @sql_template ||= File.read(self.class.template)
27
27
 
data/lib/olaf.rb CHANGED
@@ -5,21 +5,19 @@ module Olaf
5
5
  # Autoloaded: each driver pulls in its own client library.
6
6
  autoload :BigQuery, File.expand_path('olaf/drivers/big_query', __dir__)
7
7
  autoload :Fake, File.expand_path('olaf/drivers/fake', __dir__)
8
- autoload :Snowflake, File.expand_path('olaf/drivers/snowflake', __dir__)
9
8
 
10
9
  DEFAULT_DRIVER = :default
11
10
 
12
11
  # Configures the drivers used to execute queries, either a single one built
13
12
  # from the arguments given:
14
13
  #
15
- # Olaf.configure(user: 'olaf') # Olaf::Snowflake, the default
16
14
  # Olaf.configure(olaf_driver: Olaf::Fake) # ideal for testing
17
15
  #
18
16
  # or several instantiated ones, which queries pick by name with `driver`:
19
17
  #
20
18
  # Olaf.configure(
21
- # drivers: { snowflake: Olaf::Snowflake.new(user: 'olaf'), big_query: Olaf::BigQuery.new(project: 'carwow') },
22
- # default: :snowflake
19
+ # drivers: { big_query: Olaf::BigQuery.new(project: 'carwow'), fake: Olaf::Fake.new },
20
+ # default: :big_query
23
21
  # )
24
22
  #
25
23
  # @return the default Olaf driver instance
@@ -36,7 +34,9 @@ module Olaf
36
34
 
37
35
  [drivers.dup, default_driver]
38
36
  else
39
- [{ DEFAULT_DRIVER => (olaf_driver || Snowflake).new(**args) }, DEFAULT_DRIVER]
37
+ raise ArgumentError, 'Pass `drivers:` or a single `olaf_driver:` to configure' unless olaf_driver
38
+
39
+ [{ DEFAULT_DRIVER => olaf_driver.new(**args) }, DEFAULT_DRIVER]
40
40
  end
41
41
 
42
42
  instance
@@ -64,9 +64,8 @@ module Olaf
64
64
  # what keeps `Olaf::Fake` covering every query in tests.
65
65
  #
66
66
  # @return Olaf driver instance
67
- # * Olaf::Fake - Ideal for testing
68
- # * Olaf::Snowflake - Sequel.odbc driver to run queries in Snowflake
69
- # * Olaf::BigQuery - google-cloud-bigquery driver to run queries in BigQuery
67
+ # * Olaf::Fake - Ideal for testing
68
+ # * Olaf::BigQuery - google-cloud-bigquery driver to run queries in BigQuery
70
69
  #
71
70
  def self.instance(name = nil)
72
71
  drivers = @drivers || raise('You need to configure Olaf before using it!')
data/olaf.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'olaf'
3
- s.version = '0.3.0'
3
+ s.version = '1.0.1'
4
4
  s.date = Time.now.strftime('%Y-%m-%d')
5
5
  s.summary = 'Ruby wrapper for warehouse queries.'
6
6
  s.authors = ['Emiliano Mancuso']
@@ -18,9 +18,7 @@ Gem::Specification.new do |s|
18
18
 
19
19
  # Drivers load their own client libraries, so consumers only install the ones
20
20
  # they configure:
21
- # Olaf::Snowflake - sequel and ruby-odbc
22
- # Olaf::BigQuery - google-cloud-bigquery
21
+ # Olaf::BigQuery - google-cloud-bigquery
23
22
  s.add_development_dependency 'google-cloud-bigquery', '~> 1.64'
24
- s.add_development_dependency 'sequel', '~> 5.37'
25
23
  s.add_development_dependency 'test-unit', '~> 3.3'
26
24
  end
@@ -20,10 +20,10 @@ class OlafConfigureTest < Test::Unit::TestCase
20
20
  assert_equal Olaf.instance.config, { other: 'configs', like: 'user and passwd' }
21
21
  end
22
22
 
23
- def test_configure_defaults_to_snowflake
24
- Olaf.configure(random: 'stuff')
25
-
26
- assert Olaf.instance.is_a?(Olaf::Snowflake)
23
+ def test_configure_requires_a_driver
24
+ assert_raise ArgumentError do
25
+ Olaf.configure(random: 'stuff')
26
+ end
27
27
  end
28
28
 
29
29
  def test_configure_returns_the_default_driver
@@ -31,33 +31,33 @@ class OlafConfigureTest < Test::Unit::TestCase
31
31
  end
32
32
 
33
33
  def test_configure_registers_several_drivers
34
- snowflake = MockDriver.new
34
+ postgres = MockDriver.new
35
35
  big_query = MockDriver.new
36
36
 
37
- Olaf.configure(drivers: { snowflake: snowflake, big_query: big_query }, default: :snowflake)
37
+ Olaf.configure(drivers: { postgres: postgres, big_query: big_query }, default: :postgres)
38
38
 
39
- assert_equal Olaf.instance, snowflake
40
- assert_equal Olaf.instance(:snowflake), snowflake
39
+ assert_equal Olaf.instance, postgres
40
+ assert_equal Olaf.instance(:postgres), postgres
41
41
  assert_equal Olaf.instance(:big_query), big_query
42
42
  end
43
43
 
44
44
  def test_configure_defaults_to_the_first_driver_registered
45
- snowflake = MockDriver.new
45
+ postgres = MockDriver.new
46
46
 
47
- Olaf.configure(drivers: { snowflake: snowflake, big_query: MockDriver.new })
47
+ Olaf.configure(drivers: { postgres: postgres, big_query: MockDriver.new })
48
48
 
49
- assert_equal Olaf.instance, snowflake
49
+ assert_equal Olaf.instance, postgres
50
50
  end
51
51
 
52
52
  def test_configure_rejects_a_default_that_was_not_registered
53
53
  assert_raise_message(/Unknown default driver :redshift/) do
54
- Olaf.configure(drivers: { snowflake: MockDriver.new }, default: :redshift)
54
+ Olaf.configure(drivers: { postgres: MockDriver.new }, default: :redshift)
55
55
  end
56
56
  end
57
57
 
58
58
  def test_configure_rejects_both_forms_at_once
59
59
  assert_raise ArgumentError do
60
- Olaf.configure(olaf_driver: MockDriver, drivers: { snowflake: MockDriver.new })
60
+ Olaf.configure(olaf_driver: MockDriver, drivers: { postgres: MockDriver.new })
61
61
  end
62
62
  end
63
63
 
@@ -74,7 +74,7 @@ class OlafConfigureTest < Test::Unit::TestCase
74
74
  end
75
75
 
76
76
  def test_instance_raises_for_a_driver_that_was_not_registered
77
- Olaf.configure(drivers: { snowflake: MockDriver.new, big_query: MockDriver.new })
77
+ Olaf.configure(drivers: { postgres: MockDriver.new, big_query: MockDriver.new })
78
78
 
79
79
  assert_raise Olaf::UnknownDriverError do
80
80
  Olaf.instance(:redshift)
@@ -81,7 +81,7 @@ class OlafExecuteTest < Test::Unit::TestCase
81
81
  default_driver = RecordingDriver.new
82
82
  other_driver = RecordingDriver.new
83
83
 
84
- Olaf.configure(drivers: { big_query: other_driver, snowflake: default_driver }, default: :snowflake)
84
+ Olaf.configure(drivers: { big_query: other_driver, postgres: default_driver }, default: :postgres)
85
85
 
86
86
  Olaf.execute(@query_instance)
87
87
 
@@ -98,7 +98,7 @@ class OlafExecuteTest < Test::Unit::TestCase
98
98
  end
99
99
 
100
100
  def test_execute_raises_when_the_driver_declared_is_not_configured
101
- Olaf.configure(drivers: { default: RecordingDriver.new, snowflake: RecordingDriver.new })
101
+ Olaf.configure(drivers: { default: RecordingDriver.new, postgres: RecordingDriver.new })
102
102
 
103
103
  @query.driver :big_query
104
104
 
@@ -12,15 +12,13 @@ class OlafLazyLoadingTest < Test::Unit::TestCase
12
12
  assert_equal loaded_after('require "olaf"; Olaf.configure(olaf_driver: Olaf::Fake)'), []
13
13
  end
14
14
 
15
- def test_configuring_the_snowflake_driver_loads_sequel_only
16
- assert_equal loaded_after('require "olaf"; Olaf.configure(user: "olaf")'), ['sequel']
15
+ def test_configuring_the_big_query_driver_loads_its_client_only
16
+ assert_equal loaded_after('require "olaf"; Olaf.configure(olaf_driver: Olaf::BigQuery)'), ['google-cloud-bigquery']
17
17
  end
18
18
 
19
19
  private
20
20
 
21
21
  DEPENDENCIES = {
22
- 'sequel' => 'Sequel',
23
- 'odbc_utf8' => 'ODBC',
24
22
  'google-cloud-bigquery' => 'Google::Cloud::Bigquery'
25
23
  }.freeze
26
24
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: olaf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emiliano Mancuso
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-09-03 00:00:00.000000000 Z
10
+ date: 2026-10-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: google-cloud-bigquery
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.64'
26
- - !ruby/object:Gem::Dependency
27
- name: sequel
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '5.37'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '5.37'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: test-unit
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -62,7 +48,6 @@ files:
62
48
  - lib/olaf.rb
63
49
  - lib/olaf/drivers/big_query.rb
64
50
  - lib/olaf/drivers/fake.rb
65
- - lib/olaf/drivers/snowflake.rb
66
51
  - lib/olaf/errors.rb
67
52
  - lib/olaf/query_definition.rb
68
53
  - lib/olaf/query_definition/class_methods.rb
@@ -90,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
75
  - !ruby/object:Gem::Version
91
76
  version: '0'
92
77
  requirements: []
93
- rubygems_version: 4.0.20
78
+ rubygems_version: 3.6.9
94
79
  specification_version: 4
95
80
  summary: Ruby wrapper for warehouse queries.
96
81
  test_files:
@@ -1,28 +0,0 @@
1
- require 'sequel'
2
-
3
- module Olaf
4
- class Snowflake
5
- WRAPPED_ERRORS = [Sequel::DatabaseError].freeze
6
-
7
- def initialize(**config)
8
- @config = config
9
- end
10
-
11
- def fetch(olaf_query)
12
- conn.fetch(olaf_query.sql_template, **olaf_query.variables).all
13
- rescue *WRAPPED_ERRORS => error
14
- raise QueryExecutionError.new(error.message, olaf_query)
15
- end
16
-
17
- private
18
-
19
- def conn
20
- # Only opening a connection needs the ODBC system library.
21
- @conn ||= begin
22
- require 'odbc_utf8'
23
-
24
- Sequel.odbc('snowflake', **@config)
25
- end
26
- end
27
- end
28
- end