olaf 0.1.0
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 +7 -0
- data/README.md +45 -0
- data/lib/olaf.rb +46 -0
- data/lib/olaf/drivers/fake.rb +60 -0
- data/lib/olaf/drivers/snowflake.rb +17 -0
- data/lib/olaf/errors.rb +35 -0
- data/lib/olaf/query_definition.rb +67 -0
- data/lib/olaf/query_definition/class_methods.rb +79 -0
- data/olaf.gemspec +21 -0
- data/rakefile +8 -0
- data/test/helper.rb +8 -0
- data/test/olaf_configure_test.rb +28 -0
- data/test/olaf_execute_test.rb +48 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 707d135df5db1d5105754573d6fa021c43da6b87ff9d83cd9baafc879ae3401e
|
4
|
+
data.tar.gz: d8fbaa062cd9eb2c4e3438d4b40f8c8352b95e1234f2f5d59c040870013ad263
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 969ee6bec25a2f5c17dbc03fb1e5aab61ed9b6801c764c2797ac36c0a8a0482351888a9f87cdec3d78453aca5bbd82ec635d3a5d3dfca0cc03eac0df3eee193c
|
7
|
+
data.tar.gz: 7a1fe66488aa789b7f005102b668767682ae6e5bb59fa20168641897a993ebb48a9c5b359ac69795e2bafe67df78b5565be1a4b19cf11c1e68119c114cb6623b
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Olaf
|
2
|
+
[](http://badge.fury.io/rb/ork)
|
3
|
+
[](https://travis-ci.org/emancu/ork)
|
4
|
+
|
5
|
+
Olaf is a small Ruby wrapper for Snowflake queries.
|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
## Dependencies
|
10
|
+
|
11
|
+
`olaf` requires Ruby 2.2 or later, `sequel` and `odbc` driver to connect with DBs.
|
12
|
+
|
13
|
+
Install dependencies using `bundler` is easy as run:
|
14
|
+
|
15
|
+
bundle install
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
If you don't have Olaf, try this:
|
20
|
+
|
21
|
+
$ gem install olaf
|
22
|
+
|
23
|
+
## Getting started
|
24
|
+
|
25
|
+
Olaf helps developers to represent Snowflake queries as objects, to have more
|
26
|
+
control in the code and in tests.
|
27
|
+
|
28
|
+
### Example
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class FetchUsers
|
32
|
+
include Olaf::QueryDefinition
|
33
|
+
|
34
|
+
template './snowflake/users_in_department.sql'
|
35
|
+
|
36
|
+
attribute :department_id
|
37
|
+
|
38
|
+
row_object User
|
39
|
+
end
|
40
|
+
|
41
|
+
query = FetchUsers.prepare(department_id: 1337)
|
42
|
+
|
43
|
+
Olaf.execute(query)
|
44
|
+
=> [#<User id: 41, department_id: 1337, name: 'Ian'>]
|
45
|
+
```
|
data/lib/olaf.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'odbc_utf8'
|
3
|
+
|
4
|
+
require_relative 'olaf/errors'
|
5
|
+
require_relative 'olaf/query_definition'
|
6
|
+
require_relative 'olaf/drivers/fake'
|
7
|
+
require_relative 'olaf/drivers/snowflake'
|
8
|
+
|
9
|
+
module Olaf
|
10
|
+
# Configures Olaf module to execute queries in Snowflake or in a local object
|
11
|
+
# to prevent external calls. Arguments passed will be forwarded directly
|
12
|
+
# to the `olaf_driver` class specified.
|
13
|
+
#
|
14
|
+
# By default, it will set a connection with Snowflake
|
15
|
+
def self.configure(olaf_driver: Olaf::Snowflake, **args)
|
16
|
+
@instance = olaf_driver.new(**args)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Executes a query defined by Olaf::QueryDefinition with the driver
|
20
|
+
# configured previously.
|
21
|
+
#
|
22
|
+
# @return Enumerable of results.
|
23
|
+
# (i.e. Array of Hashes or `row_objects` when specified)
|
24
|
+
#
|
25
|
+
# @raises Olaf::QueryExecutionError
|
26
|
+
def self.execute(olaf_query)
|
27
|
+
row_object = olaf_query.class.row_object
|
28
|
+
row_transformer = row_object ? ->(r) { row_object.new(**r) } : Proc.new(&:itself)
|
29
|
+
|
30
|
+
instance
|
31
|
+
.fetch(olaf_query)
|
32
|
+
.map!(&row_transformer)
|
33
|
+
rescue Sequel::DatabaseError => error
|
34
|
+
raise QueryExecutionError.new(error.message, olaf_query)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns an instance to execute queries when its configured.
|
38
|
+
#
|
39
|
+
# @return Olaf driver instance
|
40
|
+
# * Olaf::Fake - Ideal for testing
|
41
|
+
# * Olaf::Snowflake - Sequel.odbc driver to run queries in Snowflake
|
42
|
+
#
|
43
|
+
def self.instance
|
44
|
+
@instance || raise('You need to configure Olaf before using it!')
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Olaf
|
2
|
+
class Fake
|
3
|
+
attr_reader :config, :execution_log, :recorded_responses
|
4
|
+
|
5
|
+
def initialize(**config)
|
6
|
+
@config = config
|
7
|
+
@execution_log = []
|
8
|
+
@recorded_responses = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch(olaf_query)
|
12
|
+
@execution_log << olaf_query
|
13
|
+
|
14
|
+
generic_response = @recorded_responses.dig(olaf_query.class, :generic)
|
15
|
+
specific_response = @recorded_responses.dig(olaf_query.class, olaf_query.variables)
|
16
|
+
|
17
|
+
specific_response || generic_response || raise_developer_error!(olaf_query)
|
18
|
+
end
|
19
|
+
|
20
|
+
def register_result(olaf_query_class, result, with: :generic)
|
21
|
+
@recorded_responses[olaf_query_class] ||= {}
|
22
|
+
@recorded_responses[olaf_query_class].merge!(with => result)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def raise_developer_error!(query)
|
28
|
+
error_msg = "No Query result registered for '#{query.class}' with: #{query.variables} found!"
|
29
|
+
|
30
|
+
available_responses =
|
31
|
+
@recorded_responses
|
32
|
+
.map { |k, v| [k, ['', *v.keys].join("\n\t\t=> ")] }
|
33
|
+
.map { |klass, args| "\t #{klass}#{args}" }
|
34
|
+
.join("\n\n")
|
35
|
+
.then { |str| "Queries responses registered: \n\n #{str}" unless str.empty?}
|
36
|
+
|
37
|
+
puts %{
|
38
|
+
#{error_msg}
|
39
|
+
|
40
|
+
This is a testing environment and the query has no result registered.
|
41
|
+
To register a query result call `Olaf.instance.register_result`
|
42
|
+
during the test setup or before executing the query.
|
43
|
+
|
44
|
+
|
45
|
+
Olaf.instance.register_result(#{query.class}, [])
|
46
|
+
|
47
|
+
|
48
|
+
To register a result for specific arguments you can specify the variables:
|
49
|
+
|
50
|
+
|
51
|
+
Olaf.instance.register_result(#{query.class}, [], with: #{query.variables})
|
52
|
+
|
53
|
+
|
54
|
+
#{available_responses}
|
55
|
+
}
|
56
|
+
|
57
|
+
raise error_msg
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Olaf
|
2
|
+
class Snowflake
|
3
|
+
def initialize(**config)
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def fetch(olaf_query)
|
8
|
+
conn.fetch(olaf_query.sql_template, **olaf_query.variables).all
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def conn
|
14
|
+
@conn ||= Sequel.odbc('snowflake', **@config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/olaf/errors.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Olaf
|
2
|
+
class MissingArgumentsError < ArgumentError
|
3
|
+
def initialize(olaf_query)
|
4
|
+
@olaf_query = olaf_query
|
5
|
+
msg = "Missing arguments: #{olaf_query.missing_arguments}"
|
6
|
+
|
7
|
+
super(msg)
|
8
|
+
end
|
9
|
+
|
10
|
+
def metadata
|
11
|
+
{
|
12
|
+
query: @olaf_query.class.name,
|
13
|
+
defined_arguments: @olaf_query.defined_arguments,
|
14
|
+
missing_arguments: @olaf_query.missing_arguments,
|
15
|
+
arguments: @olaf_query.variables
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class UndefinedArgumentsError < StandardError
|
21
|
+
def initialize(olaf_query)
|
22
|
+
super("Undefined arguments: #{olaf_query.undefined_arguments}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class QueryExecutionError < StandardError
|
27
|
+
attr_reader :metadata
|
28
|
+
|
29
|
+
def initialize(message, olaf_query)
|
30
|
+
@query = olaf_query
|
31
|
+
@metadata = olaf_query.metadata
|
32
|
+
super(message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative 'errors'
|
2
|
+
require_relative 'query_definition/class_methods'
|
3
|
+
|
4
|
+
module Olaf
|
5
|
+
module QueryDefinition
|
6
|
+
attr_reader :variables, :sql_template
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(Olaf::QueryDefinition::ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(**variables)
|
13
|
+
@variables = variables
|
14
|
+
@sql_template = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Loads the SQL template and validate all the arguments are
|
18
|
+
# defined AND present.
|
19
|
+
# The instance will be ready to be executed.
|
20
|
+
#
|
21
|
+
# @return QueryDefinition instance
|
22
|
+
#
|
23
|
+
# @raises Snowflake::UndefinedArgumentsError
|
24
|
+
# @raises Snowflake::MissingArgumentsError
|
25
|
+
def prepare
|
26
|
+
@sql_template ||= File.read(self.class.template)
|
27
|
+
|
28
|
+
raise UndefinedArgumentsError, self if undefined_arguments.any?
|
29
|
+
raise MissingArgumentsError, self if missing_arguments.any?
|
30
|
+
|
31
|
+
literal_arguments = self.class.arguments.select { |_k, v| v[:literal] }.keys
|
32
|
+
|
33
|
+
variables.slice(*literal_arguments).each do |placeholder, literal_value|
|
34
|
+
@sql_template.gsub!(":#{placeholder}", literal_value)
|
35
|
+
end
|
36
|
+
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def metadata
|
41
|
+
{
|
42
|
+
query_class: self.class.name,
|
43
|
+
arguments: variables,
|
44
|
+
sql_template: sql_template,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def defined_arguments
|
49
|
+
self.class.arguments.keys
|
50
|
+
end
|
51
|
+
|
52
|
+
def missing_arguments
|
53
|
+
defined_arguments - @variables.keys
|
54
|
+
end
|
55
|
+
|
56
|
+
# Find placeholders in the SQL file.
|
57
|
+
# Every placeholder MUST be declared as an `argument` of the query.
|
58
|
+
def undefined_arguments
|
59
|
+
@sql_template
|
60
|
+
.scan(/[^:]:(\w+)/)
|
61
|
+
.flatten
|
62
|
+
.map(&:to_sym)
|
63
|
+
.then { |required_arguments| required_arguments - defined_arguments }
|
64
|
+
.uniq
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Olaf
|
2
|
+
module QueryDefinition
|
3
|
+
module ClassMethods
|
4
|
+
# Creates a new instance of the Query defined and validates
|
5
|
+
# the parameters passed, leaving the instance in a ready-to-execute state.
|
6
|
+
# @return Snowflake::QueryDefinition instance
|
7
|
+
def prepare(**vars)
|
8
|
+
new(vars).prepare
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns ALL the arguments defined for the query, including options.
|
12
|
+
# @return Hash
|
13
|
+
def arguments
|
14
|
+
@arguments ||= {}
|
15
|
+
end
|
16
|
+
|
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)
|
21
|
+
#
|
22
|
+
# options - Hash config for each argument
|
23
|
+
# :as - Argument Type
|
24
|
+
# * :literal - Forces a string substitution with the literal value
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
#
|
28
|
+
# class OneQuery
|
29
|
+
# include Snowflake::QueryDefinition
|
30
|
+
#
|
31
|
+
# template 'reports/one.sql'
|
32
|
+
#
|
33
|
+
# argument :dealersip_id
|
34
|
+
# argument :table_name, as: :literal
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
#
|
38
|
+
def argument(name, options = {})
|
39
|
+
return if arguments.key?(name)
|
40
|
+
|
41
|
+
arguments[name] = { literal: options[:as] == :literal }
|
42
|
+
|
43
|
+
name
|
44
|
+
end
|
45
|
+
|
46
|
+
# Define the file path to the SQL template for this query.
|
47
|
+
#
|
48
|
+
# Example:
|
49
|
+
#
|
50
|
+
# class OneQuery
|
51
|
+
# include Snowflake::QueryDefinition
|
52
|
+
#
|
53
|
+
# template 'reports/one.sql'
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
#
|
57
|
+
def template(file_name = nil)
|
58
|
+
@template ||= file_name
|
59
|
+
end
|
60
|
+
|
61
|
+
# Define the object representing each row of the result.
|
62
|
+
# When not specified, each row will be a hash by default.
|
63
|
+
#
|
64
|
+
# Example:
|
65
|
+
#
|
66
|
+
# class OneQuery
|
67
|
+
# include Snowflake::QueryDefinition
|
68
|
+
#
|
69
|
+
# row_object MyOwnObject
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
#
|
73
|
+
def row_object(object_class = nil)
|
74
|
+
@row_object ||= object_class
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
data/olaf.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'olaf'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
5
|
+
s.summary = 'Ruby wrapper for Snowflake queries.'
|
6
|
+
s.authors = ['Emiliano Mancuso']
|
7
|
+
s.email = ['emiliano.mancuso@gmail.com', 'developers@carwow.co.uk']
|
8
|
+
s.homepage = 'http://github.com/carwow/olaf'
|
9
|
+
s.license = 'MIT'
|
10
|
+
|
11
|
+
s.files = Dir[
|
12
|
+
'README.md',
|
13
|
+
'rakefile',
|
14
|
+
'lib/**/*.rb',
|
15
|
+
'*.gemspec'
|
16
|
+
]
|
17
|
+
s.test_files = Dir['test/*.*']
|
18
|
+
|
19
|
+
s.add_runtime_dependency 'sequel', '~> 5.37'
|
20
|
+
s.add_runtime_dependency 'ruby-odbc', '~> 0.99'
|
21
|
+
end
|
data/rakefile
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class OlafConfigureTest < Test::Unit::TestCase
|
4
|
+
class MockDriver
|
5
|
+
attr_reader :config
|
6
|
+
|
7
|
+
def initialize(**config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def instance
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_configure_initializes_driver_instance
|
17
|
+
Olaf.configure(olaf_driver: MockDriver, other: 'configs', like: 'user and passwd')
|
18
|
+
|
19
|
+
assert Olaf.instance.is_a?(MockDriver)
|
20
|
+
assert_equal Olaf.instance.config, { other: 'configs', like: 'user and passwd' }
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_configure_defaults_to_snowflake
|
24
|
+
Olaf.configure(random: 'stuff')
|
25
|
+
|
26
|
+
assert Olaf.instance.is_a?(Olaf::Snowflake)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class OlafExecuteTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
Olaf.configure(olaf_driver: Olaf::Fake)
|
6
|
+
|
7
|
+
@query = Class.new.include(Olaf::QueryDefinition)
|
8
|
+
@query.template File.join(File.dirname(__FILE__), './fixtures/query_with_arguments.sql')
|
9
|
+
@query.argument :id
|
10
|
+
|
11
|
+
Olaf.instance.register_result(@query, [{ company: 'carwow' }])
|
12
|
+
@query_instance = @query.new(id: 1).prepare
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_execute_returns_an_enumerable
|
16
|
+
assert Olaf.execute(@query_instance).is_a?(Enumerable)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_execute_returns_a_list_of_row_objects_when_defined
|
20
|
+
@query.row_object OpenStruct
|
21
|
+
|
22
|
+
all_row_objects = Olaf.execute(@query_instance).all? { |e| e.is_a? OpenStruct }
|
23
|
+
|
24
|
+
assert all_row_objects
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_execute_returns_hashes_when_no_row_object_defined
|
28
|
+
all_hashes = Olaf.execute(@query_instance).all? { |e| e.is_a? Hash }
|
29
|
+
|
30
|
+
assert all_hashes
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_execute_raises_error
|
34
|
+
faulty_driver = Class.new do
|
35
|
+
def initialize(**args); end
|
36
|
+
|
37
|
+
def fetch(*args)
|
38
|
+
raise Sequel::DatabaseError
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Olaf.configure(olaf_driver: faulty_driver)
|
43
|
+
|
44
|
+
assert_raise Olaf::QueryExecutionError do
|
45
|
+
Olaf.execute(@query_instance)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: olaf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Emiliano Mancuso
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-10-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sequel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.37'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.37'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ruby-odbc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.99'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.99'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- emiliano.mancuso@gmail.com
|
44
|
+
- developers@carwow.co.uk
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- README.md
|
50
|
+
- lib/olaf.rb
|
51
|
+
- lib/olaf/drivers/fake.rb
|
52
|
+
- lib/olaf/drivers/snowflake.rb
|
53
|
+
- lib/olaf/errors.rb
|
54
|
+
- lib/olaf/query_definition.rb
|
55
|
+
- lib/olaf/query_definition/class_methods.rb
|
56
|
+
- olaf.gemspec
|
57
|
+
- rakefile
|
58
|
+
- test/helper.rb
|
59
|
+
- test/olaf_configure_test.rb
|
60
|
+
- test/olaf_execute_test.rb
|
61
|
+
homepage: http://github.com/carwow/olaf
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubygems_version: 3.1.4
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Ruby wrapper for Snowflake queries.
|
84
|
+
test_files:
|
85
|
+
- test/helper.rb
|
86
|
+
- test/olaf_execute_test.rb
|
87
|
+
- test/olaf_configure_test.rb
|