blazer 2.2.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of blazer might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +310 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +1041 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
- data/app/assets/images/blazer/favicon.png +0 -0
- data/app/assets/javascripts/blazer/Chart.js +14456 -0
- data/app/assets/javascripts/blazer/Sortable.js +1540 -0
- data/app/assets/javascripts/blazer/ace.js +6 -0
- data/app/assets/javascripts/blazer/ace/ace.js +21301 -0
- data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1993 -0
- data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -0
- data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -0
- data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -0
- data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -0
- data/app/assets/javascripts/blazer/application.js +81 -0
- data/app/assets/javascripts/blazer/bootstrap.js +2377 -0
- data/app/assets/javascripts/blazer/chartkick.js +2214 -0
- data/app/assets/javascripts/blazer/daterangepicker.js +1653 -0
- data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
- data/app/assets/javascripts/blazer/highlight.min.js +3 -0
- data/app/assets/javascripts/blazer/jquery-ujs.js +555 -0
- data/app/assets/javascripts/blazer/jquery.js +10364 -0
- data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
- data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1212 -0
- data/app/assets/javascripts/blazer/moment.js +3043 -0
- data/app/assets/javascripts/blazer/queries.js +110 -0
- data/app/assets/javascripts/blazer/routes.js +26 -0
- data/app/assets/javascripts/blazer/selectize.js +3891 -0
- data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
- data/app/assets/javascripts/blazer/stupidtable.js +281 -0
- data/app/assets/javascripts/blazer/vue.js +10947 -0
- data/app/assets/stylesheets/blazer/application.css +234 -0
- data/app/assets/stylesheets/blazer/bootstrap.css.erb +6756 -0
- data/app/assets/stylesheets/blazer/daterangepicker.css +269 -0
- data/app/assets/stylesheets/blazer/github.css +125 -0
- data/app/assets/stylesheets/blazer/selectize.css +403 -0
- data/app/controllers/blazer/base_controller.rb +124 -0
- data/app/controllers/blazer/checks_controller.rb +56 -0
- data/app/controllers/blazer/dashboards_controller.rb +101 -0
- data/app/controllers/blazer/queries_controller.rb +347 -0
- data/app/helpers/blazer/base_helper.rb +43 -0
- data/app/mailers/blazer/check_mailer.rb +27 -0
- data/app/mailers/blazer/slack_notifier.rb +79 -0
- data/app/models/blazer/audit.rb +6 -0
- data/app/models/blazer/check.rb +104 -0
- data/app/models/blazer/connection.rb +5 -0
- data/app/models/blazer/dashboard.rb +17 -0
- data/app/models/blazer/dashboard_query.rb +9 -0
- data/app/models/blazer/query.rb +40 -0
- data/app/models/blazer/record.rb +5 -0
- data/app/views/blazer/_nav.html.erb +15 -0
- data/app/views/blazer/_variables.html.erb +124 -0
- data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
- data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
- data/app/views/blazer/checks/_form.html.erb +79 -0
- data/app/views/blazer/checks/edit.html.erb +3 -0
- data/app/views/blazer/checks/index.html.erb +69 -0
- data/app/views/blazer/checks/new.html.erb +3 -0
- data/app/views/blazer/dashboards/_form.html.erb +76 -0
- data/app/views/blazer/dashboards/edit.html.erb +3 -0
- data/app/views/blazer/dashboards/new.html.erb +3 -0
- data/app/views/blazer/dashboards/show.html.erb +51 -0
- data/app/views/blazer/queries/_form.html.erb +250 -0
- data/app/views/blazer/queries/docs.html.erb +131 -0
- data/app/views/blazer/queries/edit.html.erb +2 -0
- data/app/views/blazer/queries/home.html.erb +163 -0
- data/app/views/blazer/queries/new.html.erb +2 -0
- data/app/views/blazer/queries/run.html.erb +198 -0
- data/app/views/blazer/queries/schema.html.erb +55 -0
- data/app/views/blazer/queries/show.html.erb +75 -0
- data/app/views/layouts/blazer/application.html.erb +24 -0
- data/config/routes.rb +20 -0
- data/lib/blazer.rb +231 -0
- data/lib/blazer/adapters/athena_adapter.rb +129 -0
- data/lib/blazer/adapters/base_adapter.rb +53 -0
- data/lib/blazer/adapters/bigquery_adapter.rb +68 -0
- data/lib/blazer/adapters/cassandra_adapter.rb +59 -0
- data/lib/blazer/adapters/drill_adapter.rb +28 -0
- data/lib/blazer/adapters/druid_adapter.rb +67 -0
- data/lib/blazer/adapters/elasticsearch_adapter.rb +46 -0
- data/lib/blazer/adapters/influxdb_adapter.rb +45 -0
- data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
- data/lib/blazer/adapters/neo4j_adapter.rb +47 -0
- data/lib/blazer/adapters/presto_adapter.rb +45 -0
- data/lib/blazer/adapters/salesforce_adapter.rb +45 -0
- data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
- data/lib/blazer/adapters/soda_adapter.rb +96 -0
- data/lib/blazer/adapters/sql_adapter.rb +221 -0
- data/lib/blazer/data_source.rb +195 -0
- data/lib/blazer/detect_anomalies.R +19 -0
- data/lib/blazer/engine.rb +43 -0
- data/lib/blazer/result.rb +218 -0
- data/lib/blazer/run_statement.rb +40 -0
- data/lib/blazer/run_statement_job.rb +18 -0
- data/lib/blazer/version.rb +3 -0
- data/lib/generators/blazer/install_generator.rb +22 -0
- data/lib/generators/blazer/templates/config.yml.tt +73 -0
- data/lib/generators/blazer/templates/install.rb.tt +46 -0
- data/lib/tasks/blazer.rake +11 -0
- metadata +231 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class PrestoAdapter < BaseAdapter
|
4
|
+
def run_statement(statement, comment)
|
5
|
+
columns = []
|
6
|
+
rows = []
|
7
|
+
error = nil
|
8
|
+
|
9
|
+
begin
|
10
|
+
columns, rows = client.run("#{statement} /*#{comment}*/")
|
11
|
+
columns = columns.map(&:name)
|
12
|
+
rescue => e
|
13
|
+
error = e.message
|
14
|
+
end
|
15
|
+
|
16
|
+
[columns, rows, error]
|
17
|
+
end
|
18
|
+
|
19
|
+
def tables
|
20
|
+
_, rows = client.run("SHOW TABLES")
|
21
|
+
rows.map(&:first)
|
22
|
+
end
|
23
|
+
|
24
|
+
def preview_statement
|
25
|
+
"SELECT * FROM {table} LIMIT 10"
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def client
|
31
|
+
@client ||= begin
|
32
|
+
uri = URI.parse(settings["url"])
|
33
|
+
query = uri.query ? CGI::parse(uri.query) : {}
|
34
|
+
Presto::Client.new(
|
35
|
+
server: "#{uri.host}:#{uri.port}",
|
36
|
+
catalog: uri.path.to_s.sub(/\A\//, ""),
|
37
|
+
schema: query["schema"] || "public",
|
38
|
+
user: uri.user,
|
39
|
+
http_debug: false
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class SalesforceAdapter < BaseAdapter
|
4
|
+
def run_statement(statement, comment)
|
5
|
+
columns = []
|
6
|
+
rows = []
|
7
|
+
error = nil
|
8
|
+
|
9
|
+
# remove comments manually
|
10
|
+
statement = statement.gsub(/--.+/, "")
|
11
|
+
# only supports single line /* */ comments
|
12
|
+
# regex not perfect, but should be good enough
|
13
|
+
statement = statement.gsub(/\/\*.+\*\//, "")
|
14
|
+
|
15
|
+
# remove trailing semicolon
|
16
|
+
statement = statement.sub(/;\s*\z/, "")
|
17
|
+
|
18
|
+
begin
|
19
|
+
response = client.query(statement)
|
20
|
+
rows = response.map { |r| r.to_hash.except("attributes").values }
|
21
|
+
columns = rows.any? ? response.first.to_hash.except("attributes").keys : []
|
22
|
+
rescue => e
|
23
|
+
error = e.message
|
24
|
+
end
|
25
|
+
|
26
|
+
[columns, rows, error]
|
27
|
+
end
|
28
|
+
|
29
|
+
def tables
|
30
|
+
# cache
|
31
|
+
@tables ||= client.describe.select { |r| r.queryable }.map(&:name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def preview_statement
|
35
|
+
"SELECT Id FROM {table} LIMIT 10"
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def client
|
41
|
+
@client ||= Restforce.new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
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
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class SodaAdapter < BaseAdapter
|
4
|
+
def run_statement(statement, comment)
|
5
|
+
columns = []
|
6
|
+
rows = []
|
7
|
+
error = nil
|
8
|
+
|
9
|
+
# remove comments manually
|
10
|
+
statement = statement.gsub(/--.+/, "")
|
11
|
+
# only supports single line /* */ comments
|
12
|
+
# regex not perfect, but should be good enough
|
13
|
+
statement = statement.gsub(/\/\*.+\*\//, "")
|
14
|
+
|
15
|
+
# remove trailing semicolon
|
16
|
+
statement = statement.sub(/;\s*\z/, "")
|
17
|
+
|
18
|
+
# remove whitespace
|
19
|
+
statement = statement.squish
|
20
|
+
|
21
|
+
uri = URI(settings["url"])
|
22
|
+
uri.query = URI.encode_www_form("$query" => statement)
|
23
|
+
|
24
|
+
req = Net::HTTP::Get.new(uri)
|
25
|
+
req["X-App-Token"] = settings["app_token"] if settings["app_token"]
|
26
|
+
|
27
|
+
options = {
|
28
|
+
use_ssl: uri.scheme == "https",
|
29
|
+
open_timeout: 3,
|
30
|
+
read_timeout: 30
|
31
|
+
}
|
32
|
+
|
33
|
+
begin
|
34
|
+
# use Net::HTTP instead of soda-ruby for types and better error messages
|
35
|
+
res = Net::HTTP.start(uri.hostname, uri.port, options) do |http|
|
36
|
+
http.request(req)
|
37
|
+
end
|
38
|
+
|
39
|
+
if res.is_a?(Net::HTTPSuccess)
|
40
|
+
body = JSON.parse(res.body)
|
41
|
+
|
42
|
+
columns = JSON.parse(res["x-soda2-fields"])
|
43
|
+
column_types = columns.zip(JSON.parse(res["x-soda2-types"])).to_h
|
44
|
+
|
45
|
+
columns.reject! { |f| f.start_with?(":@") }
|
46
|
+
# rows can be missing some keys in JSON, so need to map by column
|
47
|
+
rows = body.map { |r| columns.map { |c| r[c] } }
|
48
|
+
|
49
|
+
columns.each_with_index do |column, i|
|
50
|
+
# nothing to do for boolean
|
51
|
+
case column_types[column]
|
52
|
+
when "number"
|
53
|
+
# check if likely an integer column
|
54
|
+
if rows.all? { |r| r[i].to_i == r[i].to_f }
|
55
|
+
rows.each do |row|
|
56
|
+
row[i] = row[i].to_i
|
57
|
+
end
|
58
|
+
else
|
59
|
+
rows.each do |row|
|
60
|
+
row[i] = row[i].to_f
|
61
|
+
end
|
62
|
+
end
|
63
|
+
when "floating_timestamp"
|
64
|
+
# check if likely a date column
|
65
|
+
if rows.all? { |r| r[i].end_with?("T00:00:00.000") }
|
66
|
+
rows.each do |row|
|
67
|
+
row[i] = Date.parse(row[i])
|
68
|
+
end
|
69
|
+
else
|
70
|
+
utc = ActiveSupport::TimeZone["Etc/UTC"]
|
71
|
+
rows.each do |row|
|
72
|
+
row[i] = utc.parse(row[i])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
else
|
78
|
+
error = JSON.parse(res.body)["message"] rescue "Bad response: #{res.code}"
|
79
|
+
end
|
80
|
+
rescue => e
|
81
|
+
error = e.message
|
82
|
+
end
|
83
|
+
|
84
|
+
[columns, rows, error]
|
85
|
+
end
|
86
|
+
|
87
|
+
def preview_statement
|
88
|
+
"SELECT * LIMIT 10"
|
89
|
+
end
|
90
|
+
|
91
|
+
def tables
|
92
|
+
["all"]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module Blazer
|
2
|
+
module Adapters
|
3
|
+
class SqlAdapter < BaseAdapter
|
4
|
+
attr_reader :connection_model
|
5
|
+
|
6
|
+
def initialize(data_source)
|
7
|
+
super
|
8
|
+
|
9
|
+
@connection_model =
|
10
|
+
Class.new(Blazer::Connection) do
|
11
|
+
def self.name
|
12
|
+
"Blazer::Connection::Adapter#{object_id}"
|
13
|
+
end
|
14
|
+
establish_connection(data_source.settings["url"]) if data_source.settings["url"]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_statement(statement, comment)
|
19
|
+
columns = []
|
20
|
+
rows = []
|
21
|
+
error = nil
|
22
|
+
|
23
|
+
begin
|
24
|
+
in_transaction do
|
25
|
+
set_timeout(data_source.timeout) if data_source.timeout
|
26
|
+
|
27
|
+
result = select_all("#{statement} /*#{comment}*/")
|
28
|
+
columns = result.columns
|
29
|
+
result.rows.each do |untyped_row|
|
30
|
+
rows << (result.column_types.empty? ? untyped_row : columns.each_with_index.map { |c, i| untyped_row[i] ? result.column_types[c].send(:cast_value, untyped_row[i]) : untyped_row[i] })
|
31
|
+
end
|
32
|
+
end
|
33
|
+
rescue => e
|
34
|
+
error = e.message.sub(/.+ERROR: /, "")
|
35
|
+
error = Blazer::TIMEOUT_MESSAGE if Blazer::TIMEOUT_ERRORS.any? { |e| error.include?(e) }
|
36
|
+
reconnect if error.include?("PG::ConnectionBad")
|
37
|
+
end
|
38
|
+
|
39
|
+
[columns, rows, error]
|
40
|
+
end
|
41
|
+
|
42
|
+
def tables
|
43
|
+
sql = add_schemas("SELECT table_schema, table_name FROM information_schema.tables")
|
44
|
+
result = data_source.run_statement(sql, refresh_cache: true)
|
45
|
+
if postgresql? || redshift? || snowflake?
|
46
|
+
result.rows.sort_by { |r| [r[0] == default_schema ? "" : r[0], r[1]] }.map do |row|
|
47
|
+
table =
|
48
|
+
if row[0] == default_schema
|
49
|
+
row[1]
|
50
|
+
else
|
51
|
+
"#{row[0]}.#{row[1]}"
|
52
|
+
end
|
53
|
+
|
54
|
+
table = table.downcase if snowflake?
|
55
|
+
|
56
|
+
{
|
57
|
+
table: table,
|
58
|
+
value: connection_model.connection.quote_table_name(table)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
else
|
62
|
+
result.rows.map(&:second).sort
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def schema
|
67
|
+
sql = add_schemas("SELECT table_schema, table_name, column_name, data_type, ordinal_position FROM information_schema.columns")
|
68
|
+
result = data_source.run_statement(sql)
|
69
|
+
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]} }} }.sort_by { |t| [t[:schema] == default_schema ? "" : t[:schema], t[:table]] }
|
70
|
+
end
|
71
|
+
|
72
|
+
def preview_statement
|
73
|
+
if sqlserver?
|
74
|
+
"SELECT TOP (10) * FROM {table}"
|
75
|
+
else
|
76
|
+
"SELECT * FROM {table} LIMIT 10"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def reconnect
|
81
|
+
connection_model.establish_connection(settings["url"])
|
82
|
+
end
|
83
|
+
|
84
|
+
def cost(statement)
|
85
|
+
result = explain(statement)
|
86
|
+
if sqlserver?
|
87
|
+
result["TotalSubtreeCost"]
|
88
|
+
else
|
89
|
+
match = /cost=\d+\.\d+..(\d+\.\d+) /.match(result)
|
90
|
+
match[1] if match
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def explain(statement)
|
95
|
+
if postgresql? || redshift?
|
96
|
+
select_all("EXPLAIN #{statement}").rows.first.first
|
97
|
+
elsif sqlserver?
|
98
|
+
begin
|
99
|
+
execute("SET SHOWPLAN_ALL ON")
|
100
|
+
result = select_all(statement).each.first
|
101
|
+
ensure
|
102
|
+
execute("SET SHOWPLAN_ALL OFF")
|
103
|
+
end
|
104
|
+
result
|
105
|
+
end
|
106
|
+
rescue
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def cancel(run_id)
|
111
|
+
if postgresql?
|
112
|
+
select_all("SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND query LIKE ?", ["%,run_id:#{run_id}%"])
|
113
|
+
elsif redshift?
|
114
|
+
first_row = select_all("SELECT pid FROM stv_recents WHERE status = 'Running' AND query LIKE ?", ["%,run_id:#{run_id}%"]).first
|
115
|
+
if first_row
|
116
|
+
select_all("CANCEL #{first_row["pid"].to_i}")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def cachable?(statement)
|
122
|
+
!%w[CREATE ALTER UPDATE INSERT DELETE].include?(statement.split.first.to_s.upcase)
|
123
|
+
end
|
124
|
+
|
125
|
+
protected
|
126
|
+
|
127
|
+
def select_all(statement, params = [])
|
128
|
+
statement = connection_model.send(:sanitize_sql_array, [statement] + params) if params.any?
|
129
|
+
connection_model.connection.select_all(statement)
|
130
|
+
end
|
131
|
+
|
132
|
+
# seperate from select_all to prevent mysql error
|
133
|
+
def execute(statement)
|
134
|
+
connection_model.connection.execute(statement)
|
135
|
+
end
|
136
|
+
|
137
|
+
def postgresql?
|
138
|
+
["PostgreSQL", "PostGIS"].include?(adapter_name)
|
139
|
+
end
|
140
|
+
|
141
|
+
def redshift?
|
142
|
+
["Redshift"].include?(adapter_name)
|
143
|
+
end
|
144
|
+
|
145
|
+
def mysql?
|
146
|
+
["MySQL", "Mysql2", "Mysql2Spatial"].include?(adapter_name)
|
147
|
+
end
|
148
|
+
|
149
|
+
def sqlserver?
|
150
|
+
["SQLServer", "tinytds", "mssql"].include?(adapter_name)
|
151
|
+
end
|
152
|
+
|
153
|
+
def snowflake?
|
154
|
+
data_source.adapter == "snowflake"
|
155
|
+
end
|
156
|
+
|
157
|
+
def adapter_name
|
158
|
+
# prevent bad data source from taking down queries/new
|
159
|
+
connection_model.connection.adapter_name rescue nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def default_schema
|
163
|
+
@default_schema ||= begin
|
164
|
+
if postgresql? || redshift?
|
165
|
+
"public"
|
166
|
+
elsif sqlserver?
|
167
|
+
"dbo"
|
168
|
+
else
|
169
|
+
connection_model.connection_config[:database]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_schemas(query)
|
175
|
+
if settings["schemas"]
|
176
|
+
where = "table_schema IN (?)"
|
177
|
+
schemas = settings["schemas"]
|
178
|
+
else
|
179
|
+
where = "table_schema NOT IN (?)"
|
180
|
+
schemas = ["information_schema"]
|
181
|
+
schemas.map!(&:upcase) if snowflake?
|
182
|
+
schemas << "pg_catalog" if postgresql? || redshift?
|
183
|
+
end
|
184
|
+
connection_model.send(:sanitize_sql_array, ["#{query} WHERE #{where}", schemas])
|
185
|
+
end
|
186
|
+
|
187
|
+
def set_timeout(timeout)
|
188
|
+
if postgresql? || redshift?
|
189
|
+
execute("SET #{use_transaction? ? "LOCAL " : ""}statement_timeout = #{timeout.to_i * 1000}")
|
190
|
+
elsif mysql?
|
191
|
+
# use send as this method is private in Rails 4.2
|
192
|
+
mariadb = connection_model.connection.send(:mariadb?) rescue false
|
193
|
+
if mariadb
|
194
|
+
execute("SET max_statement_time = #{timeout.to_i * 1000}")
|
195
|
+
else
|
196
|
+
execute("SET max_execution_time = #{timeout.to_i * 1000}")
|
197
|
+
end
|
198
|
+
else
|
199
|
+
raise Blazer::TimeoutNotSupported, "Timeout not supported for #{adapter_name} adapter"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def use_transaction?
|
204
|
+
settings.key?("use_transaction") ? settings["use_transaction"] : true
|
205
|
+
end
|
206
|
+
|
207
|
+
def in_transaction
|
208
|
+
connection_model.connection_pool.with_connection do
|
209
|
+
if use_transaction?
|
210
|
+
connection_model.transaction do
|
211
|
+
yield
|
212
|
+
raise ActiveRecord::Rollback
|
213
|
+
end
|
214
|
+
else
|
215
|
+
yield
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|