sql-jarvis 1.8.0 → 1.9.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitattributes +1 -0
- data/.github/ISSUE_TEMPLATE.md +7 -0
- data/CHANGELOG.md +17 -1
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +1 -1
- data/README.md +121 -78
- data/app/assets/javascripts/blazer/application.js +1 -0
- data/app/assets/javascripts/blazer/select2.js +5833 -0
- data/app/assets/stylesheets/blazer/application.css +1 -0
- data/app/assets/stylesheets/blazer/select2.min.css +1 -0
- data/app/controllers/blazer/base_controller.rb +11 -2
- data/app/controllers/blazer/queries_controller.rb +29 -15
- data/app/models/blazer/query.rb +15 -0
- data/app/views/blazer/_variables.html.erb +1 -1
- data/app/views/blazer/queries/_form.html.erb +13 -3
- data/app/views/blazer/queries/run.html.erb +2 -0
- data/app/views/blazer/queries/schema.html.erb +2 -0
- data/app/views/blazer/queries/show.html.erb +1 -1
- data/blazer.gemspec +2 -1
- data/lib/blazer.rb +32 -7
- data/lib/blazer/adapters/bigquery_adapter.rb +5 -4
- data/lib/blazer/adapters/cassandra_adapter.rb +59 -0
- data/lib/blazer/adapters/druid_adapter.rb +67 -0
- data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
- data/lib/blazer/data_source.rb +18 -16
- data/lib/blazer/engine.rb +10 -8
- data/lib/blazer/version.rb +1 -1
- data/lib/generators/blazer/templates/{config.yml → config.yml.tt} +5 -4
- data/lib/generators/blazer/templates/{install.rb → install.rb.tt} +1 -0
- data/lib/tasks/blazer.rake +1 -1
- data/sql-jarvis-1.9.4.gem +0 -0
- data/sql-jarvis-1.9.5.gem +0 -0
- metadata +30 -6
@@ -0,0 +1,67 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class DruidAdapter < BaseAdapter
|
4
|
+
TIMESTAMP_REGEX = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\z/
|
5
|
+
|
6
|
+
def run_statement(statement, comment)
|
7
|
+
columns = []
|
8
|
+
rows = []
|
9
|
+
error = nil
|
10
|
+
|
11
|
+
header = {"Content-Type" => "application/json", "Accept" => "application/json"}
|
12
|
+
timeout = data_source.timeout ? data_source.timeout.to_i : 300
|
13
|
+
data = {
|
14
|
+
query: statement,
|
15
|
+
context: {
|
16
|
+
timeout: timeout * 1000
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
uri = URI.parse("#{settings["url"]}/druid/v2/sql/")
|
21
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
22
|
+
http.read_timeout = timeout
|
23
|
+
|
24
|
+
begin
|
25
|
+
response = JSON.parse(http.post(uri.request_uri, data.to_json, header).body)
|
26
|
+
if response.is_a?(Hash)
|
27
|
+
error = response["errorMessage"] || "Unknown error: #{response.inspect}"
|
28
|
+
if error.include?("timed out")
|
29
|
+
error = Blazer::TIMEOUT_MESSAGE
|
30
|
+
end
|
31
|
+
else
|
32
|
+
columns = (response.first || {}).keys
|
33
|
+
rows = response.map { |r| r.values }
|
34
|
+
|
35
|
+
# Druid doesn't return column types
|
36
|
+
# and no timestamp type in JSON
|
37
|
+
rows.each do |row|
|
38
|
+
row.each_with_index do |v, i|
|
39
|
+
if v.is_a?(String) && TIMESTAMP_REGEX.match(v)
|
40
|
+
row[i] = Time.parse(v)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
rescue => e
|
46
|
+
error = e.message
|
47
|
+
end
|
48
|
+
|
49
|
+
[columns, rows, error]
|
50
|
+
end
|
51
|
+
|
52
|
+
def tables
|
53
|
+
result = data_source.run_statement("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN ('INFORMATION_SCHEMA') ORDER BY TABLE_NAME")
|
54
|
+
result.rows.map(&:first)
|
55
|
+
end
|
56
|
+
|
57
|
+
def schema
|
58
|
+
result = data_source.run_statement("SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, ORDINAL_POSITION FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA NOT IN ('INFORMATION_SCHEMA') ORDER BY 1, 2")
|
59
|
+
result.rows.group_by { |r| [r[0], r[1]] }.map { |k, vs| {schema: k[0], table: k[1], columns: vs.sort_by { |v| v[2] }.map { |v| {name: v[2], data_type: v[3]} }} }
|
60
|
+
end
|
61
|
+
|
62
|
+
def preview_statement
|
63
|
+
"SELECT * FROM {table} LIMIT 10"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class SnowflakeAdapter < SqlAdapter
|
4
|
+
def initialize(data_source)
|
5
|
+
@data_source = data_source
|
6
|
+
|
7
|
+
@@registered ||= begin
|
8
|
+
require "active_record/connection_adapters/odbc_adapter"
|
9
|
+
require "odbc_adapter/adapters/postgresql_odbc_adapter"
|
10
|
+
|
11
|
+
ODBCAdapter.register(/snowflake/, ODBCAdapter::Adapters::PostgreSQLODBCAdapter) do
|
12
|
+
# Explicitly turning off prepared statements as they are not yet working with
|
13
|
+
# snowflake + the ODBC ActiveRecord adapter
|
14
|
+
def prepared_statements
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Quoting needs to be changed for snowflake
|
19
|
+
def quote_column_name(name)
|
20
|
+
name.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Override dbms_type_cast to get the values encoded in UTF-8
|
26
|
+
def dbms_type_cast(columns, values)
|
27
|
+
int_column = {}
|
28
|
+
columns.each_with_index do |c, i|
|
29
|
+
int_column[i] = c.type == 3 && c.scale == 0
|
30
|
+
end
|
31
|
+
|
32
|
+
float_column = {}
|
33
|
+
columns.each_with_index do |c, i|
|
34
|
+
float_column[i] = c.type == 3 && c.scale != 0
|
35
|
+
end
|
36
|
+
|
37
|
+
values.each do |row|
|
38
|
+
row.each_index do |idx|
|
39
|
+
val = row[idx]
|
40
|
+
if val
|
41
|
+
if int_column[idx]
|
42
|
+
row[idx] = val.to_i
|
43
|
+
elsif float_column[idx]
|
44
|
+
row[idx] = val.to_f
|
45
|
+
elsif val.is_a?(String)
|
46
|
+
row[idx] = val.force_encoding('UTF-8')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
@connection_model =
|
56
|
+
Class.new(Blazer::Connection) do
|
57
|
+
def self.name
|
58
|
+
"Blazer::Connection::SnowflakeAdapter#{object_id}"
|
59
|
+
end
|
60
|
+
if data_source.settings["conn_str"]
|
61
|
+
establish_connection(adapter: "odbc", conn_str: data_source.settings["conn_str"])
|
62
|
+
elsif data_source.settings["dsn"]
|
63
|
+
establish_connection(adapter: "odbc", dsn: data_source.settings["dsn"])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def cancel(run_id)
|
69
|
+
# todo
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/blazer/data_source.rb
CHANGED
@@ -4,24 +4,13 @@ module Blazer
|
|
4
4
|
class DataSource
|
5
5
|
extend Forwardable
|
6
6
|
|
7
|
-
attr_reader :id, :settings
|
7
|
+
attr_reader :id, :settings
|
8
8
|
|
9
9
|
def_delegators :adapter_instance, :schema, :tables, :preview_statement, :reconnect, :cost, :explain, :cancel
|
10
10
|
|
11
11
|
def initialize(id, settings)
|
12
12
|
@id = id
|
13
13
|
@settings = settings
|
14
|
-
|
15
|
-
unless settings["url"] || Rails.env.development? || ["bigquery", "athena"].include?(settings["adapter"])
|
16
|
-
raise Blazer::Error, "Empty url for data source: #{id}"
|
17
|
-
end
|
18
|
-
|
19
|
-
@adapter_instance =
|
20
|
-
if Blazer.adapters[adapter]
|
21
|
-
Blazer.adapters[adapter].new(self)
|
22
|
-
else
|
23
|
-
raise Blazer::Error, "Unknown adapter"
|
24
|
-
end
|
25
14
|
end
|
26
15
|
|
27
16
|
def adapter
|
@@ -101,7 +90,6 @@ module Blazer
|
|
101
90
|
end
|
102
91
|
|
103
92
|
def run_statement(statement, options = {})
|
104
|
-
run_id = options[:run_id]
|
105
93
|
async = options[:async]
|
106
94
|
result = nil
|
107
95
|
if cache_mode != "off"
|
@@ -154,9 +142,23 @@ module Blazer
|
|
154
142
|
|
155
143
|
protected
|
156
144
|
|
145
|
+
def adapter_instance
|
146
|
+
@adapter_instance ||= begin
|
147
|
+
unless settings["url"] || Rails.env.development? || ["bigquery", "athena", "snowflake"].include?(settings["adapter"])
|
148
|
+
raise Blazer::Error, "Empty url for data source: #{id}"
|
149
|
+
end
|
150
|
+
|
151
|
+
unless Blazer.adapters[adapter]
|
152
|
+
raise Blazer::Error, "Unknown adapter"
|
153
|
+
end
|
154
|
+
|
155
|
+
Blazer.adapters[adapter].new(self)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
157
159
|
def run_statement_helper(statement, comment, run_id)
|
158
160
|
start_time = Time.now
|
159
|
-
columns, rows, error =
|
161
|
+
columns, rows, error = adapter_instance.run_statement(statement, comment)
|
160
162
|
duration = Time.now - start_time
|
161
163
|
|
162
164
|
cache_data = nil
|
@@ -165,7 +167,7 @@ module Blazer
|
|
165
167
|
cache_data = Marshal.dump([columns, rows, error, cache ? Time.now : nil]) rescue nil
|
166
168
|
end
|
167
169
|
|
168
|
-
if cache && cache_data &&
|
170
|
+
if cache && cache_data && adapter_instance.cachable?(statement)
|
169
171
|
Blazer.cache.write(statement_cache_key(statement), cache_data, expires_in: cache_expires_in.to_f * 60)
|
170
172
|
end
|
171
173
|
|
@@ -183,7 +185,7 @@ module Blazer
|
|
183
185
|
def detect_adapter
|
184
186
|
schema = settings["url"].to_s.split("://").first
|
185
187
|
case schema
|
186
|
-
when "mongodb", "presto"
|
188
|
+
when "mongodb", "presto", "cassandra"
|
187
189
|
schema
|
188
190
|
else
|
189
191
|
"sql"
|
data/lib/blazer/engine.rb
CHANGED
@@ -11,21 +11,23 @@ module Blazer
|
|
11
11
|
Blazer.audit = Blazer.settings.key?("audit") ? Blazer.settings["audit"] : true
|
12
12
|
Blazer.user_name = Blazer.settings["user_name"] if Blazer.settings["user_name"]
|
13
13
|
Blazer.from_email = Blazer.settings["from_email"] if Blazer.settings["from_email"]
|
14
|
-
Blazer.before_action = Blazer.settings["
|
15
|
-
|
16
|
-
Blazer.user_class ||= Blazer.settings.key?("user_class") ? Blazer.settings["user_class"] : (User rescue nil)
|
17
|
-
Blazer.user_method = Blazer.settings["user_method"]
|
18
|
-
if Blazer.user_class
|
19
|
-
Blazer.user_method ||= "current_#{Blazer.user_class.to_s.downcase.singularize}"
|
20
|
-
end
|
21
|
-
|
14
|
+
Blazer.before_action = Blazer.settings["before_action_method"] if Blazer.settings["before_action_method"]
|
22
15
|
Blazer.check_schedules = Blazer.settings["check_schedules"] if Blazer.settings.key?("check_schedules")
|
16
|
+
|
23
17
|
if Blazer.settings.key?("mapbox_access_token")
|
24
18
|
Blazer.mapbox_access_token = Blazer.settings["mapbox_access_token"]
|
25
19
|
elsif ENV["MAPBOX_ACCESS_TOKEN"].present?
|
26
20
|
Blazer.mapbox_access_token = ENV["MAPBOX_ACCESS_TOKEN"]
|
27
21
|
end
|
28
22
|
|
23
|
+
if Blazer.settings.key?('assignees')
|
24
|
+
data_source = Blazer.data_sources['main']
|
25
|
+
statement = Blazer.settings['assignees']
|
26
|
+
Blazer.assignees = (Blazer::RunStatement.new.perform(data_source, statement, {}).rows rescue [])
|
27
|
+
else
|
28
|
+
Blazer.assignees = []
|
29
|
+
end
|
30
|
+
|
29
31
|
if Blazer.user_class
|
30
32
|
options = Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s)
|
31
33
|
Blazer::Query.belongs_to :creator, options
|
data/lib/blazer/version.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
data_sources:
|
4
4
|
main:
|
5
5
|
url: <%%= ENV["BLAZER_DATABASE_URL"] %>
|
6
|
-
mapbox_access_token: <%%= ENV["MAPBOX_ACCESS_TOKEN"] %>
|
7
6
|
|
8
7
|
# statement timeout, in seconds
|
9
8
|
# none by default
|
@@ -48,13 +47,15 @@ audit: true
|
|
48
47
|
# method name for the display name
|
49
48
|
# user_name: name
|
50
49
|
|
51
|
-
#
|
52
|
-
#
|
50
|
+
# custom before_action to use for auth
|
51
|
+
# before_action_method: require_admin
|
53
52
|
|
54
53
|
# email to send checks from
|
55
54
|
# from_email: blazer@example.org
|
56
55
|
|
57
|
-
|
56
|
+
mapbox_access_token: <%= ENV["MAPBOX_ACCESS_TOKEN"] %>
|
57
|
+
|
58
|
+
#assignees: 'SELECT id, name FROM users ORDER BY id ASC'
|
58
59
|
|
59
60
|
check_schedules:
|
60
61
|
- "1 day"
|
data/lib/tasks/blazer.rake
CHANGED
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,17 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sql-jarvis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ThanhKhoaIT
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - ">="
|
@@ -87,8 +101,11 @@ executables: []
|
|
87
101
|
extensions: []
|
88
102
|
extra_rdoc_files: []
|
89
103
|
files:
|
104
|
+
- ".gitattributes"
|
105
|
+
- ".github/ISSUE_TEMPLATE.md"
|
90
106
|
- ".gitignore"
|
91
107
|
- CHANGELOG.md
|
108
|
+
- CONTRIBUTING.md
|
92
109
|
- Gemfile
|
93
110
|
- LICENSE.txt
|
94
111
|
- README.md
|
@@ -120,6 +137,7 @@ files:
|
|
120
137
|
- app/assets/javascripts/blazer/moment.js
|
121
138
|
- app/assets/javascripts/blazer/queries.js
|
122
139
|
- app/assets/javascripts/blazer/routes.js
|
140
|
+
- app/assets/javascripts/blazer/select2.js
|
123
141
|
- app/assets/javascripts/blazer/selectize.js
|
124
142
|
- app/assets/javascripts/blazer/stupidtable.js
|
125
143
|
- app/assets/javascripts/blazer/vue.js
|
@@ -127,6 +145,7 @@ files:
|
|
127
145
|
- app/assets/stylesheets/blazer/bootstrap.css.erb
|
128
146
|
- app/assets/stylesheets/blazer/daterangepicker-bs3.css
|
129
147
|
- app/assets/stylesheets/blazer/github.css
|
148
|
+
- app/assets/stylesheets/blazer/select2.min.css
|
130
149
|
- app/assets/stylesheets/blazer/selectize.default.css
|
131
150
|
- app/controllers/blazer/base_controller.rb
|
132
151
|
- app/controllers/blazer/checks_controller.rb
|
@@ -167,10 +186,13 @@ files:
|
|
167
186
|
- lib/blazer/adapters/athena_adapter.rb
|
168
187
|
- lib/blazer/adapters/base_adapter.rb
|
169
188
|
- lib/blazer/adapters/bigquery_adapter.rb
|
189
|
+
- lib/blazer/adapters/cassandra_adapter.rb
|
170
190
|
- lib/blazer/adapters/drill_adapter.rb
|
191
|
+
- lib/blazer/adapters/druid_adapter.rb
|
171
192
|
- lib/blazer/adapters/elasticsearch_adapter.rb
|
172
193
|
- lib/blazer/adapters/mongodb_adapter.rb
|
173
194
|
- lib/blazer/adapters/presto_adapter.rb
|
195
|
+
- lib/blazer/adapters/snowflake_adapter.rb
|
174
196
|
- lib/blazer/adapters/sql_adapter.rb
|
175
197
|
- lib/blazer/data_source.rb
|
176
198
|
- lib/blazer/detect_anomalies.R
|
@@ -180,9 +202,11 @@ files:
|
|
180
202
|
- lib/blazer/run_statement_job.rb
|
181
203
|
- lib/blazer/version.rb
|
182
204
|
- lib/generators/blazer/install_generator.rb
|
183
|
-
- lib/generators/blazer/templates/config.yml
|
184
|
-
- lib/generators/blazer/templates/install.rb
|
205
|
+
- lib/generators/blazer/templates/config.yml.tt
|
206
|
+
- lib/generators/blazer/templates/install.rb.tt
|
185
207
|
- lib/tasks/blazer.rake
|
208
|
+
- sql-jarvis-1.9.4.gem
|
209
|
+
- sql-jarvis-1.9.5.gem
|
186
210
|
homepage: https://github.com/ThanhKhoaIT/blazer
|
187
211
|
licenses:
|
188
212
|
- MIT
|
@@ -203,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
227
|
version: '0'
|
204
228
|
requirements: []
|
205
229
|
rubyforge_project:
|
206
|
-
rubygems_version: 2.6.
|
230
|
+
rubygems_version: 2.6.14
|
207
231
|
signing_key:
|
208
232
|
specification_version: 4
|
209
233
|
summary: Fork from ankane! Explore your data with SQL. Easily create charts and dashboards,
|