athens 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +14 -0
- data/README.md +150 -0
- data/Rakefile +2 -0
- data/Vagrantfile +38 -0
- data/athens.gemspec +41 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/athens.rb +25 -0
- data/lib/athens/configuration.rb +15 -0
- data/lib/athens/connection.rb +55 -0
- data/lib/athens/error.rb +8 -0
- data/lib/athens/query.rb +186 -0
- data/lib/athens/version.rb +3 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '02197372c02d45a05efd88997b4cd8c01d66c869eafd6ca867f86da791ffe7b4'
|
4
|
+
data.tar.gz: 1753f4f6986c4fedf82f09d9a6d9ebe0b9fb630d59261011fc3c0667108e4ed0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: facc871951901270b2b61410ae471cd6d62fcb33d2a914da76855e41fbccfb5039ee4e0d086deced77bcf39bf8c07fe865c635d6659093a2acac181291c78c2a
|
7
|
+
data.tar.gz: feca9c73d6145784b74dc4ce5442c2b764efda51719ff58e5fa6896d8dfd89186224d7aaa4f9ae282d58043bf5aa4f0e3576998d40ec0d9983c51d917d6d8022
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at TODO: Write your email address. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
athens (0.1.0)
|
5
|
+
aws-sdk-athena (~> 1)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
aws-eventstream (1.0.1)
|
11
|
+
aws-partitions (1.136.0)
|
12
|
+
aws-sdk-athena (1.7.0)
|
13
|
+
aws-sdk-core (~> 3, >= 3.39.0)
|
14
|
+
aws-sigv4 (~> 1.0)
|
15
|
+
aws-sdk-core (3.46.0)
|
16
|
+
aws-eventstream (~> 1.0)
|
17
|
+
aws-partitions (~> 1.0)
|
18
|
+
aws-sigv4 (~> 1.0)
|
19
|
+
jmespath (~> 1.0)
|
20
|
+
aws-sigv4 (1.0.3)
|
21
|
+
jmespath (1.4.0)
|
22
|
+
rake (10.5.0)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
athens!
|
29
|
+
bundler (~> 1.17)
|
30
|
+
rake (~> 10.0)
|
31
|
+
|
32
|
+
BUNDLED WITH
|
33
|
+
1.17.2
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
|
+
Version 2, December 2004
|
3
|
+
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
5
|
+
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
7
|
+
copies of this license document, and changing it is allowed as long
|
8
|
+
as the name is changed.
|
9
|
+
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
12
|
+
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
14
|
+
|
data/README.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# Athens
|
2
|
+
|
3
|
+
Athens is a wrapper around the standard AWS athena sdk, with a much simpler interface for executing queries and processing results.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'athens'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install athens
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Quickstart
|
24
|
+
|
25
|
+
There are two main classes for Athens, the `Connection` and the `Query`. First "open" a connection to the database:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
conn = Athens::Connection.new(database: 'sample')
|
29
|
+
```
|
30
|
+
|
31
|
+
Then start a query:
|
32
|
+
```ruby
|
33
|
+
query = conn.execute("SELECT * FROM mytable")
|
34
|
+
```
|
35
|
+
|
36
|
+
That kicks off an Athena query in the background. If you want you can just wait for it to finish:
|
37
|
+
```ruby
|
38
|
+
query.wait
|
39
|
+
# or
|
40
|
+
query.wait(5) # Wait 5 seconds at most
|
41
|
+
```
|
42
|
+
|
43
|
+
When your query is done, grab the results as an array:
|
44
|
+
```ruby
|
45
|
+
results = query.to_a
|
46
|
+
# [
|
47
|
+
# ['column_1', 'column_2', 'column_3'],
|
48
|
+
# [15, 'data', true],
|
49
|
+
# [20, 'foo', false],
|
50
|
+
# ...
|
51
|
+
# ]
|
52
|
+
```
|
53
|
+
|
54
|
+
Or as a hash (which is really an array where each row is a hash):
|
55
|
+
```ruby
|
56
|
+
results = query.to_h
|
57
|
+
# [
|
58
|
+
# {'column_1': 15, 'column_2': 'data', 'column_3': true},
|
59
|
+
# {'column_1': 20, 'column_2': 'foo', 'column_3': false},
|
60
|
+
# ...
|
61
|
+
# ]
|
62
|
+
```
|
63
|
+
|
64
|
+
Athens attempts to parse the sql data types into their ruby equivalents, although there's currently no support for the more complex Array/Map types.
|
65
|
+
|
66
|
+
### Configuration
|
67
|
+
|
68
|
+
Configure your AWS settings in an `Athens.configure` block (in rails put this in `config/initializers/athens.rb`):
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Athens.configure do |config|
|
72
|
+
config.output_location = "s3://my-bucket/my-folder/athena/results/" # Required
|
73
|
+
config.aws_access_key = 'access' # Optional
|
74
|
+
config.aws_secret_key = 'secret' # Optional
|
75
|
+
config.aws_region = 'us-east-1' # Optional
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
The aws parameters are all "optional", in that you can omit those in favor of any of the standard AWS configuration options (i.e. IAM Roles, environment variables, .aws/credentials files).
|
80
|
+
|
81
|
+
You can also override the AWS client configuration on a per-connection basis:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
conn = Athens::Connection.new(aws_client_override: {})
|
85
|
+
```
|
86
|
+
|
87
|
+
Take a look at the [AWS Athena SDK](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Athena/Client.html#initialize-instance_method) for a list of all the available options.
|
88
|
+
|
89
|
+
### Advanced Usage
|
90
|
+
|
91
|
+
Providing a database name to the connection is optional, if you omit the name you'll have to specify it in your query:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
conn = Athens::Connection.new(database 'sample')
|
95
|
+
query = conn.execute("SELECT * FROM mytable")
|
96
|
+
|
97
|
+
# or
|
98
|
+
|
99
|
+
conn = Athens::Connection.new
|
100
|
+
query = conn.execute("SELECT * FROM sample.mytable")
|
101
|
+
```
|
102
|
+
|
103
|
+
While waiting for a query to finish, you could get one of two exceptions:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
conn = Athens::Connection.new(database 'sample')
|
107
|
+
query = conn.execute("SELECT * FROM mytable")
|
108
|
+
|
109
|
+
begin
|
110
|
+
query.wait()
|
111
|
+
rescue Athens::QueryFailedError => qfe
|
112
|
+
# Query returned a failure message, qfe.message has details
|
113
|
+
rescue Athens::QueryCancelledError => qce
|
114
|
+
# Query was canceled (usually by the user), qce.message has details
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
When a query is running you can do a few things:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
conn = Athens::Connection.new(database: 'sample')
|
122
|
+
query = conn.execute("SELECT * FROM mytable")
|
123
|
+
|
124
|
+
query.state # Returns one of QUEUED, RUNNING, SUCCEEDED, FAILED, or CANCELLED (https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Athena/Types/QueryExecutionStatus.html#state-instance_method)
|
125
|
+
query.state_reason # Further details from AWS about the state
|
126
|
+
query.query_execution_id # The id of the query returned from AWS
|
127
|
+
query.cancel # Attempts to cancel an in-progress query, returns true or false (if the query has already finished this will return false)
|
128
|
+
|
129
|
+
query.to_a(header_row: false) # If you want your query results returned without a header row of column names
|
130
|
+
```
|
131
|
+
|
132
|
+
## Development
|
133
|
+
|
134
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
135
|
+
|
136
|
+
If you want you can use Vagrant instead, there's already a `Vagrantfile` so a simple `vagrant up` should get you setup.
|
137
|
+
|
138
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
139
|
+
|
140
|
+
## Contributing
|
141
|
+
|
142
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/getletterpress/athens. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
143
|
+
|
144
|
+
## License
|
145
|
+
|
146
|
+
The gem is available as open source under the terms of the [WTFPL License](http://www.wtfpl.net/).
|
147
|
+
|
148
|
+
## Code of Conduct
|
149
|
+
|
150
|
+
Everyone interacting in the Athens project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/getletterpress/athens/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/Vagrantfile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
# Provisioning script
|
5
|
+
$script = <<SCRIPT
|
6
|
+
echo "*** Updating packages"
|
7
|
+
|
8
|
+
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y aptitude
|
9
|
+
sudo DEBIAN_FRONTEND=noninteractive aptitude update
|
10
|
+
sudo DEBIAN_FRONTEND=noninteractive aptitude -y safe-upgrade
|
11
|
+
|
12
|
+
echo "*** Installing new packages"
|
13
|
+
sudo DEBIAN_FRONTEND=noninteractive aptitude install -y curl git-core vim
|
14
|
+
|
15
|
+
if rvm -v 2>/dev/null; then
|
16
|
+
echo "*** rvm already installed, skipping"
|
17
|
+
else
|
18
|
+
echo "*** Installing rvm"
|
19
|
+
gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
|
20
|
+
curl -sSL https://get.rvm.io | bash -s stable --ruby
|
21
|
+
echo "gem: --no-document" > ~/.gemrc
|
22
|
+
|
23
|
+
echo "*** Finished installing rvm"
|
24
|
+
fi
|
25
|
+
|
26
|
+
echo "*********************"
|
27
|
+
echo "PROVISIONING FINISHED"
|
28
|
+
echo "*********************"
|
29
|
+
SCRIPT
|
30
|
+
|
31
|
+
|
32
|
+
Vagrant.configure('2') do |config|
|
33
|
+
config.vm.box = "ubuntu/xenial64"
|
34
|
+
config.vm.hostname = 'athens-dev'
|
35
|
+
|
36
|
+
# Provision the machine with the shell script above
|
37
|
+
config.vm.provision "shell", inline: $script, privileged: false
|
38
|
+
end
|
data/athens.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "athens/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "athens"
|
8
|
+
spec.version = Athens::VERSION
|
9
|
+
spec.authors = ["Chris Schulte"]
|
10
|
+
spec.email = ["chris@oceanbreezesoftware.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Run simple SQL queries in AWS Athena}
|
13
|
+
spec.description = %q{Allows you to easily access AWS Athena databases and run queries}
|
14
|
+
spec.homepage = "https://github.com/getletterpress/athens"
|
15
|
+
spec.license = "WTFPL"
|
16
|
+
|
17
|
+
if spec.respond_to?(:metadata)
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/getletterpress/athens"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/getletterpress/athens/CHANGELOG.md"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
"public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
# Specify which files should be added to the gem when it is released.
|
27
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
29
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.required_ruby_version = '~> 2.4'
|
36
|
+
|
37
|
+
spec.add_dependency "aws-sdk-athena", "~> 1"
|
38
|
+
|
39
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
40
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
41
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "athens"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/athens.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "athens/error"
|
2
|
+
require "athens/version"
|
3
|
+
require "athens/configuration"
|
4
|
+
require "athens/connection"
|
5
|
+
require "athens/query"
|
6
|
+
|
7
|
+
require 'aws-sdk-athena'
|
8
|
+
|
9
|
+
module Athens
|
10
|
+
class << self
|
11
|
+
attr_accessor :configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.reset
|
19
|
+
@configuration = Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configure
|
23
|
+
yield(configuration)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Athens
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :aws_access_key,
|
4
|
+
:aws_secret_key,
|
5
|
+
:aws_region,
|
6
|
+
:output_location
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@aws_access_key = nil
|
10
|
+
@aws_secret_key = nil
|
11
|
+
@aws_region = nil
|
12
|
+
@output_location = nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
module Athens
|
4
|
+
class Connection
|
5
|
+
attr_reader :database_name
|
6
|
+
attr_reader :client
|
7
|
+
|
8
|
+
def initialize(database: nil, aws_client_override: {})
|
9
|
+
@database_name = database
|
10
|
+
|
11
|
+
client_config = {
|
12
|
+
access_key_id: Athens.configuration.aws_access_key,
|
13
|
+
secret_access_key: Athens.configuration.aws_secret_key,
|
14
|
+
region: Athens.configuration.aws_region
|
15
|
+
}.merge(aws_client_override).compact
|
16
|
+
|
17
|
+
@client = Aws::Athena::Client.new(client_config)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Runs a query against Athena, returning an Athens::Query object
|
21
|
+
# that you can use to wait for it to finish or get the results
|
22
|
+
def execute(query)
|
23
|
+
if @database_name
|
24
|
+
resp = @client.start_query_execution(
|
25
|
+
query_string: query,
|
26
|
+
query_execution_context: context,
|
27
|
+
result_configuration: result_config
|
28
|
+
)
|
29
|
+
else
|
30
|
+
resp = @client.start_query_execution(
|
31
|
+
query_string: query,
|
32
|
+
result_configuration: result_config
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
return Athens::Query.new(self, resp.query_execution_id)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def context
|
42
|
+
Aws::Athena::Types::QueryExecutionContext.new(database: @database_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def result_config
|
46
|
+
Aws::Athena::Types::ResultConfiguration.new(
|
47
|
+
output_location: Athens.configuration.output_location,
|
48
|
+
encryption_configuration: {
|
49
|
+
encryption_option: "SSE_S3"
|
50
|
+
}
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/athens/error.rb
ADDED
data/lib/athens/query.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
module Athens
|
2
|
+
class Query
|
3
|
+
attr_reader :query_execution_id
|
4
|
+
|
5
|
+
def initialize(connection, query_execution_id)
|
6
|
+
@connection = connection
|
7
|
+
@query_execution_id = query_execution_id
|
8
|
+
@state = nil
|
9
|
+
@state_reason = nil
|
10
|
+
@cancelled = false
|
11
|
+
|
12
|
+
@results = nil
|
13
|
+
@hash_results = nil
|
14
|
+
|
15
|
+
version = RUBY_VERSION.split('.').map {|v| v.to_i}
|
16
|
+
@decimal_without_new = (version[0] >= 2 && version[1] >= 5)
|
17
|
+
end
|
18
|
+
|
19
|
+
def state
|
20
|
+
refresh_state if state_needs_refresh?
|
21
|
+
@state
|
22
|
+
end
|
23
|
+
|
24
|
+
def state_reason
|
25
|
+
refresh_state if state_needs_refresh?
|
26
|
+
@state_reason
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait(max_seconds = nil)
|
30
|
+
if max_seconds.nil?
|
31
|
+
stop_at = nil
|
32
|
+
else
|
33
|
+
stop_at = Time.now + max_seconds
|
34
|
+
end
|
35
|
+
|
36
|
+
while true
|
37
|
+
if stop_at != nil && Time.now > stop_at
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
refresh_state
|
42
|
+
|
43
|
+
if @state == 'SUCCEEDED'
|
44
|
+
return true
|
45
|
+
elsif @state == 'FAILED'
|
46
|
+
raise QueryFailedError.new(@state_reason)
|
47
|
+
elsif state == 'CANCELLED'
|
48
|
+
raise QueryCancelledError.new(@state_reason)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Wait a bit and check again
|
52
|
+
sleep(0.25)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def cancel
|
57
|
+
unless @cancelled
|
58
|
+
resp = @connection.client.stop_query_execution({
|
59
|
+
query_execution_id: @query_execution_id
|
60
|
+
})
|
61
|
+
@cancelled = true
|
62
|
+
refresh_state
|
63
|
+
end
|
64
|
+
|
65
|
+
if @state == 'CANCELLED'
|
66
|
+
return true
|
67
|
+
else
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_a(header_row: true)
|
73
|
+
raise InvalidRequestError.new("Query must be in SUCCEEDED state to return results") unless @state == 'SUCCEEDED'
|
74
|
+
|
75
|
+
if @results.nil?
|
76
|
+
# Need to load and map all of the rows from the original result
|
77
|
+
@results = []
|
78
|
+
result = @connection.client.get_query_results({query_execution_id: @query_execution_id})
|
79
|
+
|
80
|
+
metadata = result.result_set.result_set_metadata
|
81
|
+
first = true
|
82
|
+
|
83
|
+
while true
|
84
|
+
rows = result.result_set.rows
|
85
|
+
break if rows.empty?
|
86
|
+
|
87
|
+
if first
|
88
|
+
@results << rows.shift.data.map {|col| col.var_char_value}
|
89
|
+
first = false
|
90
|
+
end
|
91
|
+
|
92
|
+
rows.each do |row|
|
93
|
+
@results << map_types(metadata, row)
|
94
|
+
end
|
95
|
+
|
96
|
+
if result.next_token
|
97
|
+
result = @connection.client.get_query_results({
|
98
|
+
query_execution_id: @query_execution_id,
|
99
|
+
next_token: result.next_token
|
100
|
+
})
|
101
|
+
else
|
102
|
+
# No more rows, break out and return our mapped data
|
103
|
+
break
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if header_row
|
109
|
+
return @results
|
110
|
+
else
|
111
|
+
return @results[1, @results.size]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_h
|
116
|
+
if @hash_results.nil?
|
117
|
+
all_rows = self.to_a(header_row: true)
|
118
|
+
|
119
|
+
headers = all_rows.shift
|
120
|
+
|
121
|
+
@hash_results = []
|
122
|
+
|
123
|
+
unless headers.nil?
|
124
|
+
all_rows.each do |row|
|
125
|
+
map = {}
|
126
|
+
headers.each_with_index do |header, index|
|
127
|
+
map[header] = row[index]
|
128
|
+
end
|
129
|
+
@hash_results << map
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
return @hash_results
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
def state_needs_refresh?
|
139
|
+
@state.nil? || (['QUEUED', 'RUNNING'].include?(@state))
|
140
|
+
end
|
141
|
+
|
142
|
+
def refresh_state
|
143
|
+
resp = @connection.client.get_query_execution({query_execution_id: @query_execution_id})
|
144
|
+
|
145
|
+
@state = resp.query_execution.status.state
|
146
|
+
@state_reason = resp.query_execution.status.state_change_reason
|
147
|
+
end
|
148
|
+
|
149
|
+
def map_types(metadata, row)
|
150
|
+
mapped = []
|
151
|
+
|
152
|
+
metadata.column_info.each_with_index do |col, index|
|
153
|
+
data = row.data[index].var_char_value
|
154
|
+
|
155
|
+
case col.type
|
156
|
+
when 'tinyint', 'smallint', 'int', 'integer', 'bigint'
|
157
|
+
mapped << data.to_i
|
158
|
+
when 'timestamp'
|
159
|
+
mapped << Time.parse(data)
|
160
|
+
when 'varchar'
|
161
|
+
mapped << data
|
162
|
+
when 'float', 'double'
|
163
|
+
mapped << data.to_f
|
164
|
+
when 'decimal'
|
165
|
+
if @decimal_without_new
|
166
|
+
mapped << BigDecimal(data)
|
167
|
+
else
|
168
|
+
mapped << BigDecimal.new(data)
|
169
|
+
end
|
170
|
+
when 'date'
|
171
|
+
mapped << Date.parse(data)
|
172
|
+
when 'boolean'
|
173
|
+
mapped << (data == "true")
|
174
|
+
else
|
175
|
+
puts "WARNING: Unsupported type: #{col.type}, defaulting to string"
|
176
|
+
mapped << data
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
return mapped
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: athens
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Schulte
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-03-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk-athena
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.17'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.17'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
description: Allows you to easily access AWS Athena databases and run queries
|
56
|
+
email:
|
57
|
+
- chris@oceanbreezesoftware.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- CHANGELOG.md
|
64
|
+
- CODE_OF_CONDUCT.md
|
65
|
+
- Gemfile
|
66
|
+
- Gemfile.lock
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- Vagrantfile
|
71
|
+
- athens.gemspec
|
72
|
+
- bin/console
|
73
|
+
- bin/setup
|
74
|
+
- lib/athens.rb
|
75
|
+
- lib/athens/configuration.rb
|
76
|
+
- lib/athens/connection.rb
|
77
|
+
- lib/athens/error.rb
|
78
|
+
- lib/athens/query.rb
|
79
|
+
- lib/athens/version.rb
|
80
|
+
homepage: https://github.com/getletterpress/athens
|
81
|
+
licenses:
|
82
|
+
- WTFPL
|
83
|
+
metadata:
|
84
|
+
homepage_uri: https://github.com/getletterpress/athens
|
85
|
+
source_code_uri: https://github.com/getletterpress/athens
|
86
|
+
changelog_uri: https://github.com/getletterpress/athens/CHANGELOG.md
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '2.4'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.7.8
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Run simple SQL queries in AWS Athena
|
107
|
+
test_files: []
|