turboquery 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e52ed1a1ab83ae67ea386d648de86f4cb8852fa9
4
+ data.tar.gz: c8964efb1208aa21975ea6a6bd07680f2c7124e5
5
+ SHA512:
6
+ metadata.gz: 0ca6e4c6bdbb3118f6e1e948e06910c61abb5494564279d1f52fb76a0a309f565528bad532335a4ea2b2d9dd2be337f4ad75264e25d153e39918ee14366a40c5
7
+ data.tar.gz: a894f45341fd2f2b2c260b8df329703075cac03c4da53d73e12ec7d1f218e1fea184fd467d6d9dfa9765490a98195be846a8c23a366d1224ac6990789061184e
data/.editorconfig ADDED
@@ -0,0 +1,21 @@
1
+ # editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ charset = utf-8
9
+ trim_trailing_whitespace = true
10
+ insert_final_newline = true
11
+
12
+ [*.md]
13
+ trim_trailing_whitespace = false
14
+
15
+ [*.go]
16
+ indent_style = tab
17
+ indent_size = 4
18
+
19
+ [Makefile]
20
+ indent_style = tab
21
+ indent_size = 4
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ *.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.log
19
+ .DS_Store
20
+ .ruby-*
data/.rubocop.yml ADDED
@@ -0,0 +1,35 @@
1
+ # Disable for db/schema.rb
2
+ AllCops:
3
+ Exclude:
4
+ - db/**/*
5
+
6
+ # Disable requirement for documentation
7
+ Documentation:
8
+ Enabled: false
9
+
10
+ # Allow extra lines around class body
11
+ EmptyLinesAroundClassBody:
12
+ Enabled: false
13
+
14
+ # Allow extra lines around module body
15
+ EmptyLinesAroundModuleBody:
16
+ Enabled: false
17
+
18
+ # Allow extra lines inside block body
19
+ EmptyLinesAroundBlockBody:
20
+ Enabled: false
21
+
22
+ # Use single quotes unless necessary
23
+ StringLiterals:
24
+ EnforcedStyle: single_quotes
25
+
26
+ # Only treat is_ as a predicate name
27
+ PredicateName:
28
+ NamePrefixBlacklist:
29
+ - is_
30
+
31
+ LineLength:
32
+ Enabled: false
33
+
34
+ ClassAndModuleChildren:
35
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in turboquery.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2015 Jacob Gillespie <jacobwgillespie@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person ob-
4
+ taining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without restric-
6
+ tion, including without limitation the rights to use, copy, modi-
7
+ fy, merge, publish, distribute, sublicense, and/or sell copies of
8
+ the Software, and to permit persons to whom the Software is fur-
9
+ nished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
16
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN-
17
+ FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # turboquery [![Build Status](https://travis-ci.org/playlist-media/turboquery.svg?branch=master)](https://travis-ci.org/playlist-media/turboquery) [![Code Climate](https://codeclimate.com/repos/54e54f5e69568049a90046d9/badges/2c9a4a68230e9e594789/gpa.svg)](https://codeclimate.com/repos/54e54f5e69568049a90046d9/feed)
2
+
3
+ :gem: Turboquery executes PostgreSQL queries against your PostgreSQL database utilizing Redshift to massively speed up complex queries.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ task default: :test
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
@@ -0,0 +1,130 @@
1
+ require 'shellwords'
2
+
3
+ class Turboquery::Connection
4
+
5
+ def table_exists?(table = nil)
6
+ return false if table.nil?
7
+ connection.execute("
8
+ SELECT 1 AS exists
9
+ FROM information_schema.tables
10
+ WHERE table_schema = 'public'
11
+ AND table_name = '#{table}';
12
+ ").count == 1
13
+ end
14
+
15
+ def dump_table_ddl(table)
16
+ set_env
17
+ schema = `pg_dump -i -s -x -o -O --no-tablespaces --no-security-labels -t #{Shellwords.escape(table)} #{Shellwords.escape(config[:database])}`
18
+ schema.empty? ? '' : schema.match(/CREATE TABLE[^;]+;/)[0]
19
+ end
20
+
21
+ def copy_table_to_s3(_table)
22
+ fail 'Not implemented'
23
+ end
24
+
25
+ def copy_s3_to_table(_key, _table)
26
+ fail 'Not implemented'
27
+ end
28
+
29
+ def execute(*args, &block)
30
+ connection.execute(*args, &block)
31
+ end
32
+
33
+ def config
34
+ connection.instance_variable_get(:@config)
35
+ end
36
+
37
+ def set_env
38
+ PsqlEnv.set(config)
39
+ end
40
+
41
+ def after_fork
42
+ self.class.after_fork
43
+ end
44
+
45
+ def self.after_fork
46
+ AR.establish_connection
47
+ end
48
+
49
+ protected
50
+
51
+ def connection
52
+ AR.connection
53
+ end
54
+
55
+ def random_key
56
+ SecureRandom.hex(10)
57
+ end
58
+
59
+ def valid_objects(key)
60
+ Turboquery.s3_bucket.objects.to_a.select do |obj|
61
+ obj.key =~ /^#{key}/ && !(obj.key =~ /manifest$/)
62
+ end
63
+ end
64
+
65
+ def build_manifest(objects)
66
+ {
67
+ entries: objects.map do |obj|
68
+ {
69
+ url: "s3://#{obj.bucket_name}/#{obj.key}",
70
+ mandatory: true
71
+ }
72
+ end
73
+ }.to_json
74
+ end
75
+
76
+ def upload_manifest(key)
77
+ objects = valid_objects(key)
78
+ file = Tempfile.open('turboquery', Turboquery.tmp_path) do |f|
79
+ f.write(build_manifest(objects))
80
+ f
81
+ end
82
+ copy_file_to_s3(file.path, "#{key}manifest")
83
+ end
84
+
85
+ def copy_s3_to_tmp(key)
86
+ objects = valid_objects(key)
87
+ file = Tempfile.open('turboquery', Turboquery.tmp_path) do |f|
88
+ objects.each do |object|
89
+ f.write object.get.body.read
90
+ end
91
+ f
92
+ end
93
+ file.path
94
+ end
95
+
96
+ def copy_file_to_s3(filename, key)
97
+ object = Turboquery.s3_bucket.object(key)
98
+ object.upload_file(filename)
99
+ end
100
+
101
+ class AR < ActiveRecord::Base
102
+ self.abstract_class = true
103
+ end
104
+
105
+ module PsqlEnv
106
+ def self.set(config)
107
+ host(config) if config[:host]
108
+ port(config) if config[:port]
109
+ password(config) if config[:password]
110
+ username(config) if config[:username]
111
+ end
112
+
113
+ def self.host(config)
114
+ ENV['PGHOST'] = config[:host]
115
+ end
116
+
117
+ def self.port(config)
118
+ ENV['PGPORT'] = config[:port].to_s
119
+ end
120
+
121
+ def self.password(config)
122
+ ENV['PGPASSWORD'] = config[:password].to_s
123
+ end
124
+
125
+ def self.username(config)
126
+ ENV['PGUSER'] = config[:username].to_s
127
+ end
128
+ end
129
+
130
+ end
@@ -0,0 +1,55 @@
1
+ class Turboquery::OLAP < Turboquery::Connection
2
+ def copy_table_to_s3(table)
3
+ copy_result_to_s3("SELECT * FROM #{table}")
4
+ end
5
+
6
+ def copy_result_to_s3(query)
7
+ key = random_key
8
+ execute("UNLOAD ('#{query}') TO 's3://#{Turboquery.s3_config['bucket']}/#{key}' #{copy_options};")
9
+ key
10
+ end
11
+
12
+ def copy_s3_to_table(key, table)
13
+ execute("COPY #{table} FROM 's3://#{Turboquery.s3_config['bucket']}/#{key}manifest' #{copy_options}
14
+ DATEFORMAT 'auto' TIMEFORMAT 'auto';")
15
+ end
16
+
17
+ def execute_to_temporary_table(query)
18
+ key = random_key
19
+ sql = "CREATE TEMPORARY TABLE #{key} AS #{query}"
20
+ execute(sql)
21
+ key
22
+ end
23
+
24
+ def drop_table(key)
25
+ sql = "DROP TABLE IF EXISTS #{key}"
26
+ execute(sql)
27
+ end
28
+
29
+ def self.after_fork
30
+ ARRedshift.reconnect
31
+ end
32
+
33
+ protected
34
+
35
+ def connection
36
+ ARRedshift.connection
37
+ end
38
+
39
+ def excape_single_quotes(str)
40
+ str.gsub(/'/, %q(\\\'))
41
+ end
42
+
43
+ def copy_options
44
+ "CREDENTIALS 'aws_access_key_id=#{Turboquery.s3_config['access_key_id']};aws_secret_access_key=#{Turboquery.s3_config['secret_access_key']}'
45
+ MANIFEST DELIMITER '\\t' NULL AS '\\\\N'"
46
+ end
47
+
48
+ class ARRedshift < ActiveRecord::Base
49
+ establish_connection DatabaseUrl.new(ENV['REDSHIFT_DATABASE_URL']).to_hash
50
+
51
+ def self.reconnect
52
+ establish_connection DatabaseUrl.new(ENV['REDSHIFT_DATABASE_URL']).to_hash
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,24 @@
1
+ require 'tempfile'
2
+
3
+ class Turboquery::OLTP < Turboquery::Connection
4
+ def copy_table_to_s3(table)
5
+ set_env
6
+
7
+ temp = Tempfile.new('turboquery', Turboquery.tmp_path)
8
+ command = "echo 'COPY #{table} TO STDOUT' | psql #{Shellwords.escape(config[:database])} > #{temp.path}"
9
+ Kernel.system(command)
10
+
11
+ key = random_key
12
+ copy_file_to_s3(temp.path, key)
13
+ upload_manifest(key)
14
+ key
15
+ end
16
+
17
+ def copy_s3_to_table(key, table)
18
+ set_env
19
+ path = copy_s3_to_tmp(key)
20
+ sql = "COPY #{table} FROM STDIN"
21
+ command = "cat #{Shellwords.escape(path)} | psql -c #{Shellwords.escape(sql)} #{Shellwords.escape(config[:database])}"
22
+ Kernel.system(command)
23
+ end
24
+ end
@@ -0,0 +1,144 @@
1
+ class Turboquery::RemoteQuery
2
+
3
+ INSERT_INTO_REGEX = /^INSERT INTO (.+) SELECT/i
4
+
5
+ attr_reader :query, :opts, :destination
6
+
7
+ def initialize(query, opts = {})
8
+ @query = query.dup
9
+ @table_mapping = opts[:table_mapping] || {}
10
+ @reverse_table_mapping = {}
11
+ @copy_tables = opts[:copy_tables] || []
12
+ @opts = opts
13
+ format_query
14
+ end
15
+
16
+ def to_sql
17
+ query
18
+ end
19
+
20
+ def execute
21
+ # Copy any missing tables or requested tables
22
+ (missing_tables + @copy_tables).uniq.each { |t| copy_local_table_to_remote(t) }
23
+
24
+ # Run query and save to temporary table
25
+ execute_query
26
+
27
+ # Export results if no destination specified
28
+ return Turboquery.olap.execute("SELECT * FROM #{temp_table_key}") unless @destination
29
+
30
+ # Copy data from OLAP to OLTP
31
+ copy_remote_table_to_local(temp_table_key, @destination)
32
+
33
+ # Clean up temporary table
34
+ Turboquery.olap.execute("DROP TABLE #{temp_table_key}")
35
+
36
+ # Return success
37
+ true
38
+ end
39
+
40
+ private
41
+
42
+ def temp_table_key
43
+ @temp_table_key ||= "turboquery_#{SecureRandom.hex(10)}"
44
+ end
45
+
46
+ def insert_into
47
+ @insert_into ||= begin
48
+ match = query.match(INSERT_INTO_REGEX)
49
+ match.nil? ? nil : match[1]
50
+ end
51
+ end
52
+
53
+ def insert_into?
54
+ !insert_into.nil?
55
+ end
56
+
57
+ def format_query
58
+ @destination = insert_into || opts[:destination]
59
+ strip_insert_into if insert_into?
60
+ build_table_tokens
61
+ build_table_mapping
62
+ build_reverse_table_mapping
63
+ prefix_tables
64
+ end
65
+
66
+ def build_table_tokens
67
+ @table_tokens = locate_tables(PgQuery.parse(@query).parsetree[0])
68
+ end
69
+
70
+ def locate_tables(parsetree)
71
+ tables = []
72
+
73
+ return [] unless parsetree.is_a? Hash
74
+
75
+ parsetree.each do |key, val|
76
+ tables << { table: val, location: parsetree['location'] } if key == 'relname'
77
+ tables << locate_tables(val) if val.is_a? Hash
78
+ tables << val.map { |v| locate_tables(v) } if val.is_a? Array
79
+ end
80
+
81
+ tables.flatten
82
+ end
83
+
84
+ def build_table_mapping
85
+ @table_tokens.each do |table|
86
+ @table_mapping[table[:table]] ||= "turboquery_#{table[:table]}"
87
+ end
88
+ end
89
+
90
+ def build_reverse_table_mapping
91
+ @table_mapping.each do |local, remote|
92
+ @reverse_table_mapping[remote] = local
93
+ end
94
+ end
95
+
96
+ def prefix_tables
97
+ @table_tokens.reverse.each do |table|
98
+ @query[table[:location], table[:table].length] = @table_mapping[table[:table]]
99
+ end
100
+ end
101
+
102
+ def strip_insert_into
103
+ @query.gsub! INSERT_INTO_REGEX, 'SELECT'
104
+ end
105
+
106
+ def local_tables
107
+ @table_mapping.map { |k, _v| k }
108
+ end
109
+
110
+ def remote_tables
111
+ @table_mapping.map { |_k, v| v }
112
+ end
113
+
114
+ def missing_tables
115
+ remote_tables.select { |t| !Turboquery.olap.table_exists?(t) }
116
+ end
117
+
118
+ def copy_local_table_to_remote(table)
119
+ Turboquery::TableMover.new(
120
+ source: Turboquery.oltp,
121
+ destination: Turboquery.olap,
122
+ from_table: @reverse_table_mapping[table],
123
+ to_table: table
124
+ ).move
125
+ end
126
+
127
+ def copy_remote_table_to_local(remote, local)
128
+ Turboquery::TableMover.new(
129
+ source: Turboquery.olap,
130
+ destination: Turboquery.oltp,
131
+ from_table: remote,
132
+ to_table: local
133
+ ).move
134
+ end
135
+
136
+ def execute_query
137
+ if @destination
138
+ Turboquery.olap.execute("CREATE TABLE #{temp_table_key} AS #{@query}")
139
+ else
140
+ Turboquery.olap.execute("CREATE TEMPORARY TABLE #{temp_table_key} AS #{@query}")
141
+ end
142
+ end
143
+
144
+ end
@@ -0,0 +1,43 @@
1
+ class Turboquery::TableMover
2
+
3
+ attr_accessor :source, :destination, :from_table, :to_table
4
+
5
+ def initialize(source:, destination:, from_table:, to_table:)
6
+ self.source = source
7
+ self.destination = destination
8
+ self.from_table = from_table
9
+ self.to_table = to_table
10
+ end
11
+
12
+ def move
13
+ create_destination unless destination_exists?
14
+ key = copy_source_to_s3
15
+ copy_s3_to_destination(key)
16
+ end
17
+
18
+ private
19
+
20
+ def source_exists?
21
+ source.table_exists?(from_table)
22
+ end
23
+
24
+ def destination_exists?
25
+ destination.table_exists?(to_table)
26
+ end
27
+
28
+ def create_destination
29
+ structure = source.dump_table_ddl(from_table)
30
+ structure.gsub!(/^CREATE TABLE [^\(]+\(/, "CREATE TABLE #{to_table} (")
31
+ destination.execute(structure)
32
+ fail 'Unable to create destination table' unless destination_exists?
33
+ end
34
+
35
+ def copy_source_to_s3
36
+ source.copy_table_to_s3(from_table)
37
+ end
38
+
39
+ def copy_s3_to_destination(key)
40
+ destination.copy_s3_to_table(key, to_table)
41
+ end
42
+
43
+ end
@@ -0,0 +1,3 @@
1
+ module Turboquery
2
+ VERSION = '0.1.0'
3
+ end
data/lib/turboquery.rb ADDED
@@ -0,0 +1,42 @@
1
+ module Turboquery
2
+ class << self
3
+ def query(sql, opts = {})
4
+ Turboquery::RemoteQuery.new(sql, opts).execute
5
+ end
6
+
7
+ def update_table(table)
8
+ Turboquery::TableMover.new(source: oltp, destination: olap, from_table: table, to_table: "turboquery_#{table}")
9
+ end
10
+
11
+ def oltp
12
+ @oltp ||= Turboquery::OLTP.new
13
+ end
14
+
15
+ def olap
16
+ @olap ||= Turboquery::OLAP.new
17
+ end
18
+
19
+ def s3_bucket
20
+ @s3_bucket ||= begin
21
+ Aws::S3::Resource.new(
22
+ region: 'us-east-1',
23
+ access_key_id: s3_config['access_key_id'],
24
+ secret_access_key: s3_config['secret_access_key']
25
+ ).bucket(s3_config['bucket'])
26
+ end
27
+ end
28
+
29
+ def s3_config
30
+ Rails.application.secrets.turboquery
31
+ end
32
+
33
+ def tmp_path
34
+ ENV['TURBOQUERY_TMP_PATH'] || Rails.root.join('tmp')
35
+ end
36
+
37
+ def after_fork
38
+ olap.after_fork
39
+ oltp.after_fork
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../lib/turboquery/version', __FILE__)
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'turboquery'
5
+ spec.version = Turboquery::VERSION
6
+ spec.authors = ['Jacob Gillespie']
7
+ spec.email = ['jacobwgillespie@gmail.com']
8
+ spec.description = 'Execute with the power of Redshift'
9
+ spec.summary = 'Turboquery allows you to execute your Postgres queries on your Postgres data with Redshift.'
10
+ spec.homepage = 'https://github.com/jacobwgillespie/turboquery'
11
+ spec.license = 'MIT'
12
+
13
+ spec.files = `git ls-files`.split("\n")
14
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
15
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.add_dependency 'activerecord', '~> 4.0'
19
+ spec.add_dependency 'pg_query'
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'minitest'
24
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: turboquery
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jacob Gillespie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg_query
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Execute with the power of Redshift
84
+ email:
85
+ - jacobwgillespie@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".editorconfig"
91
+ - ".gitignore"
92
+ - ".rubocop.yml"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - LICENSE
96
+ - README.md
97
+ - Rakefile
98
+ - lib/turboquery.rb
99
+ - lib/turboquery/connection.rb
100
+ - lib/turboquery/olap.rb
101
+ - lib/turboquery/oltp.rb
102
+ - lib/turboquery/remote_query.rb
103
+ - lib/turboquery/table_mover.rb
104
+ - lib/turboquery/version.rb
105
+ - turboquery.gemspec
106
+ homepage: https://github.com/jacobwgillespie/turboquery
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.4.5
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Turboquery allows you to execute your Postgres queries on your Postgres data
130
+ with Redshift.
131
+ test_files: []
132
+ has_rdoc: