sql-jarvis 1.8.0 → 1.9.6
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 +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,
|