athena-cli 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.projections.json +20 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +75 -0
  7. data/LICENSE.md +20 -0
  8. data/README.md +128 -0
  9. data/Rakefile +44 -0
  10. data/athena-cli.gemspec +26 -0
  11. data/bin/athena-cli +10 -0
  12. data/features/athena.feature +8 -0
  13. data/features/step_definitions/athena_steps.rb +6 -0
  14. data/features/support/env.rb +15 -0
  15. data/lib/amazon_athena.rb +7 -0
  16. data/lib/amazon_athena/cli.rb +406 -0
  17. data/lib/amazon_athena/client.rb +114 -0
  18. data/lib/amazon_athena/command.rb +16 -0
  19. data/lib/amazon_athena/commands.rb +20 -0
  20. data/lib/amazon_athena/commands/alter_table_add_partition.rb +29 -0
  21. data/lib/amazon_athena/commands/alter_table_drop_partition.rb +29 -0
  22. data/lib/amazon_athena/commands/create_database.rb +23 -0
  23. data/lib/amazon_athena/commands/create_table.rb +23 -0
  24. data/lib/amazon_athena/commands/describe_table.rb +21 -0
  25. data/lib/amazon_athena/commands/drop_database.rb +23 -0
  26. data/lib/amazon_athena/commands/drop_table.rb +23 -0
  27. data/lib/amazon_athena/commands/repair_table.rb +23 -0
  28. data/lib/amazon_athena/commands/show_columns.rb +22 -0
  29. data/lib/amazon_athena/commands/show_create_table.rb +22 -0
  30. data/lib/amazon_athena/commands/show_databases.rb +17 -0
  31. data/lib/amazon_athena/commands/show_partitions.rb +28 -0
  32. data/lib/amazon_athena/commands/show_table_properties.rb +36 -0
  33. data/lib/amazon_athena/commands/show_tables.rb +23 -0
  34. data/lib/amazon_athena/partition.rb +21 -0
  35. data/lib/amazon_athena/transformer.rb +19 -0
  36. data/lib/amazon_athena/version.rb +3 -0
  37. data/lib/jdbc_helper/athena.rb +38 -0
  38. data/lib/jdbc_helper/resultset.rb +14 -0
  39. data/log4j.xml +18 -0
  40. data/script/bootstrap +5 -0
  41. data/script/package +8 -0
  42. data/script/release +16 -0
  43. data/script/test +6 -0
  44. data/test/lib/amazon_athena/commands/alter_table_add_partition_test.rb +64 -0
  45. data/test/lib/amazon_athena/commands/alter_table_drop_partition_test.rb +61 -0
  46. data/test/lib/amazon_athena/commands/create_database_test.rb +27 -0
  47. data/test/lib/amazon_athena/commands/describe_table_test.rb +23 -0
  48. data/test/lib/amazon_athena/commands/drop_database_test.rb +22 -0
  49. data/test/lib/amazon_athena/commands/drop_table_test.rb +23 -0
  50. data/test/lib/amazon_athena/commands/repair_table_test.rb +20 -0
  51. data/test/lib/amazon_athena/commands/show_columns_test.rb +23 -0
  52. data/test/lib/amazon_athena/commands/show_create_table_test.rb +23 -0
  53. data/test/lib/amazon_athena/commands/show_databases_test.rb +23 -0
  54. data/test/lib/amazon_athena/commands/show_partitions_test.rb +23 -0
  55. data/test/lib/amazon_athena/commands/show_table_properties_test.rb +25 -0
  56. data/test/lib/amazon_athena/commands/show_tables_test.rb +23 -0
  57. data/test/test_helper.rb +1 -0
  58. metadata +185 -0
@@ -0,0 +1,36 @@
1
+ require_relative '../command'
2
+
3
+ module AmazonAthena
4
+ module Commands
5
+ class ShowTableProperties < AmazonAthena::Command
6
+
7
+ def initialize(database_table)
8
+ @database_table = database_table
9
+ end
10
+
11
+ def statement
12
+ "SHOW TBLPROPERTIES #{@database_table};"
13
+ end
14
+
15
+ def run(connection)
16
+ result = connection.query(statement).raw_output
17
+
18
+ data = Hash[*result.split("\n").map {|line| line.split("\t")}.flatten]
19
+
20
+ data[:name] = @database_table
21
+
22
+ if type = data.delete('EXTERNAL')
23
+ data[:external] = type
24
+ end
25
+
26
+ if last_modified = data.delete('transient_lastDdlTime')
27
+ data[:last_modified] = Time.at(last_modified.to_i)
28
+ end
29
+
30
+ data
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,23 @@
1
+ require_relative '../command'
2
+
3
+ module AmazonAthena
4
+ module Commands
5
+ class ShowTables < AmazonAthena::Command
6
+
7
+ def initialize(database_name)
8
+ @database_name = database_name.strip
9
+ end
10
+
11
+ def statement
12
+ "SHOW TABLES IN #{@database_name};"
13
+ end
14
+
15
+ def run(connection)
16
+ connection.query(statement).map {|row| row.tab_name }
17
+ rescue Exception => e
18
+ e.getCause()
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,21 @@
1
+ module AmazonAthena
2
+ class Partition
3
+
4
+ def initialize(options: {}, location: nil)
5
+ @options = options
6
+ @location = location
7
+ end
8
+
9
+ def to_s
10
+ return nil if @options.empty?
11
+
12
+ # TODO: Sanitize and handle non-strings
13
+ opts = @options.map {|k,v| "#{k} = '#{v}'"}.join(", ")
14
+
15
+ sql = "PARTITION (#{opts})"
16
+ sql += " LOCATION '#{@location}'" if @location
17
+
18
+ sql
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module AmazonAthena
2
+ class Transformer
3
+
4
+ TABLE_NAME_PATTERN = /CREATE EXTERNAL TABLE `(?<name>\S+)`/
5
+ TABLE_LOCATION_PATTERN = /'(?<location>s3:\/\/\S+)'/
6
+
7
+ def self.transform_table(ddl, options = {})
8
+ if name = options[:name]
9
+ ddl[TABLE_NAME_PATTERN] = "CREATE EXTERNAL TABLE `#{name}`"
10
+ end
11
+
12
+ if location = options[:location]
13
+ ddl[TABLE_LOCATION_PATTERN] = "'s3://#{location}'"
14
+ end
15
+
16
+ ddl
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module AmazonAthena
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,38 @@
1
+ require "jdbc-helper"
2
+ require "jdbc_helper/resultset"
3
+
4
+ module JDBCHelper
5
+ module Athena
6
+ extend Connector
7
+
8
+ DRIVER_NAME = "com.amazonaws.athena.jdbc.AthenaDriver".freeze
9
+ JDBCHelper::Constants::Connector::DEFAULT_PARAMETERS[:athena] = {
10
+ driver: "com.amazonaws.athena.jdbc.AthenaDriver"
11
+ }
12
+
13
+ def self.connect(key: nil, secret: nil, region: "us-east-1", s3_staging_dir: nil, extra_params: {}, &block)
14
+ connect_impl :athena, {
15
+ url: "jdbc:awsathena://athena.#{region}.amazonaws.com:443",
16
+ user: key || ENV["AWS_ACCESS_KEY"],
17
+ password: secret || ENV['AWS_SECRET_KEY'],
18
+ s3_staging_dir: s3_uri(s3_staging_dir || ENV["ATHENA_S3_STAGING_DIR"])
19
+ }, {}, &block
20
+ end
21
+
22
+ def self.configure_driver_path(class_path)
23
+ paths = ENV["CLASSPATH"].to_s.split(":")
24
+ paths << class_path
25
+
26
+ ENV["CLASSPATH"] = paths.uniq.join(":")
27
+ end
28
+
29
+ def self.s3_uri(path)
30
+ return path if path.to_s.start_with?("s3://")
31
+
32
+ path = "s3://#{path}"
33
+ path = path + "/" unless path.end_with?("/")
34
+
35
+ path
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ module JDBCHelper
2
+ class Connection
3
+ class ResultSet
4
+ def raw_output(column_index = 1)
5
+ lines = []
6
+ begin
7
+ lines << @rset.getString(column_index)
8
+ end while @rset.next
9
+
10
+ lines.map(&:rstrip).join("\n")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
3
+
4
+ <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
5
+ <appender name="console" class="org.apache.log4j.ConsoleAppender">
6
+ <param name="Target" value="System.out"/>
7
+ <layout class="org.apache.log4j.PatternLayout">
8
+ <param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
9
+ </layout>
10
+ </appender>
11
+
12
+ <root>
13
+ <priority value ="OFF" />
14
+ <appender-ref ref="console" />
15
+ </root>
16
+
17
+
18
+ </log4j:configuration>
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ bundle install --quiet "$@"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: script/gem
3
+ # Updates the gemspec and builds a new gem in the pkg directory.
4
+
5
+ mkdir -p pkg
6
+ gem build *.gemspec
7
+ mv *.gem pkg
8
+
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: script/release
3
+ # Build the package, tag a commit, push it to origin, and then release the
4
+ # package publicly.
5
+
6
+ set -e
7
+
8
+ version="$(script/package | grep Version: | awk '{print $2}')"
9
+ [ -n "$version" ] || exit 1
10
+
11
+ git commit --allow-empty -a -m "Release $version"
12
+ git tag "v$version"
13
+ git push origin
14
+ git push origin "v$version"
15
+ gem push pkg/*-${version}.gem
16
+
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: script/test
3
+ # Runs the library's test suite.
4
+
5
+ script/bootstrap
6
+ bundle exec rake
@@ -0,0 +1,64 @@
1
+ require 'minitest/autorun'
2
+ require './lib/amazon_athena/commands/alter_table_add_partition'
3
+
4
+ describe AmazonAthena::Commands::AlterTableAddPartition do
5
+
6
+ before do
7
+ @klass = AmazonAthena::Commands::AlterTableAddPartition
8
+ end
9
+
10
+ it "builds a ddl statement" do
11
+ partitions = []
12
+
13
+ partitions << AmazonAthena::Partition.new(
14
+ location: 's3://mystorage/path/to/INDIA_14_May_2014',
15
+ options: {
16
+ dt: '2014-05-14',
17
+ country: 'IN'
18
+ }
19
+ )
20
+
21
+ partitions << AmazonAthena::Partition.new(
22
+ location: 's3://mystorage/path/to/INDIA_15_May_2014',
23
+ options: {
24
+ dt: '2014-05-15',
25
+ country: 'IN'
26
+ }
27
+ )
28
+
29
+ cmd = @klass.new("mydb.mytable", partitions)
30
+ expected = <<~SQL
31
+ ALTER TABLE mydb.mytable ADD
32
+ PARTITION (dt = '2014-05-14', country = 'IN') LOCATION 's3://mystorage/path/to/INDIA_14_May_2014'
33
+ PARTITION (dt = '2014-05-15', country = 'IN') LOCATION 's3://mystorage/path/to/INDIA_15_May_2014';
34
+ SQL
35
+ assert_equal expected.strip, cmd.statement
36
+ end
37
+
38
+ it "executes a query" do
39
+ partitions = []
40
+
41
+ partitions << AmazonAthena::Partition.new(
42
+ location: 's3://mystorage/path/to/INDIA_14_May_2014',
43
+ options: {
44
+ dt: '2014-05-14',
45
+ country: 'IN'
46
+ }
47
+ )
48
+
49
+ cmd = @klass.new("mydb.mytable", partitions)
50
+ sql = <<~SQL
51
+ ALTER TABLE mydb.mytable ADD
52
+ PARTITION (dt = '2014-05-14', country = 'IN') LOCATION 's3://mystorage/path/to/INDIA_14_May_2014';
53
+ SQL
54
+
55
+ results = MiniTest::Mock.new
56
+ results.expect(:raw_output, nil)
57
+
58
+ conn = MiniTest::Mock.new
59
+ conn.expect(:query, results, [sql.strip])
60
+
61
+ cmd = @klass.new("mydb.mytable", partitions)
62
+ cmd.run(conn)
63
+ end
64
+ end
@@ -0,0 +1,61 @@
1
+ require 'minitest/autorun'
2
+ require './lib/amazon_athena/commands/alter_table_drop_partition'
3
+
4
+ describe AmazonAthena::Commands::AlterTableDropPartition do
5
+
6
+ before do
7
+ @klass = AmazonAthena::Commands::AlterTableDropPartition
8
+ end
9
+
10
+ it "builds a ddl statement" do
11
+ partitions = []
12
+
13
+ partitions << AmazonAthena::Partition.new(
14
+ options: {
15
+ dt: '2014-05-14',
16
+ country: 'IN'
17
+ }
18
+ )
19
+
20
+ partitions << AmazonAthena::Partition.new(
21
+ options: {
22
+ dt: '2014-05-15',
23
+ country: 'IN'
24
+ }
25
+ )
26
+
27
+ cmd = @klass.new("mydb.mytable", partitions)
28
+ expected = <<~SQL
29
+ ALTER TABLE mydb.mytable DROP
30
+ PARTITION (dt = '2014-05-14', country = 'IN'),
31
+ PARTITION (dt = '2014-05-15', country = 'IN');
32
+ SQL
33
+ assert_equal expected.strip, cmd.statement
34
+ end
35
+
36
+ it "executes a query" do
37
+ partitions = []
38
+
39
+ partitions << AmazonAthena::Partition.new(
40
+ options: {
41
+ dt: '2014-05-14',
42
+ country: 'IN'
43
+ }
44
+ )
45
+
46
+ cmd = @klass.new("mydb.mytable", partitions)
47
+ sql = <<~SQL
48
+ ALTER TABLE mydb.mytable DROP
49
+ PARTITION (dt = '2014-05-14', country = 'IN');
50
+ SQL
51
+
52
+ results = MiniTest::Mock.new
53
+ results.expect(:raw_output, nil)
54
+
55
+ conn = MiniTest::Mock.new
56
+ conn.expect(:query, results, [sql.strip])
57
+
58
+ cmd = @klass.new("mydb.mytable", partitions)
59
+ cmd.run(conn)
60
+ end
61
+ end
@@ -0,0 +1,27 @@
1
+ require 'minitest/autorun'
2
+ require './lib/amazon_athena/commands/create_database'
3
+
4
+ describe AmazonAthena::Commands::CreateDatabase do
5
+
6
+ before do
7
+ @klass = AmazonAthena::Commands::CreateDatabase
8
+ end
9
+
10
+ it "provides a statement" do
11
+ cmd = @klass.new("mydb")
12
+
13
+ assert_equal "CREATE DATABASE IF NOT EXISTS mydb;", cmd.statement
14
+ end
15
+
16
+ it "executes a query" do
17
+ cmd = @klass.new("mydb")
18
+
19
+ results = MiniTest::Mock.new
20
+ results.expect(:raw_output, nil)
21
+
22
+ conn = MiniTest::Mock.new
23
+ conn.expect(:query, results, ["CREATE DATABASE IF NOT EXISTS mydb;"])
24
+
25
+ cmd.run(conn)
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'minitest/autorun'
2
+ require './lib/amazon_athena/commands/describe_table'
3
+
4
+ describe AmazonAthena::Commands::DescribeTable do
5
+
6
+ before do
7
+ @cmd = AmazonAthena::Commands::DescribeTable.new("mydb.mytable")
8
+ end
9
+
10
+ it "provides a db statement" do
11
+ assert_equal "DESCRIBE mydb.mytable;", @cmd.statement
12
+ end
13
+
14
+ it "executes a query" do
15
+ results = MiniTest::Mock.new
16
+ results.expect(:raw_output, nil)
17
+
18
+ conn = MiniTest::Mock.new
19
+ conn.expect(:query, results, ["DESCRIBE mydb.mytable;"])
20
+
21
+ @cmd.run(conn)
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ require 'minitest/autorun'
2
+ require './lib/amazon_athena/commands/drop_database'
3
+
4
+ describe AmazonAthena::Commands::DropDatabase do
5
+
6
+ before do
7
+ @cmd = AmazonAthena::Commands::DropDatabase.new("mydb")
8
+ end
9
+
10
+ it "provides a db statement" do
11
+ assert_equal "DROP DATABASE IF EXISTS mydb;", @cmd.statement
12
+ end
13
+
14
+ it "executes a query" do
15
+ results = MiniTest::Mock.new
16
+
17
+ conn = MiniTest::Mock.new
18
+ conn.expect(:query, results, ["DROP DATABASE IF EXISTS mydb;"])
19
+
20
+ @cmd.run(conn)
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ require 'minitest/autorun'
2
+ require './lib/amazon_athena/commands/drop_table'
3
+
4
+ describe AmazonAthena::Commands::DropTable do
5
+
6
+ before do
7
+ @cmd = AmazonAthena::Commands::DropTable.new("mydb.mytable")
8
+ end
9
+
10
+ it "provides a db statement" do
11
+ assert_equal "DROP TABLE mydb.mytable;", @cmd.statement
12
+ end
13
+
14
+ it "executes a query" do
15
+ results = MiniTest::Mock.new
16
+ results.expect(:raw_output, nil)
17
+
18
+ conn = MiniTest::Mock.new
19
+ conn.expect(:query, results, ["DROP TABLE mydb.mytable;"])
20
+
21
+ @cmd.run(conn)
22
+ end
23
+ end